Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial import

  • Loading branch information...
commit d844680173f737cee8493baa12151edb42fcd95f 0 parents
anybody authored tarsius committed
Showing with 453 additions and 0 deletions.
  1. +453 −0 itunes.el
453 itunes.el
@@ -0,0 +1,453 @@
+;;; itunes.el -- utility functions to interact with iTunes
+
+;; Author: Sean O'Rourke <sorourke@cs.ucsd.edu>
+;; Keywords: multimedia
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the Artistic License; see
+;; http://www.perl.com/language/misc/Artistic.html
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+;;; Commentary:
+
+;; Allows OS X Emacs users to control iTunes without having to leave
+;; the safe confines of Emacs. Simple stop/start/skip is supported,
+;; plus some simple playlist management (with extra software).
+
+;; The main entry point is `itunes-manip'.
+
+;;; Code:
+(require 'cl)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;_ - User-level commands:
+;;;###autoload
+(defun itunes-select-playlist (name)
+ "Select the playlist NAME from itunes' list of playlists."
+ (interactive
+ (let ((completion-ignore-case t))
+ (list (completing-read "Playlist: "
+ (itunes-get-playlists)))))
+ (do-applescript (concat
+ "tell application \"iTunes\" to play user playlist \""
+ name "\"")))
+
+;;;###autoload
+(defun itunes-toggle-repeat ()
+ "Toggle between repeat-one, repeat-all, and no repeat."
+ (interactive)
+ (let ((res (applescript-list (do-applescript "\
+tell application \"iTunes\"\r\
+ tell current playlist\r\
+ if song repeat is off then\r\
+ set song repeat to all\r return \"all\"
+ else if song repeat is all then\r\
+ set song repeat to one\r return \"one\"
+ else\r\
+ set song repeat to off\r return \"off\"
+ end if\r\
+ end tell\r\
+end"))))
+ (if (interactive-p) (message res))
+ res
+ ))
+
+;;;###autoload
+(defun itunes-select-itunes (str)
+ "Tell iTunes to select and play a set of tunes. Syntax is
+applescript with some abbreviations:
+
+ 'a artist
+ 't name
+ 'c composer
+ 'y year
+ ~ contains
+ 'op' (name op or ... or composer op)
+
+e.g.
+ 'a is \"Juno Reactor\" or ~ \"foo\"'
+"
+ (interactive "MQuery: " )
+ (let ((pl "\"From Emacs\""))
+ (dolist (kv '(("'t\\>" "name")
+ ("'a\\>" "artist")
+ ("'c\\>" "composer")
+ ("'y\\>" "year")
+ ("~" "contains")))
+ (setq str (replace-regexp-in-string (car kv) (cadr kv) str)))
+ (setq str (replace-regexp-in-string
+ "'\\([^']+\\)'"
+ "(name \\1 or artist \\1 or album \\1 or composer \\1)"
+ str))
+ (message "playing %s" str)
+ (do-applescript (concat
+ "tell application \"iTunes\"\r\
+ set fixed indexing to true\r"
+ (itunes-reset-pl pl)
+ " duplicate (every track of library playlist 1 whose ("
+ str ")) to playlist " pl "\r\
+ play user playlist " pl "\r\
+end tell"))))
+
+;;;###autoload
+(defun itunes-next-track (&optional n)
+ "Go to the next track in the current playlist (or Nth next, if N given)"
+ (interactive "p")
+ (do-applescript
+ (format
+ "tell application \"iTunes\"\r\
+repeat %d times\r\
+next track\r\
+end repeat\r\
+end tell" (or n 1))))
+
+;;;###autoload
+(defun itunes-previous-track (&optional n)
+ "Go to the previous track in the current playlist (or Nth
+previous, if N given)"
+ (interactive "p")
+ (do-applescript
+ (format
+ "tell application \"iTunes\"\r\
+repeat %d times\r\
+back track\r\
+end repeat\r\
+end tell" (or n 1))))
+
+;;;###autoload
+(defun itunes-volume-up (&optional n)
+ "Increase the volume by N (or 5%)"
+ (interactive "P")
+ (itunes-changev (or n 5)))
+
+;;;###autoload
+(defun itunes-volume-down (&optional n)
+ "Increase the volume by N (or 5%)"
+ (interactive "P")
+ (itunes-changev (- (or n 5))))
+
+;;;###autoload
+(defun itunes-playpause (&optional name)
+ (interactive)
+ (do-applescript
+ (if name
+ (concat "tell application \"iTunes\" to play user playlist \""
+ name "\"")
+ "tell application \"iTunes\" to playpause")))
+
+;;;###autoload
+(defun itunes-get-current ()
+ (interactive)
+ (destructuring-bind (name artist album)
+ (applescript-list (do-applescript
+ "tell application \"iTunes\" to get {name,artist,album} of current track"))
+ (if (interactive-p)
+ (message "%s, %s (%s)" artist name album)
+ (list name artist album))))
+
+(defun itunes-manip ()
+ "A pseudo-\"iTunes control mode\", providing single-key control
+from the minibuffer:
+
+ i,k up, down volume
+ j,l next, previous track
+ s,/ select new set of songs
+ p choose named playlist
+ <spc> play/pause
+ h help
+"
+ (interactive)
+ (catch 'itunes-done
+ (while t
+ (case (read-char (apply #'format "%s, %s (%s) ['h' for help]"
+ (itunes-get-current)))
+ (?l (itunes-next-track))
+ (?j (itunes-previous-track))
+ (?i (itunes-volume-up))
+ (?k (itunes-volume-down))
+ (?p (call-interactively 'itunes-select-playlist))
+ ((?s ?/) (call-interactively #'itunes-select))
+ (?h (save-excursion
+ (switch-to-buffer-other-window
+ (get-buffer-create "*itunes-help*"))
+ (delete-region (point-min) (point-max))
+ (insert (documentation 'itunes-manip))))
+ (?\ (itunes-playpause))
+ (t (throw 'itunes-done nil))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;_ - General support code
+
+(defvar itunes-playlists nil)
+
+(defun itunes-get-playlists ()
+ (or itunes-playlists
+ (setq itunes-playlists
+ (applescript-list
+ (do-applescript "tell application \"iTunes\" to return name of every playlist")))))
+
+(defun itunes-update-playlist (pl ids)
+ (message "Playing %s" ids)
+ (do-applescript (concat "\
+tell application \"iTunes\"\r\
+ set fixed indexing to true\r"
+ (itunes-reset-pl pl) "\
+ set pl to user playlist \"" pl "\"\r\
+ repeat with i in " (applescript-write ids) "\r\
+ duplicate (first track of library playlist 1 whose database ID is i) to pl\r\
+ end repeat\r\
+end tell")))
+
+(defun itunes-reset-pl (pl)
+ (concat
+ " if ({\"" pl "\"} is in name of (user playlists)) then\r\
+ delete user playlist \"" pl "\"\r\
+ end if\r\
+ make new user playlist with properties { name: \"" pl "\" }\r"))
+
+(defun itunes-track (&optional name attr)
+ (applescript-list
+ (do-applescript (concat "tell application \"iTunes\" to get "
+ (or attr "name") " of "
+ (or name "current track")))))
+
+(defun itunes-playlist (&optional name attr)
+ (applescript-list
+ (do-applescript (concat "tell application \"iTunes\" to get "
+ (or attr "name") " of every track of "
+ (or name "current playlist")))))
+
+(defun itunes-changev (n)
+ (do-applescript (format
+ "tell application \"iTunes\" to set sound volume to (sound volume) + %d"
+ n)))
+
+(defsubst assocd (k l &optional def)
+ (let ((res (assoc k l)))
+ (if res (cdr res) def)))
+
+(defun applescript-list (str)
+ "Read in an applescript expression, translating into a lisp
+sexp."
+ (split-string (substring str 0 -1) ", " t))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;_ - features that require XML parsing (which is painfully slow!).
+
+(when (and (boundp 'itunes-external-playlist)
+ (eq itunes-external-playlist 'xml))
+ (require 'xml)
+ (require 'cl)
+
+ (defun my-parse-xml (beg end)
+ (interactive "r")
+ (when (< end beg)
+ (let ((tmp beg))
+ (setq beg end)
+ (setq end tmp)))
+ (labels ((cleanup (node)
+ (cond
+ ((not node) nil)
+ ((stringp node)
+ (if (string-match "^[ \t\r\n\f]+$" node)
+ nil
+ (substring-no-properties node)))
+ ((listp node)
+ (destructuring-bind (name attrs &rest rest) node
+ (cons name (mapcan (lambda (x)
+ (let ((xx (cleanup x)))
+ (if xx (list xx) nil)))
+ rest))))
+ (t (list 'unknown (type-of node) node)))))
+ (let ((res (cleanup (car (xml-parse-region beg end)))))
+ res)))
+
+ (defun parse-plist (beg end)
+ (labels ((parse-thing (thing)
+ (ecase (car thing)
+ (dict (parse-kvs (cdr thing)))
+ ((key string) (cadr thing))
+ (integer (car (read-from-string (cadr thing))))
+ (plist (parse-thing (cadr thing)))
+ (t (cons 'unknown thing))))
+ (parse-kvs (kvs)
+ (let ((res nil))
+ (while kvs
+ (let ((k (car kvs))
+ (v (cadr kvs)))
+ (setq kvs (cddr kvs))
+ (push (cons (parse-thing k) (parse-thing v))
+ res)))
+ (nreverse res))))
+ (parse-thing (my-parse-xml beg end))))
+
+ (defvar itunes-library-file "~/Music/iTunes/iTunes Music Library.xml")
+ (defvar itunes-library nil)
+ (defun itunes-cleanup ()
+ (dolist (x (itunes-song-list))
+ (setf (car x) (string-to-number (car x)))
+ (dolist (y (cdr x))
+ (setf (car y)
+ (assocd (car y) '(("Track ID" . id)
+ ("Name" . title)
+ ("Artist" . artist)
+ ("Album" . album)
+ ("Track Number" . track)
+ ("Location" . file))
+ (car y))))))
+
+ (defun itunes-load-library ()
+ (interactive)
+ (with-temp-buffer
+ (insert-file-contents itunes-library-file)
+ (setq itunes-library (parse-plist (point-min) (point-max)))
+ (itunes-cleanup)))
+
+ (defun itunes-song-list ()
+ (cdr (nth 4 itunes-library)))
+
+ (defun itunes-library-playlist ()
+ (cdr (assoc "Library" (nth 5 itunes-library))))
+
+ (defun itunes-search (v)
+ (let ((lib (itunes-song-list))
+ (ids nil)
+ (nsongs 0))
+ (dolist (x lib)
+ (let ((id (car x))
+ (kvs (cdr x)))
+ (incf nsongs)
+ (if (rassoc v kvs)
+ (push id ids))))
+ (message "(%s) %d songs." ids nsongs)
+ ids))
+
+ ;; (defun itunes-select (v)
+ ;; (interactive "MMatch: ")
+ ;; (itunes-update-playlist "From Emacs 2" (itunes-search v)))
+
+ (defun itunes-track-number (n)
+ (let ((lib (itunes-song-list)))
+ (cdr (assoc (format "%d" n) lib))))
+ ) ;; End XML-based code
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;_ - MySQL-based alternative to itunes' playlist selection
+(when (and (boundp 'itunes-external-playlist)
+ (eq itunes-external-playlist 'mysql))
+
+ (defvar itunes-sql-db "music.music")
+ (defvar itunes-sql-playlist-file "~/.emacs.d/playlist.el")
+ (defvar itunes-db-proc nil)
+ (defvar itunes-sql-user "root")
+ (defvar itunes-sql-passwd "foo9bar")
+ (defvar itunes-query-history nil)
+
+ (defun itunes-db-send (query)
+ (unless (and itunes-db-proc
+ (processp itunes-db-proc)
+ (eq (process-status itunes-db-proc) 'run))
+ (let ((process-connection-type t))
+ (setq itunes-db-proc (start-process "iTunes DB" nil
+ "/usr/local/mysql/bin/mysql"
+ "-u" itunes-sql-user
+ (concat "-p" itunes-sql-passwd)
+ "-h" "localhost"
+ "-B")))
+ (set-process-filter
+ itunes-db-proc
+ (lambda (proc str)
+ (if (string-match "^select.*;\r?\n?$" str)
+ (message "ignoring cursed echo")
+ (itunes-update-playlist "Emacs SQL" (cdr (split-string str)))))))
+ (message "itunes query: %s" query)
+ (process-send-string itunes-db-proc query))
+
+ (defun itunes-expand-query (str)
+ (dolist (kv '(("'[tn]\\>" "name")
+ ("'a\\>" "artist")
+ ("'[Al]\\>" "album")
+ ("'[Tr]\\>" "track")
+ ("'c\\>" "composer")
+ ("'y\\>" "year")
+ ("~" "rlike")))
+ (setq str (replace-regexp-in-string (car kv) (cadr kv) str)))
+ str)
+
+;;;###autoload
+ (defun itunes-select (q)
+ "Select tracks using a raw SQL query."
+ (interactive
+ (list (read-from-minibuffer "Query: " nil nil nil itunes-query-history)))
+ (itunes-db-send (concat "select id from " itunes-sql-db " where "
+ (itunes-expand-query q)
+ ";\n")))
+
+;;;###autoload
+ (defun itunes-name-last-query (name)
+ "Create a name for the last SQL query you executed."
+ (interactive "sName: ")
+ (unless itunes-query-history
+ (error "No last query."))
+ (let ((res (assoc name itunes-presets))
+ (q (car itunes-query-history)))
+ (if res
+ (setf (cdr res) q)
+ (progn
+ (push (cons name q) itunes-presets)
+ (itunes-update-playlists)))))
+
+;;;###autoload
+ (defun itunes-update-db ()
+ (interactive)
+ (shell-command
+ (concat (expand-file-name "~/src/perl/itunes-export.pl") " &")))
+
+ (defun itunes-update-playlists ()
+ (with-temp-buffer
+ (prin1 itunes-presets (current-buffer))
+ (write-region (point-min) (point-max) itunes-sql-playlist-file)))
+
+ (defvar itunes-presets
+ (with-temp-buffer
+ (insert-file-contents itunes-sql-playlist-file)
+ (goto-char (point-min))
+ (read (current-buffer)))
+ "* Some predefined queries.")
+
+ (defun itunes-playlist-preset (s)
+ "Choose a playlist based on a previously-named query."
+ (interactive (list (completing-read "Playlist: " itunes-presets)))
+ (itunes-playlist-sql s)
+ (itunes-playpause "Emacs SQL"))
+ ) ;;; end SQL-based playlists
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; iTunes "tell me what's playing" action
+
+(defvar itunes-tracker nil)
+(defvar itunes-current-track nil)
+
+(defun itunes-toggle-tracker ()
+ (interactive)
+ (cond
+ (itunes-tracker
+ (message "Stopping tracker")
+ (cancel-timer itunes-tracker)
+ (setq itunes-tracker nil))
+ (t
+ (message "Starting tracker")
+ (setq itunes-tracker
+ (run-with-timer 20 20
+ (lambda ()
+ (let ((res (itunes-get-current)))
+ (unless (equal res itunes-current-track)
+ (setq itunes-current-track res)
+ (message "%s, %s (%s)" (second res)
+ (first res) (third res))))))))))
+
+(provide 'itunes)
+;;; itunes.el ends here
Please sign in to comment.
Something went wrong with that request. Please try again.