;;; emms-init.el --- Initialize emms ;; This file is part of Michael Olson's Emacs settings. ;; The code in this file may be used, distributed, and modified ;; without restriction. ;; I use initsplit.el to separate customize settings on a per-project ;; basis. ;;; Setup (add-to-list 'load-path "/home/mwolson/proj/emacs/emms/git-emms/lisp") ;; Initialize (require 'emms) (require 'emms-browser) (require 'emms-cache) (require 'emms-info) (require 'emms-lastfm) (require 'emms-player-mpd) (require 'emms-playing-time) (require 'emms-playlist-mode) (require 'emms-streams) (require 'emms-tag-editor) (require 'emms-volume) (unless my-start-gnus (emms-cache 1) (emms-playing-time 1) (emms-lastfm 1) ; must come after emms-playing-time ) ;; Load authentication info from an external source (load "~/.emacs.d/.emms-auth") (unless my-start-gnus ;; Only show files in emms-browser, not playlists (emms-browser-make-filter "all-files" (emms-browser-filter-only-type 'file)) (emms-browser-set-filter (assoc "all-files" emms-browser-filters))) ;;; Track descriptions (defun my-upcase-initials (string) "Do `upcase-initials' on STRING, but do not uppercase letters that come after quote characters." (with-temp-buffer (insert (upcase-initials string)) (goto-char (point-min)) (while (re-search-forward "['`]\\([[:upper:]]\\)" nil t) (downcase-region (match-beginning 1) (match-end 1))) (buffer-string))) (defun my-emms-info-track-description (track) "Return a description of the current track." (let ((artist (emms-track-get track 'info-artist)) (title (emms-track-get track 'info-title)) (name (emms-track-get track 'name)) (type (emms-track-type track))) (cond ((and (stringp artist) (not (string= artist "")) (stringp title) (not (string= title ""))) (let ((desc (format "%s - %s" artist title))) (if (memq type '(file url)) desc (concat (symbol-name type) ": " desc)))) ((and (stringp title) (not (string= title ""))) (my-upcase-initials title)) ((null name) "Invalid track!") ((memq type '(url streamlist)) "") (t (concat (symbol-name type) ": " name))))) ;;; Track info (defvar my-emms-info-annoying-titles '("[[:blank:]]*([^(]*\\(Album\\|LP\\)\\( Version\\)?)\\([[:blank:]]\\|$\\)" "[[:blank:]]*\\[Explicit\\]$") "List of annoying things in titles to strip out.") (defun my-emms-info-filter-titles (track) "Remove annoying things from titles." (let ((title (emms-track-get track 'info-title))) (when title (save-match-data (dolist (regexp my-emms-info-annoying-titles) (setq title (replace-regexp-in-string regexp "" title)))) (emms-track-set track 'info-title title)))) (defun my-emms-info-fromname (track) "Add track information to TRACK. This is a useful element for `emms-info-functions'." (when (and (memq (emms-track-type track) '(file playlist)) (not (or (emms-track-get track 'info-artist) (emms-track-get track 'info-title)))) (let ((name (emms-track-get track 'name)) artist title) (unless (or (null name) (string-match "\\`http://" name)) (setq name (file-name-sans-extension (file-name-nondirectory name))) (save-match-data (when (string-match "\\`\\([^-]+?\\) *- *\\(.*\\)\\'" name) (setq artist (match-string 1 name) title (match-string 2 name)) (setq artist (my-upcase-initials (emms-replace-regexp-in-string "_" " " artist))) (setq title (my-upcase-initials (emms-replace-regexp-in-string "_" " " title))) (emms-track-set track 'info-artist artist) (emms-track-set track 'info-title title))))))) ;;; Utilities ;; Show the currently-playing song (defun np (&optional arg) (interactive "P") (emms-player-mpd-show arg)) ;; Reset the cached info for all songs in the current buffer (defun my-emms-reset-cache () (interactive) (save-excursion (goto-char (point-min)) (emms-walk-tracks (let ((track (emms-playlist-track-at (point)))) (emms-cache-set (emms-track-get track 'type) (emms-track-get track 'name) nil))))) ;; Open file at point in playlist (defun my-emms-playlist-open-file () (interactive) (unless (emms-playlist-track-at) (emms-playlist-next)) (let ((name (emms-track-get (emms-playlist-track-at) 'name))) (when name (find-file name)))) ;; Switch to the radio buffer (defun my-emms-streams () (interactive) (let ((buf (get-buffer emms-stream-buffer-name))) (if buf (switch-to-buffer buf) (emms-streams)))) ;; Switch to either the radio buffer or the current EMMS playlist (defun my-emms-switch-to-current-playlist () (interactive) (if (and (boundp 'emms-stream-playlist-buffer) (eq emms-stream-playlist-buffer emms-playlist-buffer)) (switch-to-buffer emms-stream-buffer-name) (if (or (null emms-playlist-buffer) (not (buffer-live-p emms-playlist-buffer))) (error "No current Emms buffer") (switch-to-buffer emms-playlist-buffer)))) ;; Bring up the browser interface with my preferences (defvar my-emms-browser-buffer nil) (defun my-emms-switch-to-browser () (interactive) (if (and my-emms-browser-buffer (buffer-live-p my-emms-browser-buffer)) (switch-to-buffer my-emms-browser-buffer) (emms-browse-by-artist) (emms-browser-expand-to-level-2) (setq my-emms-browser-buffer (current-buffer))) (isearch-forward)) ;;; Every time the song changes, show me its description (defvar np-interval 5 "How often (in seconds) to check whether the current song has changed.") ;; Internal (defvar np-last nil "Description for most recently-played track.") (defvar np-timer nil "Timer used by `np-maybe'.") (defun np-maybe () (condition-case nil (emms-player-mpd-show nil (lambda (buffer desc) (when (and (stringp desc) (not (string= desc ""))) (unless (and (stringp np-last) (string= np-last desc)) (setq np-last desc) (message "%s" desc))))) (error (np-remove)))) (defun np-insinuate () (unless np-timer (setq np-timer (run-with-idle-timer np-interval t #'np-maybe)))) (defun np-remove () (interactive) (when np-timer (emms-cancel-timer np-timer) (setq np-timer nil))) (unless my-start-gnus (add-hook 'emms-player-started-hook #'np-insinuate) (add-hook 'emms-player-stopped-hook #'np-remove) (add-hook 'emms-player-finished-hook #'np-remove)) ;;; Fancy playlist tree display (defun my-emms-iterate-directory (dir regex file-fn dir-fn closure) "Call FILE-FN for each file under DIR that matches REGEX, and call DIR-FN for each directory under DIR that matches REGEX. The FILE-FN and DIR-FN functions should both accept three arguments: the name of the file, its depth level in the tree, and the CLOSURE argument." (let ((dirs (list (cons dir 0)))) (while dirs (cond ((file-directory-p (caar dirs)) (if (string-match "/\\.\\.?$" (caar dirs)) (setq dirs (cdr dirs)) (let ((depth (cdar dirs))) (unless (equal depth 0) (funcall dir-fn (caar dirs) (cdar dirs) closure)) (setq depth (1+ depth)) (setq dirs (condition-case nil (nconc (mapcar `(lambda (entry) (cons entry ,depth)) (directory-files (caar dirs) t)) (cdr dirs)) (error (cdr dirs))))))) ((string-match regex (caar dirs)) (funcall file-fn (caar dirs) (cdar dirs) closure) (setq dirs (cdr dirs))) (t (setq dirs (cdr dirs))))))) (defun my-fancy-directory-tree-insert-file (file depth type) (unless (let ((case-fold-search nil)) (string-match emms-source-file-exclude-regexp file)) (with-current-emms-playlist (let ((inhibit-read-only t)) (insert (make-string (* (1- depth) 2) ?\ )))) (emms-playlist-insert-track (emms-track type (expand-file-name file))))) (defun my-fancy-directory-tree-insert-dir (dir depth ignored) (emms-playlist-ensure-playlist-buffer) (let ((inhibit-read-only t)) (unless (bobp) (insert "\n")) (insert (make-string (* (1- depth) 2) ?\ ) "[ " (my-upcase-initials (file-name-nondirectory dir)) " ]\n"))) ;;;###autoload (autoload 'emms-play-fancy-playlist-directory-tree ;;;###autoload "emms-init" nil t) ;;;###autoload (autoload 'emms-add-fancy-playlist-directory-tree ;;;###autoload "emms-init" nil t) (define-emms-source fancy-playlist-directory-tree (dir) "An EMMS source for multiple directory trees of playlist files. If DIR is not specified, it is queried from the user." (interactive (list (emms-read-directory-name "Play directory tree: " emms-source-file-default-directory emms-source-file-default-directory t))) (my-emms-iterate-directory (expand-file-name dir) "^[^.]" #'my-fancy-directory-tree-insert-file #'my-fancy-directory-tree-insert-dir 'playlist)) ;;;###autoload (autoload 'emms-play-fancy-directory-tree ;;;###autoload "emms-init" nil t) ;;;###autoload (autoload 'emms-add-fancy-directory-tree ;;;###autoload "emms-init" nil t) (define-emms-source fancy-directory-tree (dir) "An EMMS source for multiple directory trees of files. If DIR is not specified, it is queried from the user." (interactive (list (emms-read-directory-name "Play directory tree: " emms-source-file-default-directory emms-source-file-default-directory t))) (my-emms-iterate-directory (expand-file-name dir) "^[^.]" #'my-fancy-directory-tree-insert-file #'my-fancy-directory-tree-insert-dir 'file)) (defun my-emms-rebuild-main () (interactive) (let ((buffer (get-buffer "*EMMS Playlist*"))) (unless buffer (setq buffer (setq emms-playlist-buffer (emms-playlist-new)))) (let ((emms-playlist-buffer buffer)) (with-current-emms-playlist (emms-playlist-mode-clear) (emms-add-fancy-playlist-directory-tree "/stuff/music/playlists"))))) ;;; Key customizations (define-key emms-playlist-mode-map "o" 'my-emms-playlist-open-file) (define-key emms-playlist-mode-map "q" 'emms-playlist-mode-current-kill) (define-key emms-playlist-mode-map (kbd "SPC") 'emms-pause) (global-set-key "\C-cm" nil) (global-set-key "\C-cm " 'emms-pause) (global-set-key "\C-cm0" 'emms-playlist-mode-clear) (global-set-key "\C-cmb" 'my-emms-switch-to-browser) (global-set-key "\C-cmc" 'emms-player-mpd-connect) (global-set-key "\C-cml" nil) (global-set-key "\C-cmlb" 'emms-lastfm-radio-ban) (global-set-key "\C-cmll" 'emms-lastfm-radio-love) (global-set-key "\C-cmls" 'emms-lastfm-radio-skip) (global-set-key "\C-cmn" 'emms-player-mpd-next) (global-set-key "\C-cmo" 'my-emms-switch-to-current-playlist) (global-set-key "\C-cmp" 'emms-player-mpd-previous) (global-set-key "\C-cmq" 'emms-stop) (global-set-key "\C-cmQ" 'emms-player-mpd-clear) (global-set-key "\C-cmr" 'my-emms-streams) (global-set-key "\C-cmR" 'my-emms-rebuild-main) (global-set-key "\C-cms" 'emms-seek) (global-set-key "\C-cm-" 'emms-volume-mode-minus) (global-set-key "\C-cm+" 'emms-volume-mode-plus) (global-set-key "\C-cm\C-c" 'my-emms-reset-cache) ;;; Custom variables (custom-set-variables '(emms-cache-file "~/.emacs.d/.emms-cache") '(emms-info-asynchronously nil) '(emms-info-functions (quote (emms-info-mpd my-emms-info-fromname))) '(emms-lastfm-radio-metadata-period nil) '(emms-player-list (quote (emms-player-mpd emms-player-lastfm-radio))) '(emms-player-mpd-music-directory "/stuff/music") '(emms-playlist-buffer-name "*EMMS Playlist*") '(emms-playlist-default-major-mode (quote emms-playlist-mode)) '(emms-playlist-mode-open-playlists t) '(emms-show-format "NP: %s") '(emms-stream-default-action "play") '(emms-track-description-function (quote my-emms-info-track-description)) '(emms-track-info-filters (quote (my-emms-info-filter-titles))) '(emms-track-initialize-functions (quote (emms-info-initialize-track))) '(emms-volume-change-amount 3) '(emms-volume-change-function (quote emms-volume-mpd-change))) (custom-set-faces) ;;; Initialization ;(define-emms-combined-source all nil ; '((emms-source-playlist-directory "/stuff/music/playlists/albums") ; (emms-source-playlist-directory "/stuff/music/albums/classical") ; (emms-source-playlist-directory "/stuff/music/albums/candy_mind") ; (emms-source-playlist-directory "/stuff/music/mixes") ; (emms-source-playlist-file "~/.emacs.d/emms-playlist"))) ;(emms-add-all) (define-emms-source misc () (emms-add-fancy-playlist-directory-tree "/stuff/music/playlists")) (my-emms-rebuild-main) (when my-start-gnus (setq emms-player-mpd-check-interval nil)) ;;; emms-init.el ends here