Commit c95ec234c3e5

Vincent Demeester <vincent@sbr.pm>
2026-07-03 09:15:33
feat(emacs): added Subsonic playlist support to empv
Implemented empv-subsonic-playlists via advice on the formatter and act-on-candidate functions. Uses getPlaylists/getPlaylist Subsonic API endpoints with manual entry-to-song mapping.
1 parent 9c1200b
Changed files (1)
dots
config
emacs
dots/config/emacs/init.el
@@ -3431,6 +3431,7 @@ With \\[universal-argument] \\[universal-argument]: always prompt with completio
              empv-subsonic-songs)
   :bind (("C-c M s" . empv-subsonic-search)
          ("C-c M a" . empv-subsonic-albums)
+         ("C-c M P" . empv-subsonic-playlists)
          ("C-c M SPC" . empv-toggle)
          ("C-c M i" . empv-display-current)
          ("C-c M n" . empv-playlist-next)
@@ -3445,7 +3446,64 @@ With \\[universal-argument] \\[universal-argument]: always prompt with completio
   :config
   (setq empv-subsonic-url "https://music.sbr.pm"
         empv-subsonic-username (passage-get "home/music.sbr.pm/username")
-        empv-subsonic-password (passage-get "home/music.sbr.pm/password")))
+        empv-subsonic-password (passage-get "home/music.sbr.pm/password"))
+
+  ;; Subsonic playlist support (not yet upstream in empv.el)
+  ;; Adds formatting, act-on-candidate handling, and interactive command
+  ;; for getPlaylists/getPlaylist Subsonic API endpoints.
+
+  (defun empv--subsonic-format-playlist (cand)
+    "Format a Subsonic playlist candidate for display."
+    (format "%s %s"
+            (propertize (alist-get 'name cand) 'face 'bold)
+            (propertize (format "[%s songs]" (or (alist-get 'songCount cand) "?")) 'face 'italic)))
+
+  ;; Extend the formatter to handle playlist kind
+  (advice-add 'empv--subsonic-format-candidate :around
+              (lambda (orig cand)
+                (if (eq (alist-get 'kind cand) 'playlist)
+                    (empv--with-text-properties
+                     (empv--subsonic-format-playlist cand)
+                     :item cand)
+                  (funcall orig cand))))
+
+  ;; Extend act-on-candidate to handle playlist kind
+  ;; Fetches playlist tracks via getPlaylist and enqueues all songs
+  (advice-add 'empv--subsonic-act-on-candidate :around
+              (lambda (orig selected)
+                (if (eq (alist-get 'kind selected) 'playlist)
+                    (let ((id (alist-get 'id selected))
+                          (name (alist-get 'name selected)))
+                      (empv--subsonic-request
+                       "getPlaylist.view"
+                       :id id
+                       (lambda (response)
+                         ;; getPlaylist returns entries under 'entry key
+                         ;; but empv's processor puts them under 'results
+                         ;; if they match result-fields. Since 'entry is
+                         ;; not in result-fields, we extract manually.
+                         (let* ((raw (alist-get 'entry response))
+                                (songs (or (alist-get 'results response) raw)))
+                           (if songs
+                               (empv-play-or-enqueue
+                                (mapcar #'empv--subsonic-item-extract-url
+                                        (if (alist-get 'results response)
+                                            songs
+                                          ;; raw entries need kind annotation
+                                          (mapcar (lambda (s)
+                                                    (thread-first s
+                                                      (map-insert 'kind 'song)
+                                                      (map-insert 'type 'subsonic)))
+                                                  songs))))
+                             (message "Playlist '%s' is empty" name))))))
+                  (funcall orig selected))))
+
+  (defun empv-subsonic-playlists ()
+    "List Subsonic playlists and act on selection."
+    (interactive)
+    (empv--subsonic-request
+     "getPlaylists.view"
+     (empv--subsonic-result-handler "Select playlist:"))))
 
 (use-package ready-player
   :init