Communitygithub.com

GetOccasion/demo-video-skill

Produce un video narrado y subtitulado de una funcionalidad de una aplicación web, con interacciones de navegador grabadas mediante agent-browser, locución local con Kokoro y subtítulos incrustados con ffmpeg.

¿Qué es demo-video-skill?

Esta habilidad convierte una funcionalidad de una aplicación web en un breve video demostrativo con interacciones reales en un navegador, locución que explica lo que se muestra en pantalla y subtítulos incrustados. Todo se ejecuta localmente: agent-browser graba las interacciones, Kokoro genera la locución, y ffmpeg junto con Pillow ensamblan el video final. El principio clave que lo hace funcionar es que **la narración es el reloj**: primero se genera la locución, se mide cada escena y se sincroniza cada acción del navegador con la palabra que la describe. De esta manera, el montaje congela el fotograma donde sea necesario para que la narración tenga tiempo suficiente, evitando la deriva temporal que ocurre al ajustar una grabación a la narración. El flujo de trabajo incluye entender la funcionalidad y elegir una historia, crear datos de demostración relevantes, escribir un guion, generar la locución, realizar un ensayo en seco de las interacciones, escribir el escenario y grabar, ensamblar el video final y revisarlo antes de entregarlo. Soporta diferentes voces, audiencias (clientes o equipo interno), y permite revisar el guion antes de la grabación. Está diseñado para plataformas como Cursor, Claude Code o Codex, y requiere tener instalados agent-browser, ffmpeg/ffprobe con libx264, Kokoro con su entorno Python y opcionalmente uv y acceso a Hugging Face.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add GetOccasion/demo-video-skill

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Video demostrativo

Convierte una funcionalidad en un video corto: interacciones reales en un navegador real, una locución que explica lo que se ve en pantalla y subtítulos. Todo se ejecuta localmente: agent-browser graba, Kokoro habla, ffmpeg y Pillow ensamblan.

La idea clave que lo hace funcionar: la narración es el reloj. Primero se renderiza la locución, se mide cada escena y se sincroniza cada acción del navegador con la palabra que la describe. Nunca ajustes la narración a una grabación; el ritmo de una grabación varía, el de un WAV no. La grabación no espera a la narración: las acciones se ejecutan una tras otra y el montaje congela el fotograma donde la narración necesite más tiempo.

Herramientas

Los scripts de esta habilidad se encuentran en scripts/ dentro de su directorio base (el que se muestra como "Directorio base para esta habilidad" al cargarla); el escenario de ejemplo se encuentra en references/ allí. Cada comando de shell comienza de nuevo, por lo que una variable exportada en uno no existe en el siguiente: comienza cada comando que las necesite con export SCRIPTS=<directorio base>/scripts DEMO=<directorio demo>; (abajo), como aquí para la verificación previa:

export SCRIPTS=<directorio base>/scripts; bash $SCRIPTS/preflight.sh

Esto verifica que agent-browser y un Chrome para él, ffmpeg/ffprobe con libx264, y el entorno Python y modelo de Kokoro estén presentes: ya sea preinstalados (DEMO_VIDEO_VENV está configurado) o uv (que ejecuta Kokoro y Pillow en Python 3.12 independientemente del Python del sistema) más acceso a Hugging Face. Si sale con código distinto de cero, detente: dile al usuario qué falta y cómo instalarlo, y no comiences con el guion, los datos ni la grabación. No sustituyas otro motor TTS, grabador o video silencioso sin preguntar; el resultado no sería lo que pidieron. Sus advertencias (falta espeak-ng, falta fuente del sistema) no bloquean; menciónalas.

$SCRIPTS/python ejecuta los scripts Python a continuación en el entorno que fija el uv.lock de la habilidad; los escenarios también leen $SCRIPTS. Mantén todos los archivos de trabajo en un solo directorio demo (DEMO, una ruta absoluta) en el área temporal (scratchpad), fuera del repositorio. Si solo puedes escribir dentro del repositorio, o el video debe adjuntarse a una solicitud de extracción desde allí, usa una carpeta que git ignore: tmp/demo-video/<nombre> cuando git check-ignore -q tmp/demo-video tenga éxito; de lo contrario, otra carpeta que confirme que está ignorada, para que nada dentro pueda ser confirmado. No edites .gitignore para esto sin preguntar.

Flujo de trabajo

1. Comprende la funcionalidad y elige la historia

Lee el diff de la rama, el plan o la descripción de la solicitud de extracción (PR) y el texto visible para el usuario de la funcionalidad (archivos i18n, tooltips, texto de ayuda). Los tooltips suelen definir las columnas y opciones mejor que nada: la narración debe coincidir con ellos palabra por palabra sobre lo que significa un número.

Elige un protagonista (una empresa ficticia que realmente usaría la funcionalidad) y una pregunta que la funcionalidad responda por ellos. Una demostración que recorre un escenario concreto desde la pregunta hasta la respuesta es mejor que un recorrido por todos los controles.

Pregunta al usuario solo lo que cambia el video: la audiencia (clientes o el equipo), la duración objetivo, la voz y si quieren leer el guion antes de grabar algo. Valores predeterminados: clientes, ~3 minutos, af_heart. Omite las preguntas que ya hayan respondido o que sean irrelevantes: alguien que pidió todo el proceso completo sin revisiones, o que claramente no está disponible para responder, recibe los valores predeterminados y sin revisión del guion.

2. Crea datos demo que cuenten la historia

Los datos vacíos o aleatorios hacen que una funcionalidad parezca defectuosa. Construye datos cuyos números hagan evidente cada punto en pantalla: casos límite visibles en la primera pantalla, totales que cambien visiblemente al activar una opción.

  • Aísla los datos. Siembra una base de datos separada y ejecuta la aplicación en su propio puerto; nunca reutilices los datos de desarrollo del usuario. Busca primero las convenciones del proyecto (CLAUDE.md, guías sobre worktrees y servidores de desarrollo, directorios de semillas). Si el proyecto tiene un framework de semillas, escribe una semilla demo reutilizable y específica para la industria (por ejemplo, db/seeds/demos/bike_shop.rb) en lugar de un script único; la siguiente demo no costará nada. Pregunta antes de confirmarla.
  • Pon todas las fechas relativas a hoy, para que la demo nunca quede obsoleta.
  • El historial necesita viaje en el tiempo. Las aplicaciones se niegan a crear pedidos o reservas en el pasado; crea cada registro en el momento en que realmente se habría creado (por ejemplo, travel_to de Rails). Vigila el estado que ignora el reloj viajero: valores memoizados en una instancia cargada antes del viaje, y columnas de estado calculadas en la creación. Recarga y actualiza dentro del bloque de viaje.
  • Copia el esquema vacío (por ejemplo, createdb -T) para que resembrar después de una corrección sea copiar y pegar, no una migración.
  • Luego abre la página y verifica cada número que planeas mencionar.

3. Escribe el guion

Escribe DEMO/scenes.json: [{"id": "01_intro", "text": "..."}, ...]. Una escena por idea, de 10 a 15 escenas. Los ids se ordenan según el orden de reproducción.

  • Duración: Kokoro habla a ~150 palabras por minuto, y cada escena añade ~1–3 s de acciones y pausas. Para 3 minutos, escribe unas 450 palabras; 420 palabras dieron 2:40.
  • Di lo que el espectador está viendo, en el orden en que lo ve. Nombra los controles por su etiqueta en pantalla.
  • Escribe para el oído: frases cortas, números deletreados donde suenen raro ("veinticinco por ciento").
  • Sigue las reglas de vocabulario del proyecto (glosarios, palabras reservadas) en la narración como en el código.
  • Nombra lo que está en pantalla, cuando está en pantalla. No nombres un valor de filtro, opción de menú o registro antes de que el espectador pueda verlo ("todo lo que vendió Sam" mientras la lista de personal sigue cerrada). Pon las palabras de apertura del menú antes del nombre, para que el escenario pueda abrir el menú primero y posar el puntero sobre la opción mientras se pronuncia.
  • Comienza con la pregunta de la propia funcionalidad. La primera oración debe ser una que solo esta funcionalidad responda. "¿Cuánto vendimos este mes?" también describe un informe vecino; "¿Qué tienda vendió más kits de inicio?" no. Verifica las funcionalidades hermanas de la aplicación (otros informes, otras páginas de configuración) antes de decidirte.
  • Escribe para la audiencia indicada. Una demo para clientes muestra lo que pueden hacer; una demo interna para marketing o gestión de cuentas también cubre cómo decide las cosas, cómo activarla, sus límites y qué decirle a un cliente que pregunte.

Si el usuario pidió revisar el guion, detente aquí y muéstralo antes de renderizar el audio: primero un breve esquema (una línea por escena: lo que se muestra y la afirmación que hace), más cualquier afirmación de la que no estés seguro, con el texto completo debajo para quien lo quiera. Cambiar una oración después significa volver a renderizar esa escena, reajustar el escenario y regrabar la toma, por lo que este es el momento más barato para cambiar la historia. De lo contrario, continúa; menciona en el informe final que el guion está en DEMO/scenes.json.

4. Renderiza la locución

export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/narrate.py $DEMO

Escribe tts/<id>.wav y tts/<id>.words.json con marcas de tiempo por palabra, e imprime la duración de cada escena. Vuelve a renderizar escenas individuales con --only <id> .... Voces: af_heart (la más natural en inglés US), am_michael, bf_emma (británica). La primera letra de la voz elige el idioma.

5. Ensayo en seco de cada interacción

Antes de grabar, ejecuta cada paso una vez con agent-browser y toma una captura de pantalla. Esto encuentra lo que se romperá en cámara: rutas incorrectas, elementos cubiertos por superposiciones, menús que se envían al cerrarse, un diseño que se desplaza cuando aparece una insignia. Las referencias de instantáneas (@e12) cambian en cada carga de página, por lo que usa selectores CSS estables (ids, dom_ids, name=) en el escenario. Prefiérelos sobre demo.click_role: los ayudantes deslizan el puntero al elemento de un selector antes de actuar, pero agent-browser no da posición para un rol, por lo que un clic en un rol hace que el puntero salte.

Oculta las superposiciones solo para desarrollo (perfiladores, barras de depuración) y elimina los destellos únicos como "Sesión iniciada como…" cargando la página dos veces antes de que comience la toma (demo.prime(url)).

6. Escribe el escenario y graba

Un escenario es un script Python corto en DEMO que usa SCRIPTS/recorder.py. Comienza desde references/example_scenario.py. Sincroniza las acciones con las palabras usando s.at("palabra"). La puntuación es su propio token, por lo que debes indicar "Octubre" y no "Octubre,", y usa nth= cuando una palabra aparezca dos veces.

Muévete solo a través de los ayudantes. Los comandos de puntero crudos de agent-browser teletransportan el puntero y una rueda del ratón salta la página, lo que se ve como un corte en cámara. demo.move, demo.hover y demo.click deslizan el puntero hasta allí y regresan una vez que ha llegado; demo.scroll anima el desplazamiento y regresa una vez que la página está quieta. Así, cada movimiento se graba completo, antes de que la siguiente pausa congele un fotograma.

Deja que el espectador lea cada menú. Una lista que se abre y se cierra en un segundo no muestra nada. Usa los ayudantes, que se detienen en la lista abierta y posan el puntero sobre la opción antes de elegirla:

  • demo.pick(open_trigger, option_selector) para menús HTML personalizados.
  • Pasa scene=s, cue="<palabra>" a cualquiera de los ayudantes para posar el puntero sobre la opción exactamente cuando el narrador la nombre; el menú se abre unos dos segundos antes. Esto necesita dos segundos de narración antes de la palabra de referencia en la misma escena, sin acciones lentas entre medias; el ensamblaje imprime warning: … runs …s late cuando no puede, y la solución está en el guion ("La tercera opción de ese menú divide…" en lugar de "O divídelo…") o en mover la acción lenta a la escena anterior.
  • demo.pick_native(select_selector, label) para un <select> nativo. Una grabación sin interfaz gráfica nunca dibuja el menú emergente de un select nativo, por lo que esto dibuja una lista sustituta a partir de las opciones reales del select, apunta a la opción y luego la selecciona.

Una vista por toma. Una toma cuyo viewport cambie a mitad de la grabación sale distorsionada. Graba un segmento de teléfono como su propia toma (demo.viewport(390, 844)); el ensamblaje lo ajusta a un lienzo. Una toma corta de cierre después vuelve al escritorio.

Graba cada toma: export SCRIPTS=… DEMO=…; python3 -I $DEMO/scenario.py desktop (y phone, outro, …). La grabación no tiene tiempo fijo: s.at() y s.until() no duermen sino que registran un punto de pausa, por lo que la toma dura solo lo que duran sus acciones (un video de 2 minutos se graba en menos de un minuto) y un selector roto o una palabra de referencia que falta en la narración falla en segundos. Esto hace que un ensayo en seco por separado sea innecesario; itera directamente sobre las tomas. Cada toma escribe <toma>.mp4 y <toma>.holds.json; el ensamblaje congela el fotograma en cada punto de pausa hasta que la narración alcanza su palabra de referencia. Si un paso falla a mitad de la toma, corrige el escenario y vuelve a ejecutar toda la toma; los puntos de pausa deben provenir de una ejecución limpia.

Cada acción debe dejar la página estable antes de la siguiente palabra de referencia, ya que ese último fotograma es el que se congela: mantén el time.sleep después de una recarga o un menú que se envía, o un fotograma congelado mostrará una página a medio cargar. Cualquier cosa que funcione con su propio reloj (un toast que se desvanece, un spinner, una cuenta atrás) solo avanza mientras el escenario está actuando, por lo que una pausa lo congela a mitad. Para mantenerlo fuera de cámara, espera hasta que haya terminado antes de la siguiente palabra de referencia, como hace prime con los avisos de inicio de sesión. Para mostrarlo, como un toast "Guardado" que aparece y se desvanece, pon un time.sleep después de la acción que cubra todo el efecto, para que se reproduzca antes de la siguiente pausa. Para una página que se anima continuamente mientras está inactiva, graba esa toma con tiempo fijo usando DEMO_TIMED=1, que espera hasta cada palabra de referencia mientras graba y escribe <toma>.timeline.json en su lugar.

7. Ensamblaje

Cierra el navegador primero (agent-browser close): Chrome y el codificador de ffmpeg juntos pueden superar la memoria de una máquina pequeña o un contenedor.

export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/assemble.py $DEMO --takes desktop phone outro --out <nombre>

Produce DEMO/build/<nombre>.mp4 con subtítulos incrustados, una pista de subtítulos suave y narración, además de <nombre>.srt.

8. Revisión antes de entregar

Puedes ver el video, pero no oírlo. Verifica lo que puedas:

export DEMO=…; ffmpeg -v error -y -i $DEMO/build/<nombre>.mp4 -vf "fps=1/6,scale=480:-1,tile=4x8" -frames:v 1 $DEMO/build/grid.png   # léelo
export DEMO=…; ffmpeg -i $DEMO/build/<nombre>.mp4 -map 0:a -af volumedetect -f null - 2>&1 | grep volume                              # que no esté en silencio

En la cuadrícula, confirma que cada escena muestra lo que describe su narración, los subtítulos nunca cubren el elemento señalado y ningún fotograma captura una página a medio cargar o un error. Toma fotogramas individuales en los tiempos de referencia (-ss <t>) para los momentos importantes.

Luego detén el servidor de desarrollo y entrega el video: en la máquina del usuario, cópialo a algún lugar donde pueda encontrarlo y ábrelo (open); en otro lugar, da su ruta o adjúntalo a la solicitud de extracción si tu herramienta lo permite. Al informar, di lo que no pudiste verificar (audio de oído) y enumera cualquier atajo tomado, como un menú sustituto.

Errores ya solventados

  • agent-browser click falla con "covered by" mientras la página se estabiliza. recorder.ab reintenta; un fallo que sobrevive los reintentos es real, así que corrige el selector.
  • Los menús que se envían al cerrarse, como las listas de verificación que se envían con Escape, tardan unos segundos en recargarse. Espera a que termine antes del siguiente clic, o ese clic caerá sobre un botón movido.
  • Overmind se niega a iniciar con un .overmind.sock obsoleto. Elimina el socket solo después de confirmar que nada responde en él (overmind status).
  • Las rutas largas del scratchpad desbordan los límites de los sockets Unix; no apuntes sockets allí.
  • Kokoro divide textos largos en fragmentos; narrate.py desplaza las marcas de tiempo de cada fragmento, así que sincroniza por palabra, nunca por segundos calculados.

Skills relacionados