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

No es ningún secreto que aquí en Transloadit nos encantan los proyectos de código abierto. Hemos lanzado varios proyectos ambiciosos, como Uppy y el protocolo tus, y nuestros fundadores incluso se conocieron al contribuir a un proyecto de código abierto. También usamos muchas herramientas de código abierto internamente, como FFmpeg, que es fundamental para 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 frecuencias 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, ofreciendo una potente
abstracción para cubrir casos de uso comunes. Pero si quieres salirte de los caminos habituales,
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 nuestras funciones predeterminadas.
El objetivo de hoy
En esta publicación, queríamos destacar algunas de las opciones de transcodificación más creativas que ofrece Transloadit mediante nuestro parámetro de FFmpeg y nuestro SDK de Node.js. Ya tenemos demostraciones que muestran cómo FFmpeg puede usarse para eliminar un flujo de video (English), cambiar una frecuencia de muestreo (English) y crear ajustes preestablecidos personalizados de encoding (English). Hoy queremos mostrarte que puedes crear una Assembly para generar imágenes de formas de onda de audio que se desplacen horizontalmente en sincronía con una pista de audio de entrada.
La configuración para lograrlo se escribirá como un documento JSON. La guardamos como un Template para usarla más adelante.
Vamos a crear nuestro Template. Ve a la sección de Template de tu cuenta de Transloadit, haz clic en «Nuevo Template» y guarda en él el siguiente JSON.
Primero, crea una credencial de Template de Amazon S3 y reemplaza
MY_S3_CREDENTIALS a continuación por el nombre con el que la guardaste. La
guía de configuración de S3 explica los permisos necesarios para el
bucket. Usa un bucket privado con el bloqueo de acceso público de S3 activado y no envíes encabezados
ACL ni de concesión. El ajuste bucket-default omite el encabezado ACL generado;
no hace que un bucket sea privado ni anula su política ni los encabezados ACL o de concesión
que se hayan proporcionado.
{
"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, veamos qué está ocurriendo. Nuestro primer Step,
llamado ":original" arriba, sirve para recibir el archivo que enviaremos con el SDK
de Node.js en este caso; nada sorprendente.
Nuestro segundo Step, "waveformed",
es donde generamos la imagen de la forma de onda. Podemos configurar opciones como
background_color y outer_color (el color exterior de la forma de onda).
Mantengamos las cosas sencillas con un fondo blanco y una forma de onda azul. En este
Step, quizá notes que hemos configurado el parámetro
width de la forma de onda generada con un valor mucho mayor de lo habitual.
Lo explicaremos con más detalle a continuación. Sin embargo, lo fundamental es que, al aumentar el
ancho, el efecto de transición por desplazamiento se vuelve más fluido.
Pasemos a nuestro tercer Step,
"merged". Como indica el nombre del
Step, combinamos las imágenes de formas de onda recién
generadas con nuestro archivo de audio de entrada original para generar un nuevo archivo de video
mp4. Usa la convención de nombres "name", "as" dentro
del parámetro use y estarás listo. Luego usamos
Assembly Variables para configurar los valores de los parámetros
width y height con las mismas dimensiones que nuestra
imagen de entrada.
Ahora viene la parte divertida. En "encode_effect", nuestro
Step, las cosas se vuelven un poco inusuales.
Aquí enviamos nuestro archivo de video de la forma de onda ya combinado a
/video/encode, un Robot.
El parámetro de FFmpeg nos permite interactuar directamente con la utilidad de línea de comandos de
FFmpeg. Usaremos la opción "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 del resultado final.
En waveformed, nuestro Step, fijamos
el ancho total en 8000, así que decidimos mostrar solo el 10 % de la imagen completa durante el
desplazamiento. Por eso, introducimos 800 como ancho de destino. De nuevo,
usamos una de las Assembly Variables para que la altura de destino
sea la del archivo de entrada. Como solo nos movemos de izquierda a derecha, establecemos
y en 0.
Para x, nuestra expresión controla la posición horizontal de la ventana
de recorte y hace que el video comience en el borde izquierdo de la imagen. Con
t, calculamos cuánto tardaría la imagen recortada en llegar al borde
opuesto de la imagen completa. 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 del archivo de entrada.
Pero esta no es la única opción que usamos dentro de nuestro parámetro de FFmpeg. También usamos
la opción t. Su única función es establecer la duración del archivo
resultante. Notarás que usamos el mismo valor para esta opción que para la opción
vf anterior.
Finalmente, exportamos nuestro resultado con /s3/store, un Robot, ¡y con eso terminamos!
Primeros pasos
Ahora que tenemos nuestro Template, configuremos la integración con el SDK. Hemos elegido Node.js para esta demostración, pero podríamos adaptarla fácilmente a nuestros otros SDK disponibles. 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 posterior y guarda el programa completo como waveform-video.mjs para que
Node lo cargue como un módulo ES. Ejecútalo con node waveform-video.mjs después de configurar
las credenciales que se indican a continuación.
A continuación, hemos dividido nuestro código en una serie de fragmentos de código. Para que no tengas que copiar cada fragmento uno por uno, al final encontrarás una versión lista para copiar y pegar.
Primero, importamos la biblioteca de Transloadit.
import { Transloadit } from 'transloadit'
Después, configuramos nuestro cliente de Transloadit. Establece TRANSLOADIT_AUTH_KEY y
TRANSLOADIT_SECRET_KEY en el entorno de tu servidor con la Auth Key y el Auth Secret de tu
Workspace. Estos no son credenciales de Template para 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 el archivo de audio que quieras usar en
el directorio de trabajo y declara las variables filePath y
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 ID guardado
del Template. El SDK devuelve una Promise; waitForCompletion espera a que termine el
procesamiento, no solo la subida. El siguiente fragmento debe estar 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 reunido, 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
})
Como archivo de audio, hemos elegido la pista «Joakim karud-rock angel», que hemos usado con frecuencia en muchas de nuestras demostraciones (English). Si quieres usar el mismo archivo, puedes encontrarlo en el repositorio de GitHub que se indica más abajo.
Una vez que nuestro programa se haya ejecutado y haya terminado, podemos iniciar sesión en S3 y ver el resultado final.
Como puedes ver, todo ha salido según lo previsto. Pero aquí es donde las cosas pueden ponerse más interesantes. 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
muy diferentes filtros.
Esperamos que esta publicación te haya mostrado lo versátil que puede ser la API de Transloadit y lo
útil que puede ser el parámetro ffmpeg al crear diversos proyectos creativos
y divertidos.
Si quieres descargar cualquiera de los archivos de este proyecto, ¡visita el siguiente repositorio de GitHub!