2013年6月30日日曜日

VapourSynth r19(テスト版)

VapourSynthがr19で色々変わるようです。
とりあえずテスト版でのr18までとの違いを簡単にまとめておきます。

*この記事は現時点でのテスト版を元に書かれており、今後の展開によっては特に断りなく修正されます*

変更1:Coreクラスがsingletonになり、Core()がget_core()になった。

singletonは、デザインパターン(オブジェクト指向プログラミングのノウハウ)の一種ですが説明はメンドイので省きます。
具体的には、いままでこう書いていたのが
import vapoursynth as vs
core = vs.Core(threads=2, add_cache=False, accept_lowercase=True)
c0 = core.std.BlankClip(format=vs.YUV420P8)
c1 = core.std.BlankClip(format=vs.YUV422P9)
c2 = core.std.BlankClip(format=vs.YUV444P16)
こうなります。
import vapoursynth as vs
core = vs.get_core(threads=2, add_cache=False, accept_lowercase=True)
c0 = core.std.BlankClip(format=vs.YUV420P8)
c1 = core.std.BlankClip(format=vs.YUV422P9)
c2 = core.std.BlankClip(format=vs.YUV444P16)
もっと言えばこう書いてもいいです(あまりオススメはしませんが)。
import vapoursynth as vs
c0 = vs.get_core(threads=2, add_cache=False, accept_lowercase=True).std.BlankClip(format=vs.YUV420P8)
c1 = vs.get_core().std.BlankClip(format=vs.YUV422P9)
c2 = vs.get_core().std.BlankClip(format=vs.YUV444P16)
これまでは
Note that the loaded plugins are per core instance and not global so if you do create
several processing cores at once (not recommended) you will have to load the plugins
you want for each core.
と、Coreオブジェクトは何個でも作れましたが、r19以降は1プロセスに付き1個しか作れなくなりました(最初にget_core()した時に作られます)。
これにより、いままではユーザー定義のクラスや関数に引数でCoreオブジェクトを渡す必要がありましたが、今後はなくなりました(必要なところでget_core()すれば、どこで呼んでもプロセス内で共通のものが使われます)。

変更2:output()がなくなり、set_output()とvspipe.exeが出来た。またlastというクリップ名に特別な意味がなくなった。

これまでは
#sample_pre.vpy
import vapoursynth as vs
core = vs.Core()
last = core.std.BlankClip(format=vs.YUV420P8)

if __name__ == '__main__':
    last.output(sys.stdout, y4m=True)
このスクリプトをコマンドラインで使う場合は
$ python sample_pre.vpy | x264 - --demuxer y4m -o out.264
という感じで実行し、VirtualDub等で読みこめばlastクリップがプレビュー出来ましたが今後はこうなります。
#sample_r19.vpy
import vapoursynth as vs
core = vs.get_core()
clip = core.std.BlankClip(format=vs.YUV420P8)
clip.set_output()
このスクリプトをVirtualDub等VfWを利用するソフトウェアで読みこめば、set_output()したクリップ(変数名はなんでもよい)がプレビューできます。
そしてx264等に入力するにはvspipe(インストールすればおまけでついてきます)を使います。
$ vspipe sample_r19.vpy - -y4m | x264 - --demuxer y4m -o out.264 
output()がなくなってしまったので、これまでのようにスクリプト内でsubprocessつかってコマンド実行が困難(出来ないことはないけど)になりましたが、かわりに外部プログラムから使うためのAPIがかなり整備されたので、そのうちx264等にAVS入力同様、VS入力が実装されるでしょう。

変更3:get_plugins()とget_functions()が追加された。

r18までは、core.list_functions()で現在使えるすべてのフィルタ及びその引数の一覧を文字列として取得出来ましたが、r19でさらにcore.get_plugins()とcore.***.get_functions()が追加されました。
get_plugins()は、list_functions()では文字列として取得できた情報がdictとして取得出来ます。
>>> import vapoursynth as vs
>>> vs.get_core().get_plugins()
{'com.vapoursynth.resize': {'namespace': 'resize', 'identifier': 'com.vapoursynt
h.resize', 'functions': {'Bicubic': 'clip:clip;width:int:opt;height:int:opt;form
at:int:opt;yuvrange:int:opt;', 'Sinc': 'clip:clip;width:int:opt;height:int:opt;f
ormat:int:opt;yuvrange:int:opt;', 'Bilinear': 'clip:clip;width:int:opt;height:in
t:opt;format:int:opt;yuvrange:int:opt;', 'Spline': 'clip:clip;width:int:opt;heig
ht:int:opt;format:int:opt;yuvrange:int:opt;', 'Gauss': 'clip:clip;width:int:opt;
height:int:opt;format:int:opt;yuvrange:int:opt;', 'Lanczos': 'clip:clip;width:in
t:opt;height:int:opt;format:int:opt;yuvrange:int:opt;', 'Point': 'clip:clip;widt
h:int:opt;height:int:opt;format:int:opt;yuvrange:int:opt;'}, 'name': 'VapourSynt
h Resize'}, 'com.vapoursynth.avisynth': {'namespace': 'avs', 'identifier': 'com.
vapoursynth.avisynth', 'functions': {'LoadPlugin': 'path:data;'}, 'name': 'Vapou
rSynth Avisynth Compatibility'}, 'com.vapoursynth.std': {'namespace': 'std', 'id
entifier': 'com.vapoursynth.std', 'functions': {'Loop': 'clip:clip;times:int:opt
;', 'PropToClip': 'clip:clip;prop:data:opt;', 'StackVertical': 'clips:clip[];',
'Transpose': 'clip:clip;', 'FlipVertical': 'clip:clip;', 'PEMVerifier': 'clip:cl
ip;upper:int[]:opt;lower:int[]:opt;', 'Splice': 'clips:clip[];mismatch:int:opt;'
, 'ClipToProp': 'clip:clip;mclip:clip;prop:data:opt;', 'PlaneDifference': 'clips
:clip[];plane:int;prop:data:opt;', 'Lut2': 'clips:clip[];lut:int[];planes:int[];
bits:int:opt;', 'AssumeFPS': 'clip:clip;src:clip:opt;fpsnum:int:opt;fpsden:int:o
pt;', 'Cache': 'clip:clip;size:int:opt;fixed:int:opt;', 'FlipHorizontal': 'clip:
clip;', 'Expr': 'clips:clip[];expr:data[];format:int:opt;', 'LoadPlugin': 'path:
data;forcens:data:opt;', 'MaskedMerge': 'clips:clip[];mask:clip;planes:int[]:opt
;first_plane:int:opt;', 'ShufflePlanes': 'clips:clip[];planes:int[];format:int;'
, 'CropRel': 'clip:clip;left:int:opt;right:int:opt;top:int:opt;bottom:int:opt;',
 'SeparateFields': 'clip:clip;tff:int;', 'Reverse': 'clip:clip;', 'StackHorizont
al': 'clips:clip[];', 'Trim': 'clip:clip;first:int:opt;last:int:opt;length:int:o
pt;', 'SelectEvery': 'clip:clip;cycle:int;offsets:int[];', 'Interleave': 'clips:
clip[];extend:int:opt;mismatch:int:opt;', 'ModifyFrame': 'clips:clip[];selector:
func;', 'Turn180': 'clip:clip;', 'Lut': 'clip:clip;lut:int[];planes:int[];', 'Pl
aneAverage': 'clip:clip;plane:int;prop:data:opt;', 'BlankClip': 'clip:clip:opt;w
idth:int:opt;height:int:opt;format:int:opt;length:int:opt;fpsnum:int:opt;fpsden:
int:opt;color:float[]:opt;', 'DoubleWeave': 'clip:clip;tff:int;', 'CropAbs': 'cl
ip:clip;width:int;height:int;x:int:opt;y:int:opt;', 'SelectClip': 'clips:clip[];
src:clip[];selector:func;', 'AddBorders': 'clip:clip;left:int:opt;right:int:opt;
top:int:opt;bottom:int:opt;color:float[]:opt;', 'Merge': 'clips:clip[];weight:fl
oat[]:opt;'}, 'name': 'VapourSynth Core Functions'}}
はい、dictのなかにさらにdictがたくさんあって、なにがなにやらさっぱりわかりません……。
get_functions()は、もう少しマシです。
>>> vs.get_core().std.get_functions()
{'Loop': 'clip:clip;times:int:opt;', 'PropToClip': 'clip:clip;prop:data:opt;', '
StackVertical': 'clips:clip[];', 'Transpose': 'clip:clip;', 'FlipVertical': 'cli
p:clip;', 'PEMVerifier': 'clip:clip;upper:int[]:opt;lower:int[]:opt;', 'Splice':
 'clips:clip[];mismatch:int:opt;', 'ClipToProp': 'clip:clip;mclip:clip;prop:data
:opt;', 'PlaneDifference': 'clips:clip[];plane:int;prop:data:opt;', 'Lut2': 'cli
ps:clip[];lut:int[];planes:int[];bits:int:opt;', 'AssumeFPS': 'clip:clip;src:cli
p:opt;fpsnum:int:opt;fpsden:int:opt;', 'Cache': 'clip:clip;size:int:opt;fixed:in
t:opt;', 'FlipHorizontal': 'clip:clip;', 'Expr': 'clips:clip[];expr:data[];forma
t:int:opt;', 'LoadPlugin': 'path:data;forcens:data:opt;', 'MaskedMerge': 'clips:
clip[];mask:clip;planes:int[]:opt;first_plane:int:opt;', 'ShufflePlanes': 'clips
:clip[];planes:int[];format:int;', 'CropRel': 'clip:clip;left:int:opt;right:int:
opt;top:int:opt;bottom:int:opt;', 'SeparateFields': 'clip:clip;tff:int;', 'Rever
se': 'clip:clip;', 'StackHorizontal': 'clips:clip[];', 'Trim': 'clip:clip;first:
int:opt;last:int:opt;length:int:opt;', 'SelectEvery': 'clip:clip;cycle:int;offse
ts:int[];', 'Interleave': 'clips:clip[];extend:int:opt;mismatch:int:opt;', 'Modi
fyFrame': 'clips:clip[];selector:func;', 'Turn180': 'clip:clip;', 'Lut': 'clip:c
lip;lut:int[];planes:int[];', 'PlaneAverage': 'clip:clip;plane:int;prop:data:opt
;', 'BlankClip': 'clip:clip:opt;width:int:opt;height:int:opt;format:int:opt;leng
th:int:opt;fpsnum:int:opt;fpsden:int:opt;color:float[]:opt;', 'DoubleWeave': 'cl
ip:clip;tff:int;', 'CropAbs': 'clip:clip;width:int;height:int;x:int:opt;y:int:op
t;', 'SelectClip': 'clips:clip[];src:clip[];selector:func;', 'AddBorders': 'clip
:clip;left:int:opt;right:int:opt;top:int:opt;bottom:int:opt;color:float[]:opt;',
 'Merge': 'clips:clip[];weight:float[]:opt;'}
たとえば「Lut2の引数ってどんな感じだったっけ?」と思ったら、
>>> core.std.get_functions()['Lut2']
'clips:clip[];lut:int[];planes:int[];bits:int:opt;'
このようにcore.name.get_functions()['フィルタ名']とタイプすれば確認できます。
状況によってlist_functions()と使い分けましょう。

変更4:スクリプトに使用する文字セットをUTF-8で統一

VSScript(外部アプリケーションとの連携用API)の導入に伴い、スクリプトはUTF-8で書くように統一されたようです。
スクリプトはPythonに渡された後、UTF-8決め打ちで一旦Pythonのバイトコードにコンパイルされます。
このためUTF-8以外で日本語用文字等を使っている場合は、内部で文字化けを起こしてデコードエラーになります。
これまでは日本語ファイル名等を扱う場合はCP932で保存したほうがよかったですが、今後はUTF-8で保存するようにしましょう(もちろんBOMなしで)。
そしてもうひとつ大事なことですが、Windowsの場合、日本語ファイル名が扱えるかどうかはプラグインの実装次第となりました。
とりあえず見た感じ、avisource.dllはちゃんと対応してたけどd2vsourceはどうも現状ではダメっぽいですな。
ちなみに拙作のソースフィルタ群も軒並みダメです。ああ、直すのメンドイorz