Various mpd updates and improvements.
authorSteve Youngs <steve@sxemacs.org>
Sun, 18 Aug 2013 04:44:47 +0000 (14:44 +1000)
committerSteve Youngs <steve@sxemacs.org>
Sun, 18 Aug 2013 04:46:28 +0000 (14:46 +1000)
This changeset primarily adds inserting coverart into the balloon-help
buffer, but it also cleans up the code and fixes a couple of tiny bugs
and prevents bytecompiler warnings.

* mpd.el (mpd-start-connection): Add #'mpd-clear-variables to
mpd-before-variables-update-hook.
(mpd-disconnect): Remove it here.
(define-mpd-command): Improve docstring.
(mpd-state): New.  Returns the value of `**mpd-var-state*'.
(mpd-stopped-p, mpd-paused-p): Use it.
(mpd-volume): New. Returns the current volume as int.
(mpd-muted-p): New.  Return t when volume is zero.
(mpd-volume-up): Use #'mpd-volume, don't run any hooks.
(mpd-volume-down): Ditto.
(mpd-volume-mute): Define with `mpd-define-command' to have hooks
run.  Use #'mpd-volume.
(mpd-volume-min): Use #'mpd-volume, improve docstring.
(mpd-songpos): Prevent bytecompiler warnings.
(mpd-seek): Ditto.
(mpd-seek-forward, mpd-seek-backward): Add docstring.
(mpd-file): New. Return a mpd track filename.
(mpd-cover-file): New. Returns a possible coverart filename.
(mpd-has-cover-p): New.  Returns t when coverart exists
for the current track.
(mpd-scale-cover): New.  Scales coverart image.
(mpd-update-cover): Rewrite with #'mpd-file, #'mpd-cover-file,
#'mpd-has-cover-p, #'mpd-scale-cover
(mpd-clear-variables): New.  Sets Title, Artist, Album, Genre, and
Date variables to nil when they don't exist in the metainfo.
(mpd-info): Prevent bytecompiler warnings.  Allow space for
coverart in balloon when necessary.
(mpd-balloon-cover): New.  Inserts coverart into mpd balloon.
(balloon-help-display-help): Advised to insert coverart.

Signed-off-by: Steve Youngs <steve@sxemacs.org>
mpd.el

diff --git a/mpd.el b/mpd.el
index c604060..f703466 100644 (file)
--- a/mpd.el
+++ b/mpd.el
@@ -161,8 +161,8 @@ Set `mpd-process' by side effect."
     (set-process-filter mpd-process 'mpd-process-filter)
     (set-process-sentinel mpd-process 'mpd-process-sentinel)
     (process-kill-without-query mpd-process)
-
     (add-hook 'mpd-after-command-hook #'mpd-update-variables)
+    (add-hook 'mpd-before-variables-update-hook #'mpd-clear-variables)
     (setq mpd-itimer
           (start-itimer "mpd-vars-update" #'mpd-update-variables
                         mpd-update-rate mpd-update-rate))))
@@ -176,6 +176,7 @@ frame."
   (let ((proc (get-process (process-name mpd-process)))
        (timer (get-itimer (itimer-name mpd-itimer))))
     (remove-hook 'mpd-after-command-hook #'mpd-update-variables)
+    (remove-hook 'mpd-before-variables-update-hook #'mpd-clear-variables)
     (when (itimerp timer)
       (delete-itimer timer))
     (when (process-live-p proc)
@@ -195,34 +196,9 @@ frame."
 (defvar mpd-music-directory (mpd-music-directory)
   "The music directory.")
 
-;; mpd variables
 (defvar mpd-zero-vars-p t)
 (defvar mpd-status-update-p nil)
 
-(defvar **mpd-var-Album* nil)
-(defvar **mpd-var-Artist* nil)
-(defvar **mpd-var-Date* nil)
-(defvar **mpd-var-Genre* nil)
-(defvar **mpd-var-Id* nil)
-(defvar **mpd-var-Pos* nil)
-(defvar **mpd-var-Time* nil)
-(defvar **mpd-var-Title* nil)
-(defvar **mpd-var-Track* nil)
-(defvar **mpd-var-audio* nil)
-(defvar **mpd-var-bitrate* nil)
-(defvar **mpd-var-file* nil)
-(defvar **mpd-var-length* nil)
-(defvar **mpd-var-playlist* nil)
-(defvar **mpd-var-playlistlength* nil)
-(defvar **mpd-var-random* nil)
-(defvar **mpd-var-repeat* nil)
-(defvar **mpd-var-song* nil)
-(defvar **mpd-var-songid* nil)
-(defvar **mpd-var-state* nil)
-(defvar **mpd-var-time* nil)
-(defvar **mpd-var-volume* nil)
-(defvar **mpd-var-xfade* nil)
-
 (defvar mpd-pre-mute-volume nil
   "Holds the value of `**mpd-var-volume* prior to muting.
 The purpose of this is so that when you unmute, it goes back to the
@@ -233,7 +209,10 @@ volume you had it set to before you muted.")
 Useful to use in `mpd-after-command-hook' hooks.")
 
 (defmacro define-mpd-command (cmd args &rest body)
-  "Define new mpd command."
+  "Define new mpd command.
+
+This will run the hooks in `mpd-after-command-hook' while
+`mpd-this-command' is let-bound to the name of the command."
   `(defun ,cmd ,args
      ,@body
      (let ((mpd-this-command ',cmd))
@@ -248,55 +227,60 @@ FORMAT and ARGS are passed directly to `format' as arguments."
       (mpd-start-connection)
       (process-send-string mpd-process string))))
 
+(defun mpd-state ()
+  "Return the current state of mpd as a string.
+
+Possible values are: \"play\", \"pause\", and \"stop\"."
+  (and-boundp '**mpd-var-state*
+    **mpd-var-state*))
+
 (defun mpd-stopped-p ()
-  (string= **mpd-var-state* "stop"))
+  "Return t if the music is stopped."
+    (string= (mpd-state) "stop"))
+
 (defun mpd-paused-p ()
-  (string= **mpd-var-state* "pause"))
-(defun mpd-muted-p ()
-  (zerop (string-to-number **mpd-var-volume*)))
+  "Return t if the music is paused."
+    (string= (mpd-state) "pause"))
 
-;; (mpd-songpos)
-(defun mpd-songpos ()
-  (if **mpd-var-time*
-      (destructuring-bind (a b)
-          (split-string **mpd-var-time* ":")
-        (cons (string-to-int a) (string-to-int b)))
-    (cons 0 1)))                        ; todo?
+;; Volume
+(defun mpd-volume ()
+  "Return the current mpd volume as an integer."
+  (or (and-boundp '**mpd-var-volume*
+       (string-to-int **mpd-var-volume*))
+      0))
+
+(defun mpd-muted-p ()
+  "Return t when the volume is muted."
+  (zerop (mpd-volume)))
 
 (defun mpd-volume-up (step)
   "Increase the volume by STEP increments.
 STEP can be given via numeric prefix arg and defaults to 1 if omitted."
   (interactive "p")
-  (let* ((oldvol (string-to-number **mpd-var-volume*))
-        (newvol (+ oldvol step))
-        (mpd-this-command 'mpd-volume-down))
+  (let* ((oldvol (mpd-volume))
+        (newvol (+ oldvol step)))
     (when (>= newvol 100)
       (setq newvol 100))
-    (mpd-send "setvol %d" newvol)
-    (run-hooks 'mpd-after-command-hook)))
+    (mpd-send "setvol %d" newvol)))
 
 (defun mpd-volume-down (step)
   "Decrease the volume by STEP increments.
 STEP can be given via numeric prefix arg and defaults to 1 if omitted."
   (interactive "p")
-  (let* ((oldvol (string-to-number **mpd-var-volume*))
-        (newvol (- oldvol step))
-        (mpd-this-command 'mpd-volume-down))
+  (let* ((oldvol (mpd-volume))
+        (newvol (- oldvol step)))
     (when (<= newvol 0)
       (setq newvol 0))
-    (mpd-send "setvol %d" newvol)
-    (run-hooks 'mpd-after-command-hook)))
+    (mpd-send "setvol %d" newvol)))
 
-(defun mpd-volume-mute (&optional unmute)
+(define-mpd-command mpd-volume-mute (&optional unmute)
   "Mute the volume.
 With prefix arg, UNMUTE, let the tunes blast again."
   (interactive "P")
   (if unmute
       (mpd-send "setvol %s" mpd-pre-mute-volume)
-    (setq mpd-pre-mute-volume **mpd-var-volume*)
-    (mpd-send "setvol 0"))
-  (let ((mpd-this-command 'mpd-volume-mute))
-    (run-hooks 'mpd-after-command-hook)))
+    (setq mpd-pre-mute-volume (mpd-volume))
+    (mpd-send "setvol 0")))
 
 (defun mpd-volume-mute/unmute ()
   "Wrapper around #'mpd-volume-mute to mute and unmute."
@@ -312,24 +296,36 @@ With prefix arg, UNMUTE, let the tunes blast again."
 
 (define-mpd-command mpd-volume-min ()
   "Set volume to minimum.
-Sets state to \"muted\" by side effect."
+Mutes the mpd audio by side effect."
   (interactive)
-  (setq mpd-pre-mute-volume **mpd-var-volume*)
+  (setq mpd-pre-mute-volume (mpd-volume))
   (mpd-send "setvol 0"))
 
+;; Seek
+(defun mpd-songpos ()
+  "Return position in song as a cons of elapsed and total seconds."
+  (and-boundp '**mpd-var-time*
+    (destructuring-bind (a b)
+       (split-string **mpd-var-time* ":")
+      (cons (string-to-int a) (string-to-int b)))))
+
 (define-mpd-command mpd-seek (time)
   "Seek current track to TIME."
-  (mpd-send "seekid %s %d" **mpd-var-Id* (+ (car (mpd-songpos)) time)))
+  (and-boundp '**mpd-var-Id*
+    (mpd-send "seekid %s %d"
+             **mpd-var-Id* (+ (car (mpd-songpos)) time))))
 
 (defun mpd-seek-forward ()
+  "Seek forward 10 seconds."
   (interactive)
   (mpd-seek 10))
 
 (defun mpd-seek-backward ()
+  "Seek backward 10 seconds."
   (interactive)
   (mpd-seek -10))
 
-;; Plaing operations
+;; Playing operations
 (define-mpd-command mpd-next-track ()
   "Start playing next track."
   (interactive)
@@ -395,33 +391,66 @@ Sets state to \"muted\" by side effect."
 This differs from `**mpd-var-file*' in that it is only updated once
 per track change instead of every time `mpd-itimer' fires.")
 
+(defun mpd-file ()
+  "Return the file name of current track from mpd.
+
+Note that this is just what is reported by mpd.  To perform operations
+on the file on disc you will need to prepend `mpd-music-directory' to
+it first."
+  (and-boundp '**mpd-var-file*
+    **mpd-var-file*))
+
+(defun mpd-cover-file ()
+  "Return a possible coverart filename.
+
+The file may not exist on disc so call `file-exists-p' on it, or see
+`mpd-has-cover-p'."
+  (let ((dir (paths-construct-path
+             (list mpd-music-directory (file-dirname (mpd-file))))))
+    (expand-file-name "cover.jpg" dir)))
+
+(defun mpd-has-cover-p ()
+  "Return t when coverart exists for the current track."
+  (file-exists-p (mpd-cover-file)))
+
+(defun mpd-scale-cover (cover height &optional width)
+  "Scale image, COVER to HEIGHT x WIDTH.
+
+Argument COVER is the image filename.  No checks are made on its
+existence, or even if it is an image file \(only JPEG is supported
+incidently\).  So you should do some rudimentary checks before
+calling this.  The file on disc is left unchanged.
+
+Argument HEIGHT is the height in pixels to scale the image to.
+
+Optional argument WIDTH is the width in pixels to scale the image to.
+If omitted it defaults to HEIGHT.
+
+A string is returned that can be used in the :data key of `make-glyph'."
+  (with-temp-buffer
+    (shell-command
+     (format (concat "jpegtopnm " "'" cover "' 2>/dev/null"
+                    "|pnmnorm 2>/dev/null"
+                    "|pnmscale -height %d -width %d"
+                    "|pnmtojpeg") height (or width height))
+                  'insert)
+    (buffer-string)))
+
 (defun mpd-update-cover ()
   "Updates the cover art glyph."
-  (unless (equal mpd-current-filename **mpd-var-file*)
+  (unless (equal mpd-current-filename (mpd-file))
     (with-current-buffer mpd-dock-buffer
-      (let* ((songdir (file-dirname **mpd-var-file*))
-            (cover (expand-file-name
-                    "cover.jpg"
-                    (paths-construct-path
-                     (list mpd-music-directory songdir))))
-            (nocover (expand-file-name "nocover.jpg" mpd-directory))
-            (scaled))
-       (if (file-exists-p cover)
-           (progn
-             (with-temp-buffer
-               (shell-command (concat "jpegtopnm " "'" cover "' 2>/dev/null"
-                                      "|pnmnorm 2>/dev/null"
-                                      "|pnmscale -height 48 -width 48"
-                                      "|pnmtojpeg")
-                              'insert)
-               (setq scaled (buffer-string)))
-             (set-extent-end-glyph
-              mpd-cover-glyph
-              (make-glyph (list (vector 'jpeg :data scaled)))))
+      (let ((cover (mpd-cover-file))
+           (nocover (expand-file-name "nocover.jpg" mpd-directory)))
+       (if (mpd-has-cover-p)
+           (set-extent-end-glyph
+            mpd-cover-glyph
+            (make-glyph
+             (list (vector 'jpeg :data (mpd-scale-cover cover 48)))))
          (set-extent-end-glyph
           mpd-cover-glyph
           (make-glyph (list (vector 'jpeg :file nocover)))))))
-    (setq mpd-current-filename **mpd-var-file*)))
+    (setq mpd-current-filename (mpd-file))))
 
 (defun mpd-update-variables ()
   "Requests status information."
@@ -432,6 +461,16 @@ per track change instead of every time `mpd-itimer' fires.")
   (mpd-send "status")
   (mpd-update-cover))
 
+(defun mpd-clear-variables ()
+  "Clears the most relevant mpd variables."
+  (with-boundp '(**mpd-var-Title* **mpd-var-Artist* **mpd-var-Album*
+                                 **mpd-var-Genre* **mpd-var-Date*)
+    (setq **mpd-var-Title* nil
+         **mpd-var-Artist* nil
+         **mpd-var-Album* nil
+         **mpd-var-Genre* nil
+         **mpd-var-Date* nil)))
+
 \f
 ;;;; Dockapp section
 (defvar mpd-dock-frame-plist
@@ -450,18 +489,43 @@ per track change instead of every time `mpd-itimer' fires.")
   "Frame properties for mpd dock.")
 
 (defun mpd-info (&rest args)
-  (let ((title (or **mpd-var-Title* "Unknown"))
-       (artist (or **mpd-var-Artist* "Unknown"))
-       (album (or **mpd-var-Album* "Unknown"))
-       (genre (or **mpd-var-Genre* "Unknown"))
-       (year (or **mpd-var-Date* "Unknown"))
-       (file (file-name-nondirectory **mpd-var-file*)))
-    (format "--[ %s ]\n
+  "Returns a string for use in the mpd balloon-help frame."
+  (with-boundp '(**mpd-var-Title* **mpd-var-Artist* **mpd-var-Album*
+                                 **mpd-var-Genre* **mpd-var-Date*)
+    (let ((title (or **mpd-var-Title* "Unknown"))
+         (artist (or **mpd-var-Artist* "Unknown"))
+         (album (or **mpd-var-Album* "Unknown"))
+         (genre (or **mpd-var-Genre* "Unknown"))
+         (year (or **mpd-var-Date* "Unknown"))
+         (file (file-name-nondirectory (mpd-file))))
+      (format (concat
+              (when (mpd-has-cover-p)
+                "\n\n")
+              "--[ %s ]\n
 Artist: %s
 Album: %s
 Year: %s  Genre: %s\n
 --[ %s ]"
-           title artist album year genre file)))
+              (when (mpd-has-cover-p)
+                "\n\n\n\n\n\n\n\n"))
+             title artist album year genre file))))
+
+(defun mpd-balloon-cover ()
+  "Inserts coverart into mpd balloon."
+  (let ((cover (mpd-cover-file)))
+    (when (mpd-has-cover-p)
+      (set-extent-begin-glyph
+       (make-extent (point-min) (point-min))
+       (make-glyph
+       (list (vector 'jpeg :data (mpd-scale-cover cover 128))))))))
+
+(defadvice balloon-help-display-help (after mpd-balloon-cover (&rest args) activate)
+  "Display cover art image in the balloon."
+  (when (process-live-p (get-process "mpd"))
+    (set-buffer balloon-help-buffer)
+    (goto-char (point-max))
+    (and (re-search-backward (file-name-nondirectory (mpd-file)) nil t)
+        (mpd-balloon-cover))))
 
 (defconst mpd-prev-map
   (let* ((map (make-sparse-keymap 'mpd-prev-map)))