Nodejs - Extraindo Frames de Vídeos
Olá meus Unicórnios! 🦄✨
No Tutorial anterior aprendemos como extrair o áudio de um vídeo.
Agora vamos além, iremos extrair frames de um vídeo, utilizando o FFmpeg.
Executando a Extração Diretamente na Linha de Comando
Inicialmente, instale o FFmpeg em seu Servidor:
O comando que iremos utilizar é o comando abaixo:
ffmpeg -i [VIDEO] -vf fps=1/[TEMPO_ENTRE_FRAMES] -t [TEMPO_DE_PROCESSAMENTO] [PASTA]/%d.jpg
Exemplo:
ffmpeg -i teste1.mp4 -vf fps=1/5 -t 50 C:\Testes\%d.jpg
Quando o comando é executado, o FFmpeg faz a extração e nos mostra os detalhes:
Porem, temos de analisar o comando que iremos realizar:
-fps
Neste campo, iremos definir o tempo entre cada frame.
No caso de "1/5" estamos dizendo:
Extraia uma imagem a cada 5 segundos
-t
Neste campo iremos indicar quando tempo do vídeo será processado.
Ou seja, se configuramos o "-fps" para "1/5" e o "-t" para "20", estamos dizendo:
Extraia uma imagem a cada 5 segundos limitando a 20 segundos do vídeo
Nesta configuração, serão gerado até 4 imagens
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-30s.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 da pasta onde ira ficar o Vídeo e as Imagens:
const InfFilename = uuidv4();
const InfTmpDir = path.join(__dirname, "tmp", InfFilename);
const InfArquivoVideo = path.join(InfTmpDir, "Video.mp4");
Isto ira garantir que todos os arquivos relacionados a um processamento fiquem na mesma pasta.
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));
Agora vamos para a parte legal, executar o "FFmpeg" para extrair os frames e salvar na pasta que criamos anteriormente:
await exec("ffmpeg -i " + InfArquivoVideo + " -vf fps=1/5 -t 50 " + InfTmpDir + "/%d.jpg");
Aqui temos várias abordagens para identificar a quantidade de imagens geradas, mas considero a mais simples utilizar o TEMPO.
Se estamos gerando uma imagem a cada 5 segundos (fps=1/5) e irmos processar até 50 segundos se vídeo (-t 50) então iremos ter até 10 imagens:
= 50 / 5 = 10
Então, vamos fazer um For, e verificar as imagens que foram geradas:
var InfImagens = [];
for( let i = 1; i <= 10; i++ )
{
var InfImagem = i + ".jpg";
if( fs.existsSync(path.join(InfTmpDir, InfImagem)) )
{
InfImagens.push(InfImagem);
}
}
E para identificar se a extração teve sucesso, vamos checar se ao menos uma imagem foi extraída:
if( InfImagem.length > 0 )
{
// Sucesso
}else{
// Falha
}
PRONTO! Temos uma API que ira nos retornar as Imagens:
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-30s.mp4"
}
O retorno será um array "arquivos" com todas as imagens geradas:
{
"arquivos": [
"/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/1.jpg",
"/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/2.jpg",
"/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/3.jpg",
"/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/4.jpg",
"/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/5.jpg",
"/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/6.jpg"
]
}
a
Depois disto, podemos carregar as imagens diretamente:
http://127.0.0.1:8019/tmp/d855ebaf-4957-42bc-bb45-05f98b80f97d/1.jpg
O que fazer com isto?
Utilizando a OpenAI podemos descrever a imagem:
Com estas descrições, podemos fazer perguntas ao ChatGPT:
E não apenas isto, podemos obter um Json, com elementos das descrições:
Por hoje é só, meus unicórnios! 🦄✨
Que a magia do arco-íris continue brilhando em suas vidas! Até mais! 🌈🌟