メディアプレーヤのmpvでは、オプション指定により動画や音声のファイルの一部だけを再生することができ、この範囲の情報をファイルに複数記述して順に再生したい場合にはシェルスクリプトを記述して実行していたが、あまりいい形の方法ではないと考えていた。
今回、同プレーヤの内部の機能をLuaスクリプトから呼び出す仕組みを用いて、再生範囲などの情報を読み書きしやすい形で複数記述したものをプレイリストのように扱って順に再生することができるかを試していたのだが、最終的に意図した動作をするものが作れたので、Luaのコードと使い方についてをここで扱う。
また、今回作成したスクリプトでは、ファイルの特定範囲のループ再生も行えるようになっている。
ソフトウェア要件
mpvでLuaを使う機能はビルド時に有効化するかどうかが選択可能で、これを有効に設定してビルドした同プレーヤのパッケージが必要。Debian/UbuntuのパッケージではLuaは有効になっている。
コード (mpv-playparts.lua)
このプログラムは単体で指定しても動作しない。後述するように、別途プレイリストとしての情報を記述したLuaスクリプトを用意し、各スクリプトからLuaのdofile()
関数によりこのプログラムが呼ばれるようにした上で各プレイリストのLuaスクリプトのほうを指定して実行する形となる。
mpv-playparts.lua
ライセンス:MIT--[[-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
mpv-playparts.lua - Lua/mpv script to play parts of media file(s)
20170720
(C) 2017 kakurasan
Licensed under MIT
Usage:
----- Your script file -----
-- Default file (optional)
default_file = "/path/to/file1.ext"
-- Playlist
-- "startpos", "endpos", "loopstart", and "loopend" are absolute times
-- ab-loop is enabled if "loopstart" or "loopend" is specified (manual loop)
-- or "autoloop" is set to true (auto loop)
-- [vorbis-tools is required to use auto loop]
-- Defaults:
-- file = default_file, ("file" or "default_file" must be specified)
-- startpos = '0', endpos = [end of file],
-- loopstart = startpos, loopend = [end of file]
playlist = {
-- Partial playback
{ file = "/path/to/file1.ext", startpos = "5", endpos = "8", title = "Title 1" },
{ file = "/path/to/file2.ext", startpos = "1:2", endpos = "1:5", title = "Title 2" },
-- Manual loop
{ file = "/path/to/file3.ext", startpos = "1:2:3", loopend = "1:2:9", title = "Title 3" },
{ file = "/path/to/file4.ext", startpos = "1:3:5", loopstart = "1:3:7", loopend = "1:3:9", title = "Title 4" },
-- Auto loop (Vorbis files which have "LOOPSTART" and optional "LOOPLENGTH")
{ file = "/path/to/file5.ogg", autoloop = true, title = "Title 5" },
-- Files in archive files (unar is used to extract files)
{ file = "path/to/file6.ext", archive = "/path/to/archive.zip", title = "Title 6" },
}
-- Execute this file
dofile ("/path/to/mpv-playparts.lua")
----- How to run mpv -----
$ mpv --script=/path/to/your_script.lua -
----- Configuration (lua-settings/playparts.conf) -----
-- "${part-title}" expands to the title in "playlist"
-- "${part-range}" expands to the playback range
osd_font_size=[OSD font size]
osd_msg3=[OSD level3 message]
window_title=[Window title]
term_playing_msg=[Terminal message (term-playing-msg)]
term_status_msg=[Terminal message (term-status-msg)]
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --]]
require 'mp.options'
function secs_from_abstime (str_abstime)
local idx_dot = str_abstime:find ('%.')
if idx_dot then
if str_abstime:find ('%.', idx_dot + 1) then
-- The string has multiple dots
return -1
end
end
local idx_colon1 = str_abstime:find (':')
if not idx_colon1 then
-- Format: "sec"
return tonumber (str_abstime)
else
local idx_colon2 = str_abstime:find (':', idx_colon1 + 1)
if not idx_colon2 then
-- Format: "min : sec"
return str_abstime:sub (1, idx_colon1 - 1) * 60
+ str_abstime:sub (idx_colon1 + 1)
else
if not str_abstime:find (':', idx_colon2 + 1) then
-- Format: "hour : min : sec"
return str_abstime:sub (1, idx_colon1 - 1) * 3600
+ str_abstime:sub (idx_colon1 + 1, idx_colon2 - 1) * 60
+ str_abstime:sub (idx_colon2 + 1)
else
-- Format: "a : b : c : d ..." (invalid)
return -1
end
end
end
end
function reformat_abstime (str_abstime)
local decimal = ''
local idx_dot = str_abstime:find ('%.')
local abstime_secs = str_abstime
if idx_dot then
decimal = str_abstime:sub (idx_dot)
abstime_secs = str_abstime:sub (1, idx_dot - 1)
if str_abstime:find ('%.', idx_dot + 1) then
return '(invalid time)'
end
end
local idx_colon1 = abstime_secs:find (':')
if not idx_colon1 then
if tonumber (abstime_secs) < 60 then
return ('00:00:%02d%s'):format (abstime_secs, decimal)
else
return ('%02d:%02d:%02d%s'):format (abstime_secs / 3600, abstime_secs / 60 % 60, abstime_secs % 60, decimal)
end
else
local idx_colon2 = abstime_secs:find (':', idx_colon1 + 1)
if not idx_colon2 then
return ('00:%02d:%02d%s'):format (abstime_secs:sub (1, idx_colon1 - 1), abstime_secs:sub (idx_colon1 + 1), decimal)
else
if not abstime_secs:find (':', idx_colon2 + 1) then
return ('%02d:%02d:%02d%s'):format (abstime_secs:sub (1, idx_colon1 - 1), abstime_secs:sub (idx_colon1 + 1, idx_colon2 - 1), abstime_secs:sub (idx_colon2 + 1), decimal)
else
return '(invalid time)'
end
end
end
end
function get_looptimes_from_vorbis (file)
local rate
local values = {}
local f = io.popen (('ogginfo "%s"'):format (file))
if not f then
return '0', nil
end
for l in f:lines () do
if not rate and l:sub (1, 6) == 'Rate: ' then
rate = tonumber (l:sub (7))
else
for k, v in l:gmatch ('(%w+)=(%w+)') do
values[k] = v
end
end
end
f:close ()
local loopstart = '0'
local loopend
if rate then
if values.LOOPSTART then
loopstart = ('%.2f'):format (values.LOOPSTART / rate)
if values.LOOPLENGTH then
loopend = ('%.2f'):format (values.LOOPSTART / rate + values.LOOPLENGTH / rate)
end
end
end
return loopstart, loopend
end
function get_script_opts ()
local script_opts = {
osd_font_size = mp.get_property_number ('options/osd-font-size') * 0.65,
osd_msg3 = '${osd-sym-cc} ${time-pos} / ${duration} (${percent-pos}%)\n[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range})${!ab-loop-a==no: <}${!ab-loop-a==no:${ab-loop-a}-${!ab-loop-b==no:${ab-loop-b}}${!ab-loop-a==no:>}}',
window_title = '[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range}) - ${filename}',
term_playing_msg = '[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range}) - ${filename}',
term_status_msg = mp.get_property ('term-status-msg'),
}
read_options (script_opts, 'playparts')
return script_opts
end
function make_tempdir ()
local tempdir = nil
local f_tempdir = io.popen ('mktemp --tmpdir -d playparts.XXXXXX')
if f_tempdir then
tempdir = f_tempdir:read ()
f_tempdir:close ()
end
if tempdir then
tempdir = tempdir .. package.config:sub (1, 1)
mp.register_event ('shutdown', function (_event)
if tempdir:match ('playparts%.') then
print (('Deleting temporary directory "%s"'):format (tempdir))
os.execute (('rm -fr "%s"'):format (tempdir))
end
end)
end
return tempdir
end
function load_files ()
local need_tempdir = false
local tempdir
for _, item in ipairs (playlist) do
if item.archive then
need_tempdir = true
end
end
if need_tempdir then
tempdir = make_tempdir ()
if not tempdir then
print ('Error: Could not create temporary directory')
mp.command ('quit')
return
end
end
local script_opts = get_script_opts ()
local unar_encoding_opt = ''
if archive_encoding then
-- Check invalid chars in encoding name
local match_format = archive_encoding:match ('^[0-9a-zA-Z%-]+$')
if match_format then
unar_encoding_opt = ('-e "%s"'):format (archive_encoding)
end
end
for i, item in ipairs (playlist) do
local do_loop = false
if item.loopstart or item.loopend then
if item.endpos then
print (('Warning in #%d: endpos is specified, disabling loop'):format (i))
else
do_loop = true
end
end
if not item.loopstart then
if item.startpos then
item.loopstart = item.startpos
else
item.loopstart = '0'
end
end
if not item.startpos then
item.startpos = '0'
end
if not item.file and not default_file then
print (('Error in #%d: No file specified'):format (i))
mp.command ('quit')
return
elseif secs_from_abstime (item.startpos) == -1 then
print (('Error in #%d: startpos (%s) is invalid'):format (i, item.startpos))
mp.command ('quit')
return
elseif item.endpos and secs_from_abstime (item.endpos) == -1 then
print (('Error in #%d: endpos (%s) is invalid'):format (i, item.endpos))
mp.command ('quit')
return
elseif not item.autoloop and item.loopend and secs_from_abstime (item.loopend) == -1 then
print (('Error in #%d: loopend (%s) is invalid'):format (i, item.loopend))
mp.command ('quit')
return
elseif item.endpos and secs_from_abstime (item.endpos) < secs_from_abstime (item.startpos) then
print (('Error in #%d: endpos (%s) < startpos (%s)'):format (i, item.endpos, item.startpos))
mp.command ('quit')
return
elseif not item.autoloop and secs_from_abstime (item.loopstart) < secs_from_abstime (item.startpos) then
print (('Error in #%d: loopstart (%s) < startpos (%s)'):format (i, item.loopstart, item.startpos))
mp.command ('quit')
return
elseif not item.autoloop and item.loopend and secs_from_abstime (item.loopend) < secs_from_abstime (item.startpos) then
print (('Error in #%d: loopend (%s) < startpos (%s)'):format (i, item.loopend, item.startpos))
mp.command ('quit')
return
else
if not item.file then
item.file = default_file
end
-- Media files in archive files
if item.archive then
item.file_in_archive = item.file
item.file = tempdir .. item.file
local extract_file = function ()
if os.execute (('unar -q %s -D -f -o "%s" "%s" "%s"'):format (unar_encoding_opt, tempdir, item.archive, item.file_in_archive)) then
else
print (('Error in #%d: unar failed (%s)'):format (i, item.archive))
mp.command ('quit')
end
end
if i == 1 then
extract_file ()
else
-- Extract 2nd ... last file asynchronously
mp.add_timeout (0.01, extract_file)
end
end
if item.autoloop then
item.loopstart, item.loopend = get_looptimes_from_vorbis (item.file)
do_loop = true
end
local opts = ('osd-font-size="%d",start="%s"'):format (script_opts.osd_font_size, item.startpos)
if do_loop then
opts = opts .. (',ab-loop-a="%s"'):format (item.loopstart)
if item.loopend then
opts = opts .. (',ab-loop-b="%s",end="%s"'):format (item.loopend, item.loopend)
end
elseif item.endpos then
opts = opts .. (',end="%s"'):format (item.endpos)
end
local range = ('%s-'):format (reformat_abstime (item.startpos))
if do_loop and item.loopend then
range = range .. reformat_abstime (item.loopend)
elseif item.endpos then
range = range .. reformat_abstime (item.endpos)
end
local window_title = script_opts.window_title:gsub ('${part%-title}', item.title and item.title or '')
window_title = window_title:gsub ('${part%-range}', range)
local osd_msg3 = script_opts.osd_msg3:gsub ('${part%-title}', item.title and item.title or '')
osd_msg3 = osd_msg3:gsub ('${part%-range}', range)
local term_playing_msg = script_opts.term_playing_msg:gsub ('${part%-title}', item.title and item.title or '')
term_playing_msg = term_playing_msg:gsub ('${part%-range}', range)
opts = opts .. (',title="%s",osd-msg3="%s",term-playing-msg="%s"'):format (window_title, osd_msg3, term_playing_msg)
if script_opts.term_status_msg ~= '' then
local term_status_msg = script_opts.term_status_msg:gsub ('${part%-title}', item.title and item.title or '')
term_status_msg = term_status_msg:gsub ('${part%-range}', range)
opts = opts .. (',term-status-msg="%s"'):format (term_status_msg)
end
mp.commandv ('loadfile', item.file, mp.get_property ('playlist-pos') and 'append' or 'replace', opts)
end
end
end
do
if playlist then
load_files ()
else
print ('Do not specify this file directly.')
end
end
(2017/7/18)--term-status-msg
オプションに相当する設定の変更を行う設定項目を追加
(2017/7/20)書庫内のファイルの再生機能を追加(unar
が必要)
使い方
上のスクリプトを任意の場所に配置し、プレイリストとなるLuaスクリプトを適切に記述した上で、mpvから--script
オプションで各プレイリストのLuaスクリプトの場所を指定して実行する。
再生対象ファイルの場所はプレイリストとしての各スクリプトに記述することになるが、mpvは--script
オプションだけを付けて*引数がない形で実行しても同プレーヤの実行方法が表示されるだけで終了してしまう仕様なので、何か引数を付けて実行する必要がある。*下の例では標準入力の “-
” を指定しているが、実際には使われない。
(標準入力を引数に指定した実行例) $ mpv --script=/path/to/your_script.lua -
Luaスクリプト内に記述したプレイリスト項目はプレーヤ内部のプレイリストに登録されるため、*再生中に<と>で再生項目を切り替えることができる。*映像のあるファイルでは “OSC” と呼ばれる画面内コントローラの三角矢印(“🞀” と “🞂”)を押してもよい。
OSDをレベル3で表示している(--osd-level=3
)場合、映像を含んだファイルの再生時には通常、再生状態を示すマークの横に再生時間,全体時間,パーセンテージが表示されるが、このスクリプトで再生中はこれらに加えてプレイリスト項目の現在値と全体数(角括弧内),プレイリストに入力したタイトル,再生時間の範囲(丸括弧内),ループ時の範囲(ループ時にのみ不等号括弧に表示)といった情報を表示する。
範囲ループ再生時はずっと再生が続くため、<と>で項目を切り替えるか、もしくはqでプレーヤを終了する。再生中にlを一度押すことでもループが解除できる(“Clear A-B loop” と短時間表示されてループ範囲が消える)。この方法でループの解除を行った場合、元々のループ終了時間まで再生が進むとその項目の再生が終了する。
OSDの文字の大きさは設定ファイルや--osd-font-size
オプションでも指定できる仕組みとなっているが、表示文字数が多くなる関係で元々の指定サイズと比べて小さめになるようにスクリプト側で倍率調整している。文字の大きさなどが好みでない場合には専用の設定ファイルやオプション指定によりカスタマイズできるようにしている(後述)。
プレイリストファイルの記述
繰り返しになるが、個別のプレイリストファイルとしてのLuaスクリプトを記述し、mpvからはこれを指定することになる。
プレイリストファイルの内容は下のような形になる。
-- 下の "playlist" 内で "file" を記述しない場合に再生されるファイル
-- 同じファイルの部分再生を複数行う場合に便利
-- 記述しない場合は全プレイリスト項目に "file = [ファイルの場所]" が必要
default_file = "/path/to/file1.ext"
-- ファイルと再生範囲の情報
-- 項目の説明は後述
playlist = {
-- 部分再生では "startpos" と "endpos" を必要に応じて指定する
{ file = "/path/to/file1.ext", startpos = "5", endpos = "8", title = "タイトル 1" },
{ file = "/path/to/file2.ext", startpos = "1:2", endpos = "1:5", title = "タイトル 2" },
-- 手動ループでは "loopstart" (記述しない場合は "startpos")から
-- "loopend" がループ範囲となる
-- "loopstart" か "loopend" が記述されて
-- "endpos" が記述されていない場合にループが有効になる
{ file = "/path/to/file3.ext", startpos = "1:2:3", loopend = "1:2:9", title = "タイトル 3" },
{ file = "/path/to/file4.ext", startpos = "1:3:5", loopstart = "1:3:7", loopend = "1:3:9", title = "タイトル 4" },
-- "LOOPSTART" と追加の "LOOPLENGTH" がコメントに入っているVorbisファイルで
-- "autoloop = true" を記述するとvorbis-toolsの外部コマンドにより
-- 範囲情報を取得・計算してループする
{ file = "/path/to/file5.ogg", autoloop = true, title = "タイトル 5" },
}
-- 必ず上のスクリプトの場所を引数に指定して記述する
dofile ("/path/to/mpv-playparts.lua")
以下は(Luaのテーブル型の集まりである) “playlist
” の中に記述する各プレイリスト項目における各種指定を行うキー名とその値の一覧となる。
項目 (連想配列のキー) | 値 |
---|---|
file | 再生するファイルの場所 (default_file がない場合は必須で、ある場合はそれが省略時のファイルとなる)・書庫内のファイルを再生する場合は書庫内のパス名 |
archive | 書庫内のファイルを再生する場合に書庫ファイル自体の場所を指定 |
startpos | 再生開始時間 (省略時は先頭) |
endpos | 再生終了時間 (省略時は末尾) |
loopstart | ループ開始時間 (loopend がある場合、省略時はstartpos があればその時間・なければ先頭) |
loopend | ループ終了時間 (loopstart がある場合、省略時は末尾となるが、endpos も同時に記述されていれば無効な指定と扱いループを行わない) |
autoloop | Vorbisファイルのループ情報を自動取得後に範囲を計算してループする場合にtrue |
title | OSD,タイトルバー,端末に表示される文字列内のタイトル名部分 |
手動のループ再生(自分で具体的な開始・終了時間を指定する場合)では
- “
loopstart
” もしくは “loopend
” が記述されている - “
endpos
” が記述されていない
の両方を満たしているときにそのプレイリスト項目に対してループが有効になる。
RPGツクールシリーズのループBGMなどの “LOOPSTART
” と “LOOPLENGTH
” のコメントを含むVorbisファイルでは “autoloop = true
” を付けることでvorbis-toolsのogginfo
でこれらの情報(サンプル数単位)とサンプリングレートを取得して自動的にループ時間を計算してループを有効にする。ただし、情報取得処理にはコマンドからの出力の書式に依存している部分があるため、将来の同コマンドでうまく動かなくなる可能性は考えられる。現時点では、Vorbis以外のコーデックのファイルに同様のタグ情報が入っているものはループ処理されないが、将来的にループ情報コメントが埋め込まれたOpusファイルが普及すればopusinfo
を用いて同様に対応する可能性はある。
なお、*プレーヤ側の問題点として、バージョン0.24.0時点では範囲ループ(ab-loop)について、ループ開始時間へのシークが正確なタイミングで行われないことがある(ループで移動する部分の再生が安定しない)。*ループ情報を含んだBGMをループ再生させる際のタイミングは1/100秒レベルの指定がされており、プレーヤへの精度の要求がシビアになっているため、今後のバージョンで改善されることを期待する。
BGMループにおける使用例
下は自動ループの例として、RPGツクールVX AceのRTPに含まれるBGMの一部をループ再生する。前述したプレイリストやループ再生に関係するキー操作を行わないと最初の曲がずっと再生されるだけなので注意。
vxa-rtp-bgm.lua
-- BGMファイルのディレクトリは実際の階層に合わせる
local dir = "/home/user/.wine/drive_c/Program Files/Common Files/Enterbrain/RGSS3/RPGVXAce/Audio/BGM/"
-- "file" で各BGMファイルの場所を指定し、他は "autoloop = true" のみを記述する
playlist = {
{ file = dir .. "Battle1.ogg", autoloop = true },
{ file = dir .. "Dungeon3.ogg", autoloop = true },
{ file = dir .. "Dungeon5.ogg", autoloop = true },
{ file = dir .. "Dungeon7.ogg", autoloop = true },
{ file = dir .. "Field4.ogg", autoloop = true },
{ file = dir .. "Town2.ogg", autoloop = true },
}
-- スクリプトの場所も実際の配置場所に合わせる
dofile ("/path/to/mpv-playparts.lua")
下は手動ループの例として、島白氏が公開していた曲の一部に独自にループ情報を付けたもの(調整が不十分なものがあるかもしれない)。こちらもBGMとスクリプトの場所は実際の配置場所に合わせる必要がある。
shima26-bgm.lua
local dir = "/path/to/bgm/"
playlist = {
{ file = dir .. "action008.mp3", loopstart = "3.76", loopend = "5:15.79", title = "シルエットの群像" },
{ file = dir .. "battle002.mp3", loopstart = "23.95", loopend = "5:26", title = "打鍵狂想曲" },
{ file = dir .. "bgm001.mp3", loopstart = "3.2", loopend = "3:5", title = "平原を西へ" },
{ file = dir .. "bgm038.mp3", loopstart = "32.7", loopend = "4:16.7", title = "思惑の螺旋" },
{ file = dir .. "bgm039.mp3", loopstart = "28.34", loopend = "6:31.61", title = "窓に降る朝の光" },
{ file = dir .. "hoshinokoe001.mp3", loopstart = "16.3", loopend = "3:7", title = "ほしのこえ" },
{ file = dir .. "Operation City.mp3", loopstart = "15.59", loopend = "3:42.1", title = "Operation city" },
{ file = dir .. "space001.mp3", loopstart = "4.1", loopend = "2:35", title = "展開メガパーセク" },
{ file = dir .. "wadatumi.mp3", loopstart = "1:7.63", loopend = "4:32.27", title = "ワダツミニトフ" },
-- 以下、ループ曲ではないっぽいものを含めてループさせてみるシリーズ
{ file = dir .. "vellion001.mp3", loopstart = "1:19.2", loopend = "4:12.64", title = "VELLION" },
{ file = dir .. "ganz.mp3", loopstart = "25.99", loopend = "4:3.63", title = "GANZ complete" },
{ file = dir .. "blast.mp3", loopstart = "1:33.3", loopend = "4:18.6", title = "BLAST" },
{ file = dir .. "moonchildren.mp3", loopstart = "1:6.48", loopend = "4:50.8", title = "ムーンチルドレン" },
}
dofile ("/path/to/mpv-playparts.lua")
書庫内ファイル再生における使用例
20170720版から書庫内のファイルをプレイリストに記述したものをunar
で一時ディレクトリ(環境変数TMPDIRもしくは/tmp
の中に作成)に展開して再生できるようにした。
例として、Webで公開されている “Cresteaju” のMP3形式のBGM集(4つのZIPファイル群から成る)の全ての曲をそのまま再生するものを貼り付ける。これを指定して実行すると、終了するまでの間、一時ディレクトリを261MiB程度使用する。
cresteaju-mp3-bgm.lua
エンコーディング:UTF-8-- 下はZIPファイルのあるディレクトリの場所に置き換える
local dir = "/path/to/cresteaju_bgm/"
-- 日本語ファイル名のファイルを含んだZIPファイルを展開する際にエンコーディングが
-- 自動検出されるが、間違ったエンコーディングが検出された場合は
-- 文字化けが起こるため、必要であればエンコーディング名
-- (unarの-eオプションの値)を記述する
-- このファイル群では自動検出で正しく動作するので指定は不要
archive_encoding = "cp932"
-- "file" は再生したいファイルの書庫内のパス名を指定して
-- "archive" に書庫ファイル自体の場所を指定する
playlist = {
{ file = "1_01_風が吹きはじめるとき.mp3", archive = dir .. "cres_01.zip", title = "風が吹きはじめるとき" },
{ file = "1_02_Dragon Breath.mp3", archive = dir .. "cres_01.zip", title = "Dragon Breath" },
{ file = "1_03_Generator.mp3", archive = dir .. "cres_01.zip", title = "Generator" },
{ file = "1_04_Endless Way.mp3", archive = dir .. "cres_01.zip", title = "Endless Way" },
{ file = "1_05_戦いの唄.mp3", archive = dir .. "cres_01.zip", title = "戦いの唄" },
{ file = "1_06_route 134.mp3", archive = dir .. "cres_01.zip", title = "route 134" },
{ file = "1_07_following the wind.mp3", archive = dir .. "cres_01.zip", title = "following the wind" },
{ file = "1_08_さかさかば.mp3", archive = dir .. "cres_01.zip", title = "さかさかば" },
-- ファイル名に綴りミスあり
{ file = "1_09_Cloudy Streat.mp3", archive = dir .. "cres_01.zip", title = "Cloudy Street" },
{ file = "1_10_三等星の流れ星.mp3", archive = dir .. "cres_01.zip", title = "三等星の流れ星" },
{ file = "1_11_一陣の風.mp3", archive = dir .. "cres_02.zip", title = "一陣の風" },
{ file = "1_12_Lunnar Road.mp3", archive = dir .. "cres_02.zip", title = "Lunnar Road" },
{ file = "1_13_It's time you had a rest.mp3", archive = dir .. "cres_02.zip", title = "It's time you had a rest" },
{ file = "1_14_トマトかじって、ひるはすぎ.mp3", archive = dir .. "cres_02.zip", title = "トマトかじって、ひるはすぎ" },
-- "The" がファイル名に含まれない
{ file = "1_15_end of long dreaming.mp3", archive = dir .. "cres_02.zip", title = "The end of long dreaming" },
{ file = "1_16_永き幸せの下で.mp3", archive = dir .. "cres_02.zip", title = "永き幸せの下で" },
{ file = "1_17_The Eternal.mp3", archive = dir .. "cres_02.zip", title = "The Eternal" },
{ file = "1_18_Heat a Heart.mp3", archive = dir .. "cres_02.zip", title = "Heat a Heart" },
{ file = "1_19_seek a way.mp3", archive = dir .. "cres_02.zip", title = "seek a way" },
{ file = "1_20_霧深き森.mp3", archive = dir .. "cres_02.zip", title = "霧深き森" },
{ file = "1_21_遠き日と遠き風と.mp3", archive = dir .. "cres_02.zip", title = "遠き日と遠き風と" },
{ file = "2_01_雲の湧く場所.mp3", archive = dir .. "cres_03.zip", title = "雲の湧く場所" },
{ file = "2_02_Sunday^2.mp3", archive = dir .. "cres_03.zip", title = "Sunday^2" },
{ file = "2_03_全ウ連本部のテーマ.mp3", archive = dir .. "cres_03.zip", title = "全ウ連本部のテーマ" },
{ file = "2_04_置き去りし過去.mp3", archive = dir .. "cres_03.zip", title = "置き去りし過去" },
{ file = "2_05_なつ.mp3", archive = dir .. "cres_03.zip", title = "なつ" },
{ file = "2_06_sunset.mp3", archive = dir .. "cres_03.zip", title = "sunset" },
-- ファイル名と曲名に表記の違いあり
{ file = "2_07_endless way_piano_version.mp3", archive = dir .. "cres_03.zip", title = "Endless Way piano version" },
{ file = "2_08_ゆりかごに翼をつけて.mp3", archive = dir .. "cres_03.zip", title = "ゆりかごに翼をつけて" },
{ file = "2_09_BREAK A FORTUNE.mp3", archive = dir .. "cres_03.zip", title = "BREAK A FORTUNE" },
{ file = "2_10_since that day.mp3", archive = dir .. "cres_03.zip", title = "since that day" },
{ file = "2_11_時の障壁.mp3", archive = dir .. "cres_04.zip", title = "時の障壁" },
{ file = "2_12_Silver Night.mp3", archive = dir .. "cres_04.zip", title = "Silver Night" },
{ file = "2_13_月夜のサミット.mp3", archive = dir .. "cres_04.zip", title = "月夜のサミット" },
{ file = "2_14_FATED FORCE.mp3", archive = dir .. "cres_04.zip", title = "FATED FORCE" },
{ file = "2_15_彗星雨.mp3", archive = dir .. "cres_04.zip", title = "彗星雨" },
{ file = "2_16_望みしその果てに.mp3", archive = dir .. "cres_04.zip", title = "望みしその果てに" },
{ file = "2_17_Never be awaken.mp3", archive = dir .. "cres_04.zip", title = "Never be awaken" },
{ file = "2_18_IN THE STREAM.mp3", archive = dir .. "cres_04.zip", title = "IN THE STREAM" },
{ file = "2_19_世界のかけら.mp3", archive = dir .. "cres_04.zip", title = "世界のかけら" },
{ file = "2_20_風は時を越えて.mp3", archive = dir .. "cres_04.zip", title = "風は時を越えて" },
}
-- スクリプトの場所も実際の場所に置き換える
dofile ("/path/to/mpv-playparts.lua")
スクリプト用の設定項目
カスタマイズのために、本スクリプト専用の幾つかの設定項目を用意している。
名前 | 説明 |
---|---|
osd_font_size | OSDのフォントサイズ |
osd_msg3 | OSDレベル3用のメッセージ |
window_title | 映像のあるファイルを再生している際のウィンドウタイトル |
term_playing_msg | ファイル再生時に端末の時間表示の上の行に表示される文字列 |
term_status_msg | ファイル再生時に端末の時間表示の行に表示される文字列 |
項目の中で “osd_font_size
” 以外の項目については、以下の記述で内容を展開することができる。これらを他のプロパティ展開(後述)とともに用いることで、OSDの表示文字列などの書式を自由にカスタマイズできる。
記述 | 展開される内容 |
---|---|
${part-title} | プレイリスト内に記述した各タイトル |
${part-range} | 開始時間と終了時間(例:“01:23:45-01:43:21”) |
下は設定ファイルの記述例。lua-settings
というディレクトリがない場合は作成し、その中にファイルを作成する。
${XDG_CONFIG_HOME}/mpv/lua-settings/playparts.conf
もしくは [ホームディレクトリ]/.config/mpv/lua-settings/playparts.conf
# OSDのフォントサイズを36にする場合
osd_font_size=36
# 下の行は本記事のスクリプトにおける既定のOSDレベル3用メッセージ
#osd_msg3=${osd-sym-cc} ${time-pos} / ${duration} (${percent-pos}%)\n[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range})${!ab-loop-a==no: <}${!ab-loop-a==no:${ab-loop-a}-${!ab-loop-b==no:${ab-loop-b}}${!ab-loop-a==no:>}}
# 下の設定では全体の長さやパーセンテージの表示をなくして1行にする
# (フォントサイズによっては複数行になるのでosd_font_sizeや本項目の調整が必要)
osd_msg3=${time-pos} ${osd-sym-cc} [${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range})${!ab-loop-a==no: <}${!ab-loop-a==no:${ab-loop-a}-${!ab-loop-b==no:${ab-loop-b}}${!ab-loop-a==no:>}}
# 下の行は本記事のスクリプトにおける既定のウィンドウタイトル
#window_title=[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range}) - ${filename}
# 下の設定ではループ有効時にループ範囲表示を付け加える
window_title=[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range}) - ${filename}${!ab-loop-a==no: <}${!ab-loop-a==no:${ab-loop-a}-${!ab-loop-b==no:${ab-loop-b}}${!ab-loop-a==no:>}}
# 下の行は本記事のスクリプトにおける端末への既定の表示文字列
#term_playing_msg=[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range}) - ${filename}
# 下の設定ではファイル名を表示しない
# また、最初に改行を入れており、タグ情報表示によりずれるのを防ぐ
term_playing_msg=\n[${playlist-pos-1}/${playlist/count}] ${part-title} (${part-range})
# 端末の再生時間表示部分をカスタマイズする場合
#term_status_msg=${?pause==yes:(Paused) }${?audio-codec:A}${?video-codec:V}: ${time-pos} / ${duration} (${percent-pos}%)${!ab-loop-a==no: <}${!ab-loop-a==no:${ab-loop-a}-${!ab-loop-b==no:${ab-loop-b}}${!ab-loop-a==no:>}}
コマンド行オプションによる指定は--script-opts=playparts-[項目名1]=[値1],playparts-[項目名2]=[値2],playparts-[項目名3]=[値3]
といった形になり、設定ファイルよりも高優先度となる。
(フォントサイズを48にする場合) $ mpv --script=/path/to/your_script.lua --script-opts=playparts-osd_font_size=48 -
上の設定ファイル例の “osd_msg3
” を適用した場合の表示は下のようになる。タイトル部分が長くなったりループ範囲が表示されたりすると表示が複数行になる可能性があるため、1行に収めたい場合はフォントサイズにも注意が必要。
OSDのフォントサイズについては、解像度の小さな動画をウィンドウ(全画面ではない表示)で再生すると
上のように文字が小さくなるため、ウィンドウでファイルを再生する場合は再生したいファイルの解像度で文字が読みづらくならないかを考えて調整する。
内部の処理などに関するメモ
- Luaスクリプトからは “
mp.[関数名]
” 形式の関数を呼び出すことでmpv内部の機能を用いるのだが、今回のスクリプトでは主に内部コマンド呼び出しのmp.commandv()
しか使っていない - mpv内部の各種情報が “プロパティ” として読み書きでき、
mp.get_property()
系関数で読み込みが、mp.set_property()
系関数で書き込みがそれぞれ可能 - 部分再生やループ再生の情報はファイル再生を行う “
loadfile
” という内部コマンドへのオプションとして渡しており、このオプション渡しは本スクリプトの中で特に重要な役割を担っている(“loadfile
” へ渡す際には先頭のハイフン2つは省く)--start
: 再生開始時間を指定--end
: 再生終了時間を指定--ab-loop-a
: 範囲ループを有効化してループ開始時間を指定--ab-loop-b
: 範囲ループを有効化してループ終了時間を指定--title
: 映像を含んだファイルでウィンドウタイトルを指定--osd-msg3
: レベル3のOSDの表示内容を指定--term-playing-msg
: 再生時の端末の時間表示の上の行への表示文字列を指定--term-status-msg
: 再生時の端末の時間表示の行への表示文字列を指定
--osd-msg3
などのオプションに渡している文字列の中で “${time-pos}
” のような表記の(一部を除いてリアルタイムで内容が変更される)プロパティ展開を用いている- 内部コマンドの “
loadfile
” では読み込んだファイルをどのタイミングで再生するかを2番目の引数で制御でき、mpvへの引数があっても “replace
” を指定することで別のファイルを読み込ませることで元々の入力(本記事の実行例では標準入力)を閉じてこれを置き換える形で再生を開始することができるが、複数のファイルを続けて再生したい場合は “append
” でプレーヤ内プレイリストに追加しておかないと(再生対象の置き換え処理が連続した結果として)いきなり最後のファイルが再生されてしまうため、今回は “playlist-pos
” のプロパティを参照して場合分けした - 再生に関する時間(位置)指定の形式は、 “
loadfile
” に渡す際にmpv側の受け付ける形であれば問題ないが、スクリプト側で無効な指定がないかチェックするようにしたかったので、Luaの文字列処理機能を用いて色々な処理を行っており、OSDなどの時間表示の整形処理も行っている - Luaスクリプト用のオプション(や設定ファイル項目)を受け取る仕組みと実装例はマニュアルの
options.read_options()
の項目に書かれており、今回はスクリプトの識別名として “playparts
” という名前を使用した
- mpv 0.24.0
- liblua 5.2.4
- vorbis-tools 1.4.0