diff --git a/config.org b/config.org index 02f42ab..72d924d 100644 --- a/config.org +++ b/config.org @@ -3638,6 +3638,155 @@ Saving seconds adds up after all! (but only so much) "Complete xkcd using `+xkcd-stored-info'" (format "xkcd:%d" (+xkcd-select)))) #+END_SRC +***** Music +First, we set up all the necessarily 'utility' functions. +#+BEGIN_SRC emacs-lisp +(after! org + (defvar org-music-player 'mpris + "Music player type. Curretly only supports mpris.") + (defvar org-music-mpris-player "Lollypop" + "Name of the mpris player, used in the form org.gnome.MPRIS.") + (defvar org-music-track-search-method 'beets + "Method to find the track file from the link.") + (defvar org-music-beets-db "~/Music/library.db" + "Location of the beets DB, when using beets as the `org-music-track-search-method'") + + (defun org-music-get-link (full &optional include-time) + "Generate link string for currently playing track, optionally including a time-stamp" + (let* ((track-metadata + (dbus-get-property :session "org.gnome.Lollypop" "/org/mpris/MediaPlayer2" + "org.mpris.MediaPlayer2.Player" "Metadata")) + (artist (caar (cadr (assoc "xesam:artist" track-metadata)))) + (album (car (cadr (assoc "xesam:album" track-metadata)))) + (track (car (cadr (assoc "xesam:title" track-metadata)))) + (seconds (when include-time + (/ (dbus-get-property :session "org.gnome.Lollypop" "/org/mpris/MediaPlayer2" + "org.mpris.MediaPlayer2.Player" "Position") 1000000)))) + (if full + (format "[[music:%s][%s by %s]]" (org-music-format-link artist album track seconds) track artist) + (org-music-format-link artist album track seconds)))) + + (defun org-music-format-link (artist album track &optional seconds) + (let ((artist (replace-regexp-in-string ":" "\\:" artist)) + (album (replace-regexp-in-string ":" "\\:" album)) + (track (replace-regexp-in-string ":" "\\:" track))) + (if seconds + (format "%s:%s::%s" artist track (org-music-seconds-to-time seconds)) + (format "%s:%s" artist track)))) + + (defun org-music-parse-link (link) + (let* ((link-dc (->> link + (replace-regexp-in-string "\\([^\\\\]\\)\\\\:" "\\1#COLON#") + (replace-regexp-in-string "\\`music:" "") + (replace-regexp-in-string "\\(::[a-z0-9]*[0-9]\\)\\'" "\\1s"))) + (link-components (mapcar (lambda (lc) (replace-regexp-in-string "#COLON#" ":" lc)) + (s-split ":" link-dc))) + (artist (nth 0 link-components)) + (track (nth 1 link-components)) + (seconds (when (and (> (length link-components) 3) + (equal (nth 2 link-components) "")) + (org-music-time-to-seconds (nth 3 link-components))))) + (list artist track seconds))) + + (defun org-music-seconds-to-time (seconds) + "Convert a number of seconds to a nice human duration, e.g. 5m21s. +This action is reversed by `org-music-time-to-seconds'." + (if (< seconds 60) + (format "%ss" seconds) + (if (< seconds 3600) + (format "%sm%ss" (/ seconds 60) (% seconds 60)) + (format "%sh%sm%ss" (/ seconds 3600) (/ (% seconds 3600) 60) (% seconds 60))))) + + (defun org-music-time-to-seconds (time-str) + "Get the number of seconds in a string produced by `org-music-seconds-to-time'." + (let* ((time-components (reverse (s-split "[a-z]" time-str))) + (seconds (string-to-number (nth 1 time-components))) + (minutes (when (> (length time-components) 2) + (string-to-number (nth 2 time-components)))) + (hours (when (> (length time-components) 3) + (string-to-number (nth 3 time-components))))) + (+ (* 3600 (or hours 0)) (* 60 (or minutes 0)) seconds))) + + (defun org-music-play-track (artist title &optional seconds) + "Play the track specified by ARTIST and TITLE, optionally skipping to SECONDS in." + (case org-music-player + ('mpris (org-music-mpris-play + (org-music-find-track-file artist title) + seconds)) + (t (user-error! "The specified music player: %s is not supported" org-music-player)))) + + (defun org-music-mpris-play (file &optional seconds) + (let ((player (concat "org.gnome." org-music-mpris-player))) + (dbus-call-method :session player + "/org/mpris/MediaPlayer2" "org.mpris.MediaPlayer2.Player" + "OpenUri" (replace-regexp-in-string " " "%20" (rng-file-name-uri file))) + (when seconds + (let ((track-id (caadr (assoc "mpris:trackid" + (dbus-get-property :session + player + "/org/mpris/MediaPlayer2" + "org.mpris.MediaPlayer2.Player" + "Metadata"))))) + (dbus-call-method :session player + "/org/mpris/MediaPlayer2" "org.mpris.MediaPlayer2.Player" + "SetPosition" + :object-path track-id + :int64 (round (* seconds 1000000))))))) + + (defun org-music-find-track-file (artist title) + (case org-music-track-search-method + ('beets (org-music-beets-find-file artist title)) + (t (user-error! "The specified music search method: %s is not supported" org-music-track-search-method)))) + + (defun org-music-beets-find-file (artist title) + "Find the file correspanding to a given artist and title." + (let* ((artist-escaped (replace-regexp-in-string "\"" "\\\"" artist)) + (title-escaped (replace-regexp-in-string "\"" "\\\"" title)) + (file + (shell-command-to-string + (format + "sqlite3 '%s' \"SELECT path FROM items WHERE artist IS '%s' AND title IS '%s' COLLATE NOCASE\"" + (expand-file-name org-music-beets-db) artist-escaped title-escaped)))) + (if (> (length file) 0) + (substring file 0 -1) + (user-error! "Could not find '%s' by '%s' in your beets database" title artist))))) +#+END_SRC + +Then we integrate this nicely with org-mode +#+BEGIN_SRC emacs-lisp +(after! org + (org-link-set-parameters "music" + :follow #'org-music-open-fn + :export #'org-music-export) + + (defun org-music-open-fn (link) + (apply #'org-music-play-track (org-music-parse-link link))) + + (defun org-music-insert-current-track (&optional include-time) + "Insert link to currest track, including a timestamp when the universal argument is supplied." + (interactive "P") + (pp include-time) + (insert (org-music-get-link t include-time))) + + (defun org-music-export (path desc backend _com) + "Convert xkcd to html/LaTeX form" + (let* ((track-info (org-music-parse-link path)) + (artist (nth 0 track-info)) + (track (nth 1 track-info)) + (seconds (nth 2 track-info)) + (description )) + (cond ((org-export-derived-backend-p backend 'html) + (if seconds + (format "%s seconds into %s by %s" seconds track artist) + (format "%s by %s" track artist))) + ((org-export-derived-backend-p backend 'latex) + (if seconds + (format "%s seconds into \\emph{%s} by %s" seconds track artist) + (format "\\emph{%s} by %s" track artist))) + (t (if seconds + (format "%s seconds into %s by %s" seconds track artist) + (format "%s by %s" track artist))))))) +#+END_SRC ***** YouTube The ~[[yt:...]]~ links preview nicely, but don't export nicely. Thankfully, we can fix that.