;;; 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