EPGStationで使うFFmpeg 4.2.4のオプション設定を考える

<2022年5月25日>Ubuntu 22.04で動作確認した、新しいバージョンを公開しました。

EPGStationで録画したtsファイルをmp4にトランスコードする際、FFmpegを使用します。

現在利用しているFFmpeg4.2.4でのオプション設定について、調査した結果を報告します。

tsからmp4へ圧縮するトランスコードで困ったこと

今まで私が使っていたFFmpeg4.2.4でのオプション設定は、以下のとおりです。

ffmpeg -y -i <入力ファイル名>\
-preset veryfast -c:v libx264\
-crf 22 -f mp4\
-c:a aac\
-strict -2 -ar 48000 -ab 192k -ac 2\
-loglevel error\
<出力ファイル名>

このオプション設定で、ほとんど問題は起こりません。
ただし、以下のような不具合はありました。

  1. NHKBSP 連続テレビ小説「おちょやん」 時々、音声が解説放送になる。
  2. NHKBS1 世界のドキュメンタリー    時々、英語音声になる。

そこで、mapオプションを入れることにしました。

ffmpeg -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -map 0 -ignore_unknown -sn\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

mapオプション「 -map 0 -ignore_unknown -sn」を入れることで、上記の不具合は解決しました。

ところが、別のもっと悪いことが発生するようになってしまいました。

  1. 2021年3月12日9:00-11:24 金曜ロードSHOW!「Fukushima 50」
  2. 「世界のドキュメンタリー」の一部(2か国語放送)
  3. 「午後エンタ 午後ロード」の一部(2か国語放送)

上記の番組で、予約したにもかかわらずmp4ができない事象が発生。
しかも、「元ファイルの自動削除」を設定したため、tsファイルが削除されていました。
つまり、FFmpegが正常終了したにもかかわらず、mp4ができない事象が発生しました。

その後の調査で、[解]解説放送、[二]二か国放送、[多]音声多重放送、でランダムにこれが発生していました。

このmapオプションは、けっこう使われていると思ったので、自分の使い方が悪いか、環境に問題があると考えました。

そこで、徹底調査を行った結果、思いも寄らない原因と、簡単な解決策がわかりました。

それは、FFmpeg4.2.4のオプション設定を以下のようにすることでした。

ffmpeg -ss 4 -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -map 0 -ignore_unknown -sn\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

簡単な解決策とは、ffmpegの最初のオプションとして、「 -ss 4」を入れることでした。
このオプションは、ファイルの最初の4秒後からトランスコードを行うという設定です。
この4秒は、環境によって若干違うかもしれません。tsファイルを確認して、録画した番組の頭がファイルの最初から何秒後か確認して設定すれば大丈夫です。


<2021年3月24日追記>この記事を書いた後、以下の事象が発生。

2021年3月24日17:58から放送の水曜映画館「レッドサン」でmp4が作成できないで正常終了が発生。
「-ss 4」「map 0 -ignore_unknown -sn」のオプションで発生。
ただし、コーデック情報を見たところ、2か国語放送とあるにもかかわらず、英語音声がありませんでした。
これは、テレビ東京のミスではないかと思います。2か国語でトラブルが多いのは、テレビ東京が多いです。

オススメのFFmpeg4.2.4のオプション設定

オプション設定について、いろいろ検討した結果、以下の結論がでました。

<すべての放送>NHK総合、NHKBS1の2か国語ニュースを除く

ffmpeg -ss 4 -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -map 0:v -map 0:a:0\
 -metadata:s:a:0 title=main\
 -metadata:s:a:0 language=jpn\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

上記設定で、映像と最初の音声をmp4にトランスコードします。
番組には、少なくとも映像と音声が1つずつあるので、失敗することはありません。

<NHK総合、NHKBS1の2か国語ニュース>

ffmpeg -ss 4 -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -filter_complex channelsplit\
 -metadata:s:a:0 title=Japanese -metadata:s:a:0  language=jpn\
 -metadata:s:a:1 title=English -metadata:s:a:1  language=eng\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

NHK総合、NHKBS1のニュースで、[二]と表記されている番組は、デュアルモノという音声になっています。
これは、左から日本語、右から英語で放送されています。上記の「 -filter_complex channelsplit」オプションを使用することで、音声トラックを分離できます。
VLCでは、以下のようになります。

また、Plex Media Playerでは、以下のようになります。

録画番組の頭出し、ssオプションの設定

ssオプションの値を決めるためには、録画番組の頭出し秒数を確認する必要があります。

まず、tsファイルをVLCで開いてください。

左下で、再生時間0秒を確認してください。次に、左下の再生ボタン▶をクリックしてください。録画番組の頭で、再度クリックしてください。

ここで、左下の再生時間を確認してください。この画面では4秒になっています。

従って、以下のように設定します。

-ss 4

切り替わるところを見たいという人は、3秒でも大丈夫だと思います。

たまたま今回は4秒でしたが、構成によっては、3秒とか5秒になるかもしれません。
自分の環境で、確認して設定してください。

設定した値が反映されているかは、できたmp4を見ればわかります。切ったところから始まりますから。

[二]2か国語放送の設定について

NHKのニュース以外の2か国語放送で、日本語と英語を切り替えたいという人もいるでしょう。

以下のようなFFmpeg4.2.4のオプション設定で、2か国語とれる場合もあります。

ffmpeg -ss 4 -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -map 0:v -map 0:a:0 -map 0:a:1 -ignore_unknown -sn -dn\
 -metadata:s:a:0 title=main -metadata:s:a:0  language=jpn\
 -metadata:s:a:1 title=sub -metadata:s:a:1  language=eng\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

これで、ほとんどの2か国語放送は、日本語と英語が切り替えられるmp4に変換できますが、失敗する場合もあり、これは原因不明です。最初に、1か国語の設定でmp4を作成した後に、試してみる方がいいでしょう。

2か国語放送のmp4は、VLCで以下のように表示されます。

Plex Media Playerでは、以下のように表示されます。

[多]音声多重放送の設定について

音声多重放送も時々聞いてみたい番組があります。
2か国語と基本的な設定はおなじです。

ffmpeg -ss 4 -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -map 0:v -map 0:a:0 -map 0:a:1 -ignore_unknown -sn -dn\
 -metadata:s:a:0 title=main -metadata:s:a:0  language=jpn\
 -metadata:s:a:1 title=sub -metadata:s:a:1  language=jpn\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

2か国語との違いは、2番めの言語が日本語になっているだけです。

VLCでの表示例

Plex Media Playerでの表示例

[解]解説放送の設定について

最近は、ほとんどの番組で解説と字幕がついています。
通常視聴する場合は、使用する人はわずかだと思います。
解説放送の設定は、以下のとおりです。

ffmpeg -ss 4 -y -i <入力ファイル名>\
 -preset veryfast -c:v libx264\
 -crf 22 -f mp4\
 -map 0:v -map 0:a:0 -map 0:a:1 -ignore_unknown -sn -dn\
 -metadata:s:a:0 title=main -metadata:s:a:0  language=jpn\
 -metadata:s:a:1 title=exp -metadata:s:a:1  language=jpn\
 -c:a aac\
 -strict -2 -ar 48000 -ab 192k -ac 2\
 -loglevel error\
 <出力ファイル名>

これも2か国語とほとんど一緒です。

VLCでの表示例。

Plex Media Playerでの表示例

トラブルの原因について

EPGStationで番組を録画する場合、番組が始まる5秒程度前から録画が始まっています。

これをそのままFFmpegでトランスコードした場合、前の番組のコーデック情報が入ってしまうのが原因でした。

トラブルになった例をあげてみましょう。

2021年3月21日14時の「世界のドキュメンタリー」のコーデック情報を再生0秒と再生5秒で比較してみましょう。

再生0秒

再生5秒

比較してみると、再生5秒では最後に「ストリーム4」が追加されています。

このファイルを「map 0 -ignore_unknown -sn」でトランスコードすると正常終了して、mp4ファイルは0バイトで作成されました。

次に、同じファイルを「-ss 4」「map 0 -ignore_unknown -sn」でトランスコードすると、2か国語切替可能なmp4が作成できます。

この場合は、オーディオメニュは、「トラック1」「トラック2」になります。

このように、0秒からトランスコードする場合と4秒からトランスコードする場合で、まったく違う結果になることがわかりました。

ただし、この場合はたまたま2か国語がとれましたが、1か国語しかとれない場合もあります。
「-ss 4」「map 0 -ignore_unknown -sn」の良いところは、エラーにならず、少なくとも1か国語は取れるというところです。

EPGStationでの変更点

今回のトランスコード処理を、EPGStation v2に反映するために必要なことは、
epgstation/configディレクトリの中で、1つのファイルを変更し、1つのファイルを追加するだけです。

(1)config.ymlの変更

<変更前>

encode:
    - name: H.264
      cmd: '%NODE% %ROOT%/config/enc.js'
      suffix: .mp4
      rate: 4.0

<変更後>

encode:
    - name: L1
      cmd: '/bin/bash %ROOT%/config/enc-libx-cpm.sh 22 veryfast L1'
      suffix: -L1.mp4
      rate: 4.0
      
    - name: M2
      cmd: '/bin/bash %ROOT%/config/enc-libx-cpm.sh 22 veryfast M2'
      suffix: -M2.mp4
      rate: 4.0
           
    - name: L2
      cmd: '/bin/bash %ROOT%/config/enc-libx-cpm.sh 22 veryfast L2'
      suffix: -L2.mp4
      rate: 4.0
      
    - name: E2
      cmd: '/bin/bash %ROOT%/config/enc-libx-cpm.sh 22 veryfast E2'
      suffix: -E2.mp4
      rate: 4.0     
           
    - name: S2
      cmd: '/bin/bash %ROOT%/config/enc-libx-cpm.sh 22 veryfast S2'
      suffix: -S2.mp4
      rate: 4.0
      
    - name: T0
      cmd: '/bin/bash %ROOT%/config/enc-libx-cpm.sh 22 veryfast T0'
      suffix: -T0.mp4
      rate: 4.0  

(2)enc-libx-cpm.shの追加

#!/bin/sh
# enc-libx-cpm.sh
# version 4.0 created on 2021-03-29 by simplelife0530
#
#パラメーターの設定
if [ $# = 3 ]
then 
crfValue=${1}
presetValue=${2}
modeValue=${3}
elif [ $# = 2 ]
then
crfValue=${1}
presetValue=${2}
modeValue="L1"
elif [ $# = 1 ]
then
crfValue=${1}
presetValue="veryfast"
modeValue="L1"
else
crfValue=22
presetValue="veryfast"
modeValue="L1"
fi
#
#エンコード設定
inputPara=" -ss 4 -y -i "
videoCodec=" -preset ${presetValue} -c:v libx264"
videoOpt=" -crf ${crfValue} -f mp4"
if [ ${modeValue} = "L1" ] 
then
mapOpt=" -map 0:v -map 0:a:0 -metadata:s:a:0 title=\"main\" -metadata:s:a:0 language=\"jpn\""
elif [ ${modeValue} = "M2" ] 
then
mapOpt=" -filter_complex channelsplit -metadata:s:a:0 title=\"Japanese\" -metadata:s:a:0  language=\"jpn\" -metadata:s:a:1 title=\"English\" -metadata:s:a:1  language=\"eng\""	
elif [ ${modeValue} = "L2" ]
then
mapOpt=" -map 0:v -map 0:a:0 -map 0:a:1 -ignore_unknown -sn -dn -metadata:s:a:0 title=\"Japanese\" -metadata:s:a:0  language=\"jpn\" -metadata:s:a:1 title=\"English\" -metadata:s:a:1  language=\"eng\""
elif [ ${modeValue} = "E2" ]
then
mapOpt=" -map 0:v -map 0:a:0 -map 0:a:1 -ignore_unknown -sn -dn -metadata:s:a:0 title=\"main\" -metadata:s:a:0  language=\"jpn\" -metadata:s:a:1 title=\"exp\" -metadata:s:a:1  language=\"jpn\""	
elif [ ${modeValue} = "S2" ]
then
mapOpt=" -map 0:v -map 0:a:0 -map 0:a:1 -ignore_unknown -sn -dn -metadata:s:a:0 title=\"main\" -metadata:s:a:0  language=\"jpn\" -metadata:s:a:1 title=\"sub\" -metadata:s:a:1  language=\"jpn\""
elif [ ${modeValue} = "T0" ]
then
	mapOpt=" -map 0 -ignore_unknown -sn -dn"		
else
	mapOpt=""	
fi
audioCodec=" -c:a aac"
audioOpt=" -strict -2 -ar 48000 -ab 192k -ac 2"
advancedOpt=" -loglevel error "
#
command="$FFMPEG$inputPara\"$INPUT\"$videoCodec$videoOpt$mapOpt$audioCodec$audioOpt$advancedOpt\"$OUTPUT\""
#
#エンコード開始
eval ${command}
#エンコード終了
exit

(3)使い方

ルールでエンコードメニューに表示されます。
L1 1音声
M2 デュアルモノを分離し2音声(NHK、NHKBS1のニュース用)
L2 2音声(2か国語用)
E2 2音声(解説放送用)
S2 2音声(多重音声用)
T0 1音声と2音声を自動で判断(メニューの文字は「トラック1」「トラック2」)

以下からダウンロードできます。

2021年03月24日 | Posted in 電脳:録画サーバー | タグ: , 15 Comments » 

関連記事

コメント15件

  • マサノリ より:

    エンコードオプションの調査ありがとうございます。参考に自分も同じ環境を導入させていただきました。

    質問なのですが、FFmpegの変換ではtsからmp4にエンコードしたとき、字幕は保持できないのでしょうか。

    何か方法がありましたら、教えていただけないでしょうか。

  • simplelife0530 より:

    マサノリさん、

    字幕でしたら、デフォールトでついているenc.jsを使うと、字幕付きのmp4ができます。
    プログレスバー(エンコードの進捗を示す)もついているので、便利です。
    ただし、NHKのニュースの2か国語が処理できません。私のスクリプトと併用すると、いいと思います。

  • マサノリ より:

    早速ご回答ありがとうございます。

    simplelife0530 さんのenc-libx-cpm.shとデフォールトのenc.jsを参考に組み合わせると、L1やL2などは可能ということであっていますか。

  • simplelife0530 より:

    マサノリさん、

    「-map 0:s? -c:s mov_text」これを私のスクリプトに追加すれば、いいと思います。

    今考えると、私のスクリプトだと、
    (1)エンコード中にストップボタンをクリックしても、中止ができない。
    (2)プログレスバーがでない。
    という点が残念なところです。
    enc.jsを改良して、2か国語に完全に対応するものを現在制作中ですが、いつ公開できるか?

  • マサノリ より:

    スクリプトの知識がないので、ご教授いただき大変助かります。帰宅後、さっそく追記してみたいと思います。

    enc.js改良版、楽しみにしています。

  • simplelife0530 より:

    マサノリさん、

    字幕オプション「-map 0:s? -c:s mov_text」を
    私のスクリプトに加えてみましたが、エンコードができませんでした。
    enc.jsでは、きちんと機能するのですが、不思議です。
    字幕オプションを加えたい場合は、enc.jsを使ってください。

  • マサノリ より:

    simplelife0530 様

     ご連絡ありがとうございます。また、結果の報告が遅れて申し訳ございません。simplelife0530 さんが作成していただいたenc-libx-cpm.shでは、「-sn」のオプションがあり、字幕が無効にされていたためかと思います。
      
     あの後、自分でenc.jsをもとにsimplelife0530さんのenc-libx-cpm.shを参考にし、ファイル名の情報などから自動でエンコードオプションを選択してくれるように改造してみました。
     
    無事に字幕付きかつsimplelife0530さんの推奨オプションでエンコードすることができています。
     
     また、1点こちらで記載の「-ss 4」というオプションが非常に効果大で感謝しています。ありがとうございます。

  • マサノリ より:

     こちらに自分が作成したenc.jsを投稿したいのですが、どうしてか「forbiden」のページになり記載できませんでした。

     申し訳ありません。

  • simplelife0530 より:

    マサノリさん

    字幕が出るようになったのは、良かったですね。
    私が困っているのは、NHKのニュースのモノラル2チャンネルが左右分離で日本語、英語がでるように書いているのですが、
    実行時にエラーになっているようです。

    「-SS 4」の効果を実感してもらえたのは、うれしいです。頭に、ゴミが入るのは気持ちが悪いですよね。

    enc.jsは、どう修正したのか見たいですね。修正部分だけ、テキストでコメント欄に入れてもらえますか?

  • マサノリ より:

    simplelife0530 様

    自分が作成したのはこんな感じです。完全素人で、元の物を参考に、こんな感じだろう、あんな感じだろうみたいに作ってます。
      
    まず、宣言?部分ですが、3つ追加しました。

    //const crfValue = ’18’;
    //const presetValue = ‘veryfast’;
    //const name = process.env.HALF_WIDTH_NAME;

    動画長取得関数はそのまま、何も変えず。
    * @param {string} filePath ファイルパス
    * @return number 動画長を返す (秒)
    */
    const getDuration = filePath => {
    return new Promise((resolve, reject) => {
    execFile(ffprobe, [‘-v’, ‘0’, ‘-show_format’, ‘-of’, ‘json’, filePath], (err, stdout) => {
    if (err) {
    reject(err);

    return;
    }

    try {
    const result = JSON.parse(stdout);
    resolve(parseFloat(result.format.duration));
    } catch (err) {
    reject(err);
    }
    });
    });
    };
    以下が特に変更した部分です。
    // 字幕用
    if (name.indexOf(‘[字]’) != -1) {
    Array.prototype.push.apply(args, [
    ‘-fix_sub_duration’
    ]);
    }
    // input 設定
    Array.prototype.push.apply(args, [‘-ss’, ‘4’, ‘-y’, ‘-i’, input]);
    // ビデオストリーム設定
    Array.prototype.push.apply(args, [‘-map’, ‘0:v’, ‘-c:v’, ‘libx264’]);
    // インターレス解除
    //Array.prototype.push.apply(args, [‘-vf’, ‘yadif’]);
    // オーディオストリーム設定
    //解説放送
    if (name.indexOf(‘[解]’) != -1) {
    Array.prototype.push.apply(args, [
    ‘-map’, ‘0:a’,
    ‘-metadata:s:a:0’, ‘title=main’, ‘-metadata:s:a:0’, ‘language=jpn’,
    ‘-metadata:s:a:1’, ‘title=exp’, ‘-metadata:s:a:1’, ‘language=jpn’
    ]);
    }
    // 二カ国語放送
    else if (name.indexOf(‘[二]’) != -1) {
    if (isDualMono) {
    Array.prototype.push.apply(args, [
    ‘-filter_complex’,
    ‘channelsplit[FL][FR]’,
    ‘-map’, ‘[FL]’,
    ‘-map’, ‘[FR]’,
    ‘-metadata:s:a:0’, ‘title=Japanese’, ‘-metadata:s:a:0’, ‘language=jpn’,
    ‘-metadata:s:a:1’, ‘title=English’, ‘-metadata:s:a:1’, ‘language=eng’
    ]);
    }
    else {
    Array.prototype.push.apply(args, [
    ‘-map’, ‘0:a’,
    ‘-metadata:s:a:0’, ‘title=Japanese’, ‘-metadata:s:a:0’, ‘language=jpn’,
    ‘-metadata:s:a:1’, ‘title=English’, ‘-metadata:s:a:1’, ‘language=eng’
    ]);
    }
    }
    // 音声多重放送
    else if (name.indexOf(‘[多]’) != -1) {
    Array.prototype.push.apply(args, [
    ‘-map’, ‘0:a’,
    ‘-metadata:s:a:0’, ‘title=main’, ‘-metadata:s:a:0’, ‘language=jpn’,
    ‘-metadata:s:a:1’, ‘title=sub’, ‘-metadata:s:a:1’, ‘language=jpn’
    ]);
    }
    // 通常
    else {
    Array.prototype.push.apply(args, [
    ‘-map’, ‘0:a’,
    ‘-metadata:s:a:0’, ‘title=main’, ‘-metadata:s:a:0’, ‘language=jpn’
    ]);
    }
    // オーディオコーデック設定
    Array.prototype.push.apply(args, [‘-c:a’, ‘aac’]);
    // オーディオオプション設定
    Array.prototype.push.apply(args, [‘-strict’, ‘-2’, ‘-ar’, ‘48000’, ‘-ab’, ‘192k’, ‘-ac’, ‘2’]);
    // 字幕ストリーム設定
    if (name.indexOf(‘[字]’) != -1) {
    Array.prototype.push.apply(args, [
    ‘-map’, ‘0:s:0’, ‘-metadata:s:s:0’, ‘title=Japanese’, ‘-metadata:s:s:0’, ‘language=jpn’, ‘-c:s’, ‘mov_text’
    ]);
    }
    else {
    Array.prototype.push.apply(args, [
    ‘-sn’
    ]);
    }
    // 品質設定
    Array.prototype.push.apply(args, [‘-preset’, presetValue, ‘-crf’, crfValue]);
    // ログレベル設定
    Array.prototype.push.apply(args, [‘-loglevel’, ‘error’]);
    //その他オプション設定
    Array.prototype.push.apply(args, [‘-dn’]);
    // 出力ファイル
    Array.prototype.push.apply(args, [‘-f’, ‘mp4’, output]);

  • simplelife0530 より:

    マサノリさん

    ありがとうございます。
    私も2か国語の部分、似たような変更をしましたが、
    うまく動いてくれませんでした。
    再度試してみます。

  • マサノリ より:

    お世話になります。

    以前、投稿させていただいたjsですが、ログレベル設定はコメントアウトしたいと動きませんでした。お気づきかもしれませんが、ご連絡させていただきます。

    // ログレベル設定
    //Array.prototype.push.apply(args, [‘-loglevel’, ‘error’]);

  • simplelife0530 より:

    マサノリさん

    連絡ありがとうございます。まだ、試していません。
    ログレベルはデフォールトよりログ出力を短くするだけですので、他に影響するものではありませんが、動かないというのは不思議ですね。

    NHKニュースのデュアルモノは、いただいたjsでは動かないと思います。NHKの夜7時や夜9時のニュースで確認してみてください。
    jsのチェックは、3月になってしまうと思いますが、必ず確認します。

  • Docker初心者 より:

    はじめまして、いつも参考に読ませていただいています。

    非常に初歩的な質問なのですが、このエンコードのjsはDockerのコンテナ内で、EPGStationに紐付けられた
    ファイルでないと実行できないのでしょうか? このスクリプトのオプションを引用しつつ
    https://www.digital-den.jp/simplelife/archives/6014
    この記事のような一括実行ができればありがたいのですが…

  • simplelife より:

    特別難しいことではないと思います。jsである必要もないと思います。pythonで作ったスクリプトを修正して、オプションを引用する方が簡単にできると思います。

  • コメントを残す

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です

    このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください