ffmpeg:如何自動決定輸出副檔名(-c:副本)

ffmpeg:如何自動決定輸出副檔名(-c:副本)

在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 編碼損壞。我已經嘗試修復它。對於單一文件來說,它可能會被簡化為:

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

但是,如果您不使用 sed 和 bash shell,那麼這將不起作用。 (即不適用於 Windows)。如果視訊檔案中的編碼通常不會對應到檔案副檔名,它也將無法運作。在 Windows 中,您可能會想出一個 powershell 或 vbscript 來完成相同的事情。

答案2

您無法自動偵測副檔名,但 FFMPEG 能夠自動偵測給定輸出容器使用哪個複用器,並且某些複用器(主要用於音訊和字幕)只能處理特定類型(編解碼器)的串流。

如果您指定得不夠,FFMPEG 也會嘗試選擇「最佳」(通常是最合適的)串流。如果您不允許重新編碼,則唯一合適的流是複用器支援的流。

這意味著如果您告訴 FFMPEG 例如將文件另存為*.AC3而不重新編碼 ( -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(成功01失敗)並僅保留已成功提取的檔案:

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 復用器在沒有-map參數的情況下僅適用於僅包含一個 mp3 流的檔案。因此,要從多音訊檔案中提取 mp3,您必須使用多個呼叫-map 0:a:X並嘗試每個音訊串流,直到找到正確的音訊串流。
  • AC3 復用器用於 AC3,但也可以處理 MP3 和 MP2 流,因此如果檔案同時包含 AC3 和 MP3/MP2 流,它將提取兩個(或第一個)而忽略副檔名。
  • 其他限制也可能存在,但我還沒找到。

更新:以下是如何解決 AC3 和 MP2/MP3 流問題的一些想法。

在 Windows Batch 中,您可以用來%~zX讀取輸入檔案的大小並if A LSS B比較兩個數字。對於 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實現最快的處理)。如果是 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,而是Spl文件訊息(其中有一個可怕API,如上面的腳本所示)。

對於相關的腳本,我給了一個簡單的舊腳本pathinfo()嘗試一下,但它是區域設定感知的,並且意外地丟失了一些文件,所以對我來說這是一個禁忌。

相關內容