Last active
April 28, 2023 23:38
-
-
Save s-fubuki/f573f252ac9bbfbfe5fb7932c9ef7b17 to your computer and use it in GitHub Desktop.
Play music file for wtag.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; playall.el -- Play music file for wtag. | |
;; Copyright (C) 2023 fubuki | |
;; Author: fubuki at frill.org | |
;; Version: @(#)$Revision: 1.28 $ | |
;; Keywords: multimedia | |
;; 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 3 of the License. | |
;; 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
;;; Commentary: | |
;; * wtag 上の再生機能を拡張します | |
;; `wtag-view-mode' 上から `数値 P' でそのトラック番号を再生. | |
;; [fn::表記上スペースで切っていますが実際は不要です] | |
;; これに加え `0 P' で全曲通して, マイナス値で全曲からシャッフルになります. | |
;; `- P' のようにマイナスのみで OK です. | |
;; 再生中は `M-n' で次のトラックに飛び 'C-c C-c' で停止です. | |
;; プレイヤーの設定は変数 `wtag-music-players' を引き継ぎますが | |
;; `playall-players' でオーバーライドできます. | |
;; * 通常ディレクトリからの再生 | |
;; 作ってみて、たれ流したいだけなら wtag 上からじゃなくてもいいじゃんと気づき | |
;; 通常の directory から実行する `directory-playall' もあります. | |
;; [fn::もっと云えば(呼び出している)mpg123 だけでいいじゃん...とも云います] | |
;; マイナーモードになり 停止: `C-c [ k' 次曲: `C-c [ n' 等となります. | |
;; `wtag-playall' と併用した場合の擦り合せがまだ甘いかもしれないです. | |
;; : M-x directory-playall | |
;; : C-u M-x directory-playall ;; Shuffle | |
;;; Installation: | |
;; (require 'playall) | |
;;; Change Log: | |
;; revision 1.28 date: 2023-04-26 14:12:53+09 | |
;; - add: 非対応ファイルをエラーハンドリングする. | |
;; - fix: wtag.el の `wtag-writable-tag-hook' で `playall-kill' を実行. | |
;; wtag でライトモードにしたとき、そのときの音楽は停止するが、 | |
;; シェードが剥されなかったり、タイマー待ちの次が再生されたりするバグがあったのをこれで修正. | |
;;; Code: | |
(require 'cl-lib) | |
(require 'wtag) | |
(defface playall-shade | |
'((t :inherit dired-ignored)) | |
"Wtag playall shade face." | |
:group 'wtag-faces) | |
(define-key wtag-view-mode-map [remap wtag-music-play] #'wtag-playall) | |
(define-key wtag-view-mode-map [remap wtag-kill-process] #'playall-kill) | |
(define-key wtag-view-mode-map "\C-c\C-s" #'playall-stop) | |
(define-key wtag-view-mode-map "\M-n" #'playall-next) | |
(add-hook 'wtag-view-mode-hook | |
#'(lambda () | |
(define-key-after | |
(lookup-key wtag-view-mode-map [menu-bar wtag]) | |
[play-next] | |
'(menu-item "Next Play" playall-next :key-sequence "\M-n") | |
'wtag-music-play))) | |
;; (add-hook 'wtag-quit-hook #'playall-kill) | |
(add-hook 'wtag-writable-tag-hook #'playall-kill) | |
;; Configurations. | |
(defvar playall-play-mode-invert nil) | |
(defvar playall-players | |
(if (boundp 'wtag-music-players) wtag-music-players '("\\.mp3\\'" "mpg123"))) | |
(defvar playall-format " `%a - %t (%T)'" | |
"Special characters: | |
%f File Name | |
%T Play Time (variable `playall-time-format') | |
%C Codec | |
%B Bit rate | |
%A Album Title | |
%a Artist Name | |
%t Track Title | |
%n Track Number | |
%g Genre | |
%y Release Year | |
%% %") | |
(defvar playall-time-format "%m'%02s\"") | |
(defvar playall-init-after-hook nil) | |
(defvar playall-kill-hook nil) | |
;; Work Variables. | |
(defvar playall-style nil) | |
(defvar playall-title nil) | |
(put 'playall-title 'risky-local-variable t) | |
(defvar playall-overlays nil) | |
(make-variable-buffer-local 'playall-overlays) | |
(defun playall-pget (prop) | |
"`playall-style' から PROP を得る." | |
(plist-get playall-style prop)) | |
(defun playall-pset (prop var) | |
"`playall-style' の PROP に VAR をセット. | |
PROP が既に在れば更新され無ければ作られる." | |
(setq playall-style (plist-put playall-style prop var))) | |
(defun playall-directory-style-p () | |
"`playall-style' のプロパティ :style の値が directory ならば non-nil." | |
(eq (playall-pget :style) 'directory)) | |
(defun playall-cancel-timer () | |
"`playall-style' にプロパティ :timer があればそのタイマーをキャンセルする." | |
(and (playall-pget :timer) (cancel-timer (playall-pget :timer)))) | |
(defun playall-pget-music-tags (mark) | |
"MARK が non-nil ならポイントから *pos を追加して stat プロパティを戻す." | |
(let ((stat (get-text-property (point) 'stat))) | |
(and mark stat (cons `(*pos nil . ,(point)) stat)))) | |
(defun playall-tags-buffer (mode) | |
"`wtag-view-mode' buffer から MODE 番号のトラックのタグリストのリストを戻す. | |
変数 `wtag-music-play-next' がセットされていれば、 | |
最後に次のトラックへポイントを進める. | |
MODE が 0 以下ならすべてのファイルのリストを戻す. | |
すべての場合マークがあればそれのみが対象となる." | |
(let ((mark-buff (wtag-buffer-mark-p "*")) | |
album files) | |
(cond | |
((< mode 1) ;; all files. | |
(save-excursion | |
(if (zerop mode) | |
(beginning-of-line) | |
(goto-char (point-min))) | |
(while (not (eobp)) | |
(push (playall-pget-music-tags | |
(or (null mark-buff) (wtag-mark-p "*"))) | |
files) | |
(forward-line)))) | |
(t | |
(goto-char (point-min)) | |
(while (wtag-common-area-p) | |
(forward-line)) | |
(forward-line -1) | |
(while (< 0 mode) | |
(forward-line) | |
(setq mode (1- mode))) | |
(push (playall-pget-music-tags t) files) | |
(when wtag-music-play-next | |
(when (numberp wtag-music-play-next) | |
(sleep-for wtag-music-play-next)) | |
(forward-line)))) | |
(reverse (delq nil files)))) | |
(defvar playall-now nil "Work variable.") | |
(define-fringe-bitmap 'playall-fringe [128 192 224 240 248 252 254 255]) | |
(defface playall-now | |
'((t :inherit font-lock-constant-face)) | |
"Playall now mark face." | |
:group 'playall-faces) | |
(defface playall-mode-line nil | |
"Playall mode line face." | |
:group 'playall-faces) | |
(defun playall-shade (pos) | |
"POS に日除けをおろす." | |
(unless (playall-directory-style-p) | |
(save-excursion | |
(with-current-buffer (playall-pget :buffer) | |
(let (beg end) | |
(goto-char pos) | |
(setq beg (line-beginning-position) | |
end (line-end-position)) | |
(and playall-now (delete-overlay playall-now)) | |
(setq playall-now (make-overlay beg (1+ beg))) | |
(overlay-put | |
playall-now 'before-string | |
(propertize "." 'display '(left-fringe playall-fringe playall-now))) | |
(overlay-put | |
(caar | |
(setq playall-overlays | |
(cons | |
(list | |
(make-overlay beg end (current-buffer)) | |
beg end) | |
playall-overlays))) | |
'face 'playall-shade)))))) | |
(defun playall-reshade () | |
"日除け位置が壊れたときの再調整." | |
(dolist (ov playall-overlays) | |
(move-overlay (car ov) (nth 1 ov) (nth 2 ov)))) | |
(defun playall-shuffle (lst) | |
"LST をシャッフルした新たなリストを戻す." | |
(let (result) | |
(random t) | |
(while lst | |
(push (nth (random (length lst)) lst) result) | |
(setq lst (cl-remove (car result) lst :test #'equal :count 1))) | |
result)) | |
(defun playall-at-pos () | |
"`wtag-vuew-mode' バッファのポイントが指しているトラック番号を戻す. | |
トラック位置になければトラック1を戻す." | |
(if (wtag-common-area-p) | |
1 | |
(1+ (- (line-number-at-pos) wtag-beginning-line-of-track)))) | |
;;;###autoload | |
(defun playall-stop () | |
"プロセスを停止するが残りの Play list をクリアしない. | |
C-u \\[playall-kill] と等価." | |
(interactive) | |
(playall-kill 'keep)) | |
;;;###autoload | |
(defun playall-kill (&optional prefix final) | |
"`wtag-process-name' の後始末. | |
PREFIX が在れば再開時継続利用する変数は初期化しない. | |
FINAL が non-nil なら `playall-kill-hook' を実行する.'" | |
(interactive "P") | |
(let ((ps wtag-process) | |
(kill "Stop")) | |
(and (get-process wtag-process-name) | |
(delete-process (get-process wtag-process-name))) | |
(setq wtag-process nil) | |
(playall-cancel-timer) | |
(and (memq 'playall-title global-mode-string) | |
(setq global-mode-string | |
(delq 'playall-title global-mode-string))) | |
(setq playall-title nil) | |
(playall-pset :timer nil) | |
(unless prefix | |
(playall-pset :buffer nil) | |
(playall-pset :directory nil) | |
(playall-pset :next nil) | |
(dolist (ov playall-overlays) | |
(delete-overlay (car ov))) | |
(and playall-now (delete-overlay playall-now)) | |
(setq playall-overlays nil | |
kill "Kill")) | |
(or (playall-directory-style-p) | |
(remove-hook 'wtag-init-hook #'playall-reshade)) | |
(and final (run-hooks 'playall-kill-hook)) | |
(and ps (message "%s music play process." kill)))) | |
(defun playall-init (mode) | |
"MODE に トラックナンバーを指定しテーブルをイニシャライズ. | |
0 なら総トラック, 負ならシャッフルで初期化される." | |
(let ((mode (if playall-play-mode-invert | |
(cond ((zerop mode) -1) ((< mode 0) 0) (t mode)) | |
mode)) | |
(func (if (playall-directory-style-p) | |
#'playall-tags-directory | |
#'playall-tags-buffer))) | |
;; (and wtag-process (playall-kill)) | |
(and (playall-pget :process) (playall-kill)) | |
(playall-pset :buffer (or (playall-pget :buffer) (current-buffer))) | |
(playall-pset :directory (or (playall-pget :directory) default-directory)) | |
(or (playall-pget :next) | |
(playall-pset :next | |
(if (> 0 mode) | |
(playall-shuffle (funcall func mode)) | |
(funcall func mode)))) | |
(or (playall-directory-style-p) | |
(add-hook 'wtag-init-hook #'playall-reshade)))) | |
(defun playall-assq-cddr (sym lst) | |
"LST 中の SYM の cdr の cdr を返す." | |
(cddr (assq sym lst))) | |
(defun playall-read-tags (file) | |
"FILE の tag list を戻す list には `filename' tags が追加される." | |
(let ((result (mf-tag-read-alias file (mf-read-size file) t))) | |
(cons `(filename nil . ,file) result))) | |
(defun playall-tags-directory (mode) | |
"`mf-lib-suffix-all' にマッチするファイル名をリストで戻す. | |
MODE を見ている箇所は今は使われていないが暫定的に残されている." | |
(let ((result (directory-files | |
default-directory t (mf-re-suffix mf-lib-suffix-all)))) | |
(if (< 0 mode) | |
(catch 'out | |
(dolist (a result) | |
(if (string-match | |
"\\`0?[0-9][0-9]?[- _]" (file-name-nondirectory a)) | |
(throw 'out (list a))))) | |
result))) | |
(defun playall-tags-directory-rec (&optional dummy) | |
"`playall-tags-directory' の再帰版. | |
DUMMY はダミー." | |
(directory-files-recursively default-directory (mf-re-suffix mf-lib-suffix-all))) | |
(defun playall-process-buffer () | |
"Process log buffer を開く." | |
(interactive) | |
(pop-to-buffer wtag-process-name) | |
(playall-log-mode)) | |
(defun playall-spec () | |
"`playall-format' にで使うスペシャルキャラのテーブル." | |
(with-no-warnings | |
`((?f . ,file) | |
(?T . ,(wtag-format playall-time-format time)) | |
(?C . ,codec) | |
(?B . ,brate) | |
(?A . ,album) | |
(?a . ,artist) | |
(?t . ,title) | |
(?n . ,track) | |
(?g . ,genre) | |
(?y . ,year) | |
(?% . "%")))) | |
(defvar playall-id2codec-table | |
'(("ID3\1" . "mp3") ("ID3\2" . "mp3") ("ID3\3" . "mp3") ("ea3\3" . "oma"))) | |
(defun playall-id2codec (id) | |
"コーデック ID の変名を戻す." | |
(or (assoc-default id playall-id2codec-table) id)) | |
(defun playall-music-directory (dir) | |
"DIR に `playall-players' に設定された拡張子のファイルが在れば t." | |
(and (file-accessible-directory-p dir) | |
(catch 'out | |
(dolist (regexp (mapcar #'car playall-players)) | |
(if (directory-files dir t regexp) | |
(throw 'out t)))))) | |
;;;###autoload | |
(defun playall-next () | |
"次のトラックを再生." | |
(interactive) | |
(playall-cancel-timer) | |
(if (playall-pget :next) | |
(playall 0) | |
(error "No next music"))) | |
;;;###autoload | |
(defalias 'dpall #'directory-playall) | |
;;;###autoload | |
(defun directory-playall (dir &optional prefix) | |
"DIR 下の `mf-lib-suffix-all' を Play all. | |
PREFIX で Shuffle." | |
(interactive | |
(let* ((dir (playall-pget :directory))) | |
(setq dir | |
(if (or (null dir) (not (equal dir default-directory))) | |
(read-directory-name "Directory: ") | |
dir)) | |
(list dir current-prefix-arg))) | |
(when (or (null (playall-pget :next)) | |
(not (eq (playall-pget :directory) dir))) | |
(playall-kill)) | |
(playall-pset :style 'directory) | |
;; (playall-pset :directory dir) | |
(unless (playall-music-directory dir) (error "No music file")) | |
(playall-minor-mode 1) | |
(playall (if prefix -1 0) t)) | |
;;;###autoload | |
(defun wtag-playall (prefix) | |
"`wtag-view-mode' で Play all. | |
PREFIX で Shuffle." | |
(interactive | |
(list (and current-prefix-arg | |
(prefix-numeric-value current-prefix-arg)))) | |
(let ((next (playall-pget :next))) | |
(when (or (null next) | |
(not (eq (playall-pget :buffer) (current-buffer)))) | |
(playall-kill) | |
(setq next nil)) | |
(playall-pset :style 'wtag) | |
;; (playall-pset :buffer (current-buffer)) | |
(when (and next (and prefix (< 1 prefix))) | |
(goto-char (point-min)) | |
(forward-line (+ prefix wtag-beginning-line-of-track))) | |
(playall (or (if next 0 prefix) (playall-at-pos)) t))) | |
(defun playall-filter (prc str) | |
"エラーハンドラフィルタ. | |
PRC 番号と STR." | |
(if (string-match "error: .+valid MPEG header" str) | |
(progn | |
(playall-kill) | |
(message "%s had the event '%s'" prc str)) | |
(when (buffer-live-p (process-buffer prc)) | |
(with-current-buffer (process-buffer prc) | |
(let ((pos (= (point) (process-mark prc))) | |
buffer-read-only) | |
(save-excursion | |
(goto-char (process-mark prc)) | |
(insert str) | |
(set-marker (process-mark prc) (point))) | |
(if pos (goto-char (process-mark prc)))))))) | |
;;;###autoload | |
(defun playall (&optional mode init) | |
"`wtag-view-mode' の曲を `playall-players' で設定されたアプリで再生. | |
MODE 番号のトラックを再生する.省略するとトラック 1になる. | |
0 ならすべてのトラックを連続再生し、マイナス値ならシャッフルプレイする. | |
INITが non-nil なら強制的に初期化. | |
関数起動(つまりノンインタラクティブ)で初期化するために使用する." | |
(interactive | |
(let ((mode (if (null current-prefix-arg) | |
(playall-at-pos) | |
(prefix-numeric-value current-prefix-arg)))) | |
(playall-init mode) | |
(list mode))) | |
(playall-cancel-timer) | |
(and init (playall-init mode)) | |
(run-hooks 'playall-init-after-hook) | |
(when global-mode-string | |
(or (memq 'playall-title global-mode-string) | |
(setq global-mode-string | |
(append global-mode-string (list 'playall-title))))) | |
(when (playall-pget :next) | |
(let* ((stat (car (playall-pget :next))) | |
(stat (if (atom stat) (playall-read-tags stat) stat)) | |
(file (playall-assq-cddr 'filename stat)) | |
(time (car (playall-assq-cddr '*time stat))) | |
(codec (playall-id2codec (playall-assq-cddr '*type stat))) | |
(brate (cadr (playall-assq-cddr '*time stat))) | |
(album (playall-assq-cddr 'album stat)) | |
(artist (playall-assq-cddr 'artist stat)) | |
(title (playall-assq-cddr 'title stat)) | |
(track (playall-assq-cddr 'track stat)) | |
(genre (playall-assq-cddr 'genre stat)) | |
(year (playall-assq-cddr 'year stat)) | |
(pos (playall-assq-cddr '*pos stat)) | |
(app (assoc-default file playall-players #'string-match)) | |
(prog (car app)) | |
(opt (cdr app)) | |
(name wtag-process-name) | |
(now (format-spec playall-format (playall-spec)))) | |
(unless app (error "Invalid file `%s'" file)) | |
(setq playall-title | |
(propertize | |
now | |
'face 'playall-mode-line | |
'help-echo now | |
'local-map | |
(make-mode-line-mouse-map 'mouse-1 #'playall-minor-mode-menu-map) | |
'mouse-face 'mode-line-highlight)) | |
(playall-pset :next (cdr (playall-pget :next))) | |
(and (get-process wtag-process-name) | |
(delete-process wtag-process-name)) | |
(setq wtag-process | |
(progn | |
(message "%s" playall-title) | |
(and (> 1 mode) (playall-shade pos)) | |
(apply #'start-process name name prog (append opt (list file))))) | |
(set-process-filter wtag-process #'playall-filter) | |
(playall-pset :timer | |
(if (playall-pget :next) | |
(run-with-timer time nil #'playall mode) | |
(run-with-timer time nil #'playall-kill nil 'final)))))) | |
(defvar playall-minor-mode-menu-map | |
(let ((menu (make-sparse-keymap "Playall Minor Mode"))) | |
(define-key menu [playall-process-buffer] '("Log" . playall-process-buffer)) | |
(define-key menu [playall-kill] '(menu-item "Kill" playall-kill)) | |
(define-key menu [playall-stop] '(menu-item "Stop" playall-stop)) | |
(define-key menu [playall-next] '("Next" . playall-next)) | |
(define-key menu [directory-playall] '("Play" . directory-playall)) | |
menu)) | |
(fset 'playall-minor-mode-menu-map playall-minor-mode-menu-map) | |
(defvar playall-minor-mode-map | |
(let ((map (make-sparse-keymap))) | |
(define-key map "\C-c[n" #'playall-next) | |
(define-key map "\C-c[s" #'playall-stop) | |
(define-key map "\C-c[k" #'playall-kill) | |
(define-key map "\C-c[l" #'playall-process-buffer) | |
(define-key map "\C-c[p" #'directory-playall) | |
(define-key map [menu-bar play] (cons "Playall" playall-minor-mode-menu-map)) | |
map)) | |
(define-minor-mode playall-minor-mode | |
"Playall Minor Mode." | |
:global t :group 'music-file :lighter " Playall") | |
(defvar playall-log-mode-font-lock | |
'(("\\([A-Z][a-z]+:\\)[ ]" 1 'link))) | |
(define-derived-mode playall-log-mode fundamental-mode "Play-log" | |
"Play log mode." | |
(setq-local font-lock-defaults (list playall-log-mode-font-lock)) | |
(define-key playall-log-mode-map "q" #'quit-window)) | |
(provide 'playall) | |
;;; playall.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment