gfx-html
Los gráficos son páginas HTML renderizadas frame a frame por Chrome headless y codificadas en una película con canal alfa. El texto completo de la solicitud está en $ARGUMENTS; el primer token suele ser el slug del proyecto. Los nombres de los verbos siguen el contrato de compilación; python engine/ve.py --help resuelve conflictos.
La regla que hace funcionar este carril
Cada gráfico es una función pura de ?t=<segundos>. Sin animaciones CSS, sin líneas de tiempo GSAP, sin requestAnimationFrame, sin transiciones. La página lee t de location.search, calcula su diseño y renderiza un frame estático.
Esto no es una preferencia de estilo. chrome --headless --screenshot se ejecuta "cuando la página parece cargada" - no hay un reloj virtual en esta compilación, no hay Emulation.setVirtualTimePolicy, no hay beginFrame. Una página con animaciones CSS muestreada de esa manera devuelve píxeles diferentes en cada ejecución. Eso destruye el almacenamiento en caché direccionado por contenido (misma clave, píxeles diferentes) y produce un parpadeo visible.
Una función pura de t muestreada por el mismo bucle ingenuo es determinista y cacheadle, y esa es toda la diferencia.
El presupuesto, expresado de antemano
Chrome renderiza a ~430-470 ms por frame en estado estable, con un arranque en frío de 7.9 s. Un gráfico de 5 segundos a 30 fps son 150 frames, aproximadamente 65 segundos en un solo hilo. El motor paraleliza cuatro procesos Chrome y almacena en caché los PNG por frame, por lo que un gráfico sin cambios se vuelve a renderizar gratis - pero un primer renderizado no es instantáneo.
Informa al usuario el número de frames y la estimación antes de comenzar uno largo. Prefiere gráficos cortos: un lower third que se mantiene durante 6 segundos no necesita 6 segundos de movimiento, necesita 0.4 s de entrada, una pausa y 0.4 s de salida.
Paso 1 - Elegir o escribir una plantilla
Las plantillas viven en presets/graphics/*.html. Reutiliza antes de escribir. Una plantilla es un único archivo HTML autocontenido que:
- Lee
t(segundos) y sus propiedades delocation.search. - Calcula todas las posiciones/opacidades a partir de
tcon aritmética simple. Las funciones de easing están bien, son puras. - Renderiza sobre un cuerpo transparente. Nunca pintes un color de fondo.
- Utiliza fuentes de
assets/fonts/mediante@font-facecon una URLfile:///. No confíes en las fuentes del sistema: lo que Chrome resuelva en esta máquina no es lo que asume el renderizado de control de QC. - Es legible a 1920x1080 y dentro del área segura de subtítulos de la plataforma. Verifica
output.platform: un lower third que se sitúa en el tercio inferior está debajo de la interfaz de TikTok.
Los detalles de diseño, las convenciones de easing y el contrato de propiedades están en docs/graphics.md. No los repitas aquí.
Paso 2 - Renderizar frames
python engine/ve.py gfx <slug>
Por frame, el motor invoca Chrome como una lista de argumentos, aproximadamente:
chrome.exe --headless=new --disable-gpu --no-sandbox --hide-scrollbars
--user-data-dir=<win-path>\prof --default-background-color=00000000
--window-size=1920,1080 --screenshot=<win-path>\frame_0007.png
"file:///C:/.../template.html?t=0.233&title=..."
Dos banderas sostienen todo el carril:
--default-background-color=00000000produce el verdadero alfa. Verificado: el píxel de la esquina lee0 0 0 0. Sin él obtienes negro opaco y la superposición cubre el video.- La URL
file:///debe ser una ruta real de Windows. Una ruta$PWDde Git Bash falla silenciosamente - tres archivos HTML diferentes produjeron el mismo md5 de una página de error en modo oscuro en blanco, salida 0, tamaño de archivo plausible. Por eso la verificación de QC (g) compara con el hash de página en blanco conocido.
Los frames se almacenan en caché bajo .cache/html/ con clave sha256(contenido_html + t + params + renderer + version_renderer). Cambiar la plantilla invalida cada frame, lo cual es correcto.
Paso 3 - Codificar con alfa intacto
Mismo comando. ve gfx renderiza los frames y los codifica en un solo paso - no hay un verbo de codificación separado, porque una secuencia de PNG sin película al lado es un artefacto a medio terminar que nada aguas abajo puede usar.
Secuencia de PNG a QuickTime RLE (qtrle, argb). ProRes 4444 y una secuencia de PNG sin procesar también conservan el alfa correctamente. VP9 en WebM descarta silenciosamente el alfa en esta compilación, incluso con -metadata alpha_mode=1 - no lo uses, y no lo aceptes de un activo de usuario sin verificar.
La salida va a .cache/ovl/<xx>/<key>.mov con un archivo lateral <key>.json.
Paso 4 - Anclar la superposición
Escribe la entrada overlays[]: id, kind: "html", template, props, anchor, duration_frames, layer, render.
anchor siempre es {segment_id, offset_frames} - nunca una posición absoluta en la línea de tiempo. Las posiciones en la línea de tiempo se derivan de una suma acumulativa sobre los segmentos habilitados. En el momento en que alguien deshabilita un segmento aguas arriba, cada posición absoluta es incorrecta y cada gráfico se desplaza. Una superposición anclada se mueve automáticamente con su segmento, que es el comportamiento que espera un editor.
layer ordena la composición; los valores más altos están encima. Los subtítulos se renderizan al final y se sitúan sobre los gráficos.
Paso 5 - Verificar antes de declarar terminado
Tres verificaciones, todas económicas:
- Prueba alfa. Lee un píxel rgba sin procesar de un frame de esquina y verifica que
a = 0. QC (d). - Verificación de página en blanco. Verifica que los frames no sean todos de un color sólido. QC (g). Esto detecta una URL
file:///incorrecta, una fuente que no se cargó y un error de JavaScript, todos los cuales de otro modo salen con 0 y un archivo de tamaño plausible. Toma muestras del primer frame, del medio y de los tres cuartos, y falla solo si todos son uniformes - cada plantilla aquí se abre con opacidad 0, por lo que el frame 0 de un gráfico funcional es legítimamente blanco y juzgar solo con él falla un renderizado correcto.
ve gfx ejecuta ambas automáticamente sobre lo que acaba de crear y sale con código 2 si alguna falla.
3. Determinismo. Renderiza un frame dos veces y compara los hashes. Si difieren, algo en la plantilla no es una función pura de t - encuéntralo. Esta verificación existe porque el error que detecta es invisible hasta que un renderizado en caché discrepa de uno nuevo.
Luego muestra al usuario un fotograma fijo en el punto medio. No describas un gráfico en prosa cuando una captura de pantalla está a un comando de distancia.
Modos de fallo
| Síntoma | Causa | Solución |
|---|---|---|
| La superposición cubre el video con negro | Falta --default-background-color, o la plantilla pinta un fondo | Agrega la bandera; haz el cuerpo transparente |
| Cada frame idéntico, tamaño de archivo plausible | Página de error en blanco por una URL file:/// incorrecta | Usa una ruta real de Windows; verifica contra el hash de página en blanco |
| Misma clave, píxeles diferentes entre ejecuciones | La plantilla no es una función pura de t | Elimina toda animación CSS y rAF |
| El texto se renderiza con la tipografía incorrecta | Retroceso a fuente del sistema | @font-face desde assets/fonts/ con una URL file:/// |
| Alfa perdido después de la codificación | VP9/WebM | Usa qtrle argb o ProRes 4444 |
| El gráfico se desplaza después de una edición aguas arriba | Anclaje absoluto en la línea de tiempo | Ancla a {segment_id, offset_frames} |
| El renderizado toma minutos | 430 ms/frame es el mínimo | Acorta el gráfico o acéptalo; el caché hace que la segunda ejecución sea gratuita |