Crear videos de forma de onda de audio con FFmpeg y Node.js

No es ningún secreto que en Transloadit nos encantan los proyectos de código abierto. Hemos lanzado varios proyectos ambiciosos, como Uppy y el protocolo tus, e incluso nuestros fundadores se conocieron a raíz de contribuir a un proyecto de código abierto. También usamos muchas herramientas de código abierto internamente, como FFmpeg, que está en el corazón mismo de nuestro encoding de video.
FFmpeg (Fast Forward MPEG) es una utilidad de línea de comandos que permite transcodificar archivos de audio y video. Convertir entre formatos, modificar tasas de muestreo y resoluciones, y mucho más. FFmpeg impulsa nuestros servicios de encoding de video y encoding de audio, cada uno con varios Robots a su disposición.
De forma predeterminada, Transloadit configura y escala FFmpeg internamente, y ofrece una abstracción
potente para cubrir los casos de uso más habituales. Pero si quieres salirte del camino trillado,
puedes hacerlo. Los Robots de estas categorías exponen un
parámetro ffmpeg que te permite interactuar directamente con FFmpeg y crear
funcionalidades más allá de los límites de nuestras funciones predeterminadas.
El objetivo de hoy
En esta publicación queríamos llamar la atención sobre opciones de transcodificación más creativas que ofrece Transloadit, usando nuestro parámetro de FFmpeg y nuestro SDK de Node.js. Ya tenemos demos que muestran cómo se puede usar FFmpeg para eliminar un stream de video (English), cambiar una tasa de muestreo (English) y crear ajustes preestablecidos de encoding personalizados (English). Hoy pensamos en mostrarte que puedes crear una Assembly que genere imágenes de forma de onda de audio que se desplazan horizontalmente al ritmo de la pista de audio de entrada.
La receta para lograrlo se escribirá como un documento JSON. Lo guardamos como un Template para usarlo más adelante.
Vamos a crear nuestro Template. Ve a la sección Template de tu cuenta de Transloadit, haz clic en «Nuevo Template» y guarda allí el siguiente JSON.
Primero crea una credencial de Template de Amazon S3 y reemplaza
MY_S3_CREDENTIALS a continuación por su nombre guardado. La
guía de configuración de S3 cubre los permisos necesarios del bucket.
Este ejemplo mantiene privados los objetos exportados.
{
"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"
}
}
}
Ahora desglosemos lo que ocurre. Nuestro primer Step,
llamado ":original" arriba, sirve para recibir el archivo que enviaremos con el
SDK de Node.js en este caso; sin sorpresas.
En nuestro segundo Step,
"waveformed", generamos nuestra imagen de forma de onda. Podemos configurar
opciones como background_color y outer_color (color exterior de la
forma de onda). Mantengamos las cosas simples y quedémonos con un fondo blanco y una forma de onda
azul. En este Step quizás notes que hemos definido el
width de la forma de onda generada mucho más grande de lo que normalmente
esperarías. Lo explicaremos con más detalle más adelante. Sin embargo, la idea clave es que, a
medida que aumenta el ancho, el efecto de transición del desplazamiento se vuelve más suave.
Pasemos a nuestro tercer Step,
"merged". Como implica el nombre del
Step, fusionamos las imágenes de forma de onda recién
generadas con nuestro archivo de audio original de entrada para generar un nuevo archivo de video
mp4. Usa la convención de nombres "name",
"as" dentro del Step
use y listo. Después usamos
Assembly Variables para que los valores de los parámetros
width y height tengan las mismas dimensiones que
nuestra imagen de entrada.
Ahora, la parte divertida. En nuestro Step
"encode_effect" las cosas se ponen un poco inusuales. Aquí canalizamos nuestro archivo
de video de forma de onda ya fusionado hacia un Robot
/video/encode. Con el parámetro de FFmpeg podemos interactuar
directamente con la utilidad de línea de comandos de FFmpeg; usaremos el flag
"vf" para generar nuestro efecto de video. ¡Veámoslo más de cerca!
"crop=w=800:h=${file.meta.height}:x='(${file.meta.width}-800)*t/${file.meta.duration}':y=0"
Aquí usamos el filtro crop para ajustar el tamaño de nuestro resultado
final. En nuestro Step waveformed
definimos el ancho total en 8000, así que pensamos en mostrar solo el 10 % de la imagen completa
durante el desplazamiento y, por eso, indicamos 800 como ancho objetivo.
De nuevo, usamos una de las Assembly Variables para que la altura
objetivo sea la altura del archivo de entrada. Ahora, como solo nos movemos de izquierda a derecha,
definimos y como 0. En el caso de
x, nuestra expresión rige la posición horizontal de la ventana de
recorte y hace que el video empiece desde el borde izquierdo de la imagen. Con
t, evaluamos cuánto tardaría la imagen recortada en llegar al borde
opuesto de nuestra imagen. Este valor será diferente para cada archivo de audio que enviemos a
nuestra Assembly, por lo que volvemos a usar una
Assembly Variable para obtener siempre el valor de tiempo correcto a
partir de los metadatos de nuestro archivo de entrada.
Pero este no es el único flag que usamos dentro de nuestro parámetro de FFmpeg. Además, usamos el
flag t. Lo único que hace es definir la duración del archivo resultante.
Notarás que usamos el mismo valor para este flag que el que usamos arriba en nuestro flag
vf.
Por último, exportamos nuestro resultado con el Robot /s3/store ¡y con eso ya está todo listo!
Primeros pasos
Ahora que tenemos nuestro Template, configuremos la integración del SDK. Hemos optado por usar Node.js para esta demostración, pero esto se puede trasladar fácilmente a los demás SDK disponibles. Pero antes de empezar, necesitamos instalar el SDK de Node.js de Transloadit. Después de inicializar npm en la carpeta de tu proyecto, ejecuta el siguiente comando.
npm i transloadit@4 --save
Usa Node.js 20 o una versión más reciente y guarda el programa completo como
waveform-video.mjs para que Node lo cargue como módulo ES. Ejecútalo con
node waveform-video.mjs después de definir las credenciales que se indican a continuación.
Más abajo hemos dividido nuestro código en una serie de Steps. Así no tienes que copiar cada Step de forma tediosa; al final habrá una versión lista para copiar y pegar.
Primero importamos la biblioteca de Transloadit.
import { Transloadit } from 'transloadit'
A continuación, configuramos nuestro cliente de Transloadit. Define
TRANSLOADIT_AUTH_KEY y TRANSLOADIT_SECRET_KEY en el entorno de tu servidor usando
la Auth Key y el Auth Secret de tu Workspace. Estas no son credenciales de Template de
almacenamiento. Ejecuta este código en tu propia computadora o en un servidor de confianza, nunca en
un navegador.
const transloadit = new Transloadit({
authKey: process.env.TRANSLOADIT_AUTH_KEY,
authSecret: process.env.TRANSLOADIT_SECRET_KEY,
})
Ahora definimos el archivo de audio que queremos usar en nuestra
Assembly. Coloca dentro del directorio de trabajo cualquier
archivo de audio que quieras usar y declara también una variable filePath y
otra fieldName. Las pasaremos a createAssembly en su opción
files.
const filePath = './joakim_karud-rock_angel.mp3'
const fieldName = 'my_file'
A partir de aquí, creamos nuestra Assembly con el Template
ID guardado. El SDK devuelve una Promise; waitForCompletion espera al procesamiento, no
solo a la subida. El siguiente fragmento va dentro de una función asíncrona, como se muestra en el
programa completo más abajo.
const opts = {
files: { [fieldName]: filePath },
waitForCompletion: true,
params: {
template_id: 'YOUR_TEMPLATE_ID',
},
}
const result = await transloadit.createAssembly(opts)
console.log(result)
Con todo nuestro código ensamblado, podemos ejecutar el programa. Aquí está de nuevo el programa completo:
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 el archivo de audio, hemos optado por usar la pista «Joakim karud-rock angel», que hemos utilizado con frecuencia en muchas de nuestras demos (English). Si quieres usar el mismo archivo, lo encontrarás en el repositorio de GitHub que aparece más abajo.
Una vez que nuestro programa se haya ejecutado y termine, podemos iniciar sesión en S3 y ver nuestro resultado final.
Como puedes ver, todo ha salido según lo planeado. Pero a partir de aquí la cosa se pone más interesante. Como puedes ver, con FFmpeg las opciones de tus Assemblies son prácticamente infinitas. ¿Por qué no pruebas a desplazar la forma de onda verticalmente o a transcodificar el video para que se reproduzca al revés?
O usa el parámetro ffmpeg para aplicar
radicalmente distintos filtros.
Esperamos que esta publicación haya logrado mostrarte lo versátil que puede ser la API de Transloadit
y lo útil que puede resultar el parámetro ffmpeg a la hora de crear todo tipo
de proyectos creativos y divertidos.
Si quieres descargar alguno de los recursos de este proyecto, ¡ve al siguiente repositorio de GitHub!