9

In ffmpeg, is it possible to determine the audio extension automatically when extracting (copying) the audio stream form a video?

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

the audio inside movie.mkv may be any format (mpeg3, aac, flac, wav, vorbis, etc..)

4
  • 2
    Not possible...
    – Gyan
    Mar 9, 2018 at 13:20
  • -ac is the audio channels option. If you try to use that to set stream copy mode you'll get an error: Expected number for ac but found: copy. Instead use -c:a \ -codec:a \ -acodec.
    – llogan
    Mar 9, 2018 at 17:51
  • Thanks, I fixed it. c:a
    – Azevedo
    Mar 9, 2018 at 20:49
  • As a workaround, you could use .mkv for the output container, as it supports aac, opus (seems to work nowadays), etc. Wouldn't really recommend this, though. Jul 3, 2018 at 13:51

3 Answers 3

2

There's a difference between containers and the encoding. m4v is a container, as is WAV, WMA, WMV, AAC, etc. They all support multiple encodings. But, there are some general patterns. ffprobe can help.

Extracting audio from video files using ffmpeg is covered very thoroughly here: https://gist.github.com/protrolium/e0dbd4bb0f1a396fcb55

In that, there is an example of how you could do what you are seeking, in some cases, using ffprobe and 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

In the linked page, the above appeared to be corrupted by html encoding. I've attempted to fix it. It could likely be simplified for a single file to:

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

But, if you aren't using sed and a bash shell, then this won't work. (ie. won't work on windows). It also won't work if the encoding in the video file doesn't map commonly to a file extension. In windows, you could probably come up with a powershell or vbscript that would do the same thing.

4
  • 6
    No need for sed: ffprobe -loglevel error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 input.foo. But, as you mentioned, this is a fragile way to do this as the codec name won't always equate to a usable file extension.
    – llogan
    Mar 9, 2018 at 18:03
  • I like that better, too. Still I don't know of a way to use the output as part of another command without a unix shell of some kind.
    – Geoduck
    Mar 9, 2018 at 18:40
  • 3
    Finally the answer I was searching for. But having to use such code is just insane! Jul 3, 2018 at 13:40
  • I got this working on MacOS via gsed, and using example ffprobe SomeFileHere.mkv 2>&1 | gsed -rn 's/^.+Audio:\s([A-z0-9]+).+/\1/p' which outputs opus in this case.
    – ThomasMH
    Dec 10, 2020 at 2:04
1

Encountering the same needs, I have crafted the following PHP script:

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];
}

This script is designed so that it can handle files that are not in the current directory, whether they are referenced by full or relative paths.

I'm not really happy as the code is too long for such a simple task. If someone can make it more concise, you are very welcome :)

Actually, the most complex code is not about ffmpeg, but about SplFileInfo (which has an horrible API, as the above script may demonstrate).

For a related script I had given the plain old pathinfo() a try, but it is locale-aware and it unexpectly missed some files, so for me it's a no-no.

1

You cannot autodetect extension, but FFMPEG is able to autodetect which muxer to use for given output container and some muxer (mostly for audio and subtitles) can process only streams of specific type (codec).

Also FFMPEG tries to select the "best" (usualy most suitable) stream, if you don't specify it enough. And if you don't allow reencoding, the only suitable streams are those supported by the muxer.

It means if you tell FFMPEG e.g. to save file as *.AC3 without reencoding it (-c copy), but don't specify which stream to process using -map, it will try to use first suitable stream; or throw error if no such stream exists. And if you use -map parameter to specify an unsuitable stream, it will also throw error.

So you can use these features, for example, to extract only DTS stream regardless of on which position in the file is it:

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

Or if you know your file contains DTS, AC3 and AAC streams but don't know in which order:

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

This will create all the mentioned files, but only those that match a suitable stream in the input file will contain the stream. So after this you just need to delete the empty files a use what remains.


In Windows command (batch), you can instead check the ERRORLEVEL (which is either 0 for success or 1 for failure) and keep only the files that were successfuly extracted:

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
...

However note that for specific muxers there may be some limitations:

  • MP3 muxer works without -map parameter only for files containing exactly one mp3 stream. So to extract mp3 from multi-audio file, you must use multiple calls with -map 0:a:X and try each audio stream until you find the correct one.
  • AC3 muxer is used on AC3 but can also process MP3 and MP2 streams so if the file contains both AC3 and MP3/MP2 streams, it will extract both (or first one) ignoring the extension.
  • other limits may also exist but I haven't found them yet.

Update: here are few ideas how to solve the problem with AC3 and MP2/MP3 streams.

Inside Windows Batch you can use %~zX to read size of an input file and if A LSS B to compare two numbers. For Linux check this.

Idea A) you can extract all (audio) streams from the file as out-1.ac3, out-2.ac3, etc. and then find the largest one (presuming AC3 is larger than MP2 or MP3 of same length).

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

And the keep_larger batch will be:

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

Now the largest file is saved as out.ac3

Idea 2) Program LAME can accept WAVE and MPEG audio as input (and convert them to MP3), but will fail on AC3. So you can extract ~5 minutes of the stream and let LAME to process it (use param -f for fastest processing). If it's WAVE or MPEG, the result will be large (1MB+) but if it was AC3, the result will be very small ~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

The keep_if_larger batch will be:

if %~z2 LSS %1 del %2

Now if the selected stream was AC3, LAME was not able to convert it to acceptably big MP3 and we could rename MP2 to AC3. Otherwise we delete MP2 and MP3 file and try another stream.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .