home / junkbox / dvdauthor makeでDVDオーサリング

28 Sep 2015
複数のAVIファイルをひとつのMPEG2ファイルにエンコードし、各AVIファイルのフレーム数からチャプターを打つ方法を追記。

 HDではなくSD画質のDVDです。 音声はffpmegで、画像はmjpegtoolsのmpeg2encでエンコードし、dvdauthorでDVDに必要なツリー構造を作ります。 全部コマンドラインからの操作なので、make一発でできるんちゃう? というのがこのページの趣旨。 覚え書きともいいます。

home / junkbox / dvdauthor 使うもの

Cygwin

 Cygwin のサイトから setup-x86.exe をダウンロードしてインストール。

bc・sed・trなど

 複数のAVIファイルを1本のmpegファイルにして、なおかつチャプターの位置を自動で計算させたい場合に必要。 全部Cygwinのsetupから入れられます。 sedとtrはbaseに入っているので、既にインストール済みのことが多いかと。 bcは自分で入れないと入ってないと思います。

GCC

 CygwinのsetupからCとC++のコンパイラをインストール。 AVIをmjpegtoolsが読み込める形式にダンプするプログラムをビルドするのに使う。

GNU make

 Cygwinのsetupからインストール。

mjpegtools

 Cygwinのportsからインストール。

ffmpeg

 これもportsから入れられますが、AC3をエンコードできる必要があります。 自分でビルドしたのを使ったのか、portsから入れたのを使ったのかちょっと失念。 もし、portsから入れてAC3にエンコードできなければ、自分でビルドする必要があります。 ライセンスの兼ね合いで結構面倒だった気がする。

genisoimage

 昔はmkisofsと言ってました。 Cygwinのsetupからインストール。

dump-yuv4mpeg2

 AVIファイルをVfW CODECを使って、mpeg2encが読める形に吐き出す拙作ツール。

 展開して、cygwinディレクトリ以下でmakeすると、cygwin/tool に dump-yuv4mpeg2.exe ができあがるので、パスが通ったところにコピーしてください。 installスクリプトのような立派なものはありません。 html/tool 以下にマニュアルがあります。 何を言われているかさっぱり分からない人は使わないほうが吉です。

 画像をmjpegtools、音声をffmpegで別々にエンコードして、mplexでMPEG2-PSを作る理由は忘れました・・・多分Chroma Upsampling Errorの関係だったと思います。

home / junkbox / dvdauthor mjpegtoolsの入力

 このツールがじゃじゃ馬(失礼)で、標準入力からYUV4MPEG2という形式のダンプを突っ込みます。 形式については検索すれば出てくるので省略しますが、テキストとバイナリが混ざった感じになっていて、最初の1行にストリームの形式が書かれています。 以後、FRAMEという文字列をキーにして、各フレームがベタっとバイナリでダンプされています。

 基本的にYUY2でダンプして、エンコードするときにYUV420でエンコードするのですが、そうするとインターレースかプログレッシブかでUVの扱いが若干変わります。 ここだけ注意する必要がありそうです。 以前いろいろ検証して、きっとこれで正しい、と思われる設定にしていたのですが、今となっては自信なし。

 だったらとりあえずYUV420でダンプしたらいいんじゃないか、というのと、mjpegtoolsにはlav2yuvというコマンドが付いていて、これでMJPEGなAVIをYUV4MPEG2でダンプできるはずなのですが、YUY2でエンコードしたせいかコアダンプしてしまうので、自前でダンプするプログラムを作りました。 それが先に書いたdump-yuv4mpeg2です。

 入力がAVIファイルの場合は自前のパーザーを使うので、4GB超のAVIファイルでも大丈夫です。 ただし、インデックスを使ったAVIファイルは読めません。 入力がAVIファイルに見えなかった場合はAVIFileOpen APIを呼び出すので、VirtualDubのフレームサーバーや、試してませんがAviSynthのスクリプトもオープンできるのではないかと思います。 したがって、VfWでYUY2にデコードできる形式で適当にエンコードしたAVIファイルを用意するか、VirtualDubやAviSynthでデータを吸い上げられるようにしておきます。 AVIファイルはフレームごとに編集できることとYUY2への親和性を考えると、可逆ならHuffYUV、非可逆ならMJPEGがよいのではないかと。

 入力データをVfW CODECでYUY2にデコードしてYUV420に変換するので、元がYUV420だった場合、CUEが出るかどうかはVfW CODECに依存します。 元がまともなYUY2の場合はCUEは出ないでしょう。 元がそれ以外、例えばRGBなどの場合はYUY2に変換できるVfW CODECが必要です。 ffdshowなんかが使えます。 VirtualDubのフレームサーバーもこの場合に相当します。 出力はプログレッシブ・インターレース、どちらも可能です。

home / junkbox / dvdauthor オーサリングの流れ

 ここでは、データの作り方、エンコードの方法などを説明します。

元データの作成

 日本で再生するDVDを作るなら、スクリーンアスペクト比 4:3・16:9、どちらの場合でもフォーマットは 720×480の29.97(1001/30)fpsです。 そのうち有効な領域は704×480です。 4:3 になるか、16:9 になるかは、ピクセルアスペクト比で決まります。 dump-yuv4mpeg2でピクセルアスペクト比PARを指定できますが、スクリーンアスペクト比が 4:3 ならばPARは10:11、16:9 ならばPARは 40:33 です。 PAR 1:1で1024×576のような画像を作って、エンコード時にy4mscalerでオンザフライで縮小するか、オーサリング時に既に720(704)×480で作って、PARを正しく指定するかのどちらかですが、他のフォーマットと共有する予定がある場合は前者、そうでなければ画質の面から後者の方が有利でしょう。

 フレームレートはもしかすると30fpsや24fpsでもいけるかもしれませんが。やったことないのでなんとも。

 音声がある場合はここでスキューを調整して、ズレがないようにしておきます。

 dvdauthorはタイトルを作るときにチャプターの位置を指定できるのですが、タイトル作成時に複数のMPEG2ファイルを指定することができ、その場合、各ファイルがチャプターになります。 ただし、この場合はチャプターの境目が若干不自然になることがあります。 チャプターの境目がシーンの切り替え位置で、なおかつ無音になっていれば、境目は目立たなくなります。

 チャプターの境目できれいにつながるようにするには、やはり1タイトルを1本のMPEG2ファイルにして、オーサリングするときにチャプターの位置を指定する必要があります。 この場合、チャプターをまたいで連続再生する場合は問題ありませんが、何も考えずにエンコードすると、チャプタースキップした場合にぴったりの位置には飛びません。 映像をエンコードする時にもチャプターの位置を指定する必要があります。

 チャプターの位置を人間がいちいちメモして指定するのは面倒ですし、ソース映像を編集するたびにチャプターの位置も変わりますから、ミスも起こりがちです。 AVIファイルを分けておけば、そのフレーム数から、bc(コマンドラインで使う計算機)、シェルスクリプトの算術展開といったものをフル活用すれば、なんとか自動でチャプター位置を生成できそうです。

 なので、いずれにせよ元データはチャプターごとにAVIファイルにまとめておくと楽です。 あるいは、AviSynthを使えば、簡単にフレーム単位での切り出しができますから、チャプターの区切りごとにスクリプトを用意しておいて、それを入力にしてもよいかもしれません。

 あと、MPEG2ファイルを分けるか分けないかで違ってくるのは作業性です。 MPEG2ファイルを分ける場合はmakeで並列実行してエンコードが可能ですし、元のAVIファイルを修正しても再エンコードになるのは対応するMPEG2ファイルだけです。 MPEG2ファイルをひとつにまとめると並列実行ができませんし、再エンコードする場合もすべてまとめて、ということになります。

チャプターの位置を決める home / junkbox / dvdauthor

 MPEG2ストリームをひとつのファイルにまとめる、と決めた場合は、チャプターの位置を求めなければなりません。 mpeg2encの--chaptersオプション、dvdauthorの-cオプションで必要になります。 どちらもストリーム先頭からの位置をコンマで区切って並べたものになりますが、mpeg2encはフレーム単位、dvdauthorは秒単位という違いがあります。 前者をdvd.chapfrm、後者をdvd.chapsecといった名前にすると仮定します。

 まず、AVIファイルの長さはdump-yuv4mpeg2の出力(標準エラー出力)をsedで加工することで得られます。 これをシェルの算術展開で足していき、chapfrmの方はそのまま出力、chapsecの方はbcで秒にしてtrで改行を削って出力し、間にコンマを挟みます。 元のファイルが既に29.97fpsになっていればこれでできあがり。 シェルスクリプトで書くとこうなります。

#!/bin/bash
TOTALFRAMES=0
for i in "$@"; do
    if [ $TOTALFRAMES -eq 0 ]; then
        echo -n "0" > dvd.chapfrm
        echo -n "0.000" > dvd.chapsec
    else
        echo -n ",$TOTALFRAMES" >> dvd.chapfrm
        echo -n "," >> dvd.chapsec
        echo "scale=3; $TOTALFRAMES * 1001 / 30000" | bc | \
            tr -d '\n' >> dvd.chapsec
    fi
    AVIFRAMES=$(dump-yuv4mpeg2 $i 2>&1 > /dev/null | \
        sed \
          -e '  /Length=/ { ' \
          -e '      s/^.*Length=// ' \
          -e '      s/,.*// ' \
          -e '      q ' \
          -e '  } ' \
          -e '  d ' \
    )
    TOTALFRAMES=$(($TOTALFRAMES + $AVIFRAMES))
done

 dvd.chapfrmの方はコンマとフレーム数を出力して終わりです。 dvd.chapsecの方はbcでフレーム数から秒数を計算し、続いてtrで改行を捨てます。 積算フレーム数が0ならば単に0もしくは0.000を出力してコンマを省略し、その他の場合は直前にコンマを出しているので、これでコンマで区切られた状態になります。

 AVIFRAMESはAVIファイルからフレーム数を取得しています。 dump-yuv4mpeg2は標準エラー出力にストリームの情報を出力するので、まずはこれを標準出力に仕向けます。 標準出力は/dev/nullに仕向けて出力されないようにします。 ストリーム情報をsedに食わせて、Lengthを含む行があれば、行頭からLength=までを削除、続いてコンマから行末までを削除することで、フレーム数を得ています。 これをシェルの算術展開でTOTALFRAMESに積算していきます。 出力してから積算しているのは、各ファイルの前にチャプターを打ちたいからです。 一番最後にチャプターマークはいらないでしょ?

 元のファイルと最終的なエンコード出力のフレームレートが違う場合は若干ややこしくなります。 yuvfpsを使ってエンコード時にオンザフライでフレームレートを変換する場合、mpeg2encが受け取るストリームは既に29.97fpsになっていますから、フレーム数を29.97fpsに換算して渡す必要があります。 dvdauthorに渡すフレーム位置も変わってきてしまいますから、mpeg2encに渡したフレーム番号で計算しなおす必要があります。

 yuvfpsはフレームレートを1000/1001倍すると、0から999フレームまでが出力された後、1000フレームを飛ばして入力の1001フレームを1000フレームとして出力、という動作をするようです。 2000フレーム近辺は2000フレームが1999フレーム、2001フレームを飛ばして2002フレームが2000フレームになります。 これはghostscriptでフレーム番号を書いた連番のPNGを出力して、VirtualDub+HuffYUVでAVIにし、mpeg2encでエンコード、AviSynthでDirectShowSourceを使ってデコードして調べました。 このときの画像サイズはとりあえず288×72、mpeg2encのパラメーターは-f 3 -b 200(Generic MPEG2・200kbps)にしました。

 元のストリームで1000フレームを指示された場合は対応する出力フレームがありません。 999か1000を指定する必要があるのですが、切り上げて1000で指定することにします。 これは、チャプターの切り替え位置はシーンが切り替わる位置に置くことが普通で、1000フレームでシーンが切り替わったということは、999フレームは前のシーン、1001フレームは次のシーンになっているからです。 チャプターとしては次のシーンを指定しないといけませんから、入力の1001フレーム、つまり出力に換算すると1000フレームを指定するのが適当です。

 これを実現するには、入力フレーム番号を1000/1001倍して切り上げる必要があります。 999の場合は999*1000/1001=998.0019を切り上げて999に、 1000の場合は1000*1000/1001=999.0009を切り上げて1000に、 1001の場合は1000*1000/1001=1000になります。 bcには丸めの機能がなく、割り算をすると小数点以下がscaleで指定した桁数で切り捨てられるようですので、まず1000倍し、あとで1001で割るのですが、切り上げ用に1000を足してから、scaleを0にして1001で割ります。 999の場合は999*1000+1000=1000000を1001で割って999(.00099)に、 1000の場合は1000*1000+1000=1001000を1001で割って1000に、 1001の場合は1001*1000+1000=1002000を1001で割って1000(.999)になります。

 結局、こんな感じになります。

    if [ $TOTALFRAMES -eq 0 ]; then
        echo -n "0" > dvd.chapfrm
        echo -n "0.000" > dvd.chapsec
    else
        FRM=`echo "scale = 0; ( $TOTALFRAMES * 1000 + 1000 ) / 1001" | bc`
        echo -n ",$FRM" >> dvd.chapfrm
        echo -n "," >> dvd.chapsec
        echo "scale=3; $FRM * 1001 / 30000" | bc | tr -d '\n' >> dvd.chapsec
    fi

 ひとつひとつのフレームが伸びるような感じになりますから、音声ストリームの位置はぴったり合わず、若干チャプターの頭が欠けたような感じになるはずです。 例えば、元のストリーム(30fps)で1000フレームにチャプターを打つと、それは出力(29.97fps)の1000フレームになります。 時刻は元のストリームが33+10/30秒、出力は33+11/30秒になるので、最大で1/30秒だけ欠けることになります。 元のストリームで1001フレームならば、どちらも33+11/30秒になるので、音声ストリームの位置は完全に一致することになります。

画像のエンコード home / junkbox / dvdauthor

 dump-yuv4mpeg2で入力データをダンプし、パイプでmpeg2encに突っ込みます。 ここで元画像が正しく解釈されるようにPARを指定し、必要ならばサイズもy4mscalerで修正します。 y4mscalerは-I norm=ntsc -O preset=DVD_WIDEのように指定すれば、よきに計らってくれます。 -O infer=exactをつけてないと、拡大率が適当に丸められてしまって、希望の結果が得られないことがあります(かなり悩んだ)。

 一方、フレームレートはyuvfpsで修正できます。補間がかかるのではなく、間引き・繰り返しをしてくれるだけですが。 これらのプログラムは全部フィルタとして動作するので、パイプでずらずらとつなげて書くことができます。 中間ファイルは生成されません。

dump-yuv4mpeg2.exe -i -denom 1001 -par 1:1 $< | \
yuvfps -v 0 -r 30000:1001 | \
y4mscaler -v 0 -I norm=ntsc -O infer=exact -O preset=DVD_WIDE | \
mpeg2enc -v 0 -f 8 -o $@

 Makefileから引っ張ってきたので入力が$<、出力が$@ですが、なんとなく分かるでしょう。 -v 0はメッセージ表示をとめるためのオプション。 mpeg2encの-f 8はdvdauthor用のMPEG2ストリームを出力するオプション。 このオプションをつけるとビットレートが7.5Mbpsに設定されるようです。 ビットレートを変更したい人は -b オプションになるのかな。

 全部のAVIファイルをまとめてひとつのファイルにエンコードしたいときは、シェルのコマンドグルーピングを使ってダンプをつなげてしまいます。 YUV4MPEG2の最初にはストリームヘッダがありますから、これを取り除きながら次々にダンプすれば勝手につながります。 ストリームヘッダはtail -n +2で簡単に取り除けます。 頭にはヘッダが必要なので、最初のファイルだけはtailを実行しないか、head -n 1でヘッダだけ取り出してあげればOKです。

{ \
    dump-yuv4mpeg2 $(DUMPYUV4MPEG2FLAGS) $< 2> /dev/null | head -n 1; \
    for i in $^; do \
        echo "*** encoding $$i" >&2; \
        dump-yuv4mpeg2 $(DUMPYUV4MPEG2FLAGS) $$i | tail -n +2; \
    done \
} | yuvfps ...

 最初のdump-yuv4mpeg2はストリームヘッダをダンプしています。 $<は最初の依存ファイル。 forの中のechoは今どのファイルをダンプしているかを表示していますが、標準出力には出せませんから、>&2で標準エラー出力にリダイレクトしています。 forの中のdump-yuv4mpeg2が実際にストリームをダンプしています。 $^はすべての依存ファイル。 これらのダンプ結果が全部つながった状態で、パイプラインの次のプログラムに入ります。

 チャプターを指定する場合、先ほどのdvd.chapfrmの内容を--chaptersオプションに渡します。 mpeg2enc ... --chapters `cat $*.chapfrm` -o $@といった感じになります。

音声のエンコード home / junkbox / dvdauthor

 ffmpegを使います。 日本のDVDはサンプリング周波数48kHzのAC3でエンコードします。

ffmpeg -y -i $< -acodec ac3 -ab 192k -ar 48k -vn $@

 -yは「上書きしますか?」のようなメッセージに強制的に「はい」と答えるオプション、-abが音声のビットレート。 -vnは入力に含まれるビデオはエンコードしません、という指定。

 複数のAVIファイルをひとつにまとめる場合、いくつかの方法がありますが、例えば次のようにします。

ffmpeg -y $(addprefix -i ,$^) \
    -filter_complex "concat=n=$(words $^):v=0:a=1" \
    -acodec ac3 -ab 192k -ar 48k -vn $@

 -iは各入力ファイルの前に必要。 付け忘れると出力ファイル扱いになって入力ファイルを壊しちゃいますので注意(実は一度やった)。 -filter_complex "concat=n=$(words $^):v=0:a=1"が入力をまとめてエンコードする指定で、nの部分に入力のストリーム数を与える必要がありますが、これはmakeが数えてくれます。 make超便利。 v=0:a=1は音声だけつなげる指定。

 ひとまとめにする場合、各AVIファイルの音声ストリームの長さが画像と合ってないと、マルチプレックスしたときにそのファイルより後ろで映像と音声がずれてしまいます。 当たり前ですが。 普通はないはずですが、VirtualDubで編集していたら1度だけそういうファイルができあがったので注意したほうがよいでしょう。 オーディオスキューを指定していて、先頭あるいは最後尾までぴったり出力するとそういうことになるのかもしれない。

画像と音声をくっつける

 画像と音声をマルチプレックスしてMPEG2 PSを作ります。 mjpegtools付属のmplexを使います。

mplex -f 8 -o $@ $^

 -f 8はやはりdvdauthor用のMPEG2 PSの指定。 $^はMakefileで「依存ファイル全部」なので、ここにMPEG2とAC3のファイルを指定します。 画像と音声のエンコードが正しければ、特に何事もなくストリームができるはず。 AC3のサンプリング周波数を間違えていると変な警告が出ます。 最初は何かと思った。

ファイルレイアウトを作成する home / junkbox / dvdauthor

 ここでdvdauthorの出番です。

dvdauthor -t -o $@ -v ntsc+16:9 -a ac3+2ch $^
dvdauthor -T -o $@

 -tオプションがある行はタイトルを作っています。 $^が入力ですから、依存ファイル全部がずらっと並んで指定されます。 放っておくと指定されたファイルを順番に結合したものがひとつのタイトルとなり、ファイルの境目がチャプターの境目になります。 -t付きで何度も実行すると、その分だけタイトルができます。 -oは出力ディレクトリを指定します。 あとは見れば分かるでしょう。 NTSCワイドでステレオなら普通はこの指定。

 ストリームはひとつでチャプターの位置を指定する場合、入力ファイルの前に-cオプションでチャプターの位置を指定します。 チャプターの指定はマニュアルには[[h:]mm:]ss[.frac]と書いてあります。 dvdauthor実行時にログにCHAPTERS: VTS[1/1] 5745.539のような出力が出るので、普通に秒単位で小数点以下まで書けばいいようです。 小数点の後ろはフレーム数ではない模様。 コンマで区切って複数のチャプターを指定できます。

 先ほどのようにチャプター位置を示すファイルを作ったとすれば、

dvdauthor -t -o $@ -v ntsc+16:9 -a ac3+2ch -c `cat dvd.chapsec` $<

のようにすればOKです。

 -Tオプションのある行はTOCを作ります。 これでメニューも何もなしのシンプルなDVDのファイルレイアウトができあがります。 IFOファイルを扱えるDVD再生ソフトなら、この段階でチャプターの確認までできるでしょう。

ディスクイメージの作成

 genisoimageでディレクトリ丸ごとISOイメージにします。

genisoimage -dvd-video -o $@ -V "VOLUME-LABEL" $<

見たまんま。 便利なもんだ。 入力はディレクトリを指定するっす。 あとはトースターでちーん。

home / junkbox / dvdauthor Makefileにまとめる

 一連の作業をMakefileにまとめてみます。 ファイル拡張子の動きですが、入力はaviで、画像をエンコードしたものをmpeg、音声をエンコードしたものをac3にします。 mpegとac3をマルチプレックスしてm2pができます。 これをdirという拡張子のついたディレクトリ(たとえばdvd.dir)以下にオーサリングして、最終的にgenisoimageでisoができあがります。 したがって、暗黙のルールは以下のとおり。

# encode video image
%.mpeg: %.avi
    dump-yuv4mpeg2.exe -i -denom 1001 -par 1:1 $< | \
    yuvfps -v 0 -r 30000:1001 | \
    y4mscaler -v 0 -I norm=ntsc -O infer=exact -O preset=DVD_WIDE | \
    mpeg2enc -v 0 -f 8 -o $@

# encode audio
%.ac3: %.avi
    ffmpeg -y -i $< -acodec ac3 -ab 192k -ar 48k -vn $@

# multiplex
%.m2p: %.mpeg %.ac3
    mplex -f 8 -o $@ $^

# authoring dvd
%.dir:
    rm -rf $@
    dvdauthor -t -o $@ -v ntsc+16:9 -a ac3+2ch $^
    dvdauthor -T -o $@

# create ISO image
%.iso: %.dir
    genisoimage -dvd-video -o $@ -V "$(VOLUMELABEL)" $<

 最終的なISOイメージの名前をdvd.isoとすると、dvd.isoをターゲットとして書けば、dvd.dir以下をISOイメージに変換してくれます。 dvd.dirは暗黙のルールに依存ファイルが書かれていませんから、m2pを明示的に指定してやる必要があります。 ここで指定したファイルが順につながって、ひとつのタイトルになるわけです。 あとはm2pをmpegとac3から作るルール、mpegとac3をaviから作るルールがあるので、放っておくと全部やってくれます。

 GNU makeのお約束として、暗黙のルールチェーンで生成された中間ファイルは最終的に消去されることになっています。 つまり、エンコードしたファイルを勝手に消してくれちゃうわけですが、エンコードをやり直したくないからmakeを使っているので、消さないでほしいわけです。 消さないためにはターゲットとして指定しておけばよろしい。 なので、ソースになるAVIファイルを指定して、そこからmpeg・ac3・m2pのファイル名を作って、ターゲットとして指定するのが楽です。 別解として.SECONDARYや.PRECIOUSを使う方法もありますが。

VOLUMELABEL ?= VOLUME-LABEL
SRCAVI = $(sort $(wildcard chapter-*.avi))

.PHONY: all
all: dvd.iso
dvd.dir: $(SRCAVI:.avi=.m2p)

$(SRCAVI:.avi=.mpeg):
$(SRCAVI:.avi=.ac3):

 これでmakeを実行すると、allターゲットを見てdvd.isoを作りに行きます。 暗黙のルールからdvd.isoはdvd.dirに依存しており、dvd.dirは暗黙のルール内に依存ファイルが書いてないので、明示的に依存関係を書いておきます。 dvd.dirの依存ファイルはタイトル作成の入力として使われますから、m2pファイルを指定します。 あとは暗黙のルールをたどって、エンコード・マルチプレックス・オーサリングが行われます。 sortしているのはwildcardは必ずしもアルファベット順でファイル名を返してくれるわけじゃないから。 不安ならば実際に使うファイルを明示的に書いたほうがいいでしょう。 最後の2行が中間ファイルを消さないためのものです。 m2pは依存ファイルになっているので放っておいても消えません。 mpegとac3はm2pができてしまえばいらないといえばいらないのですが、音声だけ再エンコードしたい場合などは残しておいたほうが便利です。

home / junkbox / dvdauthor チャプター位置を指定する場合

 dvd.iso は dvd.dir に依存、ここまでは同じです。 この先が違っていて、dvd.dir は dvd.m2p と dvd.chapsec に依存します。 dvd.m2p は dvd.mpeg と dvd.ac3 に依存し、dvd.mpegは各AVIファイルとdvd.chapfrmに依存します。 dvd.chapsec・dvd.chapfrm・dvd.ac3の3つは各AVIファイルだけに依存します。 基本的な暗黙のルールはこんな感じ。 chapterファイルはステムを示す自動変数$*を使っています。 $(word 2,$^)でもいけそうな気がしますが。

# multiplex
%.m2p: %.mpeg %.ac3
    mplex -f 8 -o $@ $^

# authoring dvd
%.dir: %.m2p %.chapsec
    rm -rf $@
    dvdauthor -t -o $@ -v ntsc+16:9 -a ac3+2ch -c `cat $*.chapsec` $<
    dvdauthor -T -o $@

# create ISO image
%.iso: %.dir
    genisoimage -dvd-video -o $@ -V "$(VOLUMELABEL)" $<

 .mpeg と .ac3 を作る部分はこんな感じ。 この例では元の画像データを720×480で作ることにして、y4mscalerを外しています。

DUMPYUV4MPEG2FLAGS += -i -denom 1001 -par 40:33

# encode video image
%.mpeg: %.chapfrm
    @{ \
        dump-yuv4mpeg2 $(DUMPYUV4MPEG2FLAGS) $(word 2,$^) 2> /dev/null | \
        head -n 1; \
        for i in $(filter %.avi,$^); do \
            echo "*** encoding $$i" >&2; \
            dump-yuv4mpeg2 $(DUMPYUV4MPEG2FLAGS) $$i | \
            tail -n +2; \
        done \
    } | \
    yuvfps -v 0 -r 30000:1001 | \
    mpeg2enc -v 0 -f 8 $(MPEG2ENCFLAGS) --chapters `cat $<` -o $@

# encode audio
%.ac3:
    ffmpeg -y $(addprefix -i ,$^) \
        -filter_complex "concat=n=$(words $^):v=0:a=1" \
        -acodec ac3 -ab 192k -ar 48k -vn $@

 dvd.mpegは暗黙のルールでdvd.chapfrmに、明示的な指定で$(SRCAVI)に依存しています。 このため、レシピ中では$<がdvd.chapfrmになり、残りがエンコードソースになります。 word関数を使ってもよかったのですが、ここではfilter関数で依存ファイルのうち拡張子がaviのものだけを取り出して使っています。 ヘッダをダンプするファイルは依存ファイルのうち2番目のものになりますから、$(word 2,$^)です。

 映像も音声も、このままでは暗黙のルールに元ファイルの指定がありませんから、あとで明示的に(まとめて)元ファイルを依存ファイルとして指定してやります。

 面倒くさいのがchapterを作る部分。 先ほどのシェルスクリプトをMakefile向けに変更します。 ごちゃっと大量にエコーが出て見づらいので、コマンドエコーは止めて、適当に情報表示用のechoを入れています。 最終的にTOTALFRAMESには指定されたファイル全体の総フレーム数が入っています。 この値は使われないのですが、せっかくなのでこれも表示しておきました。

%.chapfrm %.chapsec:
    @echo -n "creating chapter list ... "
    @TOTALFRAMES=0; \
    for i in $^; do \
        if [ $$TOTALFRAMES -eq 0 ]; then \
            echo -n "0" > $*.chapfrm; \
            echo -n "0.000" > $*.chapsec; \
        else \
            FRM=`echo "scale=0; ($$TOTALFRAMES*1000+1000)/1001" | bc`; \
            echo -n ",$$FRM" >> $*.chapfrm; \
            echo -n "," >> $*.chapsec; \
            echo "scale=3; $$FRM*1001/30000" | bc | tr -d '\n' >> $*.chapsec; \
        fi; \
        AVIFRAMES=$$(dump-yuv4mpeg2 $$i 2>&1 > /dev/null | \
            sed -e '/Length=/ {' -e 's/^.*Length=//' \
                -e 's/,.*//' -e 'q' -e '}' -e 'd' ); \
        TOTALFRAMES=$$(($$TOTALFRAMES + $$AVIFRAMES)); \
    done; \
    echo "total-frames: $$TOTALFRAMES"

 GNU makeではターゲットを複数指定すると、通常はターゲットごとにルールを書いたのと同じことになり、ターゲットごとに1度レシピが実行されます。 つまり、ターゲットが3個書いてあれば3回レシピが実行されます。 しかし、暗黙のルールの場合は、一度のレシピの実行で、すべてのターゲットが更新されたものとみなされます。 したがって、このレシピは一度だけ実行され、dvd.chapfrmとdvd.chapsecを生み出すことになります。 この場合、$@にはレシピ起動のトリガとなったターゲットが入るため、この例だとdvd.chapfrmとdvd.chapsecのどちらになるかレシピが実行されるまで分かりません。 このため、ファイル名はステム$*を使って書いてあります。

 これでエンコード・マルチプレックス・オーサリング・イメージ生成すべてのルールが書けたので、あとは依存記述を書くだけです。

VOLUMELABEL ?= VOLUME-LABEL
SRCAVI = $(sort $(wildcard chapter-*.avi))

.PHONY: all
all: dvd.iso
dvd.dir:
dvd.m2p:
dvd.mpeg dvd.ac3 dvd.chapfrm dvd.chapsec: $(SRCAVI)

 dvd.isoは暗黙のルールでdvd.dirに依存し、 dvd.dirはdvd.m2pとdvd.chapsecに、 dvd.m2pはdvd.mpegとdvd.ac3に、それぞれ依存します。 dvd.mpegは暗黙のルールでdvd.chapfrmに依存しています。

 dvd.dirとdvd.m2pは消して欲しくないので、ターゲットとして書いておきます。 mpeg・ac3・chapfrm・chapsecは元のAVIファイルが必要ですので、ターゲットにまとめてずらっと書いて、$(SRCAVI)に依存する、と指定しておきます。

home / junkbox / dvdauthor 並列実行と複数タイトル

 MPEG2ファイルを複数に分けていれば、並列実行(makeの-jオプション)も可能です。 並列実行で注意しなければならいのは、dvdauthorの実行順です。 実行した順にタイトルができ、最後にTOCを作る必要があるためで、makeの並列実行を最大限使おうと、

# WARNING: DO NOT USE SUCH A MAKEFILE!!!!!!
%.title:
	dvdautor -t -o $(OUTDIR) -v ntsc+16:9 -a ac3+2ch $^

%.dir:
    dvdauthor -T -o $@

OUTDIR = dvd.dir
$(OUTDIR): 1.title 2.title 3.title
1.title: $(SRCAVI1:%.avi=%.m2p)
2.title: $(SRCAVI2:%.avi=%.m2p)
3.title: $(SRCAVI3:%.avi=%.m2p)

のような書き方をすると、タイトルを同時に作りにいってしまうので、正常に動かないでしょう。 出力ディレクトリを消してから、TOCを作りきるまではひとつのレシピにまとめておいたほうが無難です。

 ということは、複数タイトルを作るときに、依存関係を作ってタイトルを作るのは安全ではない、ということになります。 タイトル数やタイトルで使うAVIファイルを自由に設定するのならば、makeの変数やforeachを使って、以下のようにする方法が考えられます。 動かしたことはないのでデバッグは自分でやってください。

# WARNING: THIS IS NOT YET TESTED
TITLES = title1 title2 ...
title1-srcs = title1-chapter1.avi title1-chapter2.avi ...
title2-srcs = ...

define make-title
$(foreach t,$^,dvdauthor -t -o $@ -v ntsc+16:9 -a ac3+2ch $($(t)-srcs:%.avi=%.m2p)
)
endef

%.dir:
    rm -rf $@
    $(make-title)
    dvdauthor -T -o $@

all: dvd.iso
dir: $(TITLES)
$(foreach t,$(TITLES), \
$(eval $(t): $$($(t)-srcs:%.avi=%.m2p)) \
)

 define make-title〜endefの中身は2行(2行目は閉じカッコのみ)なので注意。 こうするとタイトルごとにばらばらにシェルが起動されます。 セミコロンを使って1行にすることもできますが、チャプターの数によってはおそろしく長いコマンドラインができてしまうので。 defineってforeachやevalの中身も\なしでぶち切れるのかな?


Copyright (C) 2015 akamoz.jp

$Id: dvdauthor.htm,v 1.6 2015/11/15 00:45:12 you Exp $