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