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 编码破坏了。我试图修复它。它可能被简化为单个文件:

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 命令(批处理)中,您可以检查ERRORLEVEL0成功或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()尝试一下,但是它具有语言环境感知功能,并且意外地错过了一些文件,所以对我来说这是不行的。

相关内容