project-demo-video
Construye el video demo como código para que pueda re-renderizarse en minutos tras cualquier cambio. Todo lo que aparece en pantalla es real: la aplicación en ejecución, el resultado real de comandos, los archivos fuente reales. Nada es una maqueta.
Esta habilidad está escrita para que un agente de codificación menos capaz pueda seguirla paso a paso. Sigue las fases en orden. Cada fase tiene una línea Listo cuando: no avances hasta que sea verdad. Copia los archivos de templates/ en lugar de volver a escribirlos.
Instalación: copia toda esta carpeta (SKILL.md y templates/) a la carpeta de habilidades del agente, por ejemplo ~/.claude/skills/project-demo-video/, o simplemente apunta al agente hacia SKILL.md.
Lo que produces
| Salida | Notas |
|---|---|
<nombre>-voiced.mp4 | 1920x1080, 30 fps, H.264 + AAC, subtítulos incrustados, con voz |
<nombre>-captions-only.mp4 | misma imagen, pista de audio silenciosa (el usuario puede elegir esto o grabar su propia voz) |
<nombre>.srt | subtítulos como archivo, mismo texto que la narración |
thumbnail.png | 1280x720 |
narration.md, shotlist.md | guión con marcas de tiempo reales; cada escena con su fuente |
frames/ | los fotogramas de QA que realmente revisaste |
repo/ | archivos del proyecto cambiados o nuevos, reflejados en sus rutas de repositorio |
COPY_LIST.md, README-snippet.md, REPORT.md | qué copiar y dónde, una sección "Verlo funcionar" del README, qué está hecho y qué es riesgoso |
0. Reglas que no debes romper
- Fuente de solo lectura. Nunca escribas, hagas commit ni subas cambios desde los repositorios reales del usuario. Trabaja en una copia (
robocopy/cp, excluyendonode_modules,.git, salidas de compilación), luego ejecutanpm ciallí. Mantén los cambios que valga la pena conservar dentro de la carpeta de salida, reflejando las rutas del repositorio, másCOPY_LIST.md. Otra herramienta o persona hará el trabajo de git. - Sé honesto en pantalla. Cualquier cosa simulada (un dispositivo, un conector, un pago, un modelo) está etiquetada como simulada en el video. Nunca impliques hardware, despliegues o usuarios que no existan. Copia la propia tabla "real vs simulado" del proyecto; si el README está mal, dilo en el informe y no repitas la afirmación.
- Sin secretos en pantalla ni en los archivos que entregues: tokens, claves de API, contenido de
.env, claves privadas, correos reales. Los tokens de desarrollo que el proyecto documenta (por ejemploBearer dev-alice) están bien. Cuando muestres código, elige rangos de líneas que excluyan secretos. - No menciones la herramienta de IA que escribió el código, ni su proveedor, en fotogramas, narración, subtítulos o documentos entregados, a menos que el usuario diga lo contrario. Un ID de modelo en tiempo de ejecución que forma parte de la configuración del producto puede aparecer como nombre de modelo. Verifica con
grep -riantes de terminar. - Respeta las reglas del concurso. Lee el límite de duración (generalmente "menos de 3:00"; apunta a 2:50) y la regla de tecnología requerida. El video debe mostrar la tecnología requerida realmente funcionando, no solo mencionarla.
- Nivel gratuito, sin despliegues, sin compras. No despliegues, crees recursos en la nube ni gastes dinero. Si un paso necesita una credencial o una grabación del usuario, pregunta una vez, claramente, y continúa con todo lo demás.
- Pregunta al usuario solo por credenciales, sus propias grabaciones, o una elección que realmente sea suya (por ejemplo qué voz). Decide todo lo demás tú mismo y di lo que decidiste.
1. Prerrequisitos (verifica, no asumas)
node --version # 22+ (24 recomendado; los archivos .mjs deben ser JavaScript simple, sin conversiones de TypeScript)
- En la copia del proyecto:
@playwright/test(con Chromium instalado) yffmpeg-staticdeben existir. Verifica:node -e "import('@playwright/test').then(async m=>{const b=await m.chromium.launch();console.log(b.version());await b.close()})"ynode -e "import('ffmpeg-static').then(m=>console.log(m.default))". Si faltan y el proyecto prohíbe nuevas dependencias, colócalos en una carpeta de herramientas separada y apunta hacia ella. - Voz: Windows PowerShell con
System.Speech(incorporado). En macOS usasay, en Linuxespeak-ng; luego debes adaptartemplates/lib/tts.mjs(solosynthAllytts.ps1son específicos del SO). - No
ffprobeviene con ffmpeg-static: mide duraciones desde las cabeceras WAV (wavSecondsentemplates/lib/tts.mjs) y desde la temporización del propio grabador. - Notas para shell de Windows: prefiere las herramientas Write y Edit para cualquier archivo que contenga barras invertidas, expresiones regulares o
$. La reescritura en línea conpython -c/heredoc de JS corrompe silenciosamente los escapes. Elsleepen primer plano está bloqueado en algunos agentes: espera conuntil grep -q "done" log; do sleep 10; done.
2. Fases
Fase A: Requisitos y mapa de pruebas
Lee la página de reglas, el README, los documentos propios del proyecto y cualquier nota de la historia. Escribe una tabla en tus notas:
| El juez debe estar convencido de que | Donde el video lo prueba | Escena |
|---|---|---|
| la tecnología requerida se llama en el código | llamada real visible: registro de solicitudes, terminal, extracto de código | ... |
| la experiencia funciona | aplicación ejecutándose en un navegador | ... |
| qué es real vs simulado | la propia tabla del proyecto | ... |
Listo cuando cada fila tenga una escena.
Fase B: Espacio de trabajo
- Copia el repositorio a una carpeta temporal, luego instala:
npm ci. - Copia
templates/dentro de la copia comovideo/(mantén la estructura:video/lib,video/pages,video/scenes,video/final.mjs,video/narration.mjs, ...). Agrega apackage.jsonlos scripts:"video:final": "node video/final.mjs". No agregues dependencias. - Agrega
video/out/a.gitignoreen la copia.
Fase C: Ejecución en seco de la aplicación real y caza de errores (haz esto antes de grabar nada)
Inicia la aplicación como lo haría un juez (npm run demo o el comando del README) en su puerto predeterminado documentado, para que cada comando mostrado en pantalla sea literal. Condúcela con un pequeño script de Playwright, toma capturas de pantalla a 1920x1080 después de cada paso de la historia, y míralas. Verifica:
- inicio de sesión, paneles vacíos, banners de error rojos, errores de consola;
- cada estado que la historia necesita realmente se renderiza (la primera ejecución del proyecto de referencia mostró un inicio de sesión fallido, una bandeja de entrada vacía y una vista clave faltante, todo oculto por un borrador antiguo del video);
- texto duplicado o cortado.
Corrige errores reales en la copia, de manera mínima, ejecuta las comprobaciones propias del proyecto (npm run verify, pruebas, typecheck) y registra cada corrección para COPY_LIST.md. Si la aplicación no tiene forma de mostrar el tráfico de red, agrega un pequeño gancho opcional (un registro de solicitudes) en lugar de simular uno.
Listo cuando toda la historia se ejecute limpia dos veces seguidas y las comprobaciones del proyecto pasen.
Fase D: Storyboard y narración
- Elige escenas. Historia de referencia (aproximadamente 2:50):
| Escena | Duración | Muestra |
|---|---|---|
| gancho | 12 s | un cambio se propaga a través de varios compromisos (tarjeta animada) |
| arranque | 6 s | el único comando que lo inicia todo, resultado real |
| sim (la app) | 60 s | 8-10 momentos; registro de solicitudes abierto en 2 momentos |
| terminal(es) de prueba | 17-30 s | cliente oficial o CLI contra el mismo servidor; comprobaciones de autenticación |
| recorrido de código | 16 s | 3-4 extractos reales que prueban la tecnología requerida |
| arquitectura, pila en la nube, real vs simulado | 30 s | diagrama, fuente de la pila, tabla de honestidad, tarjeta de código abierto |
| cierre | 12 s | eslogan, enlaces al repositorio, créditos de construcción, pie de página "X es simulado" |
- Escribe
video/narration.mjs:scenes = [{ id, title, segs: [{ id, text }] }]. Cadaseges una línea hablada y un subtítulo (textse muestra tal cual). Reglas: aproximadamente 2.4 palabras por segundo; total de palabras habladas 330-380; una idea por línea; no más de aproximadamente 12 palabras en cualquier tarjeta en pantalla; mayúscula inicial; sin exageraciones. - Agrega pistas de pronunciación en la tabla
saysolo para el motor de voz (siglas deletreadas, "Alexa+" como "Alexa plus"); nunca cambies el texto del subtítulo. Listo cuando la narración se lea como una historia que un extraño entiende y cada fila del mapa de pruebas sea hablada o mostrada.
Fase E: Adapta las plantillas
Edita solo lo específico del proyecto:
| Archivo | Edición |
|---|---|
video/narration.mjs | tus escenas y líneas |
video/scenes/sim.mjs | los pasos de tu aplicación: qué escribir o hacer clic, selectores, los zooms de cámara (lens(...), zoomWire) |
video/scenes/static.mjs | comandos de terminal (CMD), codeBlocks (archivo + regex de la primera línea + recuento de líneas + resaltar regexes), llamadas a páginas de gancho/cierre |
video/proofs.mjs, video/proof-auth.mjs | los comandos reales que prueban tus afirmaciones; imprimen solo códigos de estado y nombres de error |
video/pages/*.html | texto, colores, mosaicos, cuadros de diagrama, filas de tabla, enlaces |
video/lib/overlay.mjs | selectores SIM_CSS para el diseño de tu aplicación (ver reglas de diseño), la fuente de datos del ticker |
video/final.mjs | la constante LIMIT y el orden de las escenas (el orden proviene de narration.mjs) |
video/thumbnail.mjs, video/docs.mjs | títulos y la tabla SHOT |
No edites lib/recorder.mjs, lib/director.mjs, lib/tts.* a menos que algo esté roto.
Fase F: Captura la salida real de las pruebas
node video/proofs.mjs inicia un servidor nuevo, ejecuta los comandos reales (inspector oficial o CLI de cliente, comprobaciones de autenticación, salida de instalación/arranque) y guarda lo que imprimieron en video/out/proofs.json. Las escenas de terminal reproducen esta salida capturada en una terminal HTML a un tamaño legible. Reglas: muestra el texto real del comando (acorta solo la ruta del paquete); trunca la salida muy larga visiblemente (una línea tenue "... N líneas ...") y pon "Ejecución grabada" en la etiqueta; nunca imprimas tokens; formatea JSON de forma bonita y dilo si lo haces.
Fase G: Audio de narración
Predeterminado (sin cuenta necesaria): node video/final.mjs --rate 2 hace un WAV por línea con la voz incorporada de Windows (Microsoft Zira) y mide cada duración. La tasa 1 es aproximadamente 150 s de voz para el guión de referencia, la tasa 2 aproximadamente 139 s, la tasa 3 es demasiado rápida. Elige la tasa más lenta que mantenga todo el video por debajo del límite.
Voz opcional con sonido humano: ver Apéndice A. Todo lo siguiente funciona igual con cualquiera.
Fase H: Graba y ensambla
node video/final.mjs --rate 2 # todo: tts, pruebas, grabar todas las escenas, ensamblar ambos cortes
node video/final.mjs --rate 2 --reuse-proofs # omitir volver a ejecutar pruebas
node video/final.mjs --scenes sim,close --reuse-tts --reuse-proofs # volver a grabar solo algunas escenas
node video/final.mjs --assemble-only # re-multiplexar (subtítulos, audio) sin volver a grabar
node video/final.mjs --tts-dir video/out/tts-human --name mi_video --single # construir desde otra carpeta de audio
Una ejecución completa tarda de 6 a 10 minutos; ejecútala en segundo plano y consulta el registro. El script falla si el total es 180 s o más. Apunta a 170 s.
Importante: si cambias el texto de la narración o la voz, las duraciones cambian, por lo que cada escena debe volver a grabarse (las marcas provienen de las duraciones). Cambiar solo los elementos visuales de una escena significa --scenes <id> --reuse-tts.
Fase I: QA (nunca lo saltes, nunca confíes en el registro)
- Extrae fotogramas al final de cada línea de narración del MP4 final:
ffmpeg -ss <markEnd-0.3> -i final.mp4 -frames:v 1 frame.png(la línea de tiempo está envideo/out/timeline.json). Obsérvalos. Verifica legibilidad a 1080p, espacio vacío, texto recortado, posición y tamaño de los subtítulos, sincronización (el evento del subtítulo está en pantalla), sin secretos, sin mención del proveedor, etiquetas de honestidad presentes. ffmpeg -i final.mp4 -af volumedetect -f null -: media alrededor de -22 dB, máxima por debajo de 0 dB.- Duración por debajo del límite en ambos cortes; ambos archivos tienen una pista de video y una de audio.
grep -ril "<palabras del proveedor>" video/pages video/narration.mjs <documentos de salida>no devuelve nada excepto un ID de modelo permitido.- Ejecuta las comprobaciones del proyecto nuevamente (
npm run verify; e2e si la interfaz de usuario cambió). - Corrige y vuelve a renderizar hasta que todo pase.
Fase J: Entregables
Genera narration.md y shotlist.md con node video/docs.mjs <dir_salida> (marcas de tiempo reales). Copia los MP4, SRT, miniatura (node video/thumbnail.mjs <png>), los fotogramas de QA y los archivos del repositorio cambiados (todos los archivos que difieren entre el original y tu copia, en sus rutas de repositorio). Escribe COPY_LIST.md (correcciones de errores primero, luego archivos nuevos, mensajes de commit sugeridos, "ejecutar compilación y verificar después de copiar"), README-snippet.md (comando de ejecución, archivo de punto de entrada:línea, archivo de cliente:línea, archivo de habilidad, marcador de posición de enlace de video) y REPORT.md (hecho, qué debe hacer el usuario, riesgos). Di claramente lo que no pudiste verificar (por ejemplo, no pudiste escuchar la voz).
3. Cómo funciona el pipeline (léelo antes de cambiarlo)
Grabador (lib/recorder.mjs). El recordVideo propio de Playwright limita la tasa de bits y desenfoca el texto pequeño, por lo que esto usa screencast de Chromium: los fotogramas JPEG (calidad 92) se sellan cuando llegan, se escriben en disco y se codifican a 30 fps constantes con libx264 crf 17. Las pantallas estáticas producen pocos fotogramas; el último fotograma se mantiene. Las marcas de tiempo provienen de un reloj (performance.timeOrigin + performance.now()), el mismo que usa el director.
Director (lib/director.mjs). Cada línea de narración es un segmento. d.seg(id, actions, {tail}) registra la hora de inicio, ejecuta las acciones en pantalla, luego espera hasta que la línea haya terminado de reproducirse más tail * 0.45 segundos (las colas se recortan globalmente para ajustarse al límite). Las marcas (id, inicio, fin, texto) se guardan en video/out/<escena>.marks.json. Debido a que el audio se coloca más tarde en las marcas grabadas, la imagen y la voz no pueden desviarse. Regla para un momento: haz la acción dentro del segmento y asegúrate de que la acción termine antes de que termine la narración; si la línea es más corta que la acción, divídela en dos líneas (esta es la razón por la que un momento de la historia se convirtió en sim9 y sim10).
Audio (final.mjs). Cada WAV se retrasa hasta su inicio global con adelay, se mezcla con amix=normalize=0, se limita con alimiter, y se rellena con apad=whole_dur=<total> (sin el relleno, -shortest corta el final del video).
Subtítulos. Las pistas son el texto de la narración dividido en finales de oración (decimales como "2.0" están protegidos) y en comas cuando supera los 82 caracteres, con el tiempo dividido en proporción a la longitud. El .srt se entrega. La versión incrustada usa un archivo ASS generado con PlayResX: 1920, PlayResY: 1080 y tamaño de fuente 36. No incrustes un SRT con force_style: libass lo escala contra un lienzo de 288 píxeles de alto y el texto sale aproximadamente cuatro veces demasiado grande.
Superposición (lib/overlay.mjs), inyectada solo en la página de la aplicación grabada (la propia aplicación no se edita en absoluto; el grabador captura el HTML renderizado, no la fuente).
Apéndice A: Voz opcional de ElevenLabs (con sonido humano)
- Obtén una clave API de
https://elevenlabs.io(el nivel gratuito da 10 000 caracteres/mes, suficiente para ~6 videos). - Agrega
ELEVENLABS_API_KEYa un archivo.envo pásalo como variable de entorno. - Ejecuta
npm install elevenlabs-nodeen la copia del proyecto (no en el proyecto original). - En
video/lib/tts.mjs, descomenta el bloque "ElevenLabs" y comenta el bloque "PowerShell". - Ejecuta como de costumbre:
node video/final.mjs --rate 2.
Nota: ElevenLabs es más lento (~2 s por oración) y requiere conexión a internet. El grabador espera a que el TTS termine antes de grabar, por lo que una ejecución completa puede durar el doble.
Apéndice B: Solución de problemas comunes
| Problema | Causa probable | Solución |
|---|---|---|
| El video final está en negro | El grabador no pudo conectar con el screencast de Chromium | Verifica que @playwright/test y Chromium estén instalados; ejecuta npx playwright install chromium |
| Los subtítulos son demasiado grandes | Se usó force_style en SRT en lugar del archivo ASS | Usa el generador ASS incorporado en final.mjs |
| El audio está fuera de sincronía | Las marcas de duración cambiaron sin volver a grabar las escenas | Vuelve a ejecutar con --scenes all --reuse-proofs (no --reuse-tts si la voz también cambió) |
ffmpeg-static no se encuentra | La instalación de npm falló o la ruta es incorrecta | Verifica con `node -e "import('ffmpeg-static').then(m=>console.log(m.default))"; reinstala si es necesario |
| El video excede el límite de tiempo | Demasiadas palabras o la tasa de voz es demasiado lenta | Acorta la narración (apunta a 350 palabras) o aumenta la tasa a 3 |
| Error "No se puede encontrar el módulo" | Falta una dependencia en la copia | Ejecuta npm ci en la carpeta de la copia, no en el repositorio original |
| Los comandos de Windows PowerShell fallan | La política de ejecución bloquea los scripts | Ejecuta Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass antes del comando |
| La imagen de la terminal es demasiado pequeña | El tamaño del viewport no coincide con el diseño de 1920x1080 | Ajusta viewport: { width: 1920, height: 1080 } en la configuración de Playwright |
| La superposición no aparece | El URL de la página no coincide con el patrón de inyección | Verifica el selector SIM_CSS en overlay.mjs contra la URL real de la aplicación |
| El video tiene fotogramas perdidos | El sistema es demasiado lento para mantener 30 fps | Reduce la calidad JPEG a 85 o baja la tasa de fotogramas a 24 (cambia en recorder.mjs) |