![ffmpeg:如何自动确定输出扩展名(-c:a copy)](https://linux22.com/image/1555476/ffmpeg%EF%BC%9A%E5%A6%82%E4%BD%95%E8%87%AA%E5%8A%A8%E7%A1%AE%E5%AE%9A%E8%BE%93%E5%87%BA%E6%89%A9%E5%B1%95%E5%90%8D%EF%BC%88-c%EF%BC%9Aa%20copy%EF%BC%89.png)
在 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,而是保存文件信息(具有可怕API,如上面的脚本所示)。
对于相关脚本,我给出了普通的pathinfo()
尝试一下,但是它具有语言环境感知功能,并且意外地错过了一些文件,所以对我来说这是不行的。