ラベル AvsP の投稿を表示しています。 すべての投稿を表示
ラベル AvsP の投稿を表示しています。 すべての投稿を表示

2011年1月31日月曜日

中間出力のマルチプロセス化

先日、JEEB氏がAviSynth2.6.0αの非公式ビルドを始めた。

(JEEB) そういや、うちのAvs2.6ビルドってChikuzen氏がテストした奴と性能がどんな感じ?
(Chikuzen) 若干スピードが上がってるみたいよ
(Chikuzen) ちょっとベンチマーク回してみるか
      official 2.6.0α2       JEEB's 2.6.0(20110125)
1st   00:01:11.003(14.08fps)   00:01:07.254(14.87fps)
2nd   00:01:11.010(14.08fps)   00:01:07.003(14.92fps)
3rd   00:01:10.753(14.13fps)   00:01:07.003(14.92fps) (スクリプト等は前回と同じ)
(JEEB) interesting

たしかに興味深い。
どうやら最適化はα2配布後にさらに進んでいたようである。

さて、前回、自分はマルチスレッドによる高速化ではなく、マルチプロセスによる高速化を選んだことを書いた。
ここで問題となるのが、同時にエンコードするための複数のソースがない場合である。
そもそも自分は放送ソースの保存用エンコなんてまったくしない。そんなことには興味がない。
いろいろなフィルタやエンコーダーを試して遊ぶのが好きなのである。
当然、エンコードするべきソースは大抵1本しかなく、3本同時にエンコードなんていままでに数えるほどしかやったことがない。

では、どうすればいいのか?
エンコード対象がひとつしかないのであれば、ひとつを複数に分割してやればいいだけだ。
3本同時にエンコードできる余裕があるなら、1本を3分割してそれぞれ中間出力し、ふたたびくっつけてやればよい。
それが簡単にできるのがAviSynthである。
中間出力用のファイルサイズを気にするような時代でもないしね。

てことで、自動で分割->中間出力->結合->エンコードを行うAvsPマクロを書いた。
#multiprocess intermediate file generator.py
#dividing source script into X -> generate intermediate files at same time -> final encode
#requiaments : VirtualDub.exe, vcf file, ffmpeg or x264 (optional)
import sys
import os.path
import subprocess

vdub = r'C:\VirtualDub-1.9.11\VirtualDub.exe'
vcf  = r'C:\VirtualDub-1.9.11\fastrecomp_uly0_noaudio.vcf'

entries = avsp.GetTextEntry(['Number of dividing', 'SetMemoryMax(?)'], ['3', '1024'])
dnum, mmax = [int(i) for i in entries]

imf_outdir = avsp.GetDirectory('Select a directory to output intermediate files.')

encbin  = avsp.GetFilename('Set Path to Encoder(ffmpeg or x264)')
#encbin = r'G:\Enctools\ffmpeg.exe'
#encbin = r'G:\Enctools\x264_x64.exe'
encout  = avsp.GetSaveFilename('Set final output file name')
encopts = avsp.GetTextEntry('Input commandline options').split(' ')

source = avsp.SaveScript()
frames = avsp.GetVideoFramecount()

if dnum > 1 and mmax > 0 and imf_outdir and frames and os.path.isfile(vdub) and os.path.isfile(vcf):
    start = 0
    vdubprocess = []
    catfilename = os.path.splitext(source)[0] + '_imf.avs'
    catfile = open(catfilename, 'w')

    for i in range(dnum):
        end = frames / dnum * (i + 1)
        if i < dnum - 1:
            trimline = 'trim(%i, %i)' % (start, end)
        else:
            trimline = 'trim(%i, 0)' % start
        lines = 'SetMemoryMax(%i)\nImport("%s")\n%s\n' % (mmax, source, trimline)
        start = end + 1

        imf_avsname = os.path.join(imf_outdir, os.path.basename(source)).rstrip('avs') + 'imf_%02i.avs' % i
        file = open(imf_avsname, 'w')
        file.write(lines)
        file.close()

        imf_aviname = os.path.splitext(imf_avsname)[0] + '.avi'
        vdubargs = [vdub, '/s', vcf, '/p', imf_avsname, imf_aviname, '/r', '/c', '/x']
        p = subprocess.Popen(vdubargs)
        vdubprocess.append(p)

        if i < dnum - 1:
            catfile.write('AVISource("%s") + \\\n' % imf_aviname)
        else:
            catfile.write('AVISource("%s")\n' % imf_aviname)

    catfile.close()

    if encbin and encout:
        for p in vdubprocess:
            p.wait()

        if os.path.basename(encbin).count('ffmpeg'):
            encargs = [encbin, '-i', catfilename] + encopts + ['-y', encout]
        elif os.path.basename(encbin).count('x264'):
            encargs = [encbin, catfilename, '-o', encout] + encopts
        else:
            sys.exit()

        subprocess.Popen(encargs)

2010年10月19日火曜日

AvsPのブックマーク位置をずらすマクロ

AvsP(mod)でTrimSelectionを使ってCMカットすると、各パートの終端フレームにブックマークが打たれた状態になる。
これが結構曲者で、例えば動画にチャプターを打つ場合、チャプターポイントは各パートの先頭フレームに来るように打つものであるが、ブックマークが打たれている場所が各パートの終端だと、そのまま"Bookmarks to Chapter.py"を使うわけにいかない。
各パートの終端フレームの次のフレームは当然、次のパートの先頭フレームになるわけだが、この1フレームのずれを修正するために、いちいちブックマークを消して、1フレーム移動して、もう一度ブックマークを打ち直すのはめんどくさい、なんとかならんもんかいな?

と思ったので、これもマクロで何とかしてみようというのが今回のお話。
-----------------------------------------------------------------------------
既存のブックマークすべてを1フレーム右(未来方向)にずらすだけなら簡単である。
#Shift Bookmarks right+1.py
before=avsp.GetBookmarkList()
if len(before):
    after = [i + 1 for i in before]
    after.sort()
    avsp.SetBookmark(before) #ブックマークは二重に打つと消える。
    avsp.SetBookmark(after)
しかし、これをそのまま使うと、もし最終フレームにブックマークが打たれていた場合、そのブックマークは範囲外に飛び出して消えてしまう。
それに、すでにブックマークを1フレームずらし終えたのに、何らかの操作ミスでもう一回ずらしてしまったらどうしよう? マクロを使った場合、undoは効かないよ?
はたまた、例えばブックマークを打ち終え、チャプターを出力する段になったところでトリムのし残しを発見してしまった場合は? その部分が全部で10個打ったブックマークの2番目と3番目の間に30フレームあったりしたら、1番目と2番目は打ち直すとしても、3番目以降のためにこれを30回実行するの?
ずらすフレーム数を任意に指定できるようにしたとして、全部で10000フレームしかないクリップに対して、桁を間違えて100000なんて指定してしまったら?
などと不測の事態についていろいろ考えていたら、最後にはこうなっていた。
#Shift Bookmarks.py
def newBM(present, shift, last):
    if 0 <= present + shift <= last:
        return present + shift
    elif present + shift > last:
        return newBM(present, shift - last - 1, last)
    else:
        return newBM(present, shift + last + 1, last)

input = avsp.GetTextEntry('Input an integer value',default='1')
shift = int(input)
before = avsp.GetBookmarkList()
if shift and len(before):
    framecount = avsp.GetVideoFramecount() 
    after = [newBM(old, shift, framecount - 1) for old in before]
    after.sort()
    avsp.SetBookmark(before)
    avsp.SetBookmark(after)
うーむ、再帰なんて初めて使ったよ。
-------------------------------------------------------------------------
使い方:
ブックマークが打たれた状態でマクロを実行するとダイアログが現れるので、数値を入力する。デフォルトは1。
正ならば右(未来方向)、負ならば左(過去方向)にすべてのブックマークが指定した数値分だけ移動する。
最終フレーム + 1 = 0番フレーム(0番フレーム - 1 = 最終フレーム)として循環処理される。
数値以外の文字列を入力した場合はエラーとなり何もしない。小数点以下は切り捨て。
操作を取り消したい場合はもう一度マクロを実行し、事前の指定値の反数を入力すること。

2010年10月18日月曜日

CM検出マクロ for AvsP

この前のSCx264 to Bookmarksは、少々時間がかかり過ぎるので、CMカットにはいまいち不向きなようだった。
もっと短時間で可能な方法はないものだろうか?
しばし悩んだ末、そういえばAviUtlにもなにやらCMカット用のシーンチェンジ検出プラグインがあると小耳に挟んだことを思い出したので、見てみることにした。
「チャプター編集 for AviUtl by ぽむ + 無音&シーンチェンジ検索機能 by ru」
読み込んでいる動画の音声トラックをスキャンし、無音連続部分を検出して、その「開始部分」にチャプターを打ちます。
その後チャプターへのシーク時に無音部分内のシーンチェンジを検出し、その位置へ移動できます。
うーむ、なるほど…。
シーンチェンジ検出を無音部分に限定すれば高速化が可能かも!?
-------------------------------------------------------------------------
では早速やってみましょう。

まずサンプルクリップを用意します。
今回はMPEG2-TS、音声AACで再生時間31分、総フレーム数55749フレームの地デジソース(1440x1080)です。
#sample.avs
MPEG2Source("sample.d2v")
AudioDub(last, WAVSource("sample.wav"))

#とりあえず普通に再生できる音声(FAWとかの偽装WAVはこの場合は駄目)でディレイ補正処理を済ませておく。
#今回はソースが地デジTSなので、とりあえず
#[DGIndexでdemuxしたaac -> FAWを2回通してディレイ処理済のaac -> FAADでPCM.wavに変換]
#したものを使った。
#処理終了後に差し替えれば、偽装WAVでも大丈夫。
#もしaacを直読みしたい場合は、BassAudioでも使ってDelayAudio()で補正することになる。
このクリップの無音部分を検出しなければならんわけですが、使えそうな道具は…MinMaxAudioかな?
AudioMax(0)が-100dbよりも低ければ、そのフレームは無音と判定することにしましょう(テキトー)。
#sample.avs
MPEG2Source("sample.d2v")
AudioDub(last, WAVSource("sample.wav"))

LoadPlugin("MinMaxAudio.dll")
AudioDub(BlankClip(last, width=160, height=80), last)
#スピードアップのため、クリップを映像160x80で真っ黒、音声はそのままに変換する
WriteFileIf("silence.log", "AudioMax(0) < -100", "current_frame")
#該当フレームのフレーム番号をテキストに書き出す
このavsをVirtuaDubの"Run Video Analysis pass"にかけてやると、約1250fpsくらいのスピードでsilence.logが生成されます。
結果を見てみると、どうやら無音のフレームは、各CM及び本編の切れ目あたりにだいたい10~30フレーム入るみたいです。
具体的には全55749フレームのうち、41-53、475-504、926-954、1376-1402、1825-1837、2289-2302、2725-2738、9077-9092、9990-10003、10874-10905、26915-26923、27373-27385、27808-27821、28707-28738、46494-46507、49179-49206、49628-49656、50078-50103、50525-50553、50975-50987、51439-51451、51874-51902、52783-52788、53043-53101、53523-53535、54422-54434、54886-54898、55320-55350、55735-55748の全29箇所561フレームが無音(-100db未満)です。

そこでsample.avsをこうしてやり
#sample.avs
MPEG2Source("sample.d2v")
AudioDub(last, WAVSource("sample.wav"))

PointResize(last.width / 4, last.height / 4)
#シーンチェンジ検出には320x270程度で十分なのでPointResizeでリサイズ
FreezeFrame(0,41,41)
FreezeFrame(53,475,475)
FreezeFrame(504,926,926)
#途中24行省略
FreezeFrame(54898,55320,55320)
FreezeFrame(55350,55735,55735)
FreezeFrame(55748,0, 0)

#AviSynthの仕様ではFreezeFrameでコピーされるフレームはデコードされないので、
#MPEG2Sourceのボトルネックが解消されるはず
このavsをこの前のSCx264 to Bookmarks.pyにかけてやると、x264はなんと約2200fpsという想像を絶するスピードでシーンチェンジ検出を終え、ブックマークを打ち終わりました。
あとはFreezeFrameの行を消してやれば…YATTA!!
-----------------------------------------------------------------------------
てなことがありまして、これらの操作を自動で行うべく書いたAvsPマクロが以下の通りです。
使い方は、とりあえずAudioDubまで書いたavsを用意して、マクロを実行するだけです。
FAW使用者は、実行後に音声を偽装WAVに差し替えればいいです。ちょっと書き換えれば、そこらへんも自動化できるでしょう
前回約10分かかってたのが、約1分まで縮まりました(on Q9450定格)。
著作権は…発生するのかどうか知りませんが、もし発生するのであればGPLv3です(なんとなく)。
つーか、Pythonスクリプトですから、バイナリと違って秘密にはしようがないと思うけど…。

EDIT:
よく考えたらavs2aviのnull出力とx264の--preset ultrafast -o nul ではスピードは大して変わらないので(っていうか、今度はMinMaxAudioがボトルネックになっているので)、avs2aviをx264に変更。
avs2aviは要らなくしました。
#Silent_Scenechanges to Boolmarks.py for AvsP
#Author : Chikuzen (http://csbarn.blogspot.com/)
#Requiament: MinMaxAudio.dll, x264.exe, (avs2avi.exe)

#--------------preparation----------------------------------
import os

avsname = avsp.SaveScript()
script_org = avsp.GetText()

#あらかじめ各ツールのパスを設定しておくこと
#ファイルパスを変数に入れる際、前にrをつけるのはPythonのお約束
mmaudio = r'C:\AviSynth 2.5\Plugins_x86\MinMaxAudio.dll'
x264cli = r'G:\Enctools\x264_x86.exe'
#avs2avi = r'G:\Enctools\avs2avi.exe'
if mmaudio and x264cli and avsname and script_org:
#if mmaudio and x264cli and avs2avi and avsname and script_org:
#--------------------------------------------------------------
    #Search Silence
    logfile_si = avsname + '_silence.log'
    text1 = '\nLoadPlugin("%s")' % mmaudio
    text1 += '\nAudioDub(BlankClip(last, width = 160, height = 120), last)'
    text1 += '\nWriteFileIf("%s", "AudioMax(0) < -100", "current_frame", append = false)' % logfile_si
    avsp.InsertText(text1, pos = -1, index = None)
    avsp.SaveScript()
    os.system('%s %s --preset ultrafast --crf 51 -I infinite -o nul' % (x264cli, avsname))
    #os.system('%s %s -c null -o n' % (avs2avi, avsname))

    #silence_log to freezeframelines
    avsp.SetText(script_org)
    sifile=open(logfile_si)
    lines_si=sifile.readlines()
    sifile.close()
    text2='\nPointResize(last.width / 4, last.height / 4)'
    if len(lines_si):
        x = 0
        for line_si in lines_si:
            log_si = int(line_si.strip())
            if x != log_si - 1:
                text2 += '\nFreezeFrame(%i, %i, %i)' % (x, log_si, log_si)
                x = log_si
            else:
                x += 1
        text2 += '\nFreezeFrame(%i, %i, %i)\n' % (int(lines_si[-1].strip()), 0, 0)
    avsp.InsertText(text2, pos = -1, index = None)
    avsp.SaveScript()
    
    #SCx264
    logfile_sc = avsname + '_scenechange.log'
    opts = '--preset ultrafast --crf 30 -I infinite -i 1 --scenecut 50 -o nul'
    os.system('%s %s -p 1 --stats %s %s' % (x264cli, avsname, logfile_sc, opts))
    avsp.SetText(script_org)
    avsp.SaveScript()

    #scenechange_log to bookmarks
    scfile = open(logfile_sc)
    lines_sc = scfile.readlines()
    scfile.close()
    bookmarks = []
    for line_sc in lines_sc:
        log_sc = line_sc.split(' ')
        if log_sc[2] == 'type:I':
            bmpoint = int(log_sc[0].lstrip('in:'))
            bookmarks.append(bmpoint)
    avsp.SetBookmark(bookmarks)

2010年10月16日土曜日

SCx264 to Bookmarks for AvsP

またまたAvsPマクロネタ。

さて、AviSynthのプラグインにはいろいろと毛色の変わったものがありますが、そんな中にSClavcSCXvidというものがあります。
オリジナルのSClavcはx264のペンギン様ことLoren Merritt氏作、SCXvidの作者は#darkholdのいかつい人、MyrsloikことFredrik Mellbin氏であります。(以前一度、JEEB氏に写真を見せてもらったことがあるのですが、Myrsloik氏はやたら人相の怖い太っちょでした。一方、一緒に写ってたTheFluff氏は小柄で柔和そうな感じで、普段の言動から受ける印象とあまりにも違いすぎるので、思わず大笑いしたものです)

で、これらが何のためにあるかというと、要するにffmpegやXvidの2pass用のログを利用して、クリップ内のシーンチェンジの場所とかを割り出して色々利用しようというものらしい。
なんで「らしい」かといえば、使い方がよくわからないから。
とりあえずavs書いてVirtualDubに食わせて"Run Video Analysis pass"を走らせるなりすれば、2pass用のlogファイルが作られるわけですが、そこから先がどうしたものやら…。
AviSynth.infoによれば、「元々は、Yattaにおいて、libavcodecのシーンチェンジ・メトリクスの利用を可能するために書かれた。」とありますが、Yattaってねぇ…あれの使い方がわかる日本人なんているのかしら?
7月ごろにJEEB氏に「#yattaでmenter氏が初心者講習会を開くので来ないか?」と誘われたのでROMってみましたが、何をしゃべってるのかさっぱりわかりませんでした。(そもそも知りたかったのは理論やテクニックではなく、操作方法そのものなのですが、そこらへんはやってくれなかったし…orz)

まあ、Yattaの使い方は今もさっぱりわかりませんが、2pass用logからシーンチェンジ検出というのは理解できます。
そこでふと思ったことが「それならx264のlogファイル使ったほうがよくね? lavcのmpeg4やXvidよりもlog出力速いじゃん」。
で、こんなのを書いてみた。
#SCx264 to bookmarks.py
frames = avsp.GetVideoFramecount()
logfilename = avsp.GetFilename(title='Select x264_logfile')

if logfilename:
    logfile = open(logfilename)
    logs = logfile.readlines()
    logfile.close()
    if len(logs) == frames + 1:
        bookmarks = []
        for logline in logs:
            log = logline.split(' ')
            if log[2] == 'type:I':
                bmpoint = int(log[0].lstrip('in:'))
                bookmarks.append(bmpoint)
        avsp.SetBookmark(bookmarks)
    else:
        avsp.MsgBox('This log is not corresponding to the number of frames of current clip.',title='Warning')

あらかじめx264.exeで --pass 1 をつけてエンコしたlogから、IDRフレームと判定されたフレームにブックマークを自動で打つマクロである。(またブックマークか…)
とりあえずシーンチェンジを検出して、ちゃんとIDRと判定されるようなオプションにしていれば、ほぼ100%検出できるはずである。

さて、ただlog読んでブックマーク打つだけでは面白くないので、さらに発展させてみる。
#SCx264 to bookmarks AUTO.py
import os
avsname = avsp.SaveScript()
#使用するx264.exeを毎回指定したい場合はこちら
#exe = avsp.GetFilename(title = 'Specify x264.exe used.')
#x264.exeのパスを固定したい場合は、こちらにフルパスで書いておく
exe = r'G:\Enctools\x264_x86.exe'

if avsname and exe:
    x264opt = '--preset ultrafast --crf 30 -p 1 -I infinite -i 1'
    thresh = '--scenecut 50'
    logfilename = avsname + '.sc_log'
    os.system('%s %s %s %s --stats %s -o nul' % (exe, avsname, x264opt, thresh, logfilename))

    logfile = open(logfilename)
    logs = logfile.readlines()
    logfile.close()
    bookmarks = []
    for logline in logs:
        log = logline.split(' ')
        if log[2] == 'type:I':
            bmpoint = int(log[0].lstrip('in:'))
            bookmarks.append(bmpoint)
    avsp.SetBookmark(bookmarks)

log出力も一緒にやるようにしてみた。

ううむ、これをどう使えばいいのだろうか?

例えばMPEG2Source("hogehoge.d2v")だけのavsの状態でこれを実行するとCMカットは楽になる。
なぜって、ただブックマーク移動していくだけで本編とCMの切れ目を確実にシークできるから。
AvsP初期装備のTrimEditorと併用すれば、ほとんどストレスなくCMカットできるはず(ただし、TrimEditorでTrimを実行する際は、ブックマークをすべてクリアしてからにすること。これを忘れるとプレビューの更新で随分時間を食うことになります)。
x264は--preset ultrafastならものすごく高速です。自分のそろそろ時代遅れなQ9450でも、1440x1080
のMPEG2-TSを90fps~100fpsで処理します。つーかMPEG2Sourceがボトルネックになっているので、本当はもっと速いです。2本同時にlog出力したりするといいかもしれません。

なお、「30分のTSだとlog出力だけで10分かかるじゃねえか、それだけあればCMカットなんか終わってるよ」とかいう意見は無視します…orz

2010年10月15日金曜日

分割結合マクロ for AvsP

前回のTrimEditorは、いざ使ってみるとメリットはそれほどなかったような気もするけど、まあ、マクロの練習にはなった。
さて、今回もBookmarkを使ったマクロを書いてみる。
#Divide and Concatenate.py
bookmarks=avsp.GetBookmarkList()
if bookmarks:
    bookmarks.sort()
    start, trim, cat = 0, "", "\n"
    for i in xrange(len(bookmarks)):
        trim += "\nV%02i = last.Trim(%i, %i)" % (i, start, bookmarks[i] - 1)
        cat += "V%02i ++ " % i
        start = bookmarks[i]
    i += 1
    trim += "\nV%02i = last.Trim(%i, 0)" % (i, start) 
    cat += "V%02i\n" % i
    avsp.InsertText(trim + cat, pos=None, index=None)
例えば
AVISource("hoge.avi")
AudioDub(Last, WAVSource("hoge.wav"))
なんていうavsがあったとして、1234,5678,9012フレームにブックマークを打ってからこのマクロを実行すると
AVISource("hoge.avi")
V01 = Last.Trim(0, 1233)
V02 = Last.Trim(1234, 5677)
V03 = Last.Trim(5678, 9011)
V04 = Last.Trim(9012, 0)
V01 + V02 + V03 + V04
AudioDub(Last, WAVSource("hoge.wav"))
というように、いったんブックマークの位置で分割して、最後に結合するスクリプトが挿入される(挿入位置はカーソルの位置)。
なににこんなものを使うかといえば、
AVISource("hoge.avi")
V01 = Last.Trim(0, 999).Sharpen(1.0)
V02 = Last.Trim(1000, 1999).Blur(1.0)
V03 = Last.Trim(2000, 2999).FlipVertical()
V04 = Last.Trim(3000, 0).FlipHorizontal()
V01 + V02 + V03 + V04
AudioDub(Last, WAVSource("hoge.wav"))
てな感じでFilterRangeのかわりにするとか、はたまたEasyVFRとか使う人には便利かもしれない。

2010年10月13日水曜日

俺様TrimEditor for AvsP

スクリプトなんて簡単なBash scriptとAviSynth scriptしか書けない私ですが、最近はほんの少しだけPythonをいじるようになりました。
なんでPythonかといえば、AvsP(AviSynth script editor)がPythonで書かれていて、マクロもPythonで書かないといけないから。

さて、本日のお題はTrimEditor。
AvsPには始めからTrimEditorが付いていますが、これが自分にはどうも使いにくい。
まずHomeを押すと出現するダイアログがウザい。
ある範囲を選択(削除)するごとにTrim(a,b)を1行挿入するので、Trimし終わったころには、それだけで7,8行のスクリプトになってしまうのもウザい。
というわけで、自分用のTrimEditorをでっち上げます。
#Bookmarks to TrimLine.py
bookmarks = avsp.GetBookmarkList()
num = len(bookmarks)
if num % 2 == 0 and num:
    bookmarks.sort()
    text = ''
    for i in range(0, num, 2):
        trimrange = bookmarks[i:i+2]
        cattext = '++Trim( %i, %i)' % (trimrange[0], trimrange[1])
        text += cattext
    avsp.InsertText(text.lstrip('++') + '\n', pos=None, index=None)
else:
    avsp.MsgBox('The number of bookmarks is odd (or zero).', title='Warning')
使い方:
ブックマーク(Ctrl+B)を適当にガンガン打って、マクロ実行。
2つのブックマークにはさまれた部位を残すスクリプトが、カーソル位置に1行で挿入されます。
例)ブックマークを1000,2000,3000,4000,5000,6000フレームに打って実行したら Trim(1000,2000)++Trim(3000,4000)++Trim(5000,6000) となる
なお、10行目のpos=Noneをpos=-1に書き換えると、スクリプトの最後に挿入位置が変更されます。