Producción de Videos Promocionales (cadena de herramientas local)
Agente-agnóstico: escrito para cualquier agente de IA de codificación que pueda leer archivos y ejecutar comandos de shell — instalado nativamente para Claude Code, GitHub Copilot CLI / Microsoft Scout y Google Antigravity mediante install.sh; utilizable desde Cursor, Codex, Windsurf y otros como un runbook en markdown plano.
La cadena de herramientas fue demostrada por primera vez por Idan Shimon (Microsoft, github.com/idanshimon) — su video explicativo de 58 segundos, producido por un agente de IA usando esta pila localmente a $0, es la base de la que surgió esta skill.
No asumas una ubicación de instalación: establece PROMO_VIDEO_SKILL_DIR en el directorio que contiene este SKILL.md. Su directorio scripts/ contiene el script de TTS, su asistente de pronunciación de acrónimos y las dependencias fijas de TTS; assets/ tiene el esqueleto de la composición, las plantillas de componentes espaciales y el esquema del registro de decisiones; references/ tiene la configuración inicial de la cadena de herramientas única por máquina (install.md).
Principio fundamental: el guion se bloquea primero; cada número posterior se DERIVA de la duración medida del audio de la narración, nunca se adivina.
Promocional, no explicativo: esta skill vende el POR QUÉ — audiencia, dolor, transformación, prueba, una CTA. El mecanismo ('cómo funciona') recibe como máximo un golpe, y solo cuando el usuario lo solicita; las solicitudes para enseñar cómo funciona algo pertenecen a la skill complementaria explainer-video.
Perfiles de herramientas
| Herramienta | Ubicación | Versión probada | Licencia | Rol | Invocación |
|---|---|---|---|---|---|
| OpenMontage | ~/OpenMontage | checkout de julio 2026 | AGPLv3 (solo herramienta; nada se distribuye en el video) | Gobernanza del pipeline: orden de etapas, registro de decisiones, puertas, disciplina de revisión posterior al renderizado | Lee su AGENT_GUIDE.md + pipeline_defs/animation.yaml; tú (el agente) eres el orquestador — no se ejecuta ningún daemon |
| Kokoro | PyPI | Kokoro-82M (hexgrad), kokoro==0.9.4 | Código Apache-2.0; pesos del modelo Apache-2.0 | TTS de narración, WAV mono a 24 kHz por escena | Entorno virtual Python 3.12; KPipeline(lang_code="a"), voz af_heart, parámetro speed |
| HyperFrames | ~/hyperframes | 0.7.57, compilado desde fuente | Apache 2.0 | Composición HTML/CSS/GSAP → MP4 determinista mediante Chrome headless | node ~/hyperframes/packages/cli/dist/cli.js <cmd> |
| FFmpeg | Fuente ~/FFmpeg; binario en un directorio de compilación | git master (era N-125608) | Compilación GPL (libx264 habilitado) | Síntesis de música (aevalsrc), codificación, QA con ffprobe, sonoridad, empaquetado para plataformas | Coloca el directorio de compilación en PATH antes de los comandos de hyperframes |
| bun | npm-global | 1.3.14 | — | Compila el monorepo de hyperframes | npm i -g bun (brew cask falló silenciosamente una vez) |
| Node.js | nvm | 24.x (≥22 requerido) | — | Ejecuta el CLI de hyperframes | — |
| Python | gestionado con uv | 3.12 | — | Inferencia de Kokoro | SIEMPRE un entorno virtual (uv venv --python 3.12 .venv); limpio con black + pylint |
| espeak-ng | Homebrew o apt | cualquiera | GPL | Respaldo G2P de Kokoro (requerido) | macOS: brew install espeak-ng; Debian/Ubuntu: sudo apt install espeak-ng |
| gsap | archivo vendido | 3.x gsap.min.js | Licencia estándar GSAP | Animación de línea de tiempo dentro de la composición | Copia a video/assets/ — NUNCA un <script src> de CDN (determinismo offline) |
Verificación del entorno (una vez por máquina)
export FFMPEG_BUILD_DIR="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}"
export HYPERFRAMES_DIR="${HYPERFRAMES_DIR:-$HOME/hyperframes}"
export PATH="$FFMPEG_BUILD_DIR:$PATH"
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" doctor
FFmpeg, FFprobe, Node y Chrome deben pasar antes de la producción (las otras filas de doctor son componentes opcionales que esta skill no usa). Si falla alguno de los cuatro, sigue $PROMO_VIDEO_SKILL_DIR/references/install.md — la configuración inicial completa única por máquina: paquetes del sistema (macOS/Linux), clonación de las herramientas fuente (incluyendo el checkout de referencia de OpenMontage), compilación de FFmpeg desde fuente con libx264, compilación del CLI de HyperFrames y consejos de permisos para el agente. No cargues ese archivo cuando doctor ya pasa.
Chrome: el CLI usa automáticamente un chrome-headless-shell en caché bajo ~/.cache/puppeteer/ (se descarga una vez si está ausente). La primera ejecución de TTS también descarga paquetes de Python y los pesos de Kokoro-82M. Después de esas descargas únicas, la síntesis, validación, renderizado y empaquetado tienen una ruta offline.
Estructura del proyecto (crear por video, repositorio git propio, commits atómicos)
<proyecto>/
.venv/ # uv venv, python 3.12
.gitignore # .venv/ __pycache__/ video/snapshots/ .DS_Store
README.md # cómo se hizo + reproducción (escribir ÚLTIMO)
production/
research/research-brief.md # hechos con fuentes + tabla honesta de competidores
proposal.md # concepto, lista de escenas, promesa de entrega, decisión de duración
script/script.md # narración BLOQUEADA
scene_plan/scene-plan.md # notas visuales y de movimiento por escena
checkpoints/decision-log.json # solo añadir; (categoría, asunto) es la clave
checkpoints/self-review.md # evidencia posterior al renderizado
assets/audio/ # WAVs de kokoro + durations.json
renders/ # master + derivados por plataforma
youtube/ # metadata.md + thumbnail.jpg
tools/tts_generate.py # copiar desde esta skill, editar SCENE_NARRATIONS
tools/tts_pronounce.py # desde scripts/, pronunciación de acrónimos (no editar)
tools/pronunciation.local.json # léxico opcional del proyecto
video/
index.html # LA composición
assets/gsap.min.js # vendido
assets/audio/ # copias de los WAVs (rutas relativas a la composición)
Etapa 1 — Investigación (offline primero)
Explora el repositorio/README/documentación del asunto — buscando resultados y evidencia (números, adopción, resultados antes/después), no arquitectura. Produce research-brief.md con:
- Bloque de posicionamiento (requerido — el guion no puede bloquearse sin él). Una oración cada uno: audiencia (para quién es), dolor (qué sienten hoy), transformación (qué cambia para ellos), diferenciador (la única razón para elegir esto). Este es el mensaje; cada etapa posterior lo sirve.
- Hechos del producto, cada uno verificado contra la fuente — mantenidos solo si respaldan un beneficio (la prueba del "¿y qué?": ¿qué obtiene el espectador?).
- Una tabla de competidores (debilidad → cómo el asunto la aborda). Regla de honestidad: concede puntos reales a los competidores (una ✓ en su columna compra credibilidad para la tuya).
- Una lista de "afirmaciones autorizadas para el guion".
Etapa 2 — Propuesta + decisiones
Escribe proposal.md: reafirma el bloque de posicionamiento, concepto de 5–7 escenas, objetivo ≈60s, promesa de entrega ("liderado por movimiento, no una presentación de diapositivas"), tabla de la cadena de herramientas. Registra en decision-log.json — comienza desde el esquema vacío en $PROMO_VIDEO_SKILL_DIR/assets/decision-log.json (campos: category, subject, selected, options_considered, rejected_because, confidence): pipeline_selection, render_runtime_selection (registrar Remotion como la alternativa considerada — regla estricta de OpenMontage), composition_mode (atelier para trabajo principal), voice_selection, music_selection, output_profile, approval_gates y cta_selection — exactamente UNA llamada a la acción (hacer estrella al repositorio / instalar / ver la demo / visitar el sitio); los promocionales mueren al terminar con una lista de funciones en lugar de una petición. Si el resumen del usuario fijó una elección, marca approved_by: "user brief". ¿Cambiaste una decisión más tarde? AÑADE una nueva entrada con la misma (categoría, asunto) — nunca edites la anterior.
Etapa 3 — Guion (el punto de bloqueo)
~150–155 palabras ≈ 60s en kokoro af_heart velocidad 1.1. Arco promocional (vender el por qué): gancho de apuestas/dolor → revelación (nombre + la transformación que promete) → beneficios en el mundo del espectador → prueba (comparación honesta, resultados reales) → aspiración → la única CTA del registro de decisiones. Las funciones aparecen solo como evidencia de un beneficio: cada oración debe pasar la prueba del "¿y qué?" (¿qué obtiene el espectador?) o ser reescrita. Como máximo UN golpe de cómo funciona, y solo cuando el usuario lo solicita — enseñar el mecanismo es trabajo de la skill explainer-video. Voz: segunda persona, tiempo presente, oraciones cortas y contundentes ("envías más rápido", no "permite envíos más rápidos"). Escribe los acrónimos de forma natural ("OID", "MCP", "IA") tanto en el guion como en el texto en pantalla — en el momento de la síntesis, tools/tts_pronounce.py deriva la forma hablada (palabra vs letra por letra) de su léxico, y los acrónimos desconocidos se deletrean letra por letra y se informan. Clasifica los términos del dominio en un tools/pronunciation.local.json opcional ({"NASA": "word", "ECG": "letters", "NGINX": "engine X"}). Evita el énfasis en MAYÚSCULAS: una palabra en mayúsculas se trata como un acrónimo. Presupuesto de palabras por escena ≈ su parte de 60s × 2.55 palabras/s.
Etapa 4 — Narración + derivación de temporización (el truco clave)
Crea el entorno Python 3.12 del proyecto a partir de las dependencias directas fijas incluidas, copia la plantilla genérica y edita solo SCENE_NARRATIONS:
uv venv --clear --python 3.12 .venv
uv pip install --python .venv/bin/python \
-r "$PROMO_VIDEO_SKILL_DIR/scripts/tts-requirements.txt"
mkdir -p tools
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_generate.py" tools/tts_generate.py
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_pronounce.py" tools/tts_pronounce.py
PYTORCH_ENABLE_MPS_FALLBACK=1 .venv/bin/python tools/tts_generate.py
mkdir -p video/assets
curl -fL https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js \
-o video/assets/gsap.min.js
La primera síntesis descarga los pesos de Kokoro-82M (~330 MB); las ejecuciones posteriores usan la caché y pueden ser offline. Escribe WAVs por escena + durations.json. Si la ejecución imprime una ADVERTENCIA de acrónimo desconocido, clasifica esos tokens en tools/pronunciation.local.json y vuelve a ejecutar antes de derivar la temporización.
Trata 150–155 palabras a velocidad 1.1 como una estimación inicial, no una calibración. ¿Total >62s? Aumenta la velocidad o recorta palabras, luego mide de nuevo.
Deriva la línea de tiempo (relleno ≈0.3–0.5s entre escenas):
n_start[0] = 0.5 # introducción
scene_start[i] = n_start[i] - 0.15..0.4 # los visuales llegan justo antes de la VO
n_start[i+1] = n_start[i] + dur[i] + 0.3..0.35
TOTAL = último n_end + 0.6..0.9 cola (fundido a negro)
Estos números van en TRES lugares que deben coincidir: data-start/data-duration de la escena, valores <audio data-start> y las constantes de línea de tiempo JS const S1..Sn. Copia los WAVs a video/assets/audio/.
Etapa 5 — Banda musical (sin descargas)
Genera un pad sintetizado determinista con FFmpeg compilado desde fuente. Establece TOTAL_SECONDS al total de la composición derivada; no se necesita ninguna muestra externa o referencia privada:
TOTAL_SECONDS=60.5
FADE_SECONDS=3.5
FFMPEG_BIN="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}/ffmpeg"
FADE_OUT_START="$(awk -v total="$TOTAL_SECONDS" -v fade="$FADE_SECONDS" \
'BEGIN { printf "%.3f", total - fade }')"
mkdir -p production/assets/audio
"$FFMPEG_BIN" -hide_banner -y \
-f lavfi \
-i "aevalsrc=0.07*(sin(2*PI*110*t)+0.65*sin(2*PI*110.7*t)+0.45*sin(2*PI*164.81*t)+0.30*sin(2*PI*220.9*t)):s=48000:d=${TOTAL_SECONDS}" \
-af "highpass=f=55,lowpass=f=1200,afade=t=in:st=0:d=2,afade=t=out:st=${FADE_OUT_START}:d=${FADE_SECONDS},pan=stereo|c0=c0|c1=c0" \
-c:a pcm_s16le production/assets/audio/music-bed.wav
Mantenlo escaso; mezclado a data-volume="0.12..0.16" se lee como ambiente — para energía promocional, sitúate hacia la parte superior de ese rango, nunca por encima de la narración. Copia el WAV a video/assets/audio/ y usa una pista de audio distinta.
Etapa 6 — Autoría de la composición (contrato de HyperFrames)
Comienza desde $PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html (encarna cada regla a continuación); antes de escribir HTML, hojea $HYPERFRAMES_DIR/skills/hyperframes-core/SKILL.md. Estructura: index.html independiente, raíz <div data-composition-id="X" data-start="0" data-duration="TOTAL" data-width="1920" data-height="1080">, escenas como <section class="clip" data-start data-duration data-track-index="1">, UNA gsap.timeline({paused:true}) registrada sincrónicamente en window.__timelines["X"], posiciones de los tweens = S<i> + local_offset.
Reglas que cuestan tiempo real de depuración — obedece de inmediato:
- Cada
<video>/<audio>necesita unidexplícito y no vacío. En HyperFrames, el audio sin uno puede reproducirse en una vista previa del navegador, pero renderiza SILENCIO (lint informa errores; no lo ignores). - Nunca pongas un
transform:inicial en CSS en un elemento animado con GSAP — usatl.fromTo(el, {y:24, opacity:0}, {y:0, opacity:1, ...}); GSAP posee la transformación completa. (opacity: 0solo en CSS está bien para revelaciones solo con.to()). - El relleno de fondo va en un HIJO a sangre completa (
position:absolute; inset:0), nunca en la raíz (el fondo de la raíz puede renderizar en negro). - Los elementos multimedia son hijos directos de la raíz,
data-track-indexen pistas de audio distintas (ej. 7=VO, 8=música). - Sin repeticiones infinitas (
repeat: -1prohibido — usa finito, ej. parpadeo de cursorrepeat: 9, yoyo: true), sinDate.now/Math.random, sin búsquedas de red, sin scripts CDN. idúnico en todas partes; anima solo transformaciones/opacidad (+strokeDashoffsetpara dibujos de líneas SVG).- Elementos que aparecen a mitad de escena: estado inicial oculto + tween en
S<i>+offset.fromTotieneimmediateRenderpara que las búsquedas previas al tween muestren el estado desde — eso es lo que lo hace seguro para búsquedas. - Tokens de diseño en vars CSS
:root= el tema; intercambia ground/ink/accent + pila de tipos para cambiar la apariencia. Usa una fuente que el renderizador incluya (JetBrains Mono) — las fuentes del sistema se sustituyen de todos modos (la información de lint indica la asignación). - Mantén el texto secundario ≥ WCAG AA sobre el color de fondo — la verificación
checkFALLA en las compilaciones en contraste e imprime el color conforme a usar. - Superposiciones/scroll-unders intencionales: marca con
data-layout-allow-overflow/-overlap/-occlusionen lugar de dejar los hallazgos sin explicación.
Consejo de motivos de escena: muestra el estado RESULTADO — el mundo del espectador mejorado (contraste antes/después, el dolor desaparecido, el resultado en pantalla). Extrae destellos de apoyo de la interfaz/dominio real del asunto (paleta de búsqueda falsa, terminal, árbol, tabla) como evidencia de un beneficio, no un recorrido del mecanismo. Los arcos narrativos son las plantillas; los motivos no deben serlo.
$PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html contiene el esqueleto completo de la composición con todas las reglas anteriores incorporadas. Cópialo en video/index.html y personaliza las escenas, los tweens y los elementos multimedia según el plan de escenas.
Etapa 7 — Gobernanza con OpenMontage
OpenMontage gobierna el pipeline sin ejecutar un daemon. Lee $HOME/OpenMontage/AGENT_GUIDE.md y $HOME/OpenMontage/pipeline_defs/animation.yaml para entender las puertas de aprobación (gates), el orden de las etapas y el registro de decisiones. Cada vez que completas una etapa, registra la decisión en production/checkpoints/decision-log.json (solo añadir, nunca editar). Antes del renderizado final, ejecuta una autoevaluación y escribe production/checkpoints/self-review.md con evidencia de que cada etapa se completó correctamente.
Etapa 8 — Renderizado y empaquetado
Renderiza el video master con HyperFrames:
cd video
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" render index.html
El resultado es un MP4 determinista. Luego, usa FFmpeg para empaquetar para plataformas de destino (YouTube, Twitter, etc.) según el perfil de salida registrado en el registro de decisiones.
# Ejemplo: empaquetado para YouTube con sonoridad normalizada
"$FFMPEG_BIN" -i renders/master.mp4 \
-af loudnorm=I=-14:LRA=1:TP=-1 \
-c:v libx264 -preset medium -crf 18 \
-c:a aac -b:a 192k \
renders/youtube.mp4
Guarda los renders finales en production/renders/ y la metadata de la plataforma en production/youtube/.
Notas finales
- Offline primero: después de las descargas iniciales, todo el pipeline funciona sin conexión a internet.
- Determinismo: sin números aleatorios, sin fechas, sin búsquedas de red, sin dependencias CDN.
- Presupuesto de palabras: 150–155 palabras para ~60s. Cada oración debe pasar la prueba del "¿y qué?" o ser reescrita.
- CTA única: exactamente una llamada a la acción al final del video. Los promocionales mueren al terminar con una lista de funciones.
- Honestidad: en la tabla de competidores, concede puntos reales a los competidores para ganar credibilidad.
- Agente-agnóstico: la skill funciona con cualquier agente de IA de codificación; sigue el runbook en markdown plano.
Para más detalles sobre la instalación inicial de la cadena de herramientas, consulta $PROMO_VIDEO_SKILL_DIR/references/install.md.