|
;;; shuffle-all.el --- Play the Music file -*- lexical-binding:t -*- |
|
;; Copyright (C) 2023-2025 fubuki |
|
|
|
;; Author: fubuki at frill.org |
|
;; Version: @(#)$Revision: 3.1 $$Name: $ |
|
;; 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: |
|
|
|
;; Play the music file on wtag-view-mode. |
|
;; Or for music file play in Directory recursively. |
|
;; Need the `mf-tag-write.el', `wtag.el' library. |
|
|
|
;;; Installation: |
|
|
|
;; (autoload 'shuffle-all "shuffle-all" nil t) |
|
|
|
;; or for wtag |
|
|
|
;; (require 'shuffle-all) |
|
|
|
;;; Change Log: |
|
|
|
;;; Code: |
|
(require 'mf-tag-write) |
|
(require 'wtag) |
|
(require 'tab-bar) |
|
(require 'repeat) |
|
(require 'hl-line) |
|
(require 'seq) |
|
|
|
(define-key wtag-view-mode-map [remap wtag-music-play] #'shuffle-wtag-play) |
|
(define-key wtag-view-mode-map [remap wtag-kill-process] #'shuffle-kill) |
|
(define-key wtag-view-mode-map "\C-c\C-s" #'shuffle-stop) |
|
(define-key wtag-view-mode-map "\M-n" #'shuffle-next) |
|
(define-key wtag-view-mode-map "/" #'shuffle-single-play) |
|
(define-key wtag-view-mode-map [mouse-1] #'shuffle-play-mouse) |
|
|
|
(add-hook 'wtag-view-mode-hook |
|
#'(lambda () |
|
(define-key-after |
|
(lookup-key wtag-view-mode-map [menu-bar wtag]) |
|
[shuffle-next] |
|
'(menu-item "Next Play" shuffle-next :key-sequence "\M-n") |
|
'wtag-music-play) |
|
(define-key-after |
|
(lookup-key wtag-view-mode-map [menu-bar wtag]) |
|
[shuffle-single-play] |
|
(list 'menu-item "Single Play" #'shuffle-single-play |
|
:button '(:toggle . shuffle-single-play)) |
|
'wtag-kill-process))) |
|
|
|
(add-hook 'wtag-writable-mode-hook #'shuffle-kill-silent) |
|
|
|
(defgroup shuffle-all nil |
|
"Group Shuffle all." |
|
:group 'music-file) |
|
|
|
(defcustom shuffle-directory nil |
|
"Default shuffle directory name." |
|
:type '(choice directory (const nil)) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-music-file "\\.\\(mp3\\)\\'" |
|
"Shuffle file name regexp." |
|
:type 'regexp |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-music-file-wtag nil |
|
"Shuffle file name regexp for `shuffle-wtag-play'. |
|
If nil, `shuffle-music-file' will be used." |
|
:type '(choice regexp (const nil)) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-ignore-regexp nil |
|
"Directory name for exclude." |
|
:type '(choice regexp (const nil)) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-players (or wtag-music-players '(("\\.mp3\\'" "mpg123"))) |
|
"Shuffle music player." |
|
:type '(repeat |
|
(list regexp |
|
(file :tag "Player" :must-match t) |
|
(repeat :inline t :tag "Option" string))) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-sleep-timer-time nil |
|
"オフタイマー時間を秒数か \"2 hour 10 min\" 等の文字列で指定." |
|
:type '(choice string integer (const nil)) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-format " `%a - %t (%T)'" |
|
"Shuffle mode line format. |
|
Special characters: |
|
%f File Name |
|
%T Play Time (variable `shuffle-time-format') |
|
%C Codec |
|
%B Bit rate |
|
%A Album Title |
|
%a Artist Name |
|
%t Track Title |
|
%n Track Number |
|
%g Genre |
|
%y Release Year |
|
%% %" |
|
:type 'string |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-time-format "%m'%02s\"" |
|
"See function `format-seconds'." |
|
:type 'string |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-follow-cursor t |
|
"Follow the cursor." |
|
:type 'boolean |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-single-play nil |
|
"non-nil if single play." |
|
:local t |
|
:initialize 'custom-initialize-default |
|
:type 'boolean |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-move-shade-over nil |
|
"Shuffle move shade over." |
|
:type '(choice (const nil) (const t) number) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-gap '(("mpg123" . 0.25)) |
|
"Seconds of track margin. |
|
Number, Cons list \((Player . Number) ...) or nil." |
|
:type '(choice (const :tag "Disable" nil) |
|
(number :tag "Gap") |
|
(repeat :tag "Individual" |
|
(cons (regexp :tag "Player") (number :tag "Gap ")))) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-all-startup-hook nil |
|
"Shuffle all startup hook." |
|
:type 'hook |
|
:group 'shuffle-all) |
|
|
|
(defgroup shuffle-all-faces nil |
|
"Group Shuffle all face." |
|
:group 'shuffle-all |
|
:group 'faces) |
|
|
|
(defface shuffle-mode-line nil |
|
"Shuffle all mode line face." |
|
:group 'shuffle-all-faces) |
|
|
|
(defface shuffle-shade |
|
'((t :inherit shadow :foreground "dim gray" :extend t)) |
|
"Wtag shuffle shade face." |
|
:group 'shuffle-all-faces) |
|
|
|
(defvar shuffle-stat nil) |
|
(defvar shuffle-title nil) |
|
(put 'shuffle-title 'risky-local-variable t) |
|
|
|
(defcustom shuffle-global-mode-string nil |
|
"`shuffle-title' Add `global-mode-string'." |
|
:type 'boolean |
|
:group 'shuffle-all) |
|
|
|
(unless (featurep 'wtag) |
|
(defvar wtag-process-name "*wtag process*") |
|
(defvar wtag-process nil)) |
|
|
|
(defvar shuffle-fringe-bitmap-alist |
|
'((shuffle-all . [128 192 224 240 248 252 254 255]) |
|
(shuffle-single . [96 112 124 127 127 124 112 96]) |
|
(shuffle-shuffle . [255 254 252 248 240 224 192 128])) |
|
"\(symbol name/face name . bitmap data)") |
|
|
|
(dolist (a shuffle-fringe-bitmap-alist) |
|
(define-fringe-bitmap (car a) (cdr a)) |
|
(set-fringe-bitmap-face (car a) (car a))) |
|
|
|
(defvar-local shuffle-fringe nil) |
|
(defvar-local shuffle-position nil) |
|
|
|
(defface shuffle-all |
|
'((t :inherit font-lock-constant-face)) |
|
"All mode face." |
|
:group 'shuffle-all-faces) |
|
|
|
(defface shuffle-single |
|
'((t :inherit font-lock-property-name-face)) |
|
"Single mode face." |
|
:group 'shuffle-all-faces) |
|
|
|
(defface shuffle-shuffle |
|
'((t :inherit font-lock-function-name-face)) |
|
"Shuffle mode face." |
|
:group 'shuffle-all-faces) |
|
|
|
(defvar shuffle-prefix nil) |
|
|
|
(defcustom shuffle-progress-interval 5 |
|
"Progress time interval. |
|
INTEGER is Progress interval Seconds. if NIL is disable. " |
|
:type '(choice integer (const :tag "Disable" nil)) |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-progress-timer-mode-line-update nil |
|
"non-nil なら残り計算時に mode-line 表示を常に更新する." |
|
:type 'boolean |
|
:group 'shuffle-all) |
|
|
|
(defcustom shuffle-all-minor-mode-lighter-label "▶" |
|
"`shuffle-all-minor-mode-lighter' Play label." |
|
:type '(choice string (const nil)) |
|
:group 'shuffle-all) |
|
|
|
(defsubst shuffle-get (prop) |
|
(plist-get shuffle-stat prop)) |
|
|
|
(defsubst shuffle-put (prop val) |
|
(setq shuffle-stat (plist-put shuffle-stat prop val))) |
|
|
|
(defsubst shuffle-member (prop) |
|
(plist-member shuffle-stat prop)) |
|
|
|
(defvar shuffle-all-progress-exp |
|
`(shuffle-progress-interval |
|
(:propertize |
|
(:eval (format " %s%d%%%%" |
|
(or shuffle-all-minor-mode-lighter-label "") |
|
(- 100 (/ (shuffle-get :progress) |
|
(/ (shuffle-get :total-time) 100.0))))) |
|
mouse-face mode-line-highlight |
|
local-map ,(make-mode-line-mouse-map 'mouse-1 'shuffle-all-minor-mode-menu-map) |
|
help-echo (shuffle-get :now)))) |
|
|
|
(put 'shuffle-all-progress-exp 'risky-local-variable t) |
|
|
|
(defcustom shuffle-all-minor-mode-lighter 'shuffle-all-progress-exp |
|
"shuffle all minor mode lighter." |
|
:type '(choice symbol string sexp) |
|
:group 'shuffle-all) |
|
|
|
(put 'shuffle-all-minor-mode-lighter 'risky-local-variable t) |
|
|
|
(defun shuffle-progress-time-timer () |
|
(run-with-timer shuffle-progress-interval shuffle-progress-interval |
|
#'(lambda () |
|
(shuffle-put |
|
:progress (- (or (shuffle-get :progress) 0) shuffle-progress-interval)) |
|
(and shuffle-progress-timer-mode-line-update |
|
(force-mode-line-update)) |
|
(unless (and (shuffle-get :progress) (natnump (shuffle-get :progress))) |
|
(cancel-timer (shuffle-get :progress-timer)) |
|
(shuffle-put :progress-timer nil) |
|
(shuffle-put :now nil))))) |
|
|
|
(defun shuffle-set-gap (app) |
|
(cond |
|
((consp shuffle-gap) |
|
(or (assoc-default app shuffle-gap #'string-match) 0)) |
|
(t |
|
(or shuffle-gap 0)))) |
|
|
|
(defun shuffle-wc-point-move (buff marker) |
|
"移動したポイントを tab-bar が元の位置に戻してしまうので書換えている." |
|
(let ((tabs (funcall tab-bar-tabs-function)) tmp) |
|
(dolist (a tabs) |
|
(when (eq 'tab (car a)) |
|
(setq tmp (assq 'wc-point (cdr a))) |
|
(if (eq buff (marker-buffer (cdr tmp))) |
|
(setcdr tmp marker)))))) |
|
|
|
(defun shuffle-put-fringe (pos) |
|
(let ((fringe (cond |
|
(shuffle-single-play 'shuffle-single) |
|
((not (natnump (shuffle-get :mode))) 'shuffle-shuffle) |
|
(t 'shuffle-all)))) |
|
(setq shuffle-fringe (set-marker (make-marker) pos)) |
|
(put 'shuffle-fringe 'overlay-arrow-bitmap fringe) |
|
(add-to-list 'overlay-arrow-variable-list 'shuffle-fringe))) |
|
|
|
(defun shuffle-shade (pos buff) |
|
"ポイントを POS, カレントバッファを BUFF に移し日除けをおろす. |
|
`shuffle-follow-cursor' が non-nil ならポイント POS に移動する." |
|
(let ((prev (point)) |
|
(inhibit-field-text-motion t) |
|
beg end end2 ov) |
|
(with-current-buffer buff |
|
(goto-char pos) |
|
(and shuffle-follow-cursor (shuffle-wc-point-move buff (point-marker))) |
|
(setq beg (line-beginning-position) |
|
end (line-end-position) |
|
end2 (line-beginning-position 2)) ; for hl-line |
|
(shuffle-put-fringe beg) |
|
(setq ov (make-overlay beg end buff)) |
|
(overlay-put ov 'category 'shuffle-alls) |
|
(overlay-put ov 'shuffle-shade t) |
|
(setq ov (make-overlay beg end2 buff)) |
|
(overlay-put ov 'category 'shuffle-alls) |
|
(overlay-put ov 'priority 1024) |
|
(overlay-put ov 'face 'shuffle-shade) |
|
(or shuffle-follow-cursor (goto-char prev)) |
|
(and hl-line-mode (hl-line-highlight))))) |
|
|
|
(defun shuffle-open-shade (&optional buff) |
|
(with-current-buffer (or buff (current-buffer)) |
|
(remove-overlays (point-min) (point-max) 'category 'shuffle-alls) |
|
(setq overlay-arrow-variable-list |
|
(delq 'shuffle-fringe overlay-arrow-variable-list)))) |
|
|
|
(defun shuffle-move-shade (times func limit) |
|
(let ((pos (point))) |
|
(dotimes (_ times) |
|
(while (and |
|
(not (eq limit (setq pos (funcall func pos 'shuffle-shade)))) |
|
(not (get-char-property pos 'shuffle-shade))))) |
|
(if (null (eq pos limit)) |
|
(goto-char pos) |
|
(ding) |
|
(message "No Match") |
|
(when shuffle-move-shade-over |
|
(and (numberp shuffle-move-shade-over) |
|
(sit-for shuffle-move-shade-over)) |
|
(goto-char (if (= limit (point-min)) (point-max) (point-min))) |
|
(shuffle-move-shade times func limit))))) |
|
|
|
(defun shuffle-next-shade (times) |
|
(interactive "p") |
|
(shuffle-move-shade times #'next-single-char-property-change (point-max))) |
|
|
|
(defun shuffle-previous-shade (times) |
|
(interactive "p") |
|
(shuffle-move-shade times #'previous-single-char-property-change (point-min))) |
|
|
|
(defun shuffle-current-shade () |
|
(interactive) |
|
(goto-char shuffle-position)) |
|
|
|
(defun shuffle-goto-char (pos) |
|
(with-current-buffer (shuffle-get :init) |
|
(goto-char pos) |
|
(wtag-move-to-property 'title))) |
|
|
|
(defun shuffle-process-buffer () |
|
"Process log buffer を開く." |
|
(interactive) |
|
(pop-to-buffer wtag-process-name) |
|
(shuffle-log-mode)) |
|
|
|
(defvar shuffle-tags nil) |
|
|
|
(defun shuffle-spec () |
|
"`shuffle-format' にで使うスペシャルキャラのテーブル." |
|
(let* ((tags shuffle-tags)) |
|
`((?f . ,(plist-get tags :file)) |
|
(?T . ,(wtag-format shuffle-time-format (car (plist-get tags '*time)))) |
|
(?C . ,(shuffle-id2codec (plist-get tags '*type))) |
|
(?B . ,(mf-indirect-car (cadr (plist-get tags '*time)))) |
|
(?A . ,(shuffle-plist-get tags 'album)) |
|
(?a . ,(shuffle-plist-get tags 'artist)) |
|
(?t . ,(shuffle-plist-get tags 'title)) |
|
(?n . ,(shuffle-plist-get tags 'track)) |
|
(?g . ,(shuffle-plist-get tags 'genre)) |
|
(?y . ,(shuffle-plist-get tags 'year)) |
|
(?% . "%")))) |
|
|
|
(defun shuffle-now (tags) |
|
"TAGS を元に `shuffle-format' を展開して文字列にして戻す." |
|
(setq shuffle-tags tags) |
|
(format-spec shuffle-format (shuffle-spec))) |
|
|
|
(defvar shuffle-id2codec-table |
|
'(("ID3\1" . "mp3") ("ID3\2" . "mp3") ("ID3\3" . "mp3") ("ea3\3" . "oma"))) |
|
|
|
(defun shuffle-id2codec (id) |
|
"コーデック ID の変名を戻す." |
|
(or (assoc-default id shuffle-id2codec-table) id)) |
|
|
|
(defun shuffle-directory-files-directories (dir) |
|
"DIR 以下から `shuffle-music-file' の在る Directory だけを集め list で戻す. |
|
各要素には :index Property に位置番号リストで埋め込まれている." |
|
(let* ((files (directory-files dir t directory-files-no-dot-files-regexp)) |
|
(index (shuffle-make-index files)) |
|
(result (when index |
|
(progn |
|
(put-text-property 0 1 :index index dir) |
|
(list dir)))) |
|
message-log-max) |
|
(dolist (f files result) |
|
(cond |
|
((and (not (and shuffle-ignore-regexp |
|
(string-match shuffle-ignore-regexp f))) |
|
(car (file-attributes f))) |
|
(message "Scan `%s'..." f) |
|
(setq result (append result (shuffle-directory-files-directories f)))))))) |
|
|
|
(defun shuffle-make-index (files) |
|
(let ((count 0) result) |
|
(dolist (f files (reverse result)) |
|
(when (string-match shuffle-music-file f) |
|
(setq result (cons count result) |
|
count (1+ count)))))) |
|
|
|
(defun shuffle-shuffle (lst) |
|
"LST をシャッフルした新たなリストを戻す." |
|
(let (result) |
|
(random t) |
|
(while lst |
|
(push (nth (random (length lst)) lst) result) |
|
(setq lst (remove (car result) lst))) |
|
result)) |
|
|
|
(defun shuffle-sleep-timer (sec) |
|
"STR see `timer-duration-words'." |
|
(interactive "s: ") |
|
(let ((sec (if (stringp sec) (timer-duration sec) sec))) |
|
(and (shuffle-get :sleep) (cancel-timer (shuffle-get :sleep))) |
|
(shuffle-put :sleep (run-with-timer sec nil #'shuffle-kill)))) |
|
|
|
;;;###autoload |
|
(defun shuffle-init (dir) |
|
(message "Initializ shuffle...") |
|
(random t) |
|
(and (shuffle-get :timer) (shuffle-cancel-timer)) |
|
(and shuffle-sleep-timer-time (shuffle-sleep-timer shuffle-sleep-timer-time)) |
|
(shuffle-reset-stat) |
|
(shuffle-put :next (or (shuffle-directory-files-directories dir) |
|
(error "Not found `shuffle-music-file'"))) |
|
(shuffle-put :init (file-name-as-directory dir)) |
|
(and shuffle-title (shuffle-title-clear)) |
|
(message "Initializ shuffle...done.")) |
|
|
|
(defun shuffle-point-music-tags (mark) |
|
"ポイント *pos を追加した stat プロパティを戻す. |
|
但し MARK が non-nil 且つ、 |
|
stat の filename が `shuffle-music-file' にマッチしなければ nil になる." |
|
(let ((stat (get-text-property (line-end-position) 'stat))) |
|
(and mark stat (string-match |
|
(or shuffle-music-file-wtag |
|
shuffle-music-file) |
|
(cddr (assq 'filename stat))) |
|
(cons `(*pos nil . ,(point)) stat)))) |
|
|
|
(defun shuffle-alist-to-plist (lst) |
|
(let (result) |
|
(dolist (a lst result) |
|
(setq result (append result (list (car a) (cddr a))))))) |
|
|
|
(defun shuffle-single-play () |
|
(interactive) |
|
(when (bufferp (shuffle-get :init)) |
|
(with-current-buffer (shuffle-get :init) |
|
(setq shuffle-single-play (not shuffle-single-play)) |
|
(shuffle-put-fringe shuffle-position) |
|
(recenter nil t) |
|
(message "%s single play." (if shuffle-single-play "Enable" "Disable"))))) |
|
|
|
(defun shuffle-buffer-properties (mode) |
|
(let ((mark-buff (wtag-buffer-mark-p ?*)) |
|
(abs (wtag-absolutely-track-number)) |
|
stat props) |
|
(when (and (natnump mode) (null (assq mode abs))) |
|
(shuffle-reset-stat) |
|
(error "Illegal number")) |
|
(when (wtag-common-area-p) |
|
(goto-char (point-min)) |
|
(forward-line 2)) |
|
(cond |
|
((or mark-buff (not (natnump mode))) |
|
(goto-char (point-min))) |
|
((natnump mode) |
|
(goto-char (point-min)) |
|
(forward-line (1+ (cdr (assq mode abs))))) |
|
(t |
|
(beginning-of-line))) |
|
(save-excursion |
|
(while (not (eobp)) |
|
(when (or (null mark-buff) (wtag-mark-p ?*)) |
|
(setq stat (shuffle-point-music-tags ?*)) |
|
(and stat (push (shuffle-alist-to-plist stat) props))) |
|
(forward-line))) |
|
(if (not (natnump mode)) |
|
(shuffle-shuffle props) |
|
(reverse props)))) |
|
|
|
(defun shuffle-init-buff (mode) |
|
"MODE (prefix) で shuffle に関するステータスの初期化をする." |
|
(shuffle-open-shade) |
|
(and (shuffle-get :timer) (shuffle-cancel-timer)) |
|
(and shuffle-sleep-timer-time (shuffle-sleep-timer shuffle-sleep-timer-time)) |
|
(shuffle-reset-stat) |
|
(shuffle-put :mode mode) |
|
(shuffle-put :init (current-buffer)) |
|
;; (shuffle-put :window (selected-window)) |
|
(shuffle-put :next (shuffle-buffer-properties mode)) |
|
(and shuffle-title (shuffle-title-clear)) |
|
(shuffle-get :next)) |
|
|
|
(defvar shuffle-unknown "Unknown") |
|
|
|
(defun shuffle-plist-get (lst tag) |
|
(or (plist-get lst tag) shuffle-unknown)) |
|
|
|
(defun shuffle-rondom (lst) |
|
(and lst (nth (random (length lst)) lst))) |
|
|
|
(defun shuffle-cancel-timer () |
|
(interactive) |
|
(and (shuffle-get :timer) (cancel-timer (shuffle-get :timer))) |
|
(shuffle-put :timer nil) |
|
(when (shuffle-get :progress-timer) |
|
(cancel-timer (shuffle-get :progress-timer)) |
|
(shuffle-put :progress-timer nil) |
|
(shuffle-put :now nil) |
|
(shuffle-put :progress (shuffle-get :total-time)))) |
|
|
|
(defun shuffle-cancel-sleep-timer () |
|
(interactive) |
|
(and (shuffle-get :sleep) (cancel-timer (shuffle-get :sleep))) |
|
(shuffle-put :sleep nil)) |
|
|
|
(defun shuffle-title-clear () |
|
(and (memq 'shuffle-title global-mode-string) |
|
(setq global-mode-string |
|
(delq 'shuffle-title global-mode-string))) |
|
(and (buffer-live-p (shuffle-get :init)) |
|
(with-current-buffer (shuffle-get :init) |
|
(setq wtag-music-title nil))) |
|
(setq shuffle-title nil)) |
|
|
|
(defun shuffle-reset-stat () |
|
(let ((tmp (cons (shuffle-get :timer) |
|
(shuffle-get :sleep)))) |
|
(setq shuffle-stat nil) |
|
(shuffle-put :timer (car tmp)) |
|
(shuffle-put :sleep (cdr tmp)))) |
|
|
|
(defun shuffle-kill-silent (&optional prefix) |
|
(interactive "P") |
|
(shuffle-kill prefix 'silent)) |
|
|
|
;; 2.18 曲間クリック防止で再生前に delete-process しなくしたため |
|
;; 連続再生時に前のトラックと被ると process_name<1> などと番号が付き |
|
;; うまく get-process できなくなるので(nil になる) |
|
;; process-list から調べて削除するようにした. |
|
(defun shuffle-get-process () |
|
(or (get-process wtag-process-name) |
|
(seq-find |
|
(lambda (p) |
|
(string-match |
|
(concat "\\`" |
|
(regexp-quote wtag-process-name) |
|
"\\(<[[:digit:]]+>\\)?\\'") |
|
(process-name p))) |
|
(process-list)))) |
|
|
|
(defun shuffle-process-delete () |
|
(let ((ps (shuffle-get-process))) |
|
(and ps (delete-process ps)))) |
|
|
|
;;;###autoload |
|
(defun shuffle-kill (&optional prefix silent) |
|
"PREFIX が non-nil なら初期化フラグを保持、つまり再開ができる." |
|
(interactive "P") |
|
(shuffle-process-delete) |
|
(setq wtag-process nil) |
|
(shuffle-cancel-timer) |
|
;; (or prefix (shuffle-cancel-sleep-timer)) |
|
(shuffle-cancel-sleep-timer) |
|
(shuffle-title-clear) |
|
(shuffle-all-minor-mode -1) |
|
(unless prefix |
|
(and (buffer-live-p (shuffle-get :init)) |
|
(shuffle-open-shade (shuffle-get :init))) |
|
(shuffle-reset-stat) |
|
(shuffle-put :init nil)) |
|
(or silent |
|
(message "%s shuffle play process." (if prefix "Stop" "Kill")))) |
|
|
|
(defun shuffle-stop () |
|
(interactive) |
|
(shuffle-kill 'keep)) |
|
|
|
(defun shuffle-filter (prc str) |
|
"エラーハンドラフィルタ. PRC 番号と STR." |
|
(if (string-match "error: .+valid MPEG header" str) |
|
(progn |
|
(shuffle-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 shuffle-next () |
|
(interactive) |
|
(let ((interactive (called-interactively-p 'interactive))) |
|
(if (shuffle-get :next) |
|
(progn |
|
(or shuffle-all-minor-mode (shuffle-all-minor-mode 1)) |
|
(if (or (eq major-mode 'wtag-view-mode) (bufferp (shuffle-get :init))) |
|
(let ((next (shuffle-get :next))) |
|
(shuffle-put :next (cdr next)) |
|
(shuffle-play (car next) (not (cdr next)) interactive)) |
|
(shuffle-all nil interactive))) |
|
(error "Ther is Nothing")))) |
|
|
|
(defun shuffle-music-play (prefix) |
|
(interactive "p") |
|
(if (eq major-mode 'wtag-view-mode) |
|
(shuffle-wtag-play prefix) |
|
(shuffle-all))) |
|
|
|
;;;###autoload |
|
(defun shuffle-all (&optional dirs interactive) |
|
(interactive |
|
(list (if (or current-prefix-arg (null shuffle-directory)) |
|
(read-directory-name "Dir: ") |
|
shuffle-directory) |
|
(called-interactively-p 'interactive))) |
|
(let (dir files index target) |
|
(unless (and (shuffle-get :next) (shuffle-get :init)) (shuffle-init dirs)) |
|
(run-hooks 'shuffle-all-startup-hook) |
|
(shuffle-all-minor-mode 1) |
|
(setq dir (shuffle-rondom (shuffle-get :next)) |
|
index (get-text-property 0 :index dir)) |
|
(setq files (directory-files dir t shuffle-music-file) |
|
target (shuffle-rondom index) |
|
index (remq target index)) |
|
(if index |
|
(put-text-property 0 1 :index index dir) |
|
(shuffle-put :next (remove dir (shuffle-get :next)))) |
|
(shuffle-play (nth target files) (not (shuffle-get :next)) interactive))) |
|
|
|
(defun shuffle-make-prefix-number () |
|
(car (rassq |
|
(if (wtag-common-area-p) |
|
1 |
|
(- (line-number-at-pos) 2)) |
|
(wtag-absolutely-track-number)))) |
|
|
|
(defun shuffle-auto-forward (prefix) |
|
(when (and (natnump prefix) wtag-music-play-next) |
|
(and (numberp wtag-music-play-next) |
|
(sleep-for wtag-music-play-next)) |
|
(forward-line))) |
|
|
|
(defun shuffle-play-mouse (event) |
|
(interactive "e") |
|
(let* ((ev (if (eq (car event) 'mouse-1) (cadr event))) |
|
(num (- (line-number-at-pos (nth 1 ev)) 2)) |
|
(file (wtag-get-point-filename)) |
|
(ps (shuffle-get-process)) |
|
(abs (wtag-absolutely-track-number))) |
|
(if (and ps (member file (process-command ps))) |
|
(shuffle-kill) |
|
(shuffle-wtag-play (car (rassq num abs)))))) |
|
|
|
(defsubst shuffle-single-play-p () |
|
(and (shuffle-get :init) |
|
(with-current-buffer (shuffle-get :init) shuffle-single-play))) |
|
|
|
(defun shuffle-next-function (mode last) |
|
(funcall |
|
(cond |
|
((or (shuffle-single-play-p) last) |
|
#'shuffle-kill) |
|
((eq mode 'wtag) |
|
#'shuffle-next) |
|
(t |
|
#'shuffle-all)))) |
|
|
|
;;;###autoload |
|
(defun shuffle-wtag-play (prefix) |
|
(interactive |
|
(list (if current-prefix-arg |
|
(prefix-numeric-value current-prefix-arg) |
|
(shuffle-make-prefix-number)))) |
|
(let (props) |
|
(run-hooks 'shuffle-all-startup-hook) |
|
(with-current-buffer (or (shuffle-get :init) (current-buffer)) |
|
(unless (eq major-mode 'wtag-view-mode) (error "Only wtag view mode")) |
|
(when (or wtag-process (null (shuffle-get :init))) |
|
(shuffle-kill-silent) |
|
(shuffle-init-buff prefix)) |
|
;; (shuffle-auto-forward prefix) |
|
(shuffle-all-minor-mode 1) |
|
(setq props (shuffle-get :next)) |
|
(shuffle-put :next (cdr props)) |
|
(unless (car props) (shuffle-kill-silent) (error "No match file")) |
|
(shuffle-play (car props) (not (cdr props)) |
|
(called-interactively-p 'interactive))))) |
|
|
|
(defun shuffle-play (file-or-prop &optional last interactive) |
|
(let* ((mode (if (consp file-or-prop) 'wtag)) |
|
(tags (if (eq mode 'wtag) |
|
file-or-prop |
|
(mf-tag-read-plist file-or-prop nil t))) |
|
(file (if (eq mode 'wtag) (plist-get tags 'filename) file-or-prop)) |
|
(time (car (plist-get tags '*time))) |
|
(pos (plist-get tags '*pos)) |
|
(app (assoc-default file shuffle-players #'string-match)) |
|
(gap (shuffle-set-gap (car app))) |
|
(prog (car app)) |
|
(opt (cdr app)) |
|
(name wtag-process-name) |
|
(now (shuffle-now tags))) |
|
(unless app (shuffle-kill nil t) (error "Invalid file `%s'" file)) |
|
(plist-put tags :file file) |
|
(shuffle-cancel-timer) |
|
(setq shuffle-title |
|
(propertize |
|
now |
|
'face 'shuffle-mode-line |
|
'help-echo now |
|
'local-map |
|
(make-mode-line-mouse-map 'mouse-1 'shuffle-all-minor-mode-menu-map) |
|
'mouse-face 'mode-line-highlight)) |
|
(when (and shuffle-global-mode-string global-mode-string) |
|
(or (memq 'shuffle-title global-mode-string) |
|
(setq global-mode-string |
|
(append global-mode-string (list 'shuffle-title))))) |
|
(and pos |
|
(with-current-buffer (shuffle-get :init) |
|
(setq wtag-music-title shuffle-title) |
|
(setq-local shuffle-position pos))) |
|
(and interactive (shuffle-process-delete)) |
|
(setq wtag-process |
|
(progn |
|
(message "%s" shuffle-title) |
|
(and pos (shuffle-shade pos (shuffle-get :init))) |
|
(apply #'start-process name name prog (append opt (list file))))) |
|
(set-process-filter wtag-process #'shuffle-filter) |
|
(shuffle-put :timer (run-with-timer |
|
(+ time gap) nil #'shuffle-next-function mode last)) |
|
(shuffle-put :total-time time) |
|
(shuffle-put :now now) |
|
(shuffle-put :progress time) |
|
(shuffle-put :progress-timer |
|
(and shuffle-progress-interval |
|
(not (stringp shuffle-all-minor-mode-lighter)) |
|
(shuffle-progress-time-timer))) |
|
nil)) |
|
|
|
(defun shuffle-wtag-view-buffer-p () |
|
(eq major-mode 'wtag-view-mode)) |
|
|
|
(defvar shuffle-all-minor-mode-menu-map |
|
(let ((menu (make-sparse-keymap "Shuffle all Minor Mode"))) |
|
(define-key menu [shuffle-process-buffer] '("Log" . shuffle-process-buffer)) |
|
(define-key menu [dasshes1] '("--")) |
|
(define-key menu [shuffle-current-shade] |
|
'(menu-item "Current Shade" shuffle-current-shade |
|
:enable (shuffle-wtag-view-buffer-p))) |
|
(define-key menu [shuffle-previous-shade] |
|
'(menu-item "Previous Shade" shuffle-previous-shade |
|
:enable (shuffle-wtag-view-buffer-p))) |
|
(define-key menu [shuffle-next-shade] |
|
'(menu-item "Next Shade" shuffle-next-shade |
|
:enable (shuffle-wtag-view-buffer-p))) |
|
(define-key menu [dasshes2] '("--")) |
|
(define-key menu [shuffle-stop] '("Stop" . shuffle-stop)) |
|
(define-key menu [shuffle-kill] '("Kill" . shuffle-kill)) |
|
(define-key menu [shuffle-next] '("Next" . shuffle-next)) |
|
(define-key menu [shuffle-all] '("Play" . shuffle-all)) |
|
menu)) |
|
|
|
(fset 'shuffle-all-minor-mode-menu-map shuffle-all-minor-mode-menu-map) |
|
|
|
(defvar shuffle-all-minor-mode-map |
|
(let ((map (make-sparse-keymap))) |
|
(define-key map "\C-c[k" #'shuffle-kill) |
|
(define-key map "\C-c[s" #'shuffle-stop) |
|
(define-key map "\C-c[l" #'shuffle-process-buffer) |
|
(define-key map "\C-c[n" #'shuffle-next) |
|
(define-key map "\C-c[p" #'shuffle-all) |
|
(define-key map "\C-c[\C-n" #'shuffle-next-shade) |
|
(define-key map "\C-c[\C-p" #'shuffle-previous-shade) |
|
(define-key map [?\C-c ?\[ ?\C-.] #'shuffle-current-shade) |
|
(define-key map [menu-bar shuffle] (cons "Shuffle" shuffle-all-minor-mode-menu-map)) |
|
map)) |
|
|
|
(defvar-keymap shuffle-all-minor-mode-repeat-map |
|
:repeat t |
|
"C-n" #'shuffle-next-shade |
|
"C-p" #'shuffle-previous-shade |
|
"C-." #'shuffle-current-shade) |
|
|
|
(define-minor-mode shuffle-all-minor-mode |
|
"Shuffle All Minor Mode." |
|
:global t |
|
:group 'music-file |
|
:lighter shuffle-all-minor-mode-lighter |
|
(let ((inhibit-message t) message-log-max) (repeat-mode 1))) |
|
|
|
(defvar shuffle-log-mode-font-lock |
|
'(("^\\(Comment:\\)\\( .+\\)$" (1 'link) (2 'shuffle-shade)) |
|
("\\([A-Z][a-z]+:\\)[ ]" 1 'link) |
|
("^Process .+$" . font-lock-warning-face))) |
|
|
|
(define-derived-mode shuffle-log-mode fundamental-mode "Shuffle-log" |
|
"Shuffle log mode." |
|
(setq-local font-lock-defaults (list shuffle-log-mode-font-lock)) |
|
(define-key shuffle-log-mode-map "q" #'quit-window)) |
|
|
|
(provide 'shuffle-all) |
|
;; |