Define an org-link yt type
This commit is contained in:
parent
1ea7f99778
commit
00a048ef5b
262
README.org
262
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)]]
|
*** [[info:org#Adding Hyperlink Types][Making Org hyperlink types (info)]]
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: sec:making-org-hyperlink-types
|
:CUSTOM_ID: sec:make-org-hyperlink-types
|
||||||
:END:
|
: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
|
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]].
|
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
|
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=
|
=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]].
|
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
|
3. Listing [[lst:patch-org-info-link-type]] patches the function =org-info-export=
|
||||||
[[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=
|
|
||||||
and listing [[lst:emacs-lisp-setup-buffer-local-ol-info]] sets the constants
|
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
|
~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
|
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)))))
|
:description path)))))
|
||||||
#+end_src
|
#+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 "<iframe width=\"560\" height=\"315\" src=\"%s\" "
|
|
||||||
"title=\"%s\" frameborder=\"0\" allowfullscreen></iframe>")
|
|
||||||
url desc)))
|
|
||||||
(`latex (format "\\href{https:%s}{%s}" path desc))
|
|
||||||
(_ path))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
#+caption[Patch =ol-info=]:
|
#+caption[Patch =ol-info=]:
|
||||||
#+caption: Patch =ol-info=.
|
#+caption: Patch =ol-info=.
|
||||||
#+name: lst:patch-org-info-link-type
|
#+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)))
|
org-info-other-documents :test #'equal)))
|
||||||
#+end_src
|
#+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
|
||||||
|
"<iframe
|
||||||
|
frameborder=\"0\" width=\"600\" height=\"450\"
|
||||||
|
src=\"https://www.youtube.com/embed/%s%s\"
|
||||||
|
title=\"%s\"
|
||||||
|
allowfullscreen></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)]]
|
*** [[https://tecosaur.github.io/emacs-config/#translate-capital-keywords][Translate capital keywords (old) to lower case (new)]]
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: sec:convert-upper-to-lower-case-keywords
|
:CUSTOM_ID: sec:convert-upper-to-lower-case-keywords
|
||||||
|
Loading…
Reference in New Issue
Block a user