This is my first post here, and I'm kind of a rookie elisper, so any comments/suggestion or fan/hate-mail would be appreciated.
The following file is version 0.0.2 of squeeze.el which adds a bunch of squeeze box functionality to emacs. You can find the squeezebox here: http://slimdevices.com. ;;; squeeze.el --- Access your squeezebox from emacs. ;; Copyright (C) 2007 Paul Huff <[EMAIL PROTECTED]> ;; Author: Paul Huff <[EMAIL PROTECTED]> ;; Keywords: convenience ;; squeeze is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 2, or ;; {at your option} any later version. ;; squeeze 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. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING, or type `C-h C-c'. If ;; not, write to the Free Software Foundation at this address: ;; Free Software Foundation ;; 51 Franklin Street, Fifth Floor ;; Boston, MA 02110-1301 ;; USA ;;; Commentary: ;; This is version 0.0.2, so I've only tested it on my own version of emacs which is currently: ;; GNU Emacs 22.0.90.1 (i386-apple-darwin8.8.1, Carbon Version 1.6.0) of 2006-10-28 ;; Not sure if it'll work anywhere else, but it doesn't require anything apple-ish, just emacs-ish. ;; The following basic commands are implemented, currently: ;; squeeze-pause, squeeze-play, squeeze-stop, squeeze-volume-up, squeeze-volume-down ;; squeeze-next, squeeze-prev, squeeze-browse-artists, squeeze-browse-genres, squeeze-browse-artists, ;; squeeze-browse-songs, squeeze-now-playing ;; Version 0.0.2 adds squeeze-fast-forward and squeeze-rewind as well as ;; an early attempt at an ediff-like control panel. ;; |> next to an item means load as playlist, and + means add to current playlist. ;; In future versions I'd like to flesh out the navigation of the playlist, load saved playlists, allow for more than ;; one squeezebox, etc., but for right now, this satisfies my most basic needs with my squeezebox. Please feel free to send patches. ;; sourceforge.net project page: http://sf.net/projects/squeeze-el ;; Web page coming soon. ;; I put the following things in my .emacs: ;; (global-set-key [(f6)] 'squeeze-now-playing) ;; (global-set-key [(f7)] 'squeeze-volume-down) ;; (global-set-key [(f8)] 'squeeze-volume-up) ;; Additionally, I run my emacs and slimserver on the same box, so I've set slimserver-host to be localhost. You'll need to over-write that with a (setq) in your .emacs, or use the customization options. ;; I'm a newbie elisper, and I'm relatively new to my squeezebox, so please let me know if I'm a. doing things the wrong way, b. ;; making things work they way they shouldn't in elisp or slim-world. ;;; History: ;; ;;; Code: ;;;###autoload (provide 'squeeze) (require 'url-util) (require 'button) (defcustom slimserver-host "localhost" "The host your slimserver runs on" ) (defcustom slimserver-port 9090 "The port your slimserver CLI is running on") (setq squeeze-fast-forwarding nil) (setq squeeze-rewinding nil) (defun squeeze-url-encode (in_string) (let* ((parts (split-string in_string ":")) (first (nth 0 parts)) (second (cond ((< 1 (length (cdr parts))) (mapconcat 'identity (cdr parts) ":")) ((equal 1 (length (cdr parts))) (cadr parts)) (t nil)))) (if (equal second nil) (url-encode first) (concat first (url-encode ":") (url-encode second))))) (defun url-encode (string) (apply 'concat (mapcar (lambda (c) (if (or (and (>= c ?a) (<= c ?z)) (and (>= c ?A) (<= c ?Z)) (and (>= c ?0) (<= c ?9))) (string c) (format "%%%02X" c))) (encode-coding-string string 'utf-8)))) (defun squeeze-flatten (lis) "Removes nestings from a list. Thank you random site on the internet: " "http://cogsci.uwaterloo.ca/CoherenceCode/COHERE/utilities.lisp.html" (cond ((atom lis) lis) ((listp (car lis)) (append (squeeze-flatten (car lis)) (squeeze-flatten (cdr lis))) ) (t (append (list (car lis)) (squeeze-flatten (cdr lis)))))) (defun slimserver-command (command) (let* ((slimserver-process (open-network-stream "slim-connection" "slim-buffer" "localhost" 9090)) (buf (process-buffer slimserver-process)) (response "")) (process-send-string slimserver-process (concat command "\n")) (accept-process-output slimserver-process 5) (with-current-buffer buf (set-buffer-multibyte nil) (setq response (replace-regexp-in-string "\n$" "" (buffer-string))) (kill-buffer buf) (delete-process slimserver-process)) response)) (defun slimserver-command-no-decode (&rest command-parts) (let* ((flattened-command-parts (squeeze-flatten command-parts)) (slimserver-process (open-network-stream "slim-connection" "slim-buffer" "localhost" 9090)) (buf (process-buffer slimserver-process)) (command (mapconcat 'squeeze-url-encode flattened-command-parts " ")) (response "")) (message "Sending command: %s" command) (process-send-string slimserver-process (concat command "\n")) (accept-process-output slimserver-process 5) (with-current-buffer buf (set-buffer-multibyte nil) (setq response (replace-regexp-in-string "\n$" "" (buffer-string))) (kill-buffer buf) (delete-process slimserver-process)) response)) ;;A big thanks to fledermaus for helping me out. (defun url-unhex-utf-8 (in_string) (interactive) (decode-coding-string (url-unhex-string in_string t) 'utf-8)) (defun slimserver-get-player-id (which-player) (let* ((player-id-string (url-unhex-utf-8 (slimserver-command (concat "player id " (int-to-string which-player) " ?")))) (player-id-string-parts (split-string player-id-string " ")) (player-id (nth (- (length player-id-string-parts) 1) player-id-string-parts))) player-id)) ;;;###autoload (defun squeeze-now-playing () (interactive) (let* ((player-id (slimserver-get-player-id 0)) (song (replace-regexp-in-string (concat player-id " current_title ") "" (url-unhex-utf-8 (slimserver-command (concat player-id " current_title ?"))))) (artist (replace-regexp-in-string (concat player-id " artist ") "" (url-unhex-utf-8 (slimserver-command (concat player-id " artist ?"))))) (album (replace-regexp-in-string (concat player-id " album ") "" (url-unhex-utf-8 (slimserver-command (concat player-id " album ?")))))) (message "%s" (concat "Now playing: " song " from \"" album "\" - " artist)))) ;;;###autoload (defun squeeze-stop () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " stop")) (message "Sent stop command"))) ;;;###autoload (defun squeeze-on () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " power 1")) (message "Powering on player"))) ;;;###autoload (defun squeeze-off () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " power 0")) (message "Powering off player"))) ;;;###autoload (defun squeeze-volume-up () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " button volume_up")))) ;;;###autoload (defun squeeze-volume-down () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " button volume_down")))) ;;;###autoload (defun squeeze-play () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (if (or (equal squeeze-fast-forwarding t) (equal squeeze-rewinding t)) (progn (slimserver-command (concat player-id " rate 1")) (setq squeeze-fast-forwarding nil) (setq squeeze-rewinding nil))) (slimserver-command (concat player-id " play")) (message "Playing player"))) ;;;###autoload (defun squeeze-fast-forward () (interactive) (let* ((player-id (slimserver-get-player-id 0)) (current-rate (string-to-number (replace-regexp-in-string (concat player-id " rate") "" (url-unhex-utf-8 (slimserver-command (concat player-id " rate ?"))))))) (if (< current-rate 1) ;; If we're paused or rewinding, pretend that we're playing (play rate = 1 = playing...) (setq current-rate 1)) (slimserver-command (concat player-id " rate " (int-to-string (+ 1 current-rate)))) (setq squeeze-fast-forwarding t) (message "Fast forwarding"))) ;;;###autoload (defun squeeze-rewind () (interactive) (let* ((player-id (slimserver-get-player-id 0)) (current-rate (string-to-number (replace-regexp-in-string (concat player-id " rate") "" (url-unhex-utf-8 (slimserver-command (concat player-id " rate ?"))))))) (if (> current-rate 0) ;; If we're playing or fastforwarding, pretend that we're paused (play rate = 0 = pause...) (setq current-rate 0)) (slimserver-command (concat player-id " rate " (int-to-string (- current-rate 1)))) (setq squeeze-rewinding t) (message "Rewinding"))) ;;;###autoload (defun squeeze-pause () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " pause")) (message "Pausing player"))) ;;;###autoload (defun squeeze-next () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " playlist index +1")))) ;;;###autoload (defun squeeze-prev () (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " playlist index -1")))) (defun squeeze-get-genres () (interactive) (let* ((genre-count (replace-regexp-in-string "genres count:" "" (url-unhex-utf-8 (slimserver-command "genres")))) (genre-list-text (replace-regexp-in-string (concat "genres 0 " genre-count " count%3A" genre-count) "" (slimserver-command (concat "genres 0 " genre-count)))) (n 0) (genre-list-intermediate (split-string genre-list-text)) (genre-list-return '())) (progn (while (< n (string-to-number genre-count)) (setq genre-list-return (cons (cons (url-unhex-utf-8 (car genre-list-intermediate)) (url-unhex-utf-8 (cadr genre-list-intermediate))) genre-list-return)) (setq genre-list-intermediate (cddr genre-list-intermediate)) (setq n (+ n 1))) genre-list-return))) (defun squeeze-assoc-list-remove-tags (in-list) "Though it offends my sensibilities, elisp's lack of deep recursion makes me afraid to write this recursively because we might use it on a list of 2000+ songs... This function takes a assoc list and removes all the little tags that the slimserver adds to each part." (interactive) (let* ((n 0) (result-list '())) (while (< n (length in-list)) (setq result-list (cons (cons (replace-regexp-in-string "[[:word:]]+:" "" (car (nth n in-list))) (replace-regexp-in-string "[[:word:]]+:" "" (cdr (nth n in-list)))) result-list)) (setq n (+ n 1)) ) result-list)) (defun squeeze-str-cmp-p (arg1 arg2) (string< (cdr arg1) (cdr arg2))) (defun squeeze-browse-genres () (interactive) (let ((genre-list (sort (squeeze-assoc-list-remove-tags (squeeze-get-genres)) 'squeeze-str-cmp-p))) (with-output-to-temp-buffer "*Squeeze Browse*" (set-buffer "*Squeeze Browse*") (insert "Please pick a genre...\n") (mapc (lambda (c) (let ((n 4)) (insert "\n ") (insert-text-button (cdr c) 'action (lambda (b) (squeeze-browse-artists (concat "genre_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "|>" 'action (lambda (b) (squeeze-load-thingee (concat "genre_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "+" 'action (lambda (b) (squeeze-add-thingee (concat "genre_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) )) genre-list)))) (defun squeeze-get-artists (&rest qualifiers) (interactive) (let* ((qualifier-string (mapconcat 'identity (squeeze-flatten qualifiers) " ")) (encoded-qualifier-string (mapconcat 'squeeze-url-encode (squeeze-flatten qualifiers) " ")) (total-artist-count (replace-regexp-in-string "info total artists " "" (url-unhex-utf-8 (slimserver-command "info total artists ?")))) (artist-list-text (replace-regexp-in-string (concat "artists 0 " total-artist-count " " encoded-qualifier-string) "" (slimserver-command (concat "artists 0 " total-artist-count " " encoded-qualifier-string)))) (n 0) (artist-list-split (split-string artist-list-text)) (artist-list-intermediate (cdr artist-list-split)) (artist-count (replace-regexp-in-string "count:" "" (url-unhex-utf-8 (car artist-list-split)))) (artist-list-return '())) (progn (while (< n (string-to-number artist-count)) (setq artist-list-return (cons (cons (url-unhex-utf-8 (car artist-list-intermediate)) (url-unhex-utf-8 (cadr artist-list-intermediate))) artist-list-return)) (setq artist-list-intermediate (cddr artist-list-intermediate)) (setq n (+ n 1))) artist-list-return))) (defun squeeze-browse-artists (&rest args) (interactive) (let ((artist-list (if (< 0 (length args)) (sort (squeeze-assoc-list-remove-tags (squeeze-get-artists args)) 'squeeze-str-cmp-p) (sort (squeeze-assoc-list-remove-tags (squeeze-get-artists)) 'squeeze-str-cmp-p)))) (with-output-to-temp-buffer "*Squeeze Browse*" (set-buffer "*Squeeze Browse*") (insert "Please pick an artist...\n") (mapc (lambda (c) (let ((n 4)) (insert "\n ") (insert-text-button (cdr c) 'action (lambda (b) (squeeze-browse-albums (concat "artist_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "|>" 'action (lambda (b) (squeeze-load-thingee (concat "artist_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "+" 'action (lambda (b) (squeeze-add-thingee (concat "artist_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) )) artist-list)))) (defun squeeze-get-albums (&rest qualifiers) (interactive) (let* ((qualifier-string (mapconcat 'identity (squeeze-flatten qualifiers) " ")) (encoded-qualifier-string (mapconcat 'squeeze-url-encode (squeeze-flatten qualifiers) " ")) (total-artist-count (replace-regexp-in-string "info total albums " "" (url-unhex-utf-8 (slimserver-command "info total albums ?")))) (artist-list-text (replace-regexp-in-string (concat "albums 0 " total-artist-count " " encoded-qualifier-string) "" (slimserver-command (concat "albums 0 " total-artist-count " " encoded-qualifier-string)))) (n 0) (artist-list-split (split-string artist-list-text)) (artist-list-intermediate (cdr artist-list-split)) (artist-count (replace-regexp-in-string "count:" "" (url-unhex-utf-8 (car artist-list-split)))) (artist-list-return '())) (progn (while (< n (string-to-number artist-count)) (setq artist-list-return (cons (cons (url-unhex-utf-8 (car artist-list-intermediate)) (url-unhex-utf-8 (cadr artist-list-intermediate))) artist-list-return)) (setq artist-list-intermediate (cddr artist-list-intermediate)) (setq n (+ n 1))) artist-list-return))) (defun squeeze-browse-albums (&rest args) (interactive) (let ((artist-list (if (< 0 (length args)) (sort (squeeze-assoc-list-remove-tags (squeeze-get-albums args)) 'squeeze-str-cmp-p) (sort (squeeze-assoc-list-remove-tags (squeeze-get-albums)) 'squeeze-str-cmp-p)))) (with-output-to-temp-buffer "*Squeeze Browse*" (set-buffer "*Squeeze Browse*") (insert "Please pick an album...\n") (mapc (lambda (c) (let ((n 4)) (insert "\n ") (insert-text-button (cdr c) 'action (lambda (b) (squeeze-browse-songs (concat "album_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "|>" 'action (lambda (b) (squeeze-load-thingee (concat "album_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "+" 'action (lambda (b) (squeeze-add-thingee (concat "album_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) )) artist-list)))) (defun squeeze-get-songs (&rest qualifiers) (interactive) (let* ((qualifier-string (mapconcat 'identity (squeeze-flatten qualifiers) " ")) (encoded-qualifier-string (mapconcat 'squeeze-url-encode (squeeze-flatten qualifiers) " ")) (total-song-count (replace-regexp-in-string "info total songs " "" (url-unhex-utf-8 (slimserver-command "info total songs ?")))) (song-list-text (replace-regexp-in-string (concat "songs 0 " total-song-count " " encoded-qualifier-string " sort%3Atracknum tags%3A") "" (slimserver-command (concat "songs 0 " total-song-count " " encoded-qualifier-string " sort:tracknum tags:")))) (n 0) (song-list-split (split-string song-list-text)) (song-list-intermediate (cdr song-list-split)) (song-count (replace-regexp-in-string "count:" "" (url-unhex-utf-8 (car song-list-split)))) (song-list-return '())) (progn (while (< n (string-to-number song-count)) (setq song-list-return (cons (cons (url-unhex-utf-8 (car song-list-intermediate)) (url-unhex-utf-8 (cadr song-list-intermediate))) song-list-return)) (setq song-list-intermediate (cdddr song-list-intermediate)) (setq n (+ n 1))) song-list-return))) (defun squeeze-browse-songs (&rest args) (interactive) (let ((song-list (if (< 0 (length args)) (squeeze-assoc-list-remove-tags (squeeze-get-songs args)) (squeeze-assoc-list-remove-tags (squeeze-get-songs))))) (with-output-to-temp-buffer "*Squeeze Browse*" (set-buffer "*Squeeze Browse*") (insert "Please pick a song...\n") (if (< 0 (length args)) (progn (insert " ") (insert-text-button "(Play all)" 'action (lambda (b) (squeeze-load-thingee (button-get b 'expression))) 'expression (mapconcat 'identity args " ") 'follow-link t) (insert " ") (insert-text-button "(Load all)" 'action (lambda (b) (squeeze-add-thingee (button-get b 'expression))) 'expression (mapconcat 'identity args " ") 'follow-link t))) (mapc (lambda (c) (let ((n 4)) (insert "\n ") (insert-text-button (cdr c) 'action (lambda (b) (squeeze-songinfo (concat "track_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "|>" 'action (lambda (b) (squeeze-load-thingee (concat "track_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t) (insert " ") (insert-text-button "+" 'action (lambda (b) (squeeze-add-thingee (concat "track_id:" (button-get b 'expression)))) 'expression (car c) 'follow-link t 'follow-link t) )) song-list)))) (defun squeeze-insert-thingee (thingee) "Insert thingee at the begining of the playlist for player 0. It's presumed that thingee is not in need of url-encoding." (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " playlistcontrol cmd:insert " thingee)))) (defun squeeze-add-thingee (thingee) "Add thingee to the end of the playlist for player 0. It's presumed that thingee is not in need of url-encoding." (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " playlistcontrol cmd:add " thingee)))) (defun squeeze-load-thingee (thingee) "Load thingee as the playlist for player 0. It's presumed that thingee is not in need of url-encoding." (interactive) (let* ((player-id (slimserver-get-player-id 0))) (slimserver-command (concat player-id " playlistcontrol cmd:load " thingee)))) ;;;###autoload (defun squeeze-play-url (url) "Play a url." (interactive) (let* ((player-id (slimserver-get-player-id 0)) (encoded-url (squeeze-url-encode url))) (slimserver-command (concat player-id " playlist play " encoded-url)))) (defconst squeeze-control-frame-parameters1 (list '(name . "Squeeze") ;;'(unsplittable . t) '(minibuffer . nil) '(user-position . t) ; Emacs only '(vertical-scroll-bars . nil) ; Emacs only '(scrollbar-width . 0) ; XEmacs only '(scrollbar-height . 0) ; XEmacs only '(menu-bar-lines . 0) ; Emacs only '(tool-bar-lines . 0) ; Emacs 21+ only '(left-fringe . 0) '(right-fringe . 0) ;; don't lower but auto-raise '(auto-lower . nil) '(auto-raise . t) ;; make initial frame small to avoid distraction '(width . 25) '(height . 2) ;; this blocks queries from window manager as to where to put ;; squeeze's control frame. we put the frame outside the display, ;; so the initial frame won't jump all over the screen (cons 'top (if (fboundp 'squeeze-display-pixel-height) (1+ (squeeze-display-pixel-height)) 3000)) (cons 'left (if (fboundp 'squeeze-display-pixel-width) (1+ (squeeze-display-pixel-width)) 3000)) )) (defcustom squeeze-display-panel 'f "Whether squeezebox panel frame is displayed") (setq squeeze-ctrl-frame nil) ;;;###autoload (defun squeeze-toggle-panel () "Toggle the squeeze panel frame." (interactive) (let ((populate 'f)) (if (equal squeeze-ctrl-frame nil) (setq squeeze-ctrl-frame (make-frame squeeze-control-frame-parameters1)) (progn (delete-frame squeeze-ctrl-frame t) (kill-buffer "*Squeeze Panel*") (setq squeeze-ctrl-frame nil))) (if (not (equal squeeze-ctrl-frame nil)) ;;Probably a much more lispish way to do this. Perhaps roll all the rest of this in a (progn) in the first equal? (progn (select-frame squeeze-ctrl-frame) (if (equal (get-buffer "*Squeeze Panel*") nil) (setq populate 't)) (set-buffer (get-buffer-create "*Squeeze Panel*")) (if populate (progn (insert " ") (insert-text-button "<<" 'action (lambda (b) (squeeze-prev)) 'follow-link t) (insert " ") (insert-text-button "<" 'action (lambda (b) (squeeze-rewind)) 'follow-link t) (insert " ") (insert-text-button "||" 'action (lambda (b) (squeeze-pause)) 'follow-link t) (insert " ") (insert-text-button "|>" 'action (lambda (b) (squeeze-play)) 'follow-link t) (insert " ") (insert-text-button ">" 'action (lambda (b) (squeeze-fast-forward)) 'follow-link t) (insert " ") (insert-text-button ">>" 'action (lambda (b) (squeeze-next)) 'follow-link t))) (switch-to-buffer "*Squeeze Panel*"))))) _______________________________________________ gnu-emacs-sources mailing list [email protected] http://lists.gnu.org/mailman/listinfo/gnu-emacs-sources
