diff --git a/README.org b/README.org index fb8bc82..78e293a 100644 --- a/README.org +++ b/README.org @@ -2670,19 +2670,16 @@ environments and non-floating breakable LaTeX environments by means of *** [[info:org#Adding Hyperlink Types][Making Org hyperlink types (info)]] :PROPERTIES: -:CUSTOM_ID: sec:making-org-hyperlink-types +:CUSTOM_ID: sec:make-org-hyperlink-types :END: -The listings below implement four groups of =org-link= types: +The listings below implement or reimplement three groups of =org-link= types: 1. Listing [[lst:org-ref-like-org-link-types]] defines =org-link= types for backwards compatibility with [[https://github.com/jkitchin/org-ref][org-ref]]. 2. Listing [[lst:define-org-pdfview-link-type]] uses ideas from the definition of =docview-org-link= and =doi-org-link= types to define an =pdfview-org-link= type for use with [[https://github.com/vedang/pdf-tools][pdf-tools]]. -3. Listing [[lst:define-org-yt-link-type]] reimplements the code of the post - [[https://bitspook.in/blog/extending-org-mode-to-handle-youtube-links/][Extending org-mode to handle YouTube links]] allowing to open the YouTube link - [[yt://www.youtube.com/watch?v=eaZUZCzaIgw][Extending org-mode to handle YouTube links]] link. -4. Listing [[lst:patch-org-info-link-type]] patches the function =org-info-export= +3. Listing [[lst:patch-org-info-link-type]] patches the function =org-info-export= and listing [[lst:emacs-lisp-setup-buffer-local-ol-info]] sets the constants ~org-info-emacs-documents~ and ~org-info-other-documents~ as buffer local variables in order to export the =info-org-link= types in this document to @@ -2781,52 +2778,6 @@ facilate moving single directories or whole directory trees." :description path))))) #+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 -(with-eval-after-load 'ol - ;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-links/ - (org-link-set-parameters "yt" - :follow #'org-yt-open - :export #'org-yt-export) - - (defun org-yt-open (path prefix) - "Open PATH with \"emms\", \"mpv\", or `browse-url' (when PREFIX)." - (let ((url (format "https:%s" path))) - (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))))) - - (defun org-yt-export (path desc backend _) - "Export an \"yt\" link." - (pcase backend - (`html - (let* ((video-id (cadar (url-parse-query-string path))) - (url (if (string-empty-p video-id) - path - (format "//youtube.com/embed/%s" video-id)))) - (format - (concat "") - url desc))) - (`latex (format "\\href{https:%s}{%s}" path desc)) - (_ path)))) -#+end_src - #+caption[Patch =ol-info=]: #+caption: Patch =ol-info=. #+name: lst:patch-org-info-link-type @@ -2879,6 +2830,213 @@ See `org-link-parameters' for details about PATH, DESC and FORMAT." org-info-other-documents :test #'equal))) #+end_src +*** [[https://bitspook.in/blog/extending-org-mode-to-handle-youtube-links/][Extending org-mode to handle YouTube links]] +:PROPERTIES: +:CUSTOM_ID: sec:make-org-yt-link-type +: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.]] + +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 :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 +(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 +(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 +(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 +(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))))) + +(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 + (`html + (org-yt-format-iframe id timing desc)) + (`latex + (format "\\href{%s}{%s}" (org-yt-format-open-url id timing) desc)) + (_ id)))) + +(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)) +#+end_src + *** [[https://tecosaur.github.io/emacs-config/#translate-capital-keywords][Translate capital keywords (old) to lower case (new)]] :PROPERTIES: :CUSTOM_ID: sec:convert-upper-to-lower-case-keywords