2019-03-02 13:37:05 +01:00
|
|
|
|
;;; db-music.el -- Music related stuff -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2019-06-10 09:22:30 +02:00
|
|
|
|
(require 'dash)
|
2020-06-26 22:28:15 +02:00
|
|
|
|
(require 'subr-x)
|
|
|
|
|
(require 'seq)
|
2019-03-02 14:36:34 +01:00
|
|
|
|
(require 'emms)
|
2020-06-26 22:28:15 +02:00
|
|
|
|
(require 'emms-source-file)
|
2021-02-24 14:14:08 +01:00
|
|
|
|
(require 'emms-playlist-sort)
|
|
|
|
|
(require 'emms-info)
|
2020-06-27 10:16:53 +02:00
|
|
|
|
(require 'hydra)
|
|
|
|
|
(require 'db-emms)
|
2019-03-02 13:37:05 +01:00
|
|
|
|
|
2019-06-10 11:33:35 +02:00
|
|
|
|
(defgroup db-music nil
|
|
|
|
|
"General configurations for music-related functionality."
|
|
|
|
|
:prefix "db-music"
|
|
|
|
|
:group 'convenience
|
|
|
|
|
:tag "db-music")
|
|
|
|
|
|
2020-08-12 21:57:48 +02:00
|
|
|
|
|
|
|
|
|
;; Autogeneration of Playlist
|
|
|
|
|
|
2020-06-26 22:29:44 +02:00
|
|
|
|
(defcustom db/auto-playlist-file-function #'db/play-auto-playlist-from-git-annex-find
|
2021-02-24 14:14:08 +01:00
|
|
|
|
"Function returning all music files of an automatically generated playlist.
|
|
|
|
|
|
|
|
|
|
This function should return a list of file names of music files."
|
2019-06-10 11:33:35 +02:00
|
|
|
|
:group 'db-music
|
|
|
|
|
:type 'function)
|
|
|
|
|
|
2019-06-10 14:11:52 +02:00
|
|
|
|
(defun db/play-auto-playlist ()
|
2021-02-24 14:14:08 +01:00
|
|
|
|
"Automatically generate playlist and play it.
|
2019-06-10 14:11:52 +02:00
|
|
|
|
|
2021-02-24 14:14:08 +01:00
|
|
|
|
Will use `db/auto-playlist-file-function’ for generating that
|
|
|
|
|
playlist. Current backend is EMMS."
|
2019-06-10 14:11:52 +02:00
|
|
|
|
(interactive)
|
|
|
|
|
(db/-emms-playlist-from-files (funcall db/auto-playlist-file-function)))
|
|
|
|
|
|
|
|
|
|
;; Idea: make this customizable, so that we can later switch to another backend
|
|
|
|
|
;; if necessary
|
2019-06-10 11:33:35 +02:00
|
|
|
|
|
2019-06-10 09:22:30 +02:00
|
|
|
|
(defun db/-emms-playlist-from-files (files)
|
|
|
|
|
"Generate EMMS playlist from FILES.
|
|
|
|
|
|
|
|
|
|
Shuffle it and start playing it afterwards."
|
2019-06-10 09:37:50 +02:00
|
|
|
|
(when (seq-empty-p files)
|
|
|
|
|
(user-error "List of files is empty, nothing to do"))
|
2019-06-10 09:22:30 +02:00
|
|
|
|
(save-window-excursion
|
|
|
|
|
(let ((music-buffer-name "*EMMS Playlist* -- Personal"))
|
|
|
|
|
(unless (get-buffer music-buffer-name)
|
|
|
|
|
(emms-playlist-new music-buffer-name))
|
|
|
|
|
(with-current-buffer (get-buffer music-buffer-name)
|
|
|
|
|
(emms-stop)
|
|
|
|
|
(emms-playlist-set-playlist-buffer)
|
|
|
|
|
(emms-playlist-current-clear)
|
|
|
|
|
(dolist (track files)
|
|
|
|
|
(emms-playlist-current-insert-source 'emms-insert-file track))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(emms-shuffle)
|
|
|
|
|
(emms-playlist-select-first)
|
|
|
|
|
(emms-start)))))
|
|
|
|
|
|
2019-06-10 16:26:58 +02:00
|
|
|
|
(defun db/playlist-files-from-git-annex-find (match-expression)
|
2021-02-06 18:18:35 +01:00
|
|
|
|
"Generate list of files from git annex find on MATCH-EXPRESSION.
|
2019-06-10 16:26:58 +02:00
|
|
|
|
|
|
|
|
|
Prompts for MATCH-EXPRESSION when called interactively.
|
2021-02-06 18:18:35 +01:00
|
|
|
|
Generates a list of absolute file names that is comprised of
|
|
|
|
|
exactly those files that match it. Assumes the default EMMS file
|
|
|
|
|
directory as specified by `emms-source-file-default-directory’ to
|
|
|
|
|
be part of a git-annex repository, complaining otherwise."
|
2019-06-10 16:26:58 +02:00
|
|
|
|
(interactive "smatch expression: ")
|
|
|
|
|
(let* ((default-directory emms-source-file-default-directory))
|
|
|
|
|
(->> (split-string (with-output-to-string
|
|
|
|
|
(with-current-buffer standard-output
|
|
|
|
|
(let ((return-value (apply #'call-process
|
|
|
|
|
"git" nil t nil
|
|
|
|
|
"annex" "find"
|
|
|
|
|
(split-string match-expression))))
|
|
|
|
|
(unless (zerop return-value)
|
|
|
|
|
(error "Call to `git-annex-find’ failed: %s"
|
|
|
|
|
(buffer-string))))))
|
|
|
|
|
"\n")
|
|
|
|
|
(cl-remove-if-not #'(lambda (path)
|
|
|
|
|
(and (not (string-empty-p path))
|
|
|
|
|
(file-exists-p path)
|
|
|
|
|
(file-readable-p path))))
|
|
|
|
|
(mapcar #'(lambda (path)
|
|
|
|
|
(expand-file-name
|
|
|
|
|
path
|
|
|
|
|
emms-source-file-default-directory))))))
|
2019-06-10 16:27:28 +02:00
|
|
|
|
|
|
|
|
|
(defun db/play-auto-playlist-from-git-annex-find ()
|
2021-02-24 14:14:08 +01:00
|
|
|
|
"Query for match expression and play resulting audio files.
|
2019-06-10 16:27:28 +02:00
|
|
|
|
|
2021-02-24 14:14:08 +01:00
|
|
|
|
The match expression must be suitable for git-annex to find the
|
|
|
|
|
desired files. See `db/playlist-files-from-git-annex-find’ for
|
|
|
|
|
more details."
|
2019-06-10 16:27:28 +02:00
|
|
|
|
(interactive)
|
|
|
|
|
(db/-emms-playlist-from-files
|
|
|
|
|
(call-interactively #'db/playlist-files-from-git-annex-find)))
|
|
|
|
|
|
2020-06-27 10:16:53 +02:00
|
|
|
|
(defhydra music-control (:color red :hint none)
|
|
|
|
|
"
|
|
|
|
|
Playing: %s(db/emms-track-status)
|
|
|
|
|
|
|
|
|
|
_n_: ?n? _p_: ?p?
|
2021-06-13 18:05:40 +02:00
|
|
|
|
_RET_: ?RET? _s_: ?s?
|
2020-06-27 10:16:53 +02:00
|
|
|
|
_-_: lower volume _+_: ?+?
|
|
|
|
|
_P_: ?P?
|
2021-06-13 18:05:40 +02:00
|
|
|
|
_M_: ?M?
|
2020-06-27 10:16:53 +02:00
|
|
|
|
|
|
|
|
|
"
|
|
|
|
|
("n" emms-next "next")
|
|
|
|
|
("p" emms-previous "previous")
|
|
|
|
|
("RET" emms-pause "play/pause")
|
2021-06-13 18:05:40 +02:00
|
|
|
|
("s" emms-stop "stop playing")
|
2020-06-27 10:16:53 +02:00
|
|
|
|
("-" emms-volume-lower "lower volume")
|
|
|
|
|
("+" emms-volume-raise "raise volume")
|
2021-06-13 18:05:40 +02:00
|
|
|
|
("M" emms "Show playlist in new EMMS buffer")
|
2020-06-27 10:16:53 +02:00
|
|
|
|
("P" (db/play-auto-playlist)
|
|
|
|
|
"Play automatically generated playlist"))
|
|
|
|
|
|
2020-08-12 21:57:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; Radio Stations
|
|
|
|
|
|
|
|
|
|
(defcustom db/radio-stations
|
|
|
|
|
'(("RBB RadioEins" .
|
|
|
|
|
"http://rbb-radioeins-live.cast.addradio.de/rbb/radioeins/live/mp3/48/stream.mp3")
|
|
|
|
|
("Deutschlandfunk" .
|
|
|
|
|
"http://st01.dlf.de/dlf/01/64/mp3/stream.mp3")
|
|
|
|
|
("Deutschlandradio Kultur" .
|
|
|
|
|
"https://st02.sslstream.dlf.de/dlf/02/64/mp3/stream.mp3")
|
|
|
|
|
("Deutschlandfunk Nova" .
|
|
|
|
|
"https://st03.sslstream.dlf.de/dlf/03/64/mp3/stream.mp3")
|
|
|
|
|
("DR P7" .
|
|
|
|
|
"http://live-icy.gss.dr.dk/A/A21L.mp3.m3u")
|
|
|
|
|
("BBC1 -- Mainstream" .
|
|
|
|
|
"http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1_mf_p")
|
|
|
|
|
("BBC2 -- Adult Contemporary" .
|
|
|
|
|
"http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio2_mf_p")
|
|
|
|
|
("BBC4 -- Info, Drama, Documentation" .
|
|
|
|
|
"http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio4fm_mf_p")
|
|
|
|
|
("BBC6 -- Music" .
|
|
|
|
|
"http://bbcmedia.ic.llnwd.net/stream/bbcmedia_6music_mf_p")
|
|
|
|
|
("BBC World Service" .
|
|
|
|
|
"http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-eieuk")
|
|
|
|
|
("NDR1 Niedersachsen" .
|
|
|
|
|
"https://ndr-ndr1niedersachsen-hannover.sslcast.addradio.de/ndr/ndr1niedersachsen/hannover/mp3/128/stream.mp3"))
|
|
|
|
|
"An alist of radio station names and a corresponding URL."
|
|
|
|
|
:group 'db-music
|
|
|
|
|
:type '(alist :key-type (string :tag "Radio Station")
|
|
|
|
|
:value-type (string :tag "URL")))
|
|
|
|
|
|
|
|
|
|
(defun db/play-radio-stations ()
|
|
|
|
|
"Prompt for radio station and play the corresponding URL using EMMS.
|
|
|
|
|
Candidates are taken from `db/radio-stations'."
|
|
|
|
|
(interactive)
|
|
|
|
|
(-> (completing-read "Station: " db/radio-stations nil t)
|
|
|
|
|
(assoc db/radio-stations)
|
|
|
|
|
cdr
|
|
|
|
|
emms-play-url))
|
|
|
|
|
|
2020-08-15 15:02:06 +02:00
|
|
|
|
|
|
|
|
|
;; Playlist management
|
|
|
|
|
|
|
|
|
|
(cl-defun db/write-m3u-playlist-from-git-annex-find
|
2021-02-24 13:10:08 +01:00
|
|
|
|
(file match-expression
|
2020-08-15 15:02:06 +02:00
|
|
|
|
&optional (base-dir emms-source-file-default-directory) overwrite)
|
2021-02-24 13:10:08 +01:00
|
|
|
|
"Write an M3U playlist to FILE based on a git-annex MATCH-EXPRESSION.
|
|
|
|
|
The playlist will contain all files found by git-annex-find using
|
|
|
|
|
MATCH-EXPRESSION. Conduct search with git-annex-find in
|
|
|
|
|
BASE-DIR. Query for overwrite if FILE already exists, unless
|
|
|
|
|
OVERWRITE is non-nil."
|
2020-08-15 15:02:06 +02:00
|
|
|
|
(interactive "FFile name of playlist: \nsPlaylist name: \nsgit annex match-expression: ")
|
|
|
|
|
(let ((base-dir (expand-file-name base-dir)))
|
|
|
|
|
(unless (file-accessible-directory-p base-dir)
|
|
|
|
|
(user-error "Error: “%s” is not a valid directory" base-dir))
|
|
|
|
|
(unless (or (not (file-exists-p file))
|
|
|
|
|
overwrite
|
|
|
|
|
(yes-or-no-p (format "File %s already exists, overwrite?" file)))
|
2021-02-24 13:10:08 +01:00
|
|
|
|
(user-error "Error: %s exists and shall not be overwritten, aborting" file))
|
2020-08-15 15:02:06 +02:00
|
|
|
|
(let ((default-directory base-dir))
|
2021-02-24 13:10:08 +01:00
|
|
|
|
(let* ((return-code nil)
|
|
|
|
|
(output (with-output-to-string
|
|
|
|
|
(with-current-buffer standard-output
|
|
|
|
|
(setq return-code (apply #'call-process
|
|
|
|
|
"git" nil t nil
|
|
|
|
|
"annex" "find"
|
|
|
|
|
(split-string match-expression)))))))
|
|
|
|
|
(if (not (zerop return-code))
|
|
|
|
|
(error "%s" output)
|
2021-02-24 14:03:17 +01:00
|
|
|
|
(let ((emms-source-playlist-ask-before-overwrite nil)
|
2021-02-24 13:33:20 +01:00
|
|
|
|
(emms-temp-playlist-buffer (emms-playlist-new " *EMMS Playlist Export*"))
|
|
|
|
|
(emms-info-asynchronously nil))
|
2021-02-24 13:10:08 +01:00
|
|
|
|
(with-current-buffer emms-temp-playlist-buffer
|
2021-02-24 13:33:20 +01:00
|
|
|
|
(let ((emms-playlist-buffer (current-buffer)))
|
|
|
|
|
(emms-playlist-clear)
|
2021-02-24 13:58:10 +01:00
|
|
|
|
(dolist (track (split-string output "[\n\r]" 'omit-nulls))
|
2021-02-24 13:33:20 +01:00
|
|
|
|
(emms-insert-file track))
|
|
|
|
|
(emms-playlist-sort-by-info-title)
|
|
|
|
|
(emms-playlist-sort-by-info-artist)
|
|
|
|
|
;; When writing the playlist, we simulate the current buffer to be
|
|
|
|
|
;; the current playlist, as otherwise `emms-playlist-save' will
|
|
|
|
|
;; ask for confirmation.
|
2021-02-24 13:10:08 +01:00
|
|
|
|
(emms-playlist-save 'm3u file)))
|
2021-02-24 13:58:43 +01:00
|
|
|
|
(kill-buffer emms-temp-playlist-buffer)
|
|
|
|
|
|
|
|
|
|
;; Convert absolute file names to relative file names
|
|
|
|
|
(with-current-buffer (or (find-buffer-visiting file)
|
|
|
|
|
(find-file-noselect file))
|
|
|
|
|
;; Make sure the current buffer is up to date with the file on
|
|
|
|
|
;; disk, in case it had been visited before
|
|
|
|
|
(revert-buffer 'ignore-auto 'noconfirm)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (re-search-forward "^.+$" nil 'noerror)
|
|
|
|
|
(replace-match (file-relative-name (match-string 0))))
|
|
|
|
|
(save-buffer))))))))
|
2020-08-15 15:02:06 +02:00
|
|
|
|
|
|
|
|
|
(defun db/update-playlist-files ()
|
|
|
|
|
"Update personal playlist files."
|
|
|
|
|
(interactive)
|
|
|
|
|
(message "Update favorites playlist")
|
|
|
|
|
(db/write-m3u-playlist-from-git-annex-find
|
2021-02-24 13:10:08 +01:00
|
|
|
|
"~/Documents/media/audio/others/daniels-favorite.m3u"
|
|
|
|
|
"../songs/ --metadata rating-daniel>=0.9"
|
|
|
|
|
"~/Documents/media/audio/others/"
|
|
|
|
|
'overwrite)
|
2020-08-15 15:02:06 +02:00
|
|
|
|
(message "Update work playlist")
|
|
|
|
|
(db/write-m3u-playlist-from-git-annex-find
|
2021-02-24 13:10:08 +01:00
|
|
|
|
"~/Documents/media/audio/others/daniels-work-list.m3u"
|
|
|
|
|
"../songs/ --metadata db-work=include"
|
|
|
|
|
"~/Documents/media/audio/others/"
|
|
|
|
|
'overwrite))
|
2020-08-15 15:02:06 +02:00
|
|
|
|
|
2020-08-12 21:57:48 +02:00
|
|
|
|
|
|
|
|
|
|
2019-03-02 13:37:05 +01:00
|
|
|
|
(provide 'db-music)
|
|
|
|
|
|
|
|
|
|
;;; db-music ends here
|