Haz videos cortos con código
La imagen solo depende del tiempo: la página expone window.render(t), y al darle un número de segundos, dibuja la escena de ese momento. El script de renderizado usa Chrome sin cabeza para llamarlo fotograma a fotograma y tomar capturas, y ffmpeg combina las capturas con la narración en un mp4. Cambiar la imagen es cambiar el código y volver a renderizar.
Este enfoque se originó en un artículo de xilo en X (2026-09-29, https://x.com/xilo2991/status/2104912748794589515). Esta habilidad añade sincronización de narración, portadas, verificación de fotogramas fijos y un conjunto de reglas para escribir guiones y dibujar escenas.
Primeros pasos
Necesitas Node 20+, uv, ffmpeg y Google Chrome instalado localmente. Las fuentes predeterminadas son Songti, PingFang y SF Mono de macOS; en otros sistemas, cambia las fuentes en brand.json bajo fonts por las que tengas instaladas.
- Copia toda la carpeta
template/de esta habilidad como un nuevo directorio de proyecto. - Ejecuta
npm install(solo instalaplaywright-core) en el nuevo directorio. - Modifica
brand.json:serieses el nombre de la serie en la esquina superior izquierda de la portada,handlees la firma en la portada y los créditos finales,avatares la ruta relativa de la imagen del avatar (puede sernull),colorsyfontsson la paleta de colores y las fuentes. - Ejecuta el ejemplo:
uv run tools/voice.py demo # Narración, música de fondo, línea de tiempo -> episodes/01-demo/build/
node tools/render.mjs demo --stills auto # Un fotograma fijo al final de cada escena
node tools/render.mjs demo # Renderiza el video completo -> episodes/01-demo/out/video.mp4 y cover.png
sh tools/covers.sh demo # Portadas 4:3 y 3:4
sh tools/sheet.sh demo # Portada más el último fotograma de cada escena en una hoja build/sheet.png
En los comandos solo se escribe el slug; el script busca automáticamente episodes/<número-episodio>-<slug>/. La narración envía el texto del guión al servicio de TTS en línea de Microsoft.
Estructura del proyecto
brand.json: Marca. Cámbialo aquí, no en el motor.src/lib.js: Herramientas de dibujo, montadas enwindow.E.src/run.js: Programación de la línea de tiempo. El fotograma 0 es la portada, se detiene 0.3 segundos y luego se desplaza hacia la primera escena; las escenas se colocan horizontalmente y se conectan con desplazamientos; los subtítulos y la barra de progreso también se dibujan aquí.src/stage.html: Página de renderizado.tools/voice.py: Sintetiza la narración línea por línea, usa marcas de tiempo palabra por palabra para determinar los puntos de inicio de las frases, sintetiza música de fondo y sonidos de alerta, y normaliza el volumen a -14 LUFS. Si la síntesis falla, reintenta; después de tres fallos, muestra un error.tools/render.mjs: Cuatro páginas paralelas para capturar fotogramas, ffmpeg codifica a 30 fotogramas por segundo.tools/covers.sh,tools/cover-vertical.mjs: Portada horizontal 4:3 (la portada 16:9 con color de fondo arriba y abajo) y portada vertical 3:4 con diseño independiente.tools/sheet.sh: Extrae la portada y el último fotograma de cada escena del video terminado para formar una hoja de contacto.episodes/<número-episodio>-<slug>/: Un directorio por episodio, que contienescript.json,shots.js, y las carpetas generadasbuild/yout/.
Flujo de un episodio
Hazlo en orden; no pases al siguiente paso sin completar el anterior.
El contenido y los datos los preparas tú; esta habilidad comienza desde la escritura del guión.
- Guión: Crea
episodes/<número-episodio>-<slug>/, escribescript.json, y escribe el número de episodio también encover.no. - Escenas: Escribe
shots.jsen el mismo directorio; el número de escenas debe coincidir con el número de líneas de narración. - Narración:
uv run tools/voice.py <slug>. Vuelve a ejecutarlo si cambias el guión, la voz o el texto de la portada. - Verificación de fotogramas fijos: Ver más abajo "Verificación de fotogramas fijos".
- Video final:
node tools/render.mjs <slug>, luego ejecutacovers.shysheet.sh, y revisa la hoja de contacto. - Entrega: Ver la última sección de este documento.
Al ajustar el número de episodio, cambia tanto el prefijo del nombre del directorio como cover.no, luego vuelve a ejecutar narración y renderizado. Si solo cambias las palabras de la portada, no necesitas volver a hacer la narración: cambia cover en script.json, reemplaza el cover en build/timeline.js con el mismo contenido, luego ejecuta renderizado y covers.sh.
script.json
{
"title": "Este video está dibujado con código",
"cover": { "no": "01", "lines": ["Este video", "está dibujado con código"], "hot": 1, "tag": "Sin software de edición ni modelos de video" },
"voice": "zh-CN-YunxiNeural",
"rate": "+6%",
"lines": [["Cada fotograma que ves, ", "está dibujado con código."], ["La página expone solo una función: ", "dale un tiempo, ", "y dibuja la escena de ese momento."]]
}
- Una línea es una escena, y cada frase dentro de una línea es un ritmo visual. Las frases se pueden escribir como
[texto a pronunciar, texto del subtítulo]: por ejemplo, pronunciar "por ciento setenta y cinco" y mostrar "75%"; también se usa para corregir la pronunciación de términos en inglés. - Portada:
linesmáximo dos líneas,hotes la línea que usa el color principal,tages un gancho con un número concreto o una conclusión,noes el número de episodio de dos dígitos. El título de la portada, el título de la plataforma y la primera línea de narración deben hablar de lo mismo.
Cómo escribir el guión
- Para videos cortos en vertical, 75–90 segundos y 8–9 escenas son adecuados. Con una velocidad de
+6%, aproximadamente 4.8 caracteres por segundo (incluyendo pausas), y la narración debe tener alrededor de 380–430 caracteres (caracteres chinos más palabras en inglés). Si excede, elimina palabras antes de grabar la narración. - Los primeros dos segundos deben mostrar lo más atractivo del episodio. Antes de escribir el guión, pregúntate: ¿cuál es el punto realmente impactante de este episodio? Generalmente es un número contra intuitivo, un resultado concreto, o un problema que la audiencia está enfrentando. En la plantilla, la narración comienza a los 0.5 segundos (
HEADentools/voice.py), y a los 2 segundos solo se pueden pronunciar 8–9 caracteres, así que la primera frase debe ser corta y las primeras palabras deben ser ese punto; la primera escena debe mostrarlo en letras grandes antes de 1.2 segundos, y eltagde la portada también debe decir ese punto. Usa el lenguaje de la audiencia, incluyendo palabras clave del tema ("No pongas la intensidad de razonamiento al máximo", luego "De High a Max, cuesta 2.9 veces más, la puntuación solo sube 0.6"), y luego entra en el contenido principal. No hagas introducciones, no te presentes, no digas "hoy hablaremos de", no uses "¿Qué es X?" sin contexto, ni uses frases de relleno como "el mismo A, el mismo B" sin dar información. Esto es una observación basada en pocos videos y se trata como hipótesis por verificar. - Los siguientes órdenes pueden servir como punto de partida, ajústalos según el contenido.
- Tutoriales: Resultado final → Un paso, una escena → Errores comunes → Cierre en una frase.
- Presentación de producto: Qué problema resuelve → Dos o tres funciones principales, una por escena → Cómo empezar → Cierre en una frase.
- Explicación de conceptos: Conclusión y números → Enfoque anterior o conceptos erróneos → Dónde está el problema → Nuevo enfoque → Mecanismo específico → Datos oficiales o pruebas → Cierre en una frase.
- Hallazgos de investigación: Conclusión y números → Conceptos erróneos comunes → Cómo se hizo el experimento → Resultados → Investigación posterior → Causa → Cierre en una frase.
- El cierre debe primero resumir la conclusión o dar una acción en una frase, luego hacer una pregunta que la audiencia pueda responder con su propia experiencia ("¿Qué nivel usas normalmente?") para generar comentarios. La pregunta debe coincidir con el comentario fijado. Esto también está por verificar, según la cantidad de comentarios. No incluyas una escena separada para limitaciones; las condiciones de los datos deben escribirse en las notas al pie de la escena de datos.
- No digas "en el próximo episodio" o "en el episodio anterior", ya que el orden de publicación puede cambiar. Si necesitas referirte a otro episodio, di "en el episodio que habla de X".
- Cuando el texto original da un margen de error, las diferencias dentro del margen se describen como "casi sin cambio". Los números calculados por ti mismo deben marcarse como evidencia débil en la entrega.
- No incluyas URLs en la narración, ni digas "ve al sitio web X" o "busca X" (las plataformas consideran esto como derivación externa, ver el skill video-publish).
Cómo dibujar las escenas
shots.js es una función inmediata que termina con window.SHOTS = [...]. Cada escena es (c, k) => {}:
ces el contexto 2D del Canvas de 1920×1080.k.at(i, delay, dur)devuelve el progreso de salida suave desde 0 hasta 1 después dedelaysegundos del inicio de la frasei, endursegundos;k.rawes el mismo progreso lineal;k.tes el tiempo transcurrido dentro de la escena,k.des la duración de esta línea de narración,k.segsson los segundos de cada frase relativos al inicio de la escena.- Las herramientas de dibujo están en
window.E:title(título de escena más trazo de marca),txt,measure,rise(desvanecimiento más desplazamiento),pop(aparición),rrect,path(dibuja líneas según el progreso),arrow,chip(bloque de etiqueta),code(bloque de código tipo máquina de escribir),stroke(trazo de marca),mark(avatar),sign(créditos finales),fmt, coloresC, fuentesF. Lee primero las firmas de las funciones ensrc/lib.jsantes de usarlas. Las herramientas que falten escríbelas enshots.jsde tu episodio, no modifiqueslib.jsnirun.jspara una sola escena.
Reglas:
- Cada frase debe corresponder a un nuevo elemento en la escena; cada 2–4 segundos debe haber un cambio. Una escena solo habla de una cosa, y debe usar gráficos: flechas de proceso, puntos de cantidad, barras de comparación, bloques de código, tablas; no pantalla llena de texto.
- Alineación a la izquierda, asimetría. No uses fondo oscuro con texto grande centrado, no hagas que todos los elementos se desvanezcan juntos, ni añadas brillo.
- El color de énfasis principal marca "el número que se debe ver en esta pantalla", no representa bueno o malo; el color de acento se usa como máximo en uno o dos lugares por pantalla.
- Para determinar el tamaño de fuente, considera que el móvil se ve en horizontal:
txteleva los tamaños menores de 56 (por ejemplo, pasa 24 a 34px, pasa 30 a 38px); el valor de entrada para la información debe ser al menos 30, las notas al pie pueden ser 24. El ancho se calcula según el tamaño elevado; las fuentes monoespaciadas tienen aproximadamente 0.6 veces el tamaño de fuente por carácter. - Límites de diseño: la barra de subtítulos está debajo de y 932, una línea tiene aproximadamente 30 caracteres de ancho completo; la frase de gancho de la portada tiene aproximadamente 14 caracteres de ancho completo antes de tocar el avatar; la escena tiene un zoom lento de 1.025x, los elementos pegados al borde deben tener al menos 20px de margen; las notas al pie con tamaño 24 tienen aproximadamente 48 caracteres de ancho completo por línea.
- La fuente Songti Black no tiene "÷", y "×" se dibuja como una cruz grande: usa PingFang para operadores, o escribe "dividido por" o "veces". En títulos grandes de 200px, las letras inglesas con descendentes pueden presionar la línea siguiente.
- Cuando la primera frase es muy corta, el inicio de la escena puede quedar vacío: coloca el esqueleto del gráfico (ejes, encabezados, contornos) en la primera frase, y los datos aparecen con las frases siguientes.
- Los íconos que no son datos reales se etiquetan como "diagrama esquemático". Los números leídos de gráficos originales se anotan con "leído del gráfico original". En la parte inferior de las escenas de datos, escribe la nota al pie de la fuente, solo el nombre ("Fuente: Documento oficial de X"), sin dominio ni URL.
- Cada escena debe usar
E.titlepara mostrar el título; la última escena usaE.signpara los créditos.
Verificación de fotogramas fijos
node tools/render.mjs <slug> --stills auto genera el último fotograma de cada escena; --stills 0,t1,t2,... genera fotogramas en segundos específicos (0 es la portada). Los fotogramas intermedios se toman aproximadamente 0.4 segundos después del inicio de cada línea y aproximadamente 1 segundo después de cada frase, las marcas de tiempo se leen de build/timeline.js del episodio. Cada ejecución de --stills borra el lote anterior, así que revisa por lotes o combina las marcas de tiempo en un solo comando. Si hay muchos fotogramas, úsalos con ffmpeg para hacer una hoja de contacto; la portada y el último fotograma se ven a tamaño original.
Verifica uno por uno:
- Si el texto sale de tarjetas, bloques de código, columnas de tabla, o está bloqueado por la barra de subtítulos o la barra de progreso.
- Si los números alineados a la derecha chocan con las etiquetas de la izquierda; si las etiquetas de los ejes y las etiquetas de las curvas se superponen.
- Si el último fotograma de la escena es el que tiene más información, y si todos los elementos han entrado.
- Si las dos líneas del título de la portada se han reducido demasiado, y si la frase de gancho se sale del ancho.
- Si hay pantallas vacías de dos o tres segundos sin nada nuevo, especialmente al inicio de las escenas.
Si encuentras problemas, corrígelos. No publiques el video final con desbordamientos conocidos. Si solo cambias las imágenes, vuelve a ejecutar el renderizado directamente.
Limitaciones conocidas
- edge-tts usa una interfaz no oficial de Microsoft. La versión 7.2.8 devuelve 403 desde octubre de 2026 (rany2/edge-tts #490);
tools/voice.pyestá fijado en el commit del PR de corrección #491; cuando se lance una nueva versión oficial, vuelve aedge-tts==<nueva versión>. Si vuelve a aparecer 403, revisa los issues del repositorio upstream. - El agente no puede escuchar la narración: la pronunciación y velocidad de términos en inglés deben ser verificadas por una persona. La interrupción de la síntesis la maneja la verificación de duración en
voice.py. - No hay desenfoque de movimiento ni edición sincronizada con el ritmo musical.
Lo que se debe mencionar en la entrega
- La narración no ha sido escuchada por nadie.
- El resultado de la verificación de fotogramas fijos, incluyendo problemas conocidos que aún persisten.
- Las rutas del video final, las tres portadas y la hoja de contacto, así como la duración.