在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
(成功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 復用器在沒有
-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()
嘗試一下,但它是區域設定感知的,並且意外地丟失了一些文件,所以對我來說這是一個禁忌。