Creación de videos con fframes
fframes es una biblioteca Rust que renderiza videos a partir de código. Un video es una estructura Rust con un método render_frame(frame) -> Svgr: para cada fotograma devuelve un árbol SVG, construido con la macro svgr! (marcado SVG con {expresiones rust}). Las escenas dividen la línea de tiempo, timeline! y los muelles animan valores, un mapa de audio coloca el sonido, y ffmpeg codifica el resultado.
Cada proyecto tiene una línea de comandos (de fframes::cli). Úsala para todo: renderizar un video, abrir la ventana de vista previa en tiempo real, y ver fotogramas, hojas de contacto y valores de volumen como archivos que puedes leer. Lee references/design.md antes de diseñar, references/api.md mientras escribes código, references/audio.md para sonido y references/3d.md para planos 3D (fframes::three_d).
Al hacer coincidir un video proporcionado, primero sigue la guía de análisis de referencias en references/design.md. Sus cortes, ritmo y movimiento tienen prioridad sobre los valores predeterminados de diseño a continuación; las ediciones rápidas y los fotogramas negros intencionales pueden ser parte del resultado solicitado.
Dos reglas antes que nada
Planifica antes de escribir código
No abras la plantilla y empieces a animar. Primero:
- Define el briefing: quién lo ve y dónde (la plataforma decide formato y duración), lo único que el espectador debe recordar, el tono y los recursos existentes (logotipo, fotos del producto, metraje, música, voz en off). Pregunta al usuario lo que falta y es importante. Nunca inventes hechos, números o citas del producto.
- Cuando el video se corta con música o voz, elige el sonido primero. Su tempo, ritmos y frases marcan la sincronización de todo lo demás.
- Planifica primero el diseño de movimiento, sincronízalo con los picos de sonido para crear un flujo único: de dónde aparecerán los elementos, cómo se transformarán. Intenta crear un diseño de movimiento único para cada escena para que se vea único y pulido. El video no debería parecer una presentación de diapositivas.
- Intenta razonar sobre la sensación de la persona que ve el video final y ajusta cada pieza de diseño en consecuencia: el flujo, tempo, ritmos, movimiento y colores.
Sin slop
Slop es un video hecho de opciones predeterminadas: podría tratarse de cualquier producto con solo cambiar el texto, y los espectadores lo reconocen en un segundo. A menos que el usuario lo solicite, no entregues:
- Restos de plantilla: el diseño inicial, colores, fuente o decoraciones, texto de marcador de posición, logotipos falsos, números inventados.
- Espam de ritmo: todo el fotograma acercándose, sacudiéndose o parpadeando en cada ritmo. Sincroniza cosas pequeñas con el ritmo (un trazo, una palabra, una chispa) y cambia todo el fotograma solo en unos pocos cortes.
- Un solo movimiento para todo: cada elemento desvaneciéndose hacia arriba 40 px con la misma suavización y duración.
- Escenas estáticas: una cosa aparece, se mantiene y se desvanece mientras nada más se mueve.
- Copia genérica: "Presentamos", "Desata", "Sin fisuras", "Siguiente nivel", eslóganes de tres palabras ("Rápido. Sencillo. Potente."). Escribe el texto en pantalla como lo diría una persona.
- Texto en general: la cantidad de texto debe ser limitada y precisa.
En su lugar, dale a cada elemento una razón vinculada al tema y elimina lo que no tenga ninguna. Dedica esfuerzo al detalle y al movimiento en lugar de las palabras: movimiento secundario, entradas escalonadas, trazos dibujados, objetos que reaccionan entre sí, elementos que se trasladan entre cortes. Antes de mostrar algo al usuario, mira una tira y pregúntate si el video seguiría funcionando para un producto diferente con solo cambiar el texto. Si es así, cambia la idea, no los colores.
1. Instalación
Rust desde https://rustup.rs, luego las bibliotecas del sistema con las que se construye ffmpeg:
# macOS
brew install pkg-config ffmpeg x264 x265 opus nasm ninja
# Debian / Ubuntu
sudo apt-get install -y yasm nasm ffmpeg libx264-dev libx265-dev libopus-dev libclang-dev clang ninja-build libvpx-dev libasound2-dev
# Arch
sudo pacman -S ninja yasm nasm ffmpeg x264 x265 opus clang
Windows enlaza una compilación compartida precompilada de FFmpeg 9 (establece FFMPEG_DIR en la compilación ffmpeg-n9.0-latest-win64-gpl-shared descomprimida de BtbN/FFmpeg-Builds, agrega su bin a PATH, instala LLVM con winget install LLVM.LLVM y establece LIBCLANG_PATH). Los detalles están en el README de fframes.
2. Crear un proyecto
curl -fsSL https://raw.githubusercontent.com/dmtrKovalenko/fframes/main/scripts/new-video.sh | bash -s -- my-video --yes
# o instala el generador una vez y úsalo directamente:
cargo install --locked cargo-fframes
cargo fframes new my-video --format landscape --fps 30 --yes
Opciones: --template single-scene|multi-scene, --format landscape|portrait|square|uhd,
--fps, --title, --backend, --dir. El proyecto depende de la versión de fframes que coincida con el cargo-fframes instalado; --git usa la rama main del repositorio. Siempre pasa --yes para que no espere entrada.
Plantillas:
single-scene(predeterminada): una escena. Se adapta a todos los formatos; empieza aquí para clips de una sola toma, tarjetas de título y video vertical.multi-scene: dos escenas. Empieza aquí para cualquier cosa con varias escenas; solo aceptalandscapeyuhd.
El contenido de la plantilla es código de marcador de posición que muestra la API. Reemplaza su diseño, colores, fuentes y decoraciones con un diseño hecho para el video que el usuario solicitó.
Usa el backend de GPU Skia (el predeterminado). cargo fframes new elige Skia en Metal (macOS) o Vulkan (Linux, Windows). Renderiza aproximadamente 10 veces más rápido que el backend de CPU y te da la ventana de preview en tiempo real. En macOS y Linux, la primera compilación descarga Skia y ffmpeg precompilados y solo compila las dependencias de Rust (aproximadamente un minuto; otros destinos, o metal y vulkan juntos, compilan Skia desde la fuente durante ~20 minutos). Inícialo de inmediato y escribe el video mientras se ejecuta:
cd my-video && cargo build --release # ejecuta en segundo plano, la primera compilación es la lenta
--backend cpu no necesita Skia pero no tiene ventana de vista previa; elígelo solo cuando no haya GPU.
El proyecto se renderiza tal como se genera:
my-video/
src/lib.rs # el video (desde la plantilla)
src/main.rs # la línea de comandos
media/ # fuentes, imágenes y audio compilados en el binario (una fuente inicial)
README.md # los comandos a continuación
Cada comando a continuación es cargo run --release -- <comando>. Define una función corta de shell (una variable como R="cargo run --release --" no se divide en palabras en zsh, el shell de macOS):
R() { cargo run --release -- "$@"; }
3. El bucle
No escribas pruebas en absoluto al usar esta habilidad. No crees pruebas unitarias, de integración, de instantáneas o de regresión visual, módulos de prueba o infraestructura de pruebas. Valida el video con inspect, fotogramas renderizados, hojas de contacto, comprobaciones de audio y la vista previa nativa.
Previsualiza, no renderices. Si el video usa el backend de renderizado GPU y hay una GPU disponible en esta máquina, ejecuta la vista previa nativa (o muéstrale al usuario el comando) en lugar de renderizar el video después de cada cambio. Cuando los cambios estén listos para revisar:
- Muestra un comando completo en un bloque de código de shell copiable, usando la ruta real del proyecto y el nombre del paquete; no requieras que el usuario haya definido el ayudante
$R. Por ejemplo:cd /ruta/absoluta/a/fframes && cargo run --release -p my-video -- preview 0s. - Ofrece explícitamente ejecutar esa vista previa para el usuario. Si ya han autorizado abrir o actualizar la vista previa, muestra el comando y continúa dentro de esa autorización.
Después de un cambio solicitado, establece la hora de inicio del comando en un segundo específico en o justo antes de la sección modificada, con suficiente anticipación para ver su entrada o transición. Por ejemplo, preview 8.5s permite al usuario revisar un cambio alrededor de 9 segundos sin reproducir el principio. Indica qué sección verán. Renderiza el video completo cuando el usuario solicite la exportación final; no requieras otra confirmación si esa solicitud ya es explícita.
Después de cada cambio:
$R timelinelista las escenas con sus rangos de fotogramas y segundos, y cada pista de audio. Verifica la estructura y el ritmo aquí antes de mirar los píxeles.$R inspectverifica un fotograma cada 0.25 s más el primer y último fotograma de cada escena. Reporta imágenes, fuentes o glifos faltantes, texto cortado por el borde del lienzo, SVG no válido (rectángulos de tamaño cero, radios incorrectos), transformaciones rotas y pánicos, cada uno con su hora y escena. Sale con código 2 en caso de error. Arregla todo lo que reporta. Una advertencia que solo aparece en los primeros fotogramas de una escena suele ser una entrada; verifícala con una tira.$R strip <escena o rango> -n 12escribe una hoja de contacto (strip.png): fotogramas espaciados uniformemente en una imagen etiquetada. Ábrela con tu herramienta de imágenes. Es la forma más rápida de juzgar el diseño, el ritmo y el movimiento.$R strip 2s,5s,9scoloca las horas elegidas una al lado de la otra.$R frame Intro@end,Outro@50%escribe PNG de tamaño completo enframes/para comprobaciones de detalle (tipografía, alineación, contraste) e imprime problemas encontrados en esos fotogramas. Juzga los detalles pequeños (contacto entre elementos, bordes, formas pequeñas) en un recorte ampliado nítido, nunca en un fotograma reducido:$R frame 3s --crop 1100,520,240,120(píxeles de video, ampliados a aproximadamente 960 px de ancho).--idsdelinea y nombra cada elemento que tiene unid, para encontrar qué forma es cuál.$R onion "[email protected]@1s" -n 6combina fotogramas en una imagen (onion.png) para mostrar la trayectoria y el espaciado de un movimiento: suavización, sobreimpulso, escalonamiento.$R preview Introabre una ventana en tiempo real con sonido para que el usuario vea (espacio reproducir/pausa, h/l avanzar un segundo, j/k avanzar un fotograma, q salir). Se bloquea hasta que se cierra, así que inícialo en segundo plano cuando lo ejecutes para el usuario. Muestra el comando y ofrece lanzarlo como se describió anteriormente; después de las ediciones, usa un segundo específico cerca del cambio.
Cuando se necesite una muestra codificada, $R render Intro --draft codifica una escena a la mitad de resolución. $R render escribe el video completo para una exportación final solicitada.
Antes de mostrar un video terminado, ejecuta $R inspect --pops: renderiza cada fotograma pequeño y advierte sobre pops, partes de la imagen que cambian en un fotograma entre fotogramas fijos, como un elemento que aparece, desaparece o salta delante de otro en lugar de moverse allí. Cada advertencia da el comando frame ... --crop ... para mirarlo; las repeticiones de la misma región (contadores, luces parpadeantes) se agrupan en una advertencia.
Reglas:
- Mira los PNG antes de decir que algo se ve bien.
- Prefiere
stripa muchas llamadasframe; agrega--scale 0.5cuando solo necesites composición. - Agrega
--jsonal analizar la salida: el resultado va a stdout, el progreso a stderr. - Mantén
--release: las compilaciones debug renderizan muchas veces más lento.
Direccionamiento de tiempo
120 (fotograma), 3.2s, 500ms, 1:05.5, 50%, start, end; escenas por nombre de estructura, Intro (también coincide con IntroScene, no distingue mayúsculas), #3 (índice de escena), Intro[1] (segunda escena de ese tipo); dentro de una escena [email protected], Intro@12, Intro@50%, Intro@end. Rangos: a..b (extremo exclusivo), a.., ..b, all, o un nombre de escena para toda la escena. Separa varias horas con comas.
Todos los comandos
| comando | úsalo para |
|---|---|
timeline | ver escenas, duraciones, pistas de audio y su configuración de mezcla |
inspect [RANGO] [--every 0.1s | --all-frames] [--fail-on warning] [--pops] | encontrar problemas sin renderizar píxeles; --pops también encuentra elementos que aparecen o desaparecen |
strip [RANGO | HORAS] -n N [--columns 4] [--width 480] [--crop x,y,w,h] | revisar flujo y movimiento en una imagen |
frame HORAS [-o dir] [--svg] [--crop x,y,w,h] [--zoom N] [--ids] | PNG de tamaño completo (y el SVG diseñado) de fotogramas elegidos, recortes nítidos, elementos etiquetados |
onion RANGO -n N | ver la trayectoria y suavización de un movimiento |
svg HORA | leer el SVG final de un fotograma como texto (posiciones, texto, colores) |
preview [HORA] [--paused] [--mute] | ventana en tiempo real con sonido, para el usuario |
render [RANGO] [-o out.mp4] [--draft] | codificar el video o una parte (audio cortado para coincidir) |
audio analyze [RANGO] [--waveform w.png] | volumen (LUFS), pico real, recorte, silencio, por escena |
audio at HORAS | qué sonidos suenan en un momento, dónde en su archivo y qué tan fuerte |
audio render [RANGO] -o a.wav | la mezcla como archivo WAV |
Editor del navegador
fframes también tiene un editor web con una línea de tiempo y desplazamiento, útil cuando una persona quiere ajustar un video de forma interactiva. Ejecuta el video compilado a WebAssembly y necesita Node.js además de wasm-pack; la configuración es la carpeta editor/ del ejemplo hello-world en el repositorio de fframes (https://github.com/dmtrKovalenko/fframes/tree/main/examples/hello-world). Menciónalo cuando el usuario pida un editor GUI. Para ver, usa preview, y para tu propia revisión, usa la CLI.
4. Escribiendo el video
impl Video for MyVideo<'_> {
const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080;
fn duration(&self) -> Duration<'_> { Duration::Auto } // suma de las escenas
fn audio(&self) -> AudioMap<'_> { AudioMap::none() } // ver references/audio.md
fn define_scenes(&self) -> Scenes<'_> { Scenes::from(vec![&self.intro as &dyn Scene, &self.main]) }
fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> {
fframes::svgr!(<svg xmlns="http://www.w3.org/2000/svg" width={Self::WIDTH} height={Self::HEIGHT}>
<rect width={Self::WIDTH} height={Self::HEIGHT} fill={BACKGROUND} />
{ctx.render_scenes(&frame)}
</svg>)
}
}
- Una escena por idea, de 2 a 6 s cada una. Dentro de una escena,
frame.seconds()cuenta desde el inicio de la escena. - Predeterminado a
frame.animate(fframes::timeline!(...))directamente en atributossvgr!para movimiento con valores fijos.svgr!eleva estas animaciones a estáticas en caché. Define suavizaciones en línea en la línea de tiempo, incluyendoEasing::Spring { mass: 1.0, stiffness: 180.0, damping: 20.0 }; las constantes de suavización con nombre son opcionales. Deja el tiempo de finalización fuera de un muelle para que
…
[truncado para traducción]