ffmpeg: 出力拡張子を自動的に決定する方法 (-c:a copy)

ffmpeg: 出力拡張子を自動的に決定する方法 (-c:a copy)

ffmpeg では、ビデオからオーディオ ストリームを抽出 (コピー) するときに、オーディオ拡張子を自動的に判別することは可能ですか?

ffmpeg -i movie.mkv -vn -c:a copy audioOnly.{?}

movie.mkv 内のオーディオは任意の形式 (mpeg3、aac、flac、wav、vorbis など) にすることができます。

答え1

コンテナとエンコーディングには違いがあります。m4v はコンテナであり、WAV、WMA、WMV、AAC などもコンテナです。これらはすべて複数のエンコーディングをサポートしています。ただし、一般的なパターンがいくつかあります。ffprobe が役立ちます。

ffmpeg を使用してビデオ ファイルからオーディオを抽出する方法については、ここで詳しく説明されています。 https://gist.github.com/protrolium/e0dbd4bb0f1a396fcb55

その中に、場合によっては ffprobe と sed を使用して、求めているものを実現する方法の例があります。

for file in *mp4 *avi; do ffmpeg -i "$file" -vn -acodec copy "$file".`ffprobe "$file" 2>&1 |sed -rn 's/.Audio: (...), ./\1/p'`; done

リンクされたページでは、上記は HTML エンコードによって破損しているように見えます。修正を試みました。おそらく、1 つのファイルに簡略化できるでしょう。

ffmpeg -i "myfile.m4v" -vn -acodec copy "myfile".`ffprobe "myfile.m4v" 2>&1 |sed -rn 's/.Audio: (...), ./\1/p'`

ただし、sed と bash シェルを使用していない場合、これは機能しません (つまり、Windows では機能しません)。また、ビデオ ファイルのエンコードがファイル拡張子に共通にマップされていない場合も機能しません。Windows では、おそらく同じことを実行する PowerShell または VBScript を思いつくでしょう。

答え2

拡張子を自動検出することはできませんが、FFMPEG は、指定された出力コンテナーに使用する muxer を自動検出することができ、一部の muxer (主にオーディオと字幕用) は、特定のタイプ (コーデック) のストリームのみ処理できます。

また、十分に指定されていない場合、FFMPEG は「最良」(通常は最も適切な) ストリームを選択しようとします。また、再エンコードを許可しない場合は、マルチプレクサーによってサポートされているストリームのみが適切なストリームになります。

*.AC3つまり、FFMPEG に、例えばファイルを再エンコードせずにとして保存するように指示したが( -c copy)、 を使用して処理するストリームを指定しなかった場合-map、最初の適切なストリームを使用しようとするか、そのようなストリームが存在しない場合はエラーをスローします。また、-mapパラメータを使用して不適切なストリームを指定した場合も、エラーがスローされます。

したがって、これらの機能を使用すると、たとえば、ファイル内のどの位置にあるかに関係なく、DTS ストリームのみを抽出できます。

ffmpeg -i in.mkv -c copy out.dts

または、ファイルに DTS、AC3、AAC ストリームが含まれていることはわかっているが、その順序がわからない場合は、次の手順を実行します。

ffmpeg -i in.mkv -c copy -map 0:a:0 out-1.dts
ffmpeg -i in.mkv -c copy -map 0:a:0 out-1.ac3
ffmpeg -i in.mkv -c copy -map 0:a:0 out-1.aac
ffmpeg -i in.mkv -c copy -map 0:a:1 out-2.dts
ffmpeg -i in.mkv -c copy -map 0:a:1 out-2.ac3
ffmpeg -i in.mkv -c copy -map 0:a:1 out-2.aac
ffmpeg -i in.mkv -c copy -map 0:a:2 out-3.dts
ffmpeg -i in.mkv -c copy -map 0:a:2 out-3.ac3
ffmpeg -i in.mkv -c copy -map 0:a:2 out-3.aac

これにより、上記のすべてのファイルが作成されますが、入力ファイル内の適切なストリームに一致するファイルのみにストリームが含まれます。そのため、この後は空のファイルを削除して、残ったファイルを使用するだけです。


Windows コマンド (バッチ) では、代わりにERRORLEVEL(0成功または1失敗のいずれか) をチェックし、正常に抽出されたファイルのみを保持できます。

ffmpeg -i in.mkv -c copy -map 0:a:0 out-1.dts
if [1] == [%ERRORLEVEL%] del out-1.dts
ffmpeg -i in.mkv -c copy -map 0:a:0 out-1.ac3
if [1] == [%ERRORLEVEL%] del out-1.ac3
...

ただし、特定のマルチプレクサーにはいくつかの制限がある可能性があることに注意してください。

  • MP3 muxer は、-map正確に 1 つの mp3 ストリームを含むファイルに対してのみ、パラメータなしで動作します。したがって、複数のオーディオ ファイルから mp3 を抽出するには、複数の呼び出しを使用して-map 0:a:X、正しいものが見つかるまで各オーディオ ストリームを試す必要があります。
  • AC3 マルチプレクサーは AC3 で使用されますが、MP3 および MP2 ストリームも処理できるため、ファイルに AC3 と MP3/MP2 ストリームの両方が含まれている場合は、拡張子を無視して両方 (または最初の 1 つ) を抽出します。
  • 他にも制限が存在する可能性がありますが、まだ見つけていません。

アップデート:AC3 および MP2/MP3 ストリームの問題を解決する方法についていくつかのアイデアを紹介します。

%~zXWindows バッチ内では、入力ファイルのサイズを読み取り、if A LSS B2 つの数値を比較するために使用できます。Linuxの場合これ

アイデア A) ファイルからすべての (オーディオ) ストリームを out-1.ac3、out-2.ac3 などとして抽出し、最大のストリームを見つけます (AC3 が同じ長さの MP2 または MP3 よりも大きいと想定)。

ffmpeg -i in.mkv -c copy -map 0:a:0 out-0.ac3
ffmpeg -i in.mkv -c copy -map 0:a:1 out-1.ac3
call keep_larger.cmd out-0.ac3 out-1.ac3 out.ac3

バッチは次keep_largerのようになります。

if %~z1 LSS %~z2 goto del
del %2
ren %1 %3
goto end
:del
del %1
ren %2 %3
:end

最大のファイルはout.ac3として保存されます

アイデア 2) プログラム LAME は、WAVE および MPEG オーディオを入力として受け入れ (MP3 に変換) できますが、AC3 では失敗します。そのため、ストリームの約 5 分間を抽出し、LAME で処理することができます (-f最速の処理には param を使用します)。WAVE または MPEG の場合、結果は大きくなります (1MB 以上) が、AC3 の場合、結果は約 5kB と非常に小さくなります。

ffmpeg -i in.mkv -c copy -map 0:a:0 -t "5:00" out.mp2
lame -f out.mp2 out.mp3
call keep_if_larger.cmd 500000 out.mp3
if not exist out.mp3 ren out.mp2 out.ac3
if not exist out.ac3 del out.mp2
if not exist out.ac3 del out.mp3

バッチはkeep_if_larger次のようになります:

if %~z2 LSS %1 del %2

選択したストリームが AC3 の場合、LAME はそれを許容できる大きさの MP3 に変換できなかったため、MP2 の名前を AC3 に変更しました。それ以外の場合は、MP2 と MP3 ファイルを削除して、別のストリームを試します。

答え3

同じニーズに直面して、私は次の PHP スクリプトを作成しました。

isset($argv[1]) || exit('You have to specify a file.');


$file = new SplFileInfo($argv[1]);

$file->isFile() || exit('File not found.');


$input = '"' . $file->getPathname() . '"';


// full path to the containing folder
$full_dir = $file->getPathInfo()->getRealPath();

// filename only: without path, without extension
$base_name = $file->getBasename('.' . $file->getExtension());

// deduce file extension from the audio stream
$output_extension = get_output_extension($file->getPathname());

// combine all that stuff
$output = '"' . $full_dir . '/' . $base_name . '.' . $output_extension . '"';


exec('ffmpeg -i ' . $input . ' -vn -acodec copy ' . $output);


function get_output_extension($file)
{
    $file = '"' . trim($file, '"') . '"';

    $stream_info = shell_exec('ffprobe -v quiet -print_format json -show_streams -select_streams a ' . $file);

    $data = json_decode($stream_info);

    if (!isset($data->streams[0]->codec_name)) {
        exit('Audio not found - ' . $file);
    }

    $audio_format = $data->streams[0]->codec_name;

    $output_extensions = [
        'aac' => 'm4a',
        'mp3' => 'mp3',
        'opus' => 'opus',
        'vorbis' => 'ogg',
    ];

    if (!isset($output_extensions[$audio_format])) {
        exit('Audio not supported - ' . $file);
    }

    return $output_extensions[$audio_format];
}

このスクリプトは、フルパスまたは相対パスで参照されているかどうかに関係なく、現在のディレクトリにないファイルを処理できるように設計されています。

単純なタスクなのにコードが長すぎるので、あまり満足していません。もっと簡潔にできる方がいたら、大歓迎です :)

実際、最も複雑なコードはffmpegに関するものではなく、スプラッシュファイル情報(これには最悪(上記のスクリプトが示すように、API を使用します)。

関連するスクリプトでは、私は単純な古いpathinfo()試してみましたが、ロケールを認識し、予期せずいくつかのファイルが欠落したため、私にとってはダメです。

関連情報