読者です 読者をやめる 読者になる 読者になる

emacs で mp3 を聞く(シンプルなmpg123-mode)

emacs

下のほうの表示の問題は解決したので github に直したものを置いておきました。
GitHub - ryo1miya/mpg123-simple: a simple interface to mpg123

追記 よそ様に同タイトルの記事があったのでタイトル変更しました
追記 helm 用のソースも追加しました

ちょっと息抜きとして書いたものですが、まずまず使えそうなので公開することに。ただし動く保障はありません。

動機 コマンドラインで mp3 を再生する mpg123 というソフトウェアがあり、これを emacs 上で操作したい。ついでに大量にあるプレイリストを絞り込み検索したい。

自分は使ったことがありませんが、高名な広瀬氏が本格的なものをすでに書いています。

http://www.gentei.org/~yuuji/software/mpg123el/mpg123.el

ここでは init.el(.emacs.el) に貼り付けられるくらいシンプルなものを。 mpg123 はすでにインストールされていると仮定します。

追記 ディレクトリがこういう構成になっている人向けです

mp3/
   | artist1/
   |        | album1/
   |        | album2/
   |        | album3/
   |
   | artist1-album1.m3u
   | artist1-album2.m3u
   | artist1-album3.m3u
   |
   | artist2/
   |        | album1/
   |        | album2/
   |
   | artist2-album1.m3u
   | artist2-album2.m3u

直下の *mpg123-dir* には m3u ファイルがあるディレクトリを指定。
追記 loop のところでエラーがでるようなら (require 'cl) するか、 あるいは (require 'cl-lib) して loop を cl-loop に変更してください。

(defvar *mpg123-dir* "~/mp3/") ;; <- 末尾はスラッシュ
(defvar *mpg123-buf* "*mpg123*")
(defvar *mpg123-proc* nil)
(defvar *mpg123-current-m3u* nil)

(defun mpg123-make-keylist (lst)
  (loop for elt in lst collect 
        `(define-key mpg123-mode-map ,elt (lambda () (interactive) (process-send-string *mpg123-proc* ,elt)))))

(defmacro mpg123-defkey (&rest lst) `(progn ,@(mpg123-make-keylist lst)))

(defun mpg123-quit ()
  (interactive)
  (ignore-errors (process-send-string *mpg123-proc* "q")
                 (delete-process *mpg123-proc*))
  (setq *mpg123-proc* nil))

(defun mpg123-replay () (interactive) (mpg123-mode *mpg123-current-m3u*))
(defun mpg123-shuffle () (interactive) (mpg123-mode *mpg123-current-m3u* '(4)))
(defun mpg123-loop (i) (interactive "nInput Track Number : ") (mpg123-mode *mpg123-current-m3u* i))

(defun mpg123-mode (f &optional arg)
  (interactive 
   (list (file-name-nondirectory (read-file-name "Input PlayList : " *mpg123-dir*))
         current-prefix-arg))
  ;; initial settings
  (unless (boundp 'mpg123-mode-map)
    (setq mpg123-mode-map (make-keymap))
    (mpg123-defkey "s" " " "f" "d" "b" "p" "." "," ":" ";" ">" "<" "+" "-" "r" "v" "l" "t" "m" "h" "c" "C" "x" "X" "w" "k")
    (define-key mpg123-mode-map "q" 'mpg123-quit)
    (define-key mpg123-mode-map "R" 'mpg123-replay)
    (define-key mpg123-mode-map "S" 'mpg123-shuffle)
    (define-key mpg123-mode-map "L" 'mpg123-loop)
    (suppress-keymap mpg123-mode-map)
    (make-variable-buffer-local '*mpg123-proc*)
    (make-variable-buffer-local '*mpg123-current-m3u*))
  (when *mpg123-proc* (mpg123-quit))
  ;; main part
  (let ((buf (get-buffer-create *mpg123-buf*)))
    (pop-to-buffer buf)
    (erase-buffer)
    (setq default-directory (expand-file-name *mpg123-dir*)
          major-mode 'mpg123-mode mode-name "mpg123" *mpg123-current-m3u* f
          *mpg123-proc*
          (cond ((numberp arg) ;; loop arg-th track
                 (start-process "mpg123" buf "mpg123" "--loop" "-1" "-l" (number-to-string arg) "-C@" f))
                ((equal arg '(4)) ;; shuffle
                 (start-process "mpg123" buf "mpg123" "-Cz@" f))
                (t ;; normal
                 (start-process "mpg123" buf "mpg123" "-C@" f))))
    (use-local-map mpg123-mode-map)))

(add-hook 'kill-buffer-hook 
          (lambda () (when (string= *mpg123-buf* (buffer-name (current-buffer))) (mpg123-quit))))

M-x mpg123-mode RET hoge.m3u RET で再生開始。 (C-u M-x hoge.m3u でシャッフル再生。 num M-x hoge.m3u で hoge.m3u の num 番目のトラックをリピート再生。)
ただし、(..)の呼び出しを使うことは無いと思う。 *mpg123* バッファで S,L を押すのをオススメしたい。

 *mpg123* バッファでの使い方は、追加の機能を除けば素の mpg123 と同じなので h を押してヘルプを参照。 停止は q .

いくつか注意点など。

追加の機能として R,S,L がある。 R はプレイリストの最初から再生(mpg123-replay). S でシャッフル再生(mpg123-shuffle). L で一曲のみのリピート再生(mpg123-loop). L を押すとミニバッファで曲番を聞かれるので番号入力するとループ再生する。曲番がわからないときは l を押してプレイリストを表示して何曲目かを数える。

v を押すとトラックの時間などを表示できるが表示が大変なことになる。プロセスフィルターを書くのが面倒なので放置している。誰か書きませんか。
ボリュームコントロールはパソコン本体をいじった方が良いと思う。
プレイリスト全体のループ再生は、シェルスクリプトを使うか emacs 側で対応しないといけないらしい。これも放置している。とりあえずの処置として追加の機能 R, S をつけた。
podcast の url を m3u ファイルに書いておくのも吉。

後はプレイリストを絞り込み検索できるようにすれば快適に使える。

(defvar anything-mpg123-playlists
  '((name . "Play List")
    (candidates . (lambda () (directory-files *mpg123-dir* nil "\\.m3u$")))
    (action . (("Open PlayList" . anything-mpg123-open-playlist)))))

(defun anything-mpg123 () (interactive) (anything (list anything-mpg123-playlists)))
(defun anything-mpg123-open-playlist (f) (interactive) (mpg123-mode f))

anything-mpg123 を適当なキーにバインドする。自分は残っているキーがあまり無いので

(global-set-key (kbd "C-^") 'anything-mpg123)

としている。

helm でも同じことができるはず。最近は helm のほうが勢いがあるようですが、ともかくこういうケースでは絞り込み検索は強力です。

追記 helm 用のソース (anything とほとんど同じでした)

(defvar helm-mpg123-playlists
  '((name . "Play List")
    (candidates . (lambda () (directory-files *mpg123-dir* nil "\\.m3u$")))
    (action . helm-mpg123-open-playlist)))

(defun helm-mpg123 () (interactive) (helm :sources (list helm-mpg123-playlists)))
(defun helm-mpg123-open-playlist (f) (interactive) (mpg123-mode f))