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, то это не сработает. (т. е. не сработает в 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 muxer работает без -mapпараметров только для файлов, содержащих ровно один поток mp3. Поэтому для извлечения mp3 из многоаудиофайла вам необходимо использовать несколько вызовов -map 0:a:Xи пробовать каждый аудиопоток, пока не найдете правильный.
  • Мультиплексор AC3 используется в AC3, но может также обрабатывать потоки MP3 и MP2, поэтому, если файл содержит как потоки AC3, так и потоки MP3/MP2, он извлечет оба потока (или первый), игнорируя расширение.
  • Возможно, существуют и другие ограничения, но я их пока не нашел.

Обновлять:вот несколько идей, как решить проблему с потоками AC3 и MP2/MP3.

В пакетной службе Windows вы можете использовать ее %~zXдля считывания размера входного файла и if A LSS Bсравнения двух чисел.Для проверки Linuxэтот.

Идея А) Вы можете извлечь все (аудио) потоки из файла как 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, результат будет большим (1 МБ+), но если это был AC3, результат будет очень маленьким (~5 КБ).

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, а проSplFileInfo(который имеетужасныйAPI, как может продемонстрировать приведенный выше скрипт).

Для похожего сценария я дал простому старомуpathinfo()попробуйте, но он учитывает локаль и неожиданно пропустил некоторые файлы, так что для меня это табу.

Связанный контент