X-Git-Url: https://thelambdalab.xyz/gitweb/index.cgi?a=blobdiff_plain;f=emus.el;h=e8b960d487f01fa981292486a7e38f85c3024ae2;hb=3726c4f1fec667e364ba18b8e26651f266312c6f;hp=2c879c46b541db102bf863ea164a91ff1e253baf;hpb=869c7b36b90740b4ac744270ef38d20226f2c391;p=emus.git diff --git a/emus.el b/emus.el index 2c879c4..e8b960d 100644 --- a/emus.el +++ b/emus.el @@ -57,11 +57,13 @@ :type '(string)) (defface emus-artist - '((t :inherit font-lock-string-face :background "#333")) + '((((background dark)) :inherit font-lock-string-face :background "#222" :extend t) + (t :inherit font-lock-string-face :background "#ddd" :extend t)) "Face used for artist names in browser.") (defface emus-album - '((t :inherit font-lock-constant-face :background "#222")) + '((((background dark)) :inherit font-lock-constant-face :background "#111" :extend t) + (t :inherit font-lock-constant-face :background "#eee" :extend t)) "Face used for album names in browser.") (defface emus-track @@ -69,7 +71,7 @@ "Face used for track titles in browser.") (defface emus-track-current - '((t :inherit font-lock-keyword-face :inverse-video t)) + '((t :inherit font-lock-keyword-face :inverse-video t :extend t)) "Face used for track titles in browser.") (defface emus-cursor @@ -98,6 +100,9 @@ Used to prevent commands from interfering with library construction.") (defvar emus-current-volume 100 "The current playback volume.") +(defvar emus-current-progress "" + "String describing the progress through the current track.") + ;;; mpg123 process ;; @@ -115,10 +120,9 @@ Used to prevent commands from interfering with library construction.") emus-process (let ((proc (make-process :name "emus-process" - ;; :buffer (get-buffer-create "*emus-process*") :command `(,emus-mpg123-program "-R")))) (set-process-query-on-exit-flag proc nil) - (process-send-string proc "silence\n") + ;; (process-send-string proc "silence\n") proc)))) (defun emus--send-cmd-raw (cmd &rest args) @@ -135,13 +139,34 @@ be used by `emus--load-library'." (unless emus--proc-in-use (apply #'emus--send-cmd-raw cmd args))) +(defun emus-kill-process () + "Kill any existing mpg123 process." + (let ((emus-process (get-process "emus-process"))) + (if emus-process + (kill-process emus-process)))) + +(defun emus-restart () + "Restart emus, regardless of current state." + (interactive) + (emus-kill-process) + (setq emus-state 'stopped + emus--proc-in-use nil + emus-tracks nil)) ;;; Library ;; (defun emus-get-audio-files () "Get all mp3 files in main emus directory." - (directory-files-recursively emus-directory ".*\\.mp3")) + (mapcar + #'expand-file-name + (directory-files-recursively emus-directory ".*\\.mp3"))) + +(defun emus-get-playlist-files () + "Get all m3u files in the main emus music directory." + (mapcar + #'expand-file-name + (directory-files-recursively emus-directory ".*\\.m3u"))) (defun emus-make-track (artist album title filename &optional pos) "Create an object representing an emus track. @@ -149,7 +174,7 @@ ARTIST, ALBUM and TITLE are used to describe the track, FILENAME refers to the mp3 file containing the track. If non-nil, POS specifies the position of the record representing this track in the emus browser buffer." - (vector artist album title filename pos)) + (list artist album title filename pos)) (defun emus-track-artist (track) "The artist corresponding to TRACK." @@ -173,7 +198,7 @@ emus browser buffer." (defun emus-set-track-browser-pos (track pos) "Set the location of the browser buffer record corresponding to TRACK to POS." - (aset track 4 pos)) + (setf (seq-elt track 4) pos)) (defun emus--load-library (then) "Initialize the emus track library. @@ -182,10 +207,10 @@ Once the library is initialized, the function THEN is called." (setq emus--proc-in-use t) (emus--suspend-cp) (setq emus-state 'stopped) + (setq emus-tracks nil) (let ((proc (emus-get-process)) (tagstr "") (filenames (emus-get-audio-files))) - (setq emus-tracks nil) (set-process-filter proc (lambda (proc string) (setq tagstr (concat tagstr string)) (when (string-suffix-p "@P 1\n" string) @@ -199,6 +224,7 @@ Once the library is initialized, the function THEN is called." (set-process-filter proc nil) (setq emus-tracks (reverse emus-tracks)) (emus--sort-tracks) + (emus--add-tracks-from-playlist-files) (unless emus-current-track (setq emus-current-track (car emus-tracks))) (funcall then) @@ -206,6 +232,9 @@ Once the library is initialized, the function THEN is called." (setq emus--proc-in-use nil))))) (emus--send-cmd-raw "lp" (car filenames))))) +(defun emus--dump-tracks () + emus-tracks) + (defun emus--make-track-from-tagstr (filename tagstr) "Parse TAGSTR to populate the fields of a track corresponding to FILENAME." (let ((artist "") @@ -219,26 +248,53 @@ Once the library is initialized, the function THEN is called." (found-artist (setq artist found-artist)) (found-album (setq album found-album)) (found-title (setq title found-title))))) - (emus-make-track artist album title filename nil))) + (emus-make-track artist album title filename))) + +(defun emus--add-tracks-from-playlist-files () + (let ((tracks nil)) + (dolist (filename (emus-get-playlist-files)) + (let ((artist "Playlists") + (album (file-name-base filename)) + (title nil) + (lines (split-string (with-temp-buffer + (insert-file-contents filename) + (buffer-string)) + "\n"))) + (dolist (line lines) + (pcase (string-trim line) + ((rx (: string-start + (* space) + "#extinf:" + (* (not ",")) "," + (let display-title (* any)) + string-end)) + (setq title display-title)) + ((rx (: string-start (* space) "#"))) ;skip other comments + ((rx (let filename (+ any))) + (setq tracks (cons (emus-make-track artist album (or title filename) filename) + tracks)) + (setq title nil)))))) + (setq emus-tracks (append emus-tracks (reverse tracks))))) (defun emus--sort-tracks () "Sort the library tracks according to artist and album. Leaves the track titles unsorted, so they will appear in the order specified by the filesystem." - (sort emus-tracks - (lambda (r1 r2) - (let ((artist1 (emus-track-artist r1)) - (artist2 (emus-track-artist r2))) - (if (string= artist1 artist2) - (let ((album1 (emus-track-album r1)) - (album2 (emus-track-album r2))) - (string< album1 album2)) - (string< artist1 artist2)))))) + (setq emus-tracks + (sort emus-tracks + (lambda (r1 r2) + (let ((artist1 (emus-track-artist r1)) + (artist2 (emus-track-artist r2))) + (if (string= artist1 artist2) + (let ((album1 (emus-track-album r1)) + (album2 (emus-track-album r2))) + (string< album1 album2)) + (string< artist1 artist2))))))) (defmacro emus--with-library (&rest body) "Evaluate BODY with the library initialized." `(if emus-tracks - (progn ,@body) + (unless emus--proc-in-use ,@body) (emus--load-library (lambda () ,@body)))) @@ -252,13 +308,13 @@ by the filesystem." (defun emus--resume-cp () "Resume continuous playback." - (setq emus-continuous-playback t) - (set-process-filter (emus-get-process) - (lambda (_proc string) - (and emus-continuous-playback - (eq emus-state 'playing) - (string-suffix-p "@P 0\n" string) - (emus-play-next))))) + (setq emus-continuous-playback t)) + +(defun emus--timestamp (seconds-total) + "Produce a timestamp string representation of SECONDS-TOTAL." + (let* ((seconds (truncate (mod seconds-total 60))) + (minutes (truncate (/ seconds-total 60)))) + (format "%02d:%02d" minutes seconds))) (defun emus-play-track (track) "Set TRACK as current and start playing." @@ -267,6 +323,31 @@ by the filesystem." (emus-send-cmd "l" (emus-track-file track)) (setq emus-state 'playing) (setq emus-current-track track) + (setq emus-current-progress "") + (set-process-filter + (emus-get-process) + (lambda (_proc string) + (pcase string + ((and "@P 0\n" + (guard emus-continuous-playback) + (guard (eq emus-state 'playing))) + (emus-play-next)) + ((rx (: string-start + "@F " + (+ digit) + " " + (+ digit) + " " + (let left-str (+ (not " "))) + " " + (let right-str (+ any)))) + (let* ((left (string-to-number left-str)) + (right (string-to-number right-str)) + (total (+ left right))) + (setq emus-current-progress + (format " %s/%s" + (emus--timestamp left) + (emus--timestamp total)))))))) (emus--update-track old-track) (emus--update-track track) (emus--resume-cp) @@ -400,6 +481,16 @@ If PREV is non-nil, plays the last track of the previous album." (interactive) (emus-jump -10)) +(defun emus-jump-1m-forward () + "Jump 1 minute forward in current track." + (interactive) + (emus-jump 60)) + +(defun emus-jump-1m-backward () + "Jump 1 minute backward in current track." + (interactive) + (emus-jump -60)) + (defun emus-display-status () "Display the current playback status in the minibuffer." (interactive) @@ -408,15 +499,17 @@ If PREV is non-nil, plays the last track of the previous album." (concat "Emus: Volume %d%%" (pcase emus-state ('stopped " [Stopped]") - ('paused " [Paused]") - ('playing " [Playing]") + ('paused (format " [Paused%s]" emus-current-progress)) + ('playing (format " [Playing%s]" emus-current-progress)) (_ "")) - (if emus-current-track - (format " - %.30s (%.20s)" - (emus-track-title emus-current-track) - (emus-track-artist emus-current-track)) - "")) - emus-current-volume))) + " %s") + emus-current-volume + (if emus-current-track + (format "- %.30s (%.20s, %.20s)" + (emus-track-title emus-current-track) + (emus-track-album emus-current-track) + (emus-track-artist emus-current-track)) + "")))) ;;; Browser @@ -433,7 +526,9 @@ and thus requires both artist and album headers." (let* ((artist (emus-track-artist track)) (album (emus-track-album track)) (title (emus-track-title track)) - (help-str (format "mouse-1, RET: Play '%.30s' (%.20s)" title artist))) + (album-symb (intern (concat artist album))) + (help-str (format "mouse-1, RET: Play '%.30s' (%.20s)" title artist)) + (field (intern album))) ;Allows easy jumping between albums with cursor. (when (or prev-track first) (unless (equal (emus-track-artist prev-track) artist) (insert-text-button @@ -441,16 +536,22 @@ and thus requires both artist and album headers." 'action #'emus--click-track 'follow-link t 'help-echo help-str - 'emus-track track) - (insert (propertize "\n" 'face 'emus-artist))) + 'emus-track track + 'field field) + (insert (propertize "\n" + 'face 'emus-artist + 'field field))) (unless (equal (emus-track-album prev-track) album) (insert-text-button (propertize (concat " " album) 'face 'emus-album) 'action #'emus--click-track 'follow-link t 'help-echo help-str - 'emus-track track) - (insert (propertize "\n" 'face 'emus-album)))) + 'emus-track track + 'field field) + (insert (propertize "\n" + 'face 'emus-album + 'field field)))) (emus-set-track-browser-pos track (point)) (let ((is-current (equal track emus-current-track))) (insert-text-button @@ -470,11 +571,15 @@ and thus requires both artist and album headers." 'action #'emus--click-track 'follow-link t 'help-echo help-str - 'emus-track track) + 'emus-track track + 'invisible album-symb + 'field field) (insert (propertize "\n" 'face (if is-current 'emus-track-current - 'emus-track)))))) + 'emus-track) + 'field field + 'invisible album-symb))))) (defun emus--update-track (track) "Rerender entry for TRACK in emus browser buffer. @@ -522,7 +627,7 @@ Used to update browser display when `emus-current-track' and/or `emus-state' cha "Switch to *emus* audio library browser." (interactive) (emus--with-library - (pop-to-buffer "*emus*") + (pop-to-buffer-same-window "*emus*") (emus-browser-mode) (emus--render-tracks) (emus-goto-current))) @@ -598,6 +703,18 @@ Used to update browser display when `emus-current-track' and/or `emus-state' cha (emus-jump-10s-backward) (emus-display-status)) +(defun emus-jump-1m-forward-status () + "Jump 10s forward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-1m-forward) + (emus-display-status)) + +(defun emus-jump-1m-backward-status () + "Jump 10s backward in current track, then display the emus status in the minibuffer." + (interactive) + (emus-jump-1m-backward) + (emus-display-status)) + (defun emus-goto-current-status () "Move point to the current track, then display the emus status in the minibuffer." (interactive) @@ -627,6 +744,8 @@ Used to update browser display when `emus-current-track' and/or `emus-state' cha (define-key map (kbd "P") 'emus-play-prev-album-status) (define-key map (kbd ",") 'emus-jump-10s-backward-status) (define-key map (kbd ".") 'emus-jump-10s-forward-status) + (define-key map (kbd "<") 'emus-jump-1m-backward-status) + (define-key map (kbd ">") 'emus-jump-1m-forward-status) (define-key map (kbd "c") 'emus-goto-current-status) (when (fboundp 'evil-define-key*) (evil-define-key* 'motion map @@ -642,12 +761,20 @@ Used to update browser display when `emus-current-track' and/or `emus-state' cha (kbd "P") 'emus-play-prev-album-status (kbd ",") 'emus-jump-10s-backward-status (kbd ".") 'emus-jump-10s-forward-status + (kbd "<") 'emus-jump-1m-backward-status + (kbd ">") 'emus-jump-1m-forward-status (kbd "c") 'emus-goto-current-status)) map) "Keymap for emus browser.") +(defun emus-restart () + "Restart emus." + (interactive) + (emus-kill-process)) + (define-derived-mode emus-browser-mode special-mode "emus-browser" - "Major mode for EMUS music player file browser.") + "Major mode for EMUS music player file browser." + (setq-local buffer-invisibility-spec nil)) (when (fboundp 'evil-set-initial-state) (evil-set-initial-state 'emus-browser-mode 'motion))