Criar vídeos de forma de onda de áudio com FFmpeg & Node.js

Não é segredo que adoramos projetos de código aberto aqui na Transloadit. Lançamos vários projetos ambiciosos, como Uppy e tus, e nossos fundadores até se conheceram por contribuírem para um projeto de código aberto. Também usamos internamente muitas ferramentas de código aberto, como o FFmpeg, que está no centro da nossa codificação de vídeo.
O FFmpeg (Fast Forward MPEG) é um utilitário de linha de comando que permite transcodificar arquivos de áudio e vídeo, convertendo formatos, alterando taxas de amostragem e resoluções e muito mais. O FFmpeg é a base dos nossos serviços de codificação de vídeo e codificação de áudio, cada um com vários Robots à disposição.
Por padrão, a Transloadit configura e dimensiona o FFmpeg internamente, oferecendo uma abstração
poderosa para atender a casos de uso comuns. Mas, se você quiser explorar outros caminhos, pode.
Os Robots dessas categorias expõem um parâmetro
ffmpeg que permite interagir diretamente com o FFmpeg e criar funcionalidades
além dos limites dos nossos recursos padrão.
Objetivo de hoje
Neste post, queremos destacar algumas opções mais criativas de transcodificação que a Transloadit oferece usando nosso parâmetro FFmpeg e nosso SDK Node.js. Já temos demonstrações que mostram como usar o FFmpeg para remover um fluxo de vídeo (English), alterar uma taxa de amostragem (English) e criar predefinições de codificação personalizadas (English). Hoje, pensamos em mostrar como você pode criar uma Assembly para gerar imagens de forma de onda de áudio que se deslocam horizontalmente em sincronia com uma faixa de áudio fornecida como entrada.
A receita para isso será escrita como um documento JSON. Vamos salvá-la como um Template para usar depois.
Vamos criar nosso Template. Acesse a seção de Template da sua conta na Transloadit, clique em “Novo Template” e salve o JSON a seguir nele.
Primeiro, crie uma credencial de Template do Amazon S3 e substitua
MY_S3_CREDENTIALS abaixo pelo nome com que ela foi salva.
O guia de configuração do S3 (English) explica as permissões necessárias para o
bucket. Use um bucket privado com o S3 Block Public Access ativado e não forneça cabeçalhos de ACL
ou de concessão de acesso. A configuração bucket-default omite o cabeçalho de ACL
gerado; ela não torna um bucket privado nem se sobrepõe à política dele ou a quaisquer cabeçalhos
de ACL ou de concessão de acesso fornecidos.
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"waveformed": {
"robot": "/audio/waveform",
"use": ":original",
"result": true,
"width": 8000,
"height": 600,
"background_color": "ffffffff",
"outer_color": "1974c7aa",
"center_color": "1974c7aa",
"format": "image"
},
"merged": {
"robot": "/video/merge",
"use": {
"steps": [
{
"name": "waveformed",
"as": "image"
},
{
"name": ":original",
"as": "audio"
}
]
},
"result": true,
"ffmpeg_stack": "v7",
"preset": "ipad-high",
"width": "${file.meta.width}",
"height": "${file.meta.height}"
},
"encode_effect": {
"robot": "/video/encode",
"use": "merged",
"ffmpeg_stack": "v7",
"preset": "empty",
"ffmpeg": {
"vf": "crop=w=800:h=${file.meta.height}:x='(${file.meta.width}-800)*t/${file.meta.duration}':y=0",
"t": "${file.meta.duration}"
}
},
"exported": {
"use": "encode_effect",
"robot": "/s3/store",
"credentials": "MY_S3_CREDENTIALS",
"acl": "bucket-default"
}
}
}
Agora, vamos detalhar o que acontece. Nosso primeiro Step,
chamado ":original" acima, serve para receber o arquivo que enviaremos, neste caso,
com o SDK Node.js; sem surpresas.
Nosso segundo Step, "waveformed",
é onde geramos nossa imagem de forma de onda. Podemos definir opções como
background_color e outer_color (cor externa da forma de onda).
Vamos simplificar e usar um fundo branco com uma forma de onda azul. Neste
Step, você pode perceber que definimos o valor de
width da forma de onda gerada como muito maior do que normalmente se
esperaria. Explicaremos isso com mais detalhes abaixo. O ponto principal, porém, é que, à medida
que a largura aumenta, o efeito de transição por deslocamento fica mais suave.
Vamos ao terceiro Step, "merged".
Como o nome do Step indica, combinamos as imagens de
forma de onda recém-geradas com nosso arquivo de áudio original para gerar um novo arquivo de
vídeo mp4. Use a convenção de nomes "name", "as"
no parâmetro use e tudo estará pronto. Em seguida, usamos
Assembly Variables para definir os valores dos parâmetros
width e height com as mesmas dimensões da imagem de
entrada.
Agora vem a parte divertida. Com "encode_effect", nosso
Step, as coisas ficam um pouco diferentes.
Aqui, encaminhamos nosso arquivo de vídeo de forma de onda já combinado para o
Robot /video/encode.
Com o parâmetro FFmpeg, podemos interagir diretamente com o utilitário de linha de comando FFmpeg.
Usaremos a opção "vf" para gerar nosso efeito de vídeo.
Vamos examinar isso mais de perto!
"crop=w=800:h=${file.meta.height}:x='(${file.meta.width}-800)*t/${file.meta.duration}':y=0"
Usamos o filtro crop aqui para ajustar o tamanho do resultado final.
No nosso waveformed Step,
definimos a largura total como 8000 e decidimos mostrar apenas 10% da imagem inteira durante o
deslocamento. Por isso, usamos 800 como largura desejada. Mais uma vez,
usamos uma das Assembly Variables para definir a altura desejada
como a altura do arquivo de entrada. Como o movimento ocorre apenas da esquerda para a direita,
definimos y como 0.
Para x, nossa expressão controla a posição horizontal da janela de
recorte e faz o vídeo começar na borda esquerda da imagem. Usando t,
calculamos quanto tempo a imagem recortada levaria para alcançar a borda oposta da imagem.
Esse valor será diferente para cada arquivo de áudio que enviarmos à nossa
Assembly. Por isso, usamos novamente uma
Assembly Variable para sempre obter o valor correto de tempo a
partir dos metadados do arquivo de entrada.
Mas essa não é a única opção que usamos no parâmetro FFmpeg. Também usamos a opção
t. Ela apenas define a duração do arquivo resultante. Você perceberá
que usamos o mesmo valor para essa opção e para a opção vf acima.
Por fim, exportamos nosso resultado usando o Robot /s3/store e terminamos!
Primeiros passos
Agora que temos nosso Template, vamos configurar a integração com o SDK. Optamos por usar Node.js nesta demonstração, mas ela poderia ser facilmente adaptada aos nossos outros SDKs disponíveis. Antes de começar, porém, precisamos instalar o SDK Node.js da Transloadit. Depois de inicializar o npm na pasta do projeto, execute o comando a seguir.
npm i transloadit@4 --save
Use Node.js 20 ou mais recente e salve o programa completo como waveform-video.mjs
para que o Node o carregue como um módulo ES. Execute-o com node waveform-video.mjs depois
de definir as credenciais abaixo.
Abaixo, dividimos nosso código em uma série de fragmentos de código. Para que você não precise copiar cada fragmento individualmente, haverá uma versão pronta para copiar e colar no final.
Primeiro, importamos a biblioteca da Transloadit.
import { Transloadit } from 'transloadit'
Em seguida, configuramos nosso cliente da Transloadit. Defina TRANSLOADIT_AUTH_KEY e
TRANSLOADIT_SECRET_KEY no ambiente do servidor usando a Auth Key e o Auth Secret do seu
Workspace. Essas não são credenciais de Template para armazenamento. Execute este código no seu
próprio computador ou em um servidor confiável, nunca em um navegador.
const transloadit = new Transloadit({
authKey: process.env.TRANSLOADIT_AUTH_KEY,
authSecret: process.env.TRANSLOADIT_SECRET_KEY,
})
Agora, definimos o arquivo de áudio que queremos usar na nossa
Assembly. Coloque o arquivo de áudio que quiser usar no
diretório de trabalho e declare as variáveis filePath e
fieldName. Vamos passá-las para createAssembly na opção
files.
const filePath = './joakim_karud-rock_angel.mp3'
const fieldName = 'my_file'
A partir daqui, criamos nossa Assembly com o Template ID
salvo. O SDK retorna uma Promise; waitForCompletion aguarda o processamento, não apenas
o upload. O fragmento a seguir deve ficar dentro de uma função assíncrona, como mostrado no programa
completo abaixo.
const opts = {
files: { [fieldName]: filePath },
waitForCompletion: true,
params: {
template_id: 'YOUR_TEMPLATE_ID',
},
}
const result = await transloadit.createAssembly(opts)
console.log(result)
Com todo o código reunido, podemos executar nosso programa. Aqui está o programa completo novamente:
import { Transloadit } from 'transloadit'
async function main() {
const transloadit = new Transloadit({
authKey: process.env.TRANSLOADIT_AUTH_KEY,
authSecret: process.env.TRANSLOADIT_SECRET_KEY,
})
const filePath = './joakim_karud-rock_angel.mp3'
const fieldName = 'my_file'
const result = await transloadit.createAssembly({
files: { [fieldName]: filePath },
waitForCompletion: true,
params: { template_id: 'YOUR_TEMPLATE_ID' },
})
console.log(result)
}
main().catch(() => {
console.error('Waveform video generation failed.')
process.exitCode = 1
})
Para o arquivo de áudio, optamos pela faixa “Joakim karud-rock angel”, que usamos com frequência em muitas das nossas demonstrações (English). Se você quiser usar o mesmo arquivo, ele está no repositório do GitHub abaixo.
Depois que o programa terminar de executar, podemos acessar o S3 e ver o resultado final.
Como você pode ver, tudo saiu conforme o planejado. Mas é aqui que as coisas podem ficar mais interessantes. Com o FFmpeg, as opções para suas Assemblies são praticamente infinitas. Que tal experimentar deslocar a forma de onda verticalmente ou transcodificar o vídeo para reproduzi-lo de trás para frente?
Ou use o parâmetro ffmpeg para aplicar uma seleção
incrivelmente variada de
filtros.
Esperamos que este post tenha mostrado como a API da Transloadit pode ser versátil e como o
parâmetro ffmpeg pode ser útil na criação de vários projetos criativos e
divertidos.
Se quiser baixar qualquer um dos arquivos deste projeto, acesse o repositório do GitHub a seguir!