No ffmpeg, é possível determinar a extensão de áudio automaticamente ao extrair (copiar) o fluxo de áudio de um vídeo?
ffmpeg -i movie.mkv -vn -c:a copy audioOnly.{?}
o áudio dentro do movie.mkv pode ser de qualquer formato (mpeg3, aac, flac, wav, vorbis, etc.)
Responder1
Há uma diferença entre contêineres e codificação. m4v é um contêiner, assim como WAV, WMA, WMV, AAC, etc. Todos eles suportam múltiplas codificações. Mas existem alguns padrões gerais. ffprobe pode ajudar.
A extração de áudio de arquivos de vídeo usando ffmpeg é abordada detalhadamente aqui: https://gist.github.com/protrolium/e0dbd4bb0f1a396fcb55
Nele, há um exemplo de como você poderia fazer o que procura, em alguns casos, usando ffprobe e 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
Na página vinculada, o texto acima parecia estar corrompido pela codificação HTML. Eu tentei consertar isso. Provavelmente poderia ser simplificado para um único arquivo para:
ffmpeg -i "myfile.m4v" -vn -acodec copy "myfile".`ffprobe "myfile.m4v" 2>&1 |sed -rn 's/.Audio: (...), ./\1/p'`
Mas, se você não estiver usando sed e um shell bash, isso não funcionará. (ou seja, não funciona no Windows). Também não funcionará se a codificação no arquivo de vídeo não for mapeada normalmente para uma extensão de arquivo. No Windows, você provavelmente poderia criar um PowerShell ou VBScript que faria a mesma coisa.
Responder2
Você não pode detectar automaticamente a extensão, mas o FFMPEG é capaz de detectar automaticamente qual muxer usar para um determinado contêiner de saída e alguns muxers (principalmente para áudio e legendas) podem processar apenas fluxos de um tipo específico (codec).
Além disso, o FFMPEG tenta selecionar o fluxo "melhor" (geralmente o mais adequado), se você não o especificar o suficiente. E se você não permitir a recodificação, os únicos fluxos adequados serão aqueles suportados pelo muxer.
Isso significa que se você disser ao FFMPEG, por exemplo, para salvar o arquivo *.AC3
sem recodificá-lo ( -c copy
), mas não especificar qual fluxo processar usando -map
, ele tentará usar o primeiro fluxo adequado; ou lançar erro se tal fluxo não existir. E se você usar -map
o parâmetro para especificar um fluxo inadequado, isso também gerará um erro.
Portanto, você pode usar esses recursos, por exemplo, para extrair apenas o fluxo DTS, independentemente da posição do arquivo:
ffmpeg -i in.mkv -c copy out.dts
Ou se você sabe que seu arquivo contém fluxos DTS, AC3 e AAC, mas não sabe em que ordem:
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
Isso criará todos os arquivos mencionados, mas somente aqueles que corresponderem a um fluxo adequado no arquivo de entrada conterão o fluxo. Então, depois disso, você só precisa excluir os arquivos vazios e usar o que resta.
No comando do Windows (lote), você pode verificar ERRORLEVEL
(que é 0
sucesso ou 1
falha) e manter apenas os arquivos que foram extraídos com sucesso:
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
...
No entanto, observe que para muxers específicos pode haver algumas limitações:
- O muxer de MP3 funciona sem
-map
parâmetros apenas para arquivos contendo exatamente um fluxo de mp3. Portanto, para extrair mp3 de um arquivo multi-áudio, você deve usar várias chamadas-map 0:a:X
e tentar cada fluxo de áudio até encontrar o correto. - O muxer AC3 é usado no AC3, mas também pode processar fluxos de MP3 e MP2, portanto, se o arquivo contiver fluxos AC3 e MP3/MP2, ele extrairá ambos (ou o primeiro) ignorando a extensão.
- outros limites também podem existir, mas ainda não os encontrei.
Atualizar:aqui estão algumas idéias de como resolver o problema com streams AC3 e MP2/MP3.
Dentro do Windows Batch você pode usar %~zX
para ler o tamanho de um arquivo de entrada e if A LSS B
comparar dois números.Para verificação do Linuxesse.
Ideia A) você pode extrair todos os fluxos (de áudio) do arquivo como out-1.ac3, out-2.ac3, etc. e então encontrar o maior (presumindo que AC3 seja maior que MP2 ou MP3 do mesmo comprimento).
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
E o keep_larger
lote será:
if %~z1 LSS %~z2 goto del
del %2
ren %1 %3
goto end
:del
del %1
ren %2 %3
:end
Agora o maior arquivo é salvo como out.ac3
Idéia 2) O programa LAME pode aceitar áudio WAVE e MPEG como entrada (e convertê-los para MP3), mas falhará no AC3. Assim, você pode extrair cerca de 5 minutos do stream e deixar o LAME processá-lo (use param -f
para processamento mais rápido). Se for WAVE ou MPEG, o resultado será grande (1MB+), mas se for AC3, o resultado será muito pequeno (~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
O keep_if_larger
lote será:
if %~z2 LSS %1 del %2
Agora, se o stream selecionado fosse AC3, o LAME não seria capaz de convertê-lo para um MP3 aceitavelmente grande e poderíamos renomear MP2 para AC3. Caso contrário, excluímos os arquivos MP2 e MP3 e tentamos outro stream.
Responder3
Encontrando as mesmas necessidades, criei o seguinte script 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];
}
Este script foi projetado para poder manipular arquivos que não estão no diretório atual, sejam eles referenciados por caminhos completos ou relativos.
Não estou muito feliz porque o código é muito longo para uma tarefa tão simples. Se alguém puder torná-lo mais conciso, seja muito bem-vindo :)
Na verdade, o código mais complexo não é sobre ffmpeg, mas sobreSplFileInfo(que tem umhorrívelAPI, como o script acima pode demonstrar).
Para um script relacionado eu dei o velhopathinfo()
uma tentativa, mas reconhece a localidade e perdeu inesperadamente alguns arquivos, então para mim é proibido.