summaryrefslogtreecommitdiff
path: root/modules/music-config.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/music-config.el')
-rw-r--r--modules/music-config.el145
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)))