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)

0 件のコメント:

コメントを投稿