Nodejs - Extraindo Áudio de Vídeos
Olá meus Unicórnios! 🦄✨
Em muitos momentos, precisamos extrair o áudio de um vídeo para permitir transcrever este áudio, ou tratá-lo de forma diferenciada.
Isto pode ser feito com o FFmpeg, uma ferramenta excelente para processar áudio/vídeo.
Extraindo Áudio (MP3) de arquivo de Vídeo (MP4)
Inicialmente, instale o FFmpeg em seu Servidor:
Para extrair o Áudio de um Vídeo, iremos utilizar o comando abaixo:
ffmpeg -i [VIDEO] -vn -acodec libmp3lame [AUDIO]
Exemplo:
ffmpeg -i teste1.mp4 -vn -acodec libmp3lame teste1.mp3
Quando o comando é executado, o FFmpeg faz a extração e nos mostra os detalhes:
Porém, simplesmente executar um comando no CMD, qualquer um faz.
Vamos melhorar isto, vamos criar uma API em Nodejs, que receba o link de um vídeo, e retorne o MP3 extraído.
Criando nossa API com NodeJs
A função que iremos criar ira se chamar "executar", ira rodar na porta "8019", e ira esperar um POST de um Json:
const fs = require("fs");
const { v4: uuidv4 } = require("uuid");
const path = require('path');
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
const express = require("express");
const bodyParser = require("body-parser");
const axios = require('axios');
const { pipeline } = require('stream/promises');
var app = express();
app.use(bodyParser.json());
app.post("/executar", async function(req, res){
//
});
app.listen(8019, function(){
console.log("API Iniciada");
});
Observe que já inclui as dependências que iremos utilizar:
- fs: Para manipular arquivos
- uuid: Para gerar uma string aleatória que iremos utilizar no nome dos arquivos
- path: Para criar os caminhos independente do sistema operacional
- promisify: Para converter funções que retornam um callback em funções que retornam promessas (Sendo controladas pelo async/await)
- exec: Para executar o "ffmpeg" que é uma aplicação instalada no servidor
- express: Para criar a API em si
- bodyParser: Para permitir configurar o Body das chamadas (Iremos utilizar para definir que esperamos receber um Json)
- axios: Para as requisições (Iremos utilizar para carregar o arquivo de vídeo)
- pipeline: Para escrevermos o Vídeo retornado em um arquivo físico
O Json que será enviado possui apenas um elemento "url":
{
"url": "https://download.samplelib.com/mp4/sample-5s.mp4"
}
Antes de começarmos a aplicar qualquer ação, precisamos garantir que a Url foi informada e que o arquivo é um arquivo de extensão "mp4":
if(!req.body.url)
{
return res.status(400).json({ Message: "Preencha o Campo 'URL'" });
}
if(!req.body.url.endsWith('.mp4'))
{
return res.status(400).json({ Message: "Preeencha a 'URL' com um arquivo MP4" });
}
Neste momento ainda não podemos validar que o arquivo é realmente um MP4, apenas podemos validar a extensão (Afinal, ainda não temos o arquivo em si apenas a Url dele).
Mas iremos fazer esta validação posteriormente, para garantir que o "ffmpeg" não falhe.
Antes de seguirmos para as partes mais interessantes, iremos criar uma constante com um nome aleatório, utilizando o "uuidv4" e já popular uma constante com o caminho completo do arquivo MP4 e o do arquivo MP3:
const InfFilename = uuidv4();
const InfArquivoVideo = path.join("tmp", InfFilename + ".mp4");
const InfArquivoAudio = path.join("tmp", InfFilename + ".mp3");
Sempre prefiro criar um nome novo, do que utilizar o nome original do arquivo.
Isto evita problemas com caracteres especiais presentes no nome do arquivo, que acaba gerando problemas de compatibilidade.
Observe que iremos utilizar a pasta "tmp", então iremos configurar o "express" para tornar público o conteúdo desta pasta:
app.use('/tmp', express.static(path.join(__dirname, 'tmp')));
Já temos o que precisamos, vamos fazer um Axios para carregar o Vídeo:
const response = await axios({
url: req.body.url,
method: 'GET',
responseType: 'stream'
});
Não podemos deixar de validar que tivemos um Sucesso
if(response.status !== 200)
{
return res.status(400).json({ Message: "Falha em Obter Arquivo da URL Informada: " + response.status });
}
E que o tipo de arquivo realmente é um MP4:
const contentType = response.headers['content-type'];
if(!contentType || !contentType.includes('video/mp4'))
{
return res.status(400).json({ Message: "A URL informada não contém um arquivo MP4" });
}
Pronto! Temos um arquivo retornado com sucesso.
Vamos salvar o conteúdo do arquivo, retornado pelo Axios, no arquivo MP4 que preparamos na constante anterior:
await pipeline(response.data, fs.createWriteStream(InfArquivoVideo));
Neste momento ficou fácil, já podemos acionar o "ffmpeg", indicando o arquivo MP4 que acabamos de baixar, e o arquivo MP3 que queremos criar:
await exec("ffmpeg -i " + InfArquivoVideo + " -vn -acodec libmp3lame " + InfArquivoAudio);
Embora podemos utilizar o try/catch para validar possíveis problemas no comando executado, sou muito a favor de validar se o arquivo MP3 foi realmente carregado:
if( fs.existsSync(InfArquivoAudio) )
{
// Sucesso
}else{
// Falha
}
PRONTO! Temos uma API que ira nos retornar o MP3:
Vamos Testar?
Iremos fazer um POST para a API "http://127.0.0.1:8019/executar" passando a Url do vídeo no Json:
{
"url": "https://download.samplelib.com/mp4/sample-5s.mp4"
}
O retorno será um elemento "arquivo" indicando o caminho do arquivo gerado:
{
"arquivo": "/tmp/83331471-3e57-41cf-917d-a06a3b9d94fa.mp3"
}
Depois disto, podemos utilizar o arquivo MP3 diretamente em:
http://127.0.0.1:8019/tmp/83331471-3e57-41cf-917d-a06a3b9d94fa.mp3
O que fazer com isto?
Este recurso é muito útil quando precisamos tratar as conversas de um vídeo.
Então, primeiro extraímos o áudio, depois enviamos para a OpenAI transcrever:
E com esta transcrição, podemos enviar para a OpenAI e fazer perguntas sobre o contexto da conversa:
E não apenas isto, podemos obter um Json, com elementos da Conversa:
Por hoje é só, meus unicórnios! 🦄✨
Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟