aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--emms-i18n.el144
-rw-r--r--emms-mark.el79
-rw-r--r--emms-mp3tag.el328
3 files changed, 534 insertions, 17 deletions
diff --git a/emms-i18n.el b/emms-i18n.el
new file mode 100644
index 0000000..4ddf54d
--- /dev/null
+++ b/emms-i18n.el
@@ -0,0 +1,144 @@
+;;; emms-i18n.el ---
+
+;; Copyright 2006 Ye Wenbin
+;;
+;; Author: wenbinye@163.com
+;; Time-stamp: <Ye Wenbin 2006-12-05 08:33:11>
+;; Version: $Id: emms-i18n.el,v 1.2 2006/12/04 13:38:17 ywb Exp $
+;; Keywords:
+;; X-URL: not distributed yet
+
+;; This program 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.
+;;
+;; This program 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 this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;; When read from process, first check the CAR part of
+;; `emms-default-coding-system', if non-nil, use this for decode, and
+;; nerver detect coding system, if nil, first call
+;; `emms-coding-dectect-functions' to get coding system, if success,
+;; decode the result, otherwise, use `emms-detect-coding-function',
+;; the emacs detect coding function, if the coding detected is not in
+;; `emms-nerver-used-coding-system', decode it, otherwise use
+;; locale-coding-system.
+;;
+;; When write send data to process, first check the CDR part of
+;; `emms-default-coding-system', if non-nil, use this to encode data,
+;; otherwise do nothing, that means use `default-process-coding-system' or
+;; `process-coding-system-alist' to encode data.
+
+;; Put this file into your load-path and the following into your ~/.emacs:
+;; (require 'emms-i18n)
+
+;;; Code:
+
+(provide 'emms-i18n)
+(eval-when-compile
+ (require 'cl))
+
+(defun emms-iconv (from to str)
+ "Convert STR from FROM coding to TO coding."
+ (if (and from to)
+ (decode-coding-string
+ (encode-coding-string str to)
+ from)
+ str))
+
+(defun emms-iconv-region (beg end from to)
+ (when (and from to)
+ (save-restriction
+ (narrow-to-region beg end)
+ (encode-coding-region (point-min) (point-max) to)
+ (decode-coding-region (point-min) (point-max) from))))
+
+(defun emms-iconv-buffer (from to &optional buf)
+ (save-excursion
+ (and buf (set-buffer buf))
+ (emms-iconv-region-1 (point-min) (point-max) from to)))
+
+(defun emms-set-default-coding-system (read-coding write-coding)
+ "Set `emms-default-coding-system'"
+ (interactive "zSet coding system for read: \nzSet coding system for write: ")
+ (setq emms-default-coding-system
+ (cons
+ (and (coding-system-p read-coding) read-coding)
+ (and (coding-system-p write-coding) write-coding)))
+ (message (concat
+ (if (car emms-default-coding-system)
+ (format "The coding system for read is %S." (car emms-default-coding-system))
+ "Good, you want detect coding system by me!")
+ (format " The coding system for write is %S."
+ (or (cdr emms-default-coding-system)
+ (cdr default-process-coding-system))))))
+
+(defun emms-call-process-to-string (program &rest args)
+ (with-temp-buffer
+ (let ((default-process-coding-system (copy-tree default-process-coding-system))
+ (process-coding-system-alist nil) exit)
+ (setcar default-process-coding-system (car emms-default-coding-system))
+ (setq exit (apply 'call-process (append (list program nil t nil) args)))
+ (when (and (zerop exit) (null (car emms-default-coding-system)))
+ (setq coding (emms-detect-buffer-coding-system))
+ (decode-coding-region (point-min) (point-max) coding))
+ (and (zerop exit) (buffer-string)))))
+
+(defun emms-call-process (program &rest args)
+ (with-temp-buffer
+ (if (cdr emms-default-coding-system)
+ (let ((default-process-coding-system emms-default-coding-system)
+ (process-coding-system-alist nil))
+ (apply 'call-process (append (list program nil t nil) args)))
+ (apply 'call-process (append (list program nil t nil) args)))))
+
+(defvar emms-nerver-used-coding-system
+ '(raw-text undecided)
+ "If the `emms-coding-dectect-functions' return coding system in
+this list, use `emms-default-coding-system' instead.")
+
+(defvar emms-coding-system-for-read 'utf-8
+ "If coding detect failed, use this for decode")
+
+(defvar emms-default-coding-system nil
+ "If non-nil, used for decode and encode")
+
+(defvar emms-coding-dectect-functions nil
+ "A list of function to call to detect codings")
+
+(defvar emms-detect-max-size 10000
+ "Max bytes to detect coding system. Nil mean scan whole buffer.")
+
+(defun emms-detect-coding-function (size)
+ (detect-coding-region (point)
+ (+ (if (null emms-detect-max-size)
+ size
+ (min size emms-detect-max-size))
+ (point)) t))
+
+(defun emms-detect-buffer-coding-system (&optional buf)
+ "Before call this function, make sure the buffer is literal"
+ (let ((size (buffer-size))
+ (func (append emms-coding-dectect-functions 'emms-detect-coding-function))
+ coding)
+ (save-excursion
+ (and buf (set-buffer buf))
+ (goto-char (point-min))
+ (when (> size 0)
+ (setq coding (run-hook-with-args-until-success 'func size))
+ (if (member (coding-system-base coding) emms-nerver-used-coding-system)
+ (setq coding (emms-detect-coding-function size))))
+ (if (or (null coding) (member (coding-system-base coding) emms-nerver-used-coding-system))
+ emms-coding-system-for-read
+ coding))))
+
+;;; emms-i18n.el ends here
diff --git a/emms-mark.el b/emms-mark.el
index bd9814f..17303bd 100644
--- a/emms-mark.el
+++ b/emms-mark.el
@@ -1,10 +1,10 @@
-;;; emms-mark.el.gz ---
+;;; emms-mark.el ---
;; Copyright 2006 Ye Wenbin
;;
;; Author: wenbinye@163.com
-;; Time-stamp: <Ye Wenbin 2006-11-21 17:02:28>
-;; Version: $Id: emms-mark.el,v 0.0 <2006-11-21 14:06:38> ywb Exp $
+;; Time-stamp: <Ye Wenbin 2006-12-04 22:54:28>
+;; Version: $Id: emms-mark.el,v 1.3 2006/12/04 14:54:33 ywb Exp $
;; Keywords:
;; X-URL: not distributed yet
@@ -36,17 +36,48 @@
(eval-when-compile
(require 'cl))
+(defvar emms-mark-track-desc-functions
+ '(emms-track-simple-description
+ emms-info-track-description)
+ "A list of track description function. If you want emms support
+mark, you should add your favorite track description function to this
+list and use `emms-mark-select-desc-function' to set the new track
+description function.")
+
+(defvar emms-mark-selected-desc-function
+ emms-track-description-function)
+
;;{{{ set new description-function
(defun emms-mark-track-description (track)
"Return a description of the current track."
- (concat " " ; for mark char
- (let ((artist (emms-track-get track 'info-artist))
- (title (emms-track-get track 'info-title)))
- (if (and artist title)
- (format "%s - %s" artist title)
- (emms-track-simple-description track)))))
+ (assert (not (eq emms-mark-selected-desc-function
+ 'emms-mark-track-description))
+ nil "Should never set emms-mark-selected-desc-function to emms-mark-track-description.")
+ (concat " " (funcall emms-mark-selected-desc-function track)))
(setq emms-track-description-function 'emms-mark-track-description)
+
+(defun emms-mark-select-desc-function (func)
+ (interactive
+ (list (intern
+ (completing-read "Set description function to: "
+ (mapcar 'list
+ emms-mark-track-desc-functions) nil
+ t "emms-"))))
+ (setq emms-mark-selected-desc-function func
+ emms-track-description-function 'emms-mark-track-description)
+ (emms-with-inhibit-read-only-t
+ (save-excursion
+ (dolist (buf (remove-if-not 'buffer-live-p
+ (emms-playlist-buffer-list)))
+ (set-buffer buf)
+ (let ((tracks (nreverse
+ (emms-playlist-tracks-in-region (point-min)
+ (point-max)))))
+ (erase-buffer)
+ (emms-with-inhibit-read-only-t
+ (mapc 'emms-playlist-insert-track
+ tracks)))))))
;;}}}
;;{{{ functions to mark tracks
@@ -121,24 +152,38 @@
(emms-mark-track)
(emms-mark-unmark-track))
(forward-line 1)))))
+
+(defsubst emms-mark-has-markedp ()
+ (save-excursion
+ (goto-char (point-min))
+ (re-search-forward (format "^[%c]" emms-mark-char) nil t)))
+
;;}}}
;;{{{ functions to operate marked tracks
-(defun emms-mark-do-with-marked-track (func)
- (let ((regexp (format "^[%c]" emms-mark-char)))
+(defun emms-mark-do-with-marked-track (func &optional move)
+ "If your function don't move forward, set move to non-nil."
+ (let ((regexp (format "^[%c]" emms-mark-char))
+ (newfunc func))
+ (if move
+ (setq newfunc (lambda () (funcall func) (forward-line 1))))
(save-excursion
(goto-char (point-min))
(while (re-search-forward regexp nil t)
(backward-char 1) ; move to beginning of line
- (funcall func)))))
-
-(defun emms-mark-mapcar-marked-track (func)
- (let (result)
+ (funcall newfunc)))))
+
+(defun emms-mark-mapcar-marked-track (func &optional move)
+ (let ((regexp (format "^[%c]" emms-mark-char))
+ result (newfunc func))
+ (if move
+ (setq newfunc (lambda () (let ((res (funcall func)))
+ (forward-line 1) res))))
(save-excursion
(goto-char (point-min))
- (while (re-search-forward "^[*]" nil t)
+ (while (re-search-forward regexp nil t)
(backward-char 1) ; move to beginning of line
- (setq result (cons (funcall func) result)))
+ (setq result (cons (funcall newfunc) result)))
(nreverse result))))
(defun emms-mark-delete-marked-tracks ()
diff --git a/emms-mp3tag.el b/emms-mp3tag.el
new file mode 100644
index 0000000..8761292
--- /dev/null
+++ b/emms-mp3tag.el
@@ -0,0 +1,328 @@
+;;; emms-mp3tag.el ---
+
+;; Copyright 2006 Ye Wenbin
+;;
+;; Author: wenbinye@163.com
+;; Time-stamp: <Ye Wenbin 2006-12-05 14:52:46>
+;; Version: $Id: emms-mp3tag.el,v 1.5 2006/12/05 00:57:14 ywb Exp ywb $
+;; Keywords:
+;; X-URL: not distributed yet
+
+;; This program 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.
+;;
+;; This program 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 this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;;
+
+;; Put this file into your load-path and the following into your ~/.emacs:
+;; (require 'emms-mp3tag)
+
+;;; Code:
+
+(provide 'emms-mp3tag)
+(eval-when-compile
+ (require 'cl))
+(require 'emms)
+(require 'emms-info-mp3info)
+(require 'emms-playlist-mode)
+(require 'emms-i18n)
+(require 'emms-mark)
+(require 'format-spec)
+
+(defvar emms-mp3tag-tags
+ '((info-artist . "a")
+ (info-title . "t")
+ (info-album . "l")
+ (info-tracknumber . "n")
+ (info-year . "y")
+ (info-genre . "g")
+ (info-note . "c")))
+
+(defvar emms-mp3tag-edit-buffer "*EMMS-TAGS*"
+ "Buffer name to edit mp3 tags")
+(defvar emms-mp3tag-log-buffer "*EMMS-LOG*"
+ "Buffer name of mp3tag edit log")
+
+(defvar emms-mp3tag-format
+ `(("default"
+ ,(format "%%m\n%s\n\n"
+ (mapconcat
+ (lambda (tag)
+ (concat (propertize (format "%-16s = " (symbol-name (car tag)))
+ 'read-only t 'rear-nonsticky t)
+ "%" (cdr tag)))
+ (append '((name . "f")) emms-mp3tag-tags) "\n"))
+ emms-mp3tag-default-parser))
+ "The contents of this variable should look like:
+ ((NAME FORMATER PARSER) ...)
+
+The NAME is use to select proper format, and FORMATER should be a
+string or a function. When it is a string, it can use specification
+as:
+ m -- Track description
+ f -- Track name
+ a -- Track info-artist
+ t -- Track info-title
+ l -- Track info-album
+ n -- Track info-tracknumber
+ y -- Track info-year
+ g -- Track info-genre
+ c -- Track info-note
+
+When it is a function, it recept a parameter, the track, and should
+return a string that to insert to `emms-mp3tag-edit-buffer'.
+
+The PARSER is a function to collect all track info in
+`emms-mp3tag-edit-buffer'. It should return the new tracks. If
+the track tag changed, it should add a new property tag-modified
+and set to non-nil. If the track name change, it should set new
+newname to the new file name.
+")
+
+(defvar emms-mp3tag-selected-format
+ (assoc "default" emms-mp3tag-format))
+
+(defun emms-mp3tag-select-format (format)
+ (interactive
+ (list (completing-read "Set edit format to: "
+ emms-mp3tag-format nil t)))
+ (setq emms-mp3tag-select-format (assoc "default" emms-mp3tag-format)))
+
+(defun emms-mp3tag-format-track (format track)
+ (if (stringp format)
+ (format-spec
+ format
+ (apply 'format-spec-make
+ ?m (emms-propertize (emms-track-force-description track)
+ 'face 'emms-playlist-track-face
+ 'emms-track (copy-sequence track))
+ ?f (emms-track-name track)
+ (apply 'append
+ (mapcar
+ (lambda (pair)
+ (list (aref (cdr pair) 0)
+ (or (emms-track-get track (car pair)) "")))
+ emms-mp3tag-tags))))
+ (funcall format track)))
+
+(defun emms-mp3tag-track-at (&optional pos)
+ (let ((track (emms-playlist-track-at pos))
+ newtrack)
+ (setq newtrack (copy-sequence track))
+ (emms-track-set newtrack 'position (point-marker))
+ (emms-track-set newtrack 'orig-track track)
+ newtrack))
+
+(defsubst emms-mp3tag-erase-buffer (&optional buf)
+ (let ((inhibit-read-only t))
+ (save-excursion
+ (set-buffer (get-buffer-create buf))
+ (erase-buffer))))
+
+(defun emms-mp3tag-insert-track (track)
+ (cond ((null track) nil)
+ ((not (eq (emms-track-get track 'type) 'file))
+ (emms-mp3tag-log "Track %s is not a local file!" (emms-track-name track)))
+ ((not (file-writable-p (emms-track-name track)))
+ (emms-mp3tag-log "The file %s is not writable" (emms-track-name track)))
+ (t (insert (emms-mp3tag-format-track
+ (cadr emms-mp3tag-selected-format) track)))))
+
+(defun emms-mp3tag-insert-tracks (tracks)
+ (save-excursion
+ (emms-mp3tag-erase-buffer emms-mp3tag-log-buffer)
+ (emms-mp3tag-erase-buffer emms-mp3tag-edit-buffer)
+ (set-buffer (get-buffer emms-mp3tag-edit-buffer))
+ (mapc 'emms-mp3tag-insert-track tracks)
+ (emms-mp3tag-mode 1)
+ (pop-to-buffer (current-buffer))
+ (goto-char (point-min))
+ (emms-mp3tag-display-log-buffer-maybe)))
+
+(defun emms-mp3tag-edit-track (track)
+ (interactive (list (emms-mp3tag-track-at)))
+ (if (null track)
+ (message "No track at point!")
+ (emms-mp3tag-insert-tracks (list track))))
+
+(defun emms-mp3tag-edit-marked-tracks ()
+ (interactive)
+ (let ((tracks (emms-mark-mapcar-marked-track 'emms-mp3tag-track-at t)))
+ (if (null tracks)
+ (message "No track marked!")
+ (emms-mp3tag-insert-tracks tracks))))
+
+(defun emms-mp3tag-edit ()
+ (interactive)
+ (if (emms-mark-has-markedp)
+ (emms-mp3tag-edit-marked-tracks)
+ (emms-mp3tag-edit-track (emms-mp3tag-track-at))))
+
+(defsubst emms-mp3tag-display-log-buffer-maybe ()
+ (if (> (buffer-size (get-buffer emms-mp3tag-log-buffer)) 0)
+ (display-buffer emms-mp3tag-log-buffer)))
+
+(defvar emms-mp3tag-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [tab] 'emms-mp3tag-next-field)
+ (define-key map [backtab] 'emms-mp3tag-prev-field)
+ (define-key map "\C-c\C-n" 'emms-mp3tag-next-track)
+ (define-key map "\C-c\C-p" 'emms-mp3tag-prev-track)
+ (define-key map "\C-c\C-c" 'emms-mp3tag-submit)
+ (define-key map "\C-c\C-r" 'emms-mp3tag-replace-all)
+ map))
+(define-key emms-playlist-mode-map "E" 'emms-mp3tag-edit)
+
+(define-minor-mode emms-mp3tag-mode
+ "A minor mode to edit mp3tag.
+\\{emms-mp3tag-mode-map}"
+ :lighter " MP3Tag"
+ :keymap emms-mp3tag-mode-map)
+
+(defun emms-mp3tag-replace-all (name value)
+ (interactive
+ (list (completing-read "Replace tag: "
+ emms-mp3tag-tags nil t)
+ (read-from-minibuffer "Set tag to: ")))
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward (concat "^" (regexp-quote name)) nil t)
+ (skip-chars-forward " \t=")
+ (delete-region (point) (line-end-position))
+ (insert value))))
+
+(defun emms-mp3tag-next-field (arg)
+ (interactive "p")
+ (if (> arg 0)
+ (re-search-forward "\\s-*=[ \t]*" nil nil arg)
+ (emms-mp3tag-prev-field (- arg))))
+
+(defun emms-mp3tag-prev-field (arg)
+ (interactive "p")
+ (if (< arg 0)
+ (emms-mp3tag-next-field (- arg))
+ (skip-chars-backward " \t=")
+ (re-search-backward "\\s-*=[ \t]*" nil nil arg)
+ (skip-chars-forward " \t=")))
+
+(defun emms-mp3tag-prev-track ()
+ (interactive)
+ (let ((prev (previous-single-property-change (point)
+ 'emms-track)))
+ (when (not prev)
+ (error "No previous track"))
+ (when (not (get-text-property prev 'emms-track))
+ (setq prev (or (previous-single-property-change prev 'emms-track)
+ (point-min))))
+ (when (or (not prev)
+ (not (get-text-property prev 'emms-track)))
+ (error "No previous track"))
+ (goto-char prev)))
+
+(defun emms-mp3tag-next-track ()
+ (interactive)
+ (let ((next (next-single-property-change (point)
+ 'emms-track)))
+ (when (not next)
+ (error "No next track"))
+ (when (not (get-text-property next 'emms-track))
+ (setq next (next-single-property-change next 'emms-track)))
+ (when (or (not next)
+ (= next (point-max)))
+ (error "No next track"))
+ (goto-char next)))
+
+(defun emms-mp3tag-submit ()
+ (interactive)
+ (let ((tracks (funcall (nth 2 emms-mp3tag-selected-format)))
+ filename exit old pos args val need-sync)
+ (if (not (and tracks (y-or-n-p "Submit changes? ")))
+ (message "Nothing have to do!")
+ (emms-mp3tag-erase-buffer emms-mp3tag-log-buffer)
+ (message "Wait while set tags...")
+ (save-excursion
+ (dolist (track tracks)
+ (when (emms-track-get track 'tag-modified)
+ (setq filename (emms-track-name track)
+ old (emms-track-get track 'orig-track))
+ (when (emms-track-get track 'newname)
+ (setq filename (emms-track-get track 'newname))
+ (rename-file (emms-track-name track) filename)
+ (emms-track-set old 'name filename)
+ ;; for re-enter this function
+ (setq need-sync t)
+ (emms-track-set track 'newname nil)
+ (emms-track-set track 'name filename))
+ (setq args nil)
+ (dolist (tag emms-mp3tag-tags)
+ (when (setq val (emms-track-get track (car tag)))
+ (emms-track-set old (car tag) val)
+ (setq args (append args (list (concat "-" (cdr tag)) val)))))
+ (unless (zerop (setq exit
+ (apply 'call-process emms-info-mp3info-program-name
+ nil nil nil
+ filename args)))
+ (emms-mp3tag-log "Change tags of %s failed with exit value %d" filename exit))
+ (when (and (setq pos (emms-track-get track 'position))
+ (marker-position pos))
+ (set-buffer (marker-buffer pos))
+ (goto-char pos)
+ (funcall emms-playlist-update-track-function))
+ (emms-track-set track 'tag-modified nil))))
+ (if (and need-sync (y-or-n-p "You have change some track names, sync the cache? "))
+ (emms-cache-sync))
+ (emms-mp3tag-display-log-buffer-maybe)
+ (message "Set all mp3 tag done!"))))
+
+(defun emms-mp3tag-default-parser ()
+ (let (next tracks track key val)
+ (goto-char (point-min))
+ (if (get-text-property (point) 'emms-track)
+ (setq next (point))
+ (setq next (next-single-property-change (point)
+ 'emms-track)))
+ (when next
+ (while
+ (progn
+ (goto-char next)
+ (setq track (get-text-property (point) 'emms-track))
+ (forward-line 1)
+ (mapc (lambda (pair)
+ (when (string-match "\\s-*=\\s-*" pair)
+ (setq key (intern-soft (substring pair 0 (match-beginning 0)))
+ val (substring pair (match-end 0)))
+ (when (and key
+ (> (length val) 0)
+ (not (string= val (emms-track-get track key))))
+ (if (eq key 'name)
+ (emms-track-set track 'newname val)
+ (emms-track-set track key val))
+ (emms-track-set track 'tag-modified t))))
+ (split-string (buffer-substring (point)
+ (or
+ (setq next (next-single-property-change (point) 'emms-track))
+ (point-max)))
+ "\n"))
+ (if (emms-track-get track 'tag-modified)
+ (push track tracks))
+ next))
+ tracks)))
+
+(defun emms-mp3tag-log (&rest args)
+ (with-current-buffer (get-buffer-create emms-mp3tag-log-buffer)
+ (goto-char (point-max))
+ (insert (apply 'format args) "\n")))
+
+;;; emms-mp3tagedit.el ends here