diff --git a/README.org b/README.org index 56525ac..92adc7f 100644 --- a/README.org +++ b/README.org @@ -2931,205 +2931,54 @@ See `org-link-parameters' for details about PATH, DESC and FORMAT." :END: Listing [[lst:define-org-yt-link-type][define org-yt-link type]] implements code to open the link to the -following YouTube video [[yt:eaZUZCzaIgw#6s#10s][Extending org-mode to handle YouTube links.]] +following YouTube video [[yt:eaZUZCzaIgw][Extending org-mode to handle YouTube links]] and the +following [[https://raw.githubusercontent.com/bitspook/spookmax.d/master/readme.org][Emacs setup]]. + +Opening [[yt:eaZUZCzaIgw][Extending org-mode to handle YouTube links]] may fail due to a bug in the +interface between =mpv= and =yt-dlp=. Listing [[lst:set-emms-options][set EMMS options]] indicates how to +work around this bug at the cost of making the user interface less clean. +However, the link [[yt:48JlgiBpw_I][Absolute Beginner's Guide to Emacs]] works always. The following posts provide programming information: 1. [[https://developers.google.com/youtube/v3/docs][YouTube API reference]]. 2. [[https://developers.google.com/youtube/iframe_api_reference][YouTube Player API reference for iframe embeds]]. -3. [[https://www.alphr.com/how-to-link-specific-timestamp-youtube/][How to link to a specific timestamp in a YouTube video]]. - -#+begin_src emacs-lisp -n :exports none :tangle no -;; https://www.youtube.com/embed/M03bTkQxVUg?start=30&end=120 - -(org-yt-seconds-to-hms (+ (* 3600 120) (* 60 9) 7)) -(org-yt-seconds-to-hms 3661) - -(org-yt-time-to-seconds "") -(org-yt-time-to-seconds "1") -(org-yt-time-to-seconds "1:1") -(org-yt-time-to-seconds "1:1:1") -(org-yt-time-to-seconds "1h1m1s") -(org-yt-time-to-seconds "1mX") -(org-yt-time-to-seconds "1h") -(org-yt-time-to-seconds "1H1m") -(org-yt-time-to-seconds "1h1s") -(org-yt-time-to-seconds "1m1s") -(org-yt-time-to-seconds "1h1s") -(org-yt-time-to-seconds "1h1M1s") - -(org-yt-format-open-url "WKwZHSbHmPg" "") -(org-yt-format-open-url "WKwZHSbHmPg" nil) -(org-yt-format-open-url "WKwZHSbHmPg" '("1m1s")) -(org-yt-format-open-url "WKwZHSbHmPg" '("0:2:44" "0:2:50")) - -(org-yt-open "WKwZHSbHmPg#0:2:44#0:2:50" t) - -(org-yt-export "WKwZHSbHmPg#0:2:44#0:2:50" "Title" 'html nil) -(org-yt-export "WKwZHSbHmPg#0h2m40s#0h2m50s" "Title" 'html nil) -(org-yt-export "WKwZHSbHmPg#2M40S#2M50S" "Title" 'html nil) - -(org-yt-export "WKwZHSbHmPg#0:2:44#0:2:50" "Title" 'latex nil) -(org-yt-export "WKwZHSbHmPg#0h2m40s#0h2m50s" "Title" 'latex nil) -(org-yt-export "WKwZHSbHmPg#2M40S#2M50S" "Title" 'latex nil) -#+end_src - -#+caption[Parsing function for the =org-yt= link type]: -#+caption: Parsing function for the =org-yt= link type. -#+name: lst:org-yt-parsing -#+begin_src emacs-lisp -n :results silent -(defun org-yt-time-to-seconds (text) - "Convert TEXT of the form `12:34:56' or `12h34m56s' to seconds as integers." - (let* ((case-fold-search t) - (hms (< (length (split-string text "[:]")) - (length (split-string text "[HhMmSs]")))) - (regexp (if hms - (rx (opt (group (+ digit) "h")) - (opt (group (+ digit) "m")) - (opt (group (+ digit) "s"))) - (rx (opt (group (+ digit))) - (opt ":" (group (+ digit))) - (opt ":" (group (+ digit)))))) - (_ (string-match regexp text)) - (md (match-data)) - (found (substring text (pop md) (pop md))) - (seconds 0) - lot) - (if (string< found text) - (user-error "Found `%s' which is a prefix of `%s'" found text) - (if hms - (while md - (setq lot (substring text (or (pop md) 0) (or (pop md) 0))) - (cond - ((or (string-suffix-p "h" lot) (string-suffix-p "H" lot)) - (setq seconds (+ seconds (* 3600 (string-to-number lot))))) - ((or (string-suffix-p "m" lot) (string-suffix-p "M" lot)) - (setq seconds (+ seconds (* 60 (string-to-number lot))))) - ((or (string-suffix-p "s" lot) (string-suffix-p "S" lot)) - (setq seconds (+ seconds (string-to-number lot)))))) - (while md (push (string-to-number - (substring text (or (pop md) 0) (or (pop md) 0))) - lot)) - (cond - ((length= lot 1) (setq seconds (car lot))) - ((length= lot 2) (setq seconds (+ (* 60 (cadr lot)) - (car lot)))) - ((length= lot 3) (setq seconds (+ (* 3600 (caddr lot)) - (* 60 (cadr lot)) - (car lot))))))) - seconds)) -#+end_src - -#+caption[Formatting functions to open =org-yt= links]: -#+caption: Formatting functions to open =org-yt= links. -#+name: lst:org-yt-open-formatting -#+begin_src emacs-lisp -n :results silent -(defun org-yt-seconds-to-hms (seconds) - "Convert seconds as integers to text of the form `12h34m56s'." - (let ((h (/ seconds 3600)) - (m (/ (% seconds 3600) 60)) - (s (% seconds 60))) - (concat (and (> h 0) (format "%dh" h)) - (if (> h 0) - (format "%02dm" m) - (and (> m 0) (format "%dm" m))) - (if (or (> h 0) (> m 0)) - (format "%02ds" s) - (format "%ds" s))))) - -(defconst org-yt-start - "https://www.youtube.com/watch?v=%s&t=%s" - "Format string for `org-yt-format-open-url' to fill in ID and TIMING.") - -(defconst org-yt-chunk - "https://www.youtube.com/embed/%s?autoplay=1&start=%d&end=%d" - "Format string for `org-yt-format-open-url' to fill in ID and TIMING.") - -(defun org-yt-format-open-url (id timing) - "Convert an \"yt\" type link to an URL for opening." - (cond - ((not timing) - (format org-yt-start id "0s")) - ((length= timing 1) - (format org-yt-start id - (org-yt-seconds-to-hms (org-yt-time-to-seconds (car timing))))) - ((length= timing 2) - (format org-yt-chunk id - (org-yt-time-to-seconds (car timing)) - (org-yt-time-to-seconds (cadr timing)))))) -#+end_src - -#+caption[Formatting function for HTML export of =org-yt= links]: -#+caption: Formatting function for HTML export of =org-yt= links. -#+name: lst:org-yt-html-export-formatting -#+begin_src emacs-lisp -n :results silent -(defconst org-yt-iframe - "" - "Format string for `org-yt-iframe' to fill in ID, TIMING, and TITLE.") - -(defun org-yt-format-iframe (id timing title) - "Convert an \"yt\" type link to an inline frame element for export to HTML." - (format org-yt-iframe id - (cond - ((not timing) "") - ((length= timing 1) - (format "?start=%d" id (org-yt-time-to-seconds (car timing)))) - ((length= timing 2) - (format "?start=%d&end=%d" - (org-yt-time-to-seconds (car timing)) - (org-yt-time-to-seconds (cadr timing))))) - title)) -#+end_src #+caption[Define an =org-link= type for =YouTube=]: #+caption: Define an =org-link= type for =YouTube=. #+name: lst:define-org-yt-link-type #+begin_src emacs-lisp -n :results silent -(defun org-yt-open (path prefix) - "Open an \"yt\" type link with `browse-url', `emms', or \"mpv\". -PATH is a string containing the video ID and optional timing information. -Open the link with `browse-url' if PREFIX else with `emms' or \"mpv\"." - (let* ((parts (split-string path "[#]+" t)) - (id (car parts)) - (timing (cdr parts)) - (url (org-yt-format-open-url id timing))) - (cond - (prefix - (browse-url url)) - ((require 'emms nil 'noerror) - (emms-add-url url) - (with-current-emms-playlist - (save-excursion - (emms-playlist-last) - (emms-playlist-mode-play-current-track)))) - ((executable-find "mpv") - (let ((display-buffer-alist - `((,shell-command-buffer-name-async . (display-buffer-no-window))))) - (async-shell-command (format "\"%s\" \"%s\"" url))) - (message "Launched mpv with \"%s\"" url)) - (t - (user-error "Can't open `%s': install `emms' and/or `mpv'" url))))) +;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-paths/ +(defun org-yt-emms-open (path) + "Open an \"YouTube\" link with `emms' using \"mpv\"." + (let ((url (format "https://www.youtube.com/watch?v=%s" path))) + (require 'emms nil 'noerror) + (emms-add-url url) + (with-current-emms-playlist + (save-excursion + (emms-playlist-last) + (emms-playlist-mode-play-current-track))))) + +(defun org-yt-url-browse-open (path) + "Open an \"YouTube\" link with `url-browse'." + (let ((url (format "https://www.youtube.com/watch?v=%s" path))) + (browse-url url))) (defun org-yt-export (path desc backend _) - "Export an \"yt\" type link." - (let* ((parts (split-string path "[#]+" t)) - (id (car parts)) - (timing (cdr parts))) - (pcase backend + "Export an \"YouTube\" link." + (pcase backend (`html - (org-yt-format-iframe id timing desc)) + (format "" path desc)) (`latex - (format "\\href{%s}{%s}" (org-yt-format-open-url id timing) desc)) - (_ id)))) + (format "\\href{https://www.youtube.com/watch?v=%s}{%s}" path desc)) + (_ path))) (with-eval-after-load 'ol - ;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-paths/ - (org-link-set-parameters "yt" - :follow #'org-yt-open - :export #'org-yt-export)) + (org-link-set-parameters + "yt" :follow #'org-yt-emms-open :export #'org-yt-export)) #+end_src *** [[https://tecosaur.github.io/emacs-config/#translate-capital-keywords][Translate capital keywords (old) to lower case (new)]]