diff options
Diffstat (limited to 'modules/music-config.el')
| -rw-r--r-- | modules/music-config.el | 145 |
1 files changed, 109 insertions, 36 deletions
diff --git a/modules/music-config.el b/modules/music-config.el index 90feb7eb..f60ff36a 100644 --- a/modules/music-config.el +++ b/modules/music-config.el @@ -1,8 +1,8 @@ -;;; music-config.el --- EMMS configuration with MPD integration -*- coding: utf-8; lexical-binding: t; -*- +;;; music-config.el --- EMMS configuration with MPV backend -*- coding: utf-8; lexical-binding: t; -*- ;; ;;; Commentary: ;; -;; Comprehensive music management in Emacs via EMMS with MPD backend. +;; Comprehensive music management in Emacs via EMMS with MPV backend. ;; Focus: simple, modular helpers; consistent error handling; streamlined UX. ;; ;; Highlights: @@ -10,17 +10,18 @@ ;; - Recursive directory add ;; - Dired/Dirvish integration (add selection) ;; - M3U playlist save/load/edit/reload -;; - Radio station M3U creation +;; - Radio station M3U creation (streaming URLs supported) ;; - Playlist window toggling -;; - MPD as player +;; - MPV as player (no daemon required) ;; ;;; Code: (require 'subr-x) +(require 'user-constants) ;;; Settings (no Customize) -(defvar cj/music-root (expand-file-name "~/music") +(defvar cj/music-root music-dir "Root directory of your music collection.") (defvar cj/music-m3u-root cj/music-root @@ -44,14 +45,16 @@ (defun cj/music--valid-file-p (file) "Return non-nil if FILE has an accepted music extension (case-insensitive)." - (when-let ((ext (file-name-extension file))) - (member (downcase ext) cj/music-file-extensions))) + (when (and file (stringp file)) + (when-let ((ext (file-name-extension file))) + (member (downcase ext) cj/music-file-extensions)))) (defun cj/music--valid-directory-p (dir) "Return non-nil if DIR is a non-hidden directory." - (and (file-directory-p dir) - (not (string-prefix-p "." (file-name-nondirectory - (directory-file-name dir)))))) + (when (and dir (stringp dir) (not (string-empty-p dir))) + (and (file-directory-p dir) + (not (string-prefix-p "." (file-name-nondirectory + (directory-file-name dir))))))) (defun cj/music--collect-entries-recursive (root) "Return sorted relative paths of all subdirs and music files under ROOT. @@ -105,7 +108,7 @@ Directories are suffixed with /; files are plain. Hidden dirs/files skipped." (let ((line (string-trim (match-string 0)))) (unless (string-empty-p line) (push (if (or (file-name-absolute-p line) - (string-match-p "\`\(https?\|mms\)://" line)) + (string-match-p "\\`\\(https?\\|mms\\)://" line)) line (expand-file-name line dir)) tracks)))) @@ -189,6 +192,64 @@ Directories (trailing /) are added recursively; files added singly." ;;; Commands: playlist management (load/save/clear/reload/edit) +(defun cj/music--append-track-to-m3u-file (track-path m3u-file) + "Append TRACK-PATH to M3U-FILE. Signals error on failure. +Pure function for testing - no user interaction. +TRACK-PATH should be an absolute path. +M3U-FILE should be an existing, writable M3U file path." + (unless (file-exists-p m3u-file) + (error "M3U file does not exist: %s" m3u-file)) + (unless (file-writable-p m3u-file) + (error "M3U file is not writable: %s" m3u-file)) + + ;; Convert absolute path to relative path from music root + (let ((relative-path (if (file-name-absolute-p track-path) + (file-relative-name track-path cj/music-root) + track-path))) + ;; Determine if we need a leading newline + (let ((needs-prefix-newline nil) + (file-size (file-attribute-size (file-attributes m3u-file)))) + (when (> file-size 0) + ;; Read the last character of the file to check if it ends with newline + (with-temp-buffer + (insert-file-contents m3u-file nil (max 0 (1- file-size)) file-size) + (setq needs-prefix-newline (not (= (char-after (point-min)) ?\n))))) + + ;; Append the track with proper newline handling + (with-temp-buffer + (when needs-prefix-newline + (insert "\n")) + (insert relative-path "\n") + (write-region (point-min) (point-max) m3u-file t 0)))) + t) + + +(defun cj/music-append-track-to-playlist () + "Append track at point to a selected M3U playlist file. +Prompts for M3U file selection with completion. Allows cancellation." + (interactive) + (unless (derived-mode-p 'emms-playlist-mode) + (user-error "This command must be run in the EMMS playlist buffer")) + (let ((track (emms-playlist-track-at (point)))) + (unless track + (user-error "No track at point")) + (let* ((track-path (emms-track-name track)) + (m3u-files (cj/music--get-m3u-files))) + (when (null m3u-files) + (user-error "No M3U files found in %s" cj/music-m3u-root)) + (let* ((choices (append (mapcar #'car m3u-files) '("(Cancel)"))) + (choice (completing-read "Append track to playlist: " choices nil t))) + (if (string= choice "(Cancel)") + (message "Cancelled") + (let ((m3u-file (cdr (assoc choice m3u-files)))) + (condition-case err + (progn + (cj/music--append-track-to-m3u-file track-path m3u-file) + (message "Added '%s' to %s" + (file-name-nondirectory track-path) + choice)) + (error (message "Failed to append track: %s" (error-message-string err)))))))))) + (defun cj/music-playlist-load () "Load an M3U playlist from cj/music-m3u-root. @@ -346,9 +407,7 @@ Dirs added recursively." ((file-directory-p file) (cj/music-add-directory-recursive file)) ((cj/music--valid-file-p file) (emms-add-file file)) (t (message "Skipping non-music file: %s" file)))) - (message "Added %d item(s) to playlist" (length files)))) - - (keymap-set dirvish-mode-map "p" #'cj/music-add-dired-selection)) + (message "Added %d item(s) to playlist" (length files))))) ;;; EMMS setup and keybindings @@ -361,12 +420,25 @@ Dirs added recursively." "r" #'cj/music-create-radio-station "SPC" #'emms-pause "s" #'emms-stop - "p" #'emms-playlist-mode-go + "n" #'emms-next + "p" #'emms-previous + "g" #'emms-playlist-mode-go "x" #'emms-shuffle) (keymap-set cj/custom-keymap "m" cj/music-map) (with-eval-after-load 'which-key - (which-key-add-key-based-replacements "C-; m" "music menu")) + (which-key-add-key-based-replacements + "C-; m" "music menu" + "C-; m m" "toggle playlist" + "C-; m M" "show playlist" + "C-; m a" "add music" + "C-; m r" "create radio" + "C-; m SPC" "pause" + "C-; m s" "stop" + "C-; m n" "next track" + "C-; m p" "previous track" + "C-; m g" "goto playlist" + "C-; m x" "shuffle")) (use-package emms :defer t @@ -376,7 +448,7 @@ Dirs added recursively." :commands (emms-mode-line-mode) :config (require 'emms-setup) - (require 'emms-player-mpd) + (require 'emms-player-mpv) (require 'emms-playlist-mode) (require 'emms-source-file) (require 'emms-source-playlist) @@ -385,8 +457,8 @@ Dirs added recursively." (setq emms-source-file-default-directory cj/music-root) (setq emms-playlist-default-major-mode 'emms-playlist-mode) - ;; Use only MPD as player - MUST be set before emms-all - (setq emms-player-list '(emms-player-mpd)) + ;; Use MPV as player - MUST be set before emms-all + (setq emms-player-list '(emms-player-mpv)) ;; Now initialize EMMS (emms-all) @@ -395,22 +467,18 @@ Dirs added recursively." (emms-playing-time-disable-display) (emms-mode-line-mode -1) - ;; MPD configuration - (setq emms-player-mpd-server-name "localhost") - (setq emms-player-mpd-server-port "6600") - (setq emms-player-mpd-music-directory cj/music-root) - (condition-case err - (emms-player-mpd-connect) - (error (message "Failed to connect to MPD: %s" err))) - - ;; note setopt as variable is customizeable - ;; MPD can play both local files and stream URLs - (setopt emms-player-mpd-supported-regexp - (rx (or - ;; Stream URLs - (seq bos (or "http" "https" "mms") "://") - ;; Local music files by extension - (regexp (apply #'emms-player-simple-regexp cj/music-file-extensions))))) + ;; MPV configuration + ;; MPV supports both local files and stream URLs + (setq emms-player-mpv-parameters + '("--quiet" "--no-video" "--audio-display=no")) + + ;; Update supported file types for mpv player + (setq emms-player-mpv-regexp + (rx (or + ;; Stream URLs + (seq bos (or "http" "https" "mms") "://") + ;; Local music files by extension + (seq "." (or "aac" "flac" "m4a" "mp3" "ogg" "opus" "wav") eos)))) ;; Keep cj/music-playlist-file in sync if playlist is cleared (defun cj/music--after-playlist-clear (&rest _) @@ -428,10 +496,15 @@ Dirs added recursively." ("p" . emms-playlist-mode-go) ("SPC" . emms-pause) ("s" . emms-stop) + ("n" . emms-next) + ("P" . emms-previous) + ("f" . emms-seek-forward) + ("b" . emms-seek-backward) ("x" . emms-shuffle) ("q" . emms-playlist-mode-bury-buffer) ("a" . cj/music-fuzzy-select-and-add) ;; Manipulation + ("A" . cj/music-append-track-to-playlist) ("C" . cj/music-playlist-clear) ("L" . cj/music-playlist-load) ("E" . cj/music-playlist-edit) @@ -442,7 +515,7 @@ Dirs added recursively." ("C-<down>" . emms-playlist-mode-shift-track-down) ;; Radio ("r" . cj/music-create-radio-station) - ;; Volume (MPD) + ;; Volume (MPV) ("-" . emms-volume-lower) ("=" . emms-volume-raise))) |
