From dfe998b5663c2288a7c01dfbde6afead4e554cd3 Mon Sep 17 00:00:00 2001 From: Wenbin Ye Date: Tue, 5 Dec 2006 06:54:00 +0000 Subject: Add emms-mp3tag and emms-i18n darcs-hash:20061205065407-1e472-4680607fa20dfb5e818879d67781d1379602d473.gz --- emms-i18n.el | 144 +++++++++++++++++++++++++ emms-mark.el | 79 +++++++++++--- emms-mp3tag.el | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 534 insertions(+), 17 deletions(-) create mode 100644 emms-i18n.el create mode 100644 emms-mp3tag.el 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: +;; 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: -;; Version: $Id: emms-mark.el,v 0.0 <2006-11-21 14:06:38> ywb Exp $ +;; Time-stamp: +;; 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: +;; 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 -- cgit v1.2.3