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

2013年2月22日金曜日

EasyVFR for VapourSynth

なんかEasyVFRのVS版を欲しがってる人たちがいるようなので、やってみました。

easyvfr.py

使い方

まずeasyvfr.pyをPython3/Lib/site-packagesに置きます。

あとはこんな感じ
#!/usr/bin/env python3

import vapoursynth as vs
import easyvfr

vs.get_core().std.LoadPlugin('/path/to/d2vsource.dll')
d2vsrc = vs.get_core().d2v.Source

def ivtc(clip, offset, cycle=10, tff=True):
    sf = clip.std.SeparateFields(tff)
    dw = sf.std.DoubleWeave(tff)
    return dw.std.SelectEvery(cycle, offset)

def bob(clip, tff=True):
    sf = clip.std.SeparateFields(tff)
    return sf.resize.Bicubic(height=clip.height)


src = d2vsrc('/path/to/the/source.d2v')

# 適当にTrimして、デインタレ/IVTC/Bobしたりする
av0 = ivtc(src[100: 2000], [0, 3, 5, 8]) # 24fps
op0 = ivtc(src[2000: 3456], [0, 2, 5, 7]) # 24fps
op1 = bob(src[3456: 3501]) # 60fps
op2 = ivtc(src[3501: 6541], [1, 4, 6, 9]) # 24fps
a00 = ivtc(src[8000: 12000], [1, 3, 6, 8]) #24fps
a01 = src[12000: 12151] # 30fps
a02 = ivtc(src[12151: 20000], [0, 2, 5, 7]) #24fps

# av0, op0, a00の先頭フレームにチャプタを打ち、IDRフレームにしたい
av0 = easyvfr.ChapterClip(av0, 'アバンA')
op0 = easyvfr.ChapterClip(op0, 'OP')
a00 = easyvfr.ChapterClip(a00, 'Aパート')

# 各クリップをひとつのリストにまとめる
clips = [av0, op0, op1, op2, a00, a01, a02]

'''
22行目以降はこういう書き方もあり
cc = easyvfr.ChapterClip
clips = [
    cc(ivtc(src[100: 2000], [0, 3, 5, 8]), 'アバンA'),
    cc(ivtc(src[2000: 3456], [0, 2, 5, 7]), 'OP'),
    bob(src[3456: 3501]),
    ivtc(src[3501: 6541], [1, 4, 6, 9]),
    cc(ivtc(src[8000: 12000], [1, 3, 6, 8]), 'Aパート'),
    src[12000: 12151],
    ivtc(src[12151: 20000], [0, 2, 5, 7]),
]
'''

# timecodeファイル、chapterファイル、x264用QPファイルの出力、及び各クリップを結合
vfr = easyvfr.EasyVFR(clips)
vfr.write_tcq('/path/to/the/files')
#vfr.write_timecode('/path/to/the/timecode.txt')
#vfr.write_chapter('/path/to/the/chapter.txt')
#vfr.write_qpfile('/path/to/the/qpfile.txt')
vfr.splice_clips().set_output()

timecodeのフォーマットはv2のみです。
chapterファイルのエンコーディングはUTF-8になります。

追記(20160402):最近のVapourSynthにあわせてちょっと書き直しました。

2013年1月17日木曜日

GenericFilters その5

更新しました。

GenericFilters-0.4.1.7z
https://github.com/chikuzen/GenericFilters

* Convolution/ConvolutionHV/Blur:フレームが16bitだった場合の処理を修正

uint16_tとint16_tの乗算が出来る組み込み命令がないのって面倒すぎると思うんですが…。

2013年1月13日日曜日

GenericFilters その4

更新しました。

GenericFilters-0.4.0.7z
https://github.com/chikuzen/GenericFilters

* 新関数'Binarize2'を追加

Binarize2はBinarizeと同じくクリップを二値化するフィルタですが、閾値によって分けるのではなく、Sierra-2-4Aという誤差拡散法(いわゆるディザリングで使われるアルゴリズム)の一種によって処理します。

具体的には、これが
こうなります。
簡単に出来そうだったからやってみただけですので、実用性とかは気にしてはいけません。


2013年1月12日土曜日

vsimagereader その2

更新しました。

vsimagereader-0.2.0.7z
https://github.com/chikuzen/vsimagereader

* VapourSynthのAPIバージョンを3に更新
* アルファチャンネルの読み込みに対応/'alpha'オプションの追加
* 幅/高さ/出力フォーマットがバラバラな画像の読み込みに対応

以前も書きましたが、VapourSynthはavisynth等と違って各フレームの解像度やフォーマットが異なっていても扱えるようになっています。
今回の更新で、「解像度や保存形式がバラバラなものを一度に読み込んで、解像度を揃えて出力」なんてことが出来るようになりました。

一度にプラグイン4つも更新したので、大変疲れました。

GenericFilters その3

更新しました。

GenericFilters-0.3.0.7z
https://github.com/chikuzen/GenericFilters

* 関数に'Blur'を追加。

普通の3x3のぼかしフィルタです。
単なるConvolution()のaliasですな。

vsrawsource その4

更新しました。

vsrawsource-0.3.1.7z
https://github.com/chikuzen/vsrawsource

* アルファチャンネルのサポート方法を変更