Superposiciones de video
Convierte un corte preliminar en un conjunto de superposiciones animadas que aparecen en el fotograma exacto en que el orador dice cada cosa, más un archivo de importación para Resolve.
Establece tu marca aquí
Edita estos valores una vez, en scripts/scene.html (el bloque :root al principio). Todo lo demás los lee de ahí.
| configuración | variable CSS | valor predeterminado |
|---|---|---|
| color de acento | --brand-accent (+ --brand-accent-rgb como r,g,b) | #4a90e2 |
| color de texto claro | --brand-light (+ --brand-light-rgb) | #f2f2f2 |
| color de tinta oscura | --brand-ink | #0d131a |
| fuente para mostrar (titulares, números) | --font-display | 'Anton','Bebas Neue',Impact,sans-serif |
| fuente para cuerpo de texto | --font-body | 'Inter',-apple-system,sans-serif |
La fuente para mostrar debe estar instalada localmente (por ejemplo, en ~/Library/Fonts), ya que los clips se renderizan sin conexión en un headless Chrome. Anton y Bebas Neue son gratuitas en Google Fonts. El nombre en código BLUE simplemente significa "el color de acento".
Estilo de casa: texto en escena. Sin tarjetas. El texto vive en la habitación junto a la cabeza del orador, los títulos grandes flanquean la cabeza, las preguntas se escriben a medida que se dicen. Siempre los colores de tu marca y la fuente para mostrar.
La regla más importante: mantén los gráficos detrás de la cabeza del orador o a un lado. Nada delante de la cara y sin color de fondo. Los diagramas también están en la pared al lado del orador. Todo está enmascarado, para que las manos pasen delante de los gráficos.
Una ejecución produce: N clips .mov transparentes, PLACEMENT.txt, dos archivos .fcpxml y un breve PREVIEW.mp4.
Sin sonido por defecto. No agregues música ni efectos de sonido a menos que se solicite.
Valores predeterminados de estilo (cambiar al gusto)
- Nada aparece antes de que se diga. Ancla cada clip a la marca de tiempo a nivel de palabra de la frase, más ~0.12s para que aparezca justo después de las palabras. La sincronización a nivel de sección se lee como temprana.
- Corto. De 2.5 a 6.5 segundos. La mayoría alrededor de 3. Debe cortar de un lado a otro como en redes sociales, no quedarse estático.
- Detrás de la cabeza o a un lado, nunca sobre el orador. Sin tarjetas, sin fondos, sin atenuar. Eso retira los chips de esquina, cada tarjeta
full-*ydefinepara videos nuevos. - Muestra el mecanismo. Recurre a un diagrama antes que a una oración. Usa los tipos de diagrama en escena (
dots,flow,bars,compare,math), que se colocan en la pared al lado del orador. - Divide cualquier cosa que tenga dos mitades. La forma antigua y la nueva son dos clips. El 10% y el 20% son dos clips. Tres opciones son tres clips, cada uno aparece cuando se nombra.
- Cada línea aparece en su palabra. Las listas, líneas laterales y palabras escritas llevan desplazamientos por línea (
at,t) desde las marcas de tiempo de las palabras, no un escalonado uniforme.scripts/plan.example.pymuestra cómo: cita la frase, nombra las palabras clave, y hace la búsqueda. - Una buena mezcla (alrededor de 48 clips en 11 minutos): aproximadamente un cuarto de diagramas en escena (principalmente
flow), luegoside,pair,type,list2,num. Guardapairpara los remates. - Los títulos grandes van alrededor de la cabeza, no detrás.
pairdivide cada línea en un límite de palabra en dos mitades que flanquean la cabeza, con un espacio de 1.9 veces el ancho de la caja facial (incluyendo cabello y orejas), escalado para que ambas mitades permanezcan en pantalla. Por lo tanto, cada línea depairnecesita 2+ palabras ("UNA HORA", no "HORA"): una sola palabra no se puede dividir y termina oculta."around": falseendatadevuelve el aspecto centrado. La línea delineada tiene 150px con un trazo de 5px y un relleno de acento claro (35%) para que siga siendo legible.
Antes de empezar: ¿está bloqueado el corte?
Las superposiciones están ancladas a palabras exactas, por lo que cualquier recorte posterior desplaza cada clip. Si el usuario entrega material en bruto, o el corte no está bloqueado, ejecuta primero la skill paper-edit y coloca las superposiciones en la línea de tiempo que produce.
Pipeline
1. Obtén el corte y examínalo
Pide una exportación desde Resolve (por ejemplo, a ~/Movies/), o material en bruto en <YOUR_FOOTAGE_DRIVE> para un paper edit primero. Un archivo de video está bien; no es necesario exportar el audio por separado. Cuando Resolve esté abierto, lee la línea de tiempo a través del MCP en lugar de pedir una exportación.
ffprobe -v error -show_entries format=duration -show_entries stream=codec_type,r_frame_rate -of default=nw=1 "<file>"
Anota la frecuencia de cuadros (el renderizador asume 23.976 = 24000/1001; cambia FPSN/FPSD en render.mjs para otras frecuencias) y pregunta si la línea de tiempo comienza en 00:00:00:00 o 01:00:00:00 (el valor predeterminado de Resolve es 01).
2. Transcribe con marcas de tiempo a nivel de palabra
ffmpeg -y -i "<file>" -ac 1 -ar 16000 -c:a pcm_s16le cut.wav
whisper-cli -m ~/.cache/whisper-models/ggml-small.en.bin -f cut.wav -oj -ml 1 -sow -of cut
-ml 1 -sow da un token por segmento, que es lo que permite el anclaje exacto. Luego encuentra la hora de inicio de cada frase emparejando ejecuciones de tokens en cut.json (transcription[].offsets.from está en ms).
3. Elige los momentos
Lee la transcripción completa primero. Apunta aproximadamente a un clip por cada 15 segundos de video. Para cada uno: la frase ancla, un tipo y la duración más corta que permita que la animación aterrice y respire.
Fundamenta el contenido en lo que realmente se dijo, no en notas escritas. Si las notas dicen "30 minutos" y en cámara es "media hora más o menos", la grabación gana, y señala la diferencia al usuario.
4. Escribe clips.json
Copia scripts/clips.example.json como modelo. Un objeto por clip:
{"id":"06-math-10","start":52.96,"dur":3.6,"type":"dots","cue":"\"if 10 out of 100 meals are home cooked\"",
"data":{"lit":10,"pct":"10%","count":"10 meals"}}
Mantenlo fuera de la carpeta de la skill (una ruta de scratchpad está bien) y pásalo con CLIPS=. start es el tiempo bruto de la frase en segundos; el renderizador agrega el adelanto de 0.12s por sí mismo. El orden de id establece el orden de los archivos, así que numéralos.
5. Renderiza un corte limpio (necesario para los tipos en escena)
Los tipos en escena leen la posición de la cabeza y recortan al orador de una copia del corte sin superposiciones. Renderiza la línea de tiempo bloqueada desde Resolve con cada clip de superposición deshabilitado, H.264 1080p, en el scratchpad. En la API, SetTrackEnable en V2 no funcionó en Resolve 19; item.SetClipEnabled(False) en cada clip de la pista sí funcionó. El material en bruto debe estar en línea, por lo que la unidad de material debe estar montada; si no lo está, pide al usuario que la conecte y espera el montaje en lugar de renderizar un master terminado (las superposiciones antiguas quedarían horneadas en él).
6. Renderiza
cd <skill>/scripts && npm i # solo la primera vez, instala puppeteer-core
swiftc -O person.swift -o person # solo la primera vez, el buscador de máscara + cabeza
TITLE="<video name>" CLIPS=/path/to/clips.json FOOTAGE=/path/to/clean.mp4 OUT_DIR="$HOME/Movies/<video-name>-overlays" node render.mjs
# uno o solo unos pocos clips:
ONLY=06-math-10,07-math-30 OUT_DIR=... node render.mjs
Aproximadamente de 10 a 15 segundos de tiempo real por segundo de superposición (pair es más lento, enmascara cada fotograma). Ponerlo en segundo plano y verificar es lo correcto. Escribe PLACEMENT.txt y ambos archivos .fcpxml al final.
Siempre verifica sobre el metraje real antes de un renderizado completo. node proof.mjs <clean.mp4> <clips.json> out.jpg [email protected] [email protected] ... renderiza un fotograma por clip (prefijo@segundos dentro del clip) sobre la toma real, incluida la máscara, en un mosaico de una imagen. Míralo. Texto fuera del encuadre, un título que cruza los ojos, texto sobre un fondo ocupado: todo invisible en el código y obvio en la imagen. Para diagramas, componer sobre gris sigue estando bien.
Fotogramas de prueba: extrae primero a PNG. Superponer un .mov qtrle directamente sobre una fuente de color lavfi con -ss da un fotograma en blanco. Extrae el fotograma (ffmpeg -ss 2.8 -i clip.mov -frames:v 1 f.png), luego compone el PNG.
Colocación en Resolve: duplica la línea de tiempo bloqueada como YouTube Final, importa los movs en un contenedor Overlays, y AppendToTimeline cada uno con trackIndex: 2 y recordFrame desde el código de tiempo IN en PLACEMENT.txt (((h*3600+m*60+s)*24+ff), entonces 01:00:00:00 = 86400). Analiza PLACEMENT.txt una línea a la vez (line.split()[0] es el id, [1] el código de tiempo). Una regex de todo el archivo puede leer texto de indicación como "60-second" como un id de clip y robar el código de tiempo de la siguiente línea, por lo que un clip se queda sin colocar en silencio. Verifica que el conteo colocado sea igual al conteo de clips.
7. Vista previa, luego entrega
Construye una vista previa de 10 a 15 segundos sobre el metraje real para que el usuario pueda juzgar antes de que se incluya:
ffmpeg -y -ss <t> -t 11 -i "<cut>" -itsoffset <a> -i "<clip1>.mov" -itsoffset <b> -i "<clip2>.mov" \
-filter_complex "[0:v][1:v]overlay=0:0:eof_action=pass[v1];[v1][2:v]overlay=0:0:eof_action=pass[vo];[vo]scale=1280:-2[v]" \
-map "[v]" -map 0:a -c:v libx264 -crf 20 -preset veryfast -c:a aac PREVIEW.mp4
El audio original viene con el metraje, que es todo lo que necesita la vista previa.
Luego da los pasos de importación (File > Import > Timeline > Import AAF, EDL, XML, elige el archivo starts-01, copia los clips a una pista sobre el metraje en 01:00:00:00).
Trabajando directamente dentro de Resolve (preferido cuando Resolve está abierto)
Si un servidor MCP resolve está instalado (por ejemplo, el de terceros barckley75/resolve-claude-mcp), se comunica con la API de scripting de Resolve Studio. Si la skill paper-edit incluye un ensure_resolve.sh, ejecútalo primero para que Resolve se inicie si está cerrado. Con Preferences > System > General > External scripting using: Local, prefíerelo sobre el baile de exportación e importación.
Lo que cambia:
- No se necesita corte exportado. Lee la línea de tiempo abierta para sus códigos de tiempo reales, frecuencia de cuadros, código de tiempo de inicio y diseño de clips, luego ancla las superposiciones a eso. Esto soluciona "los códigos de tiempo se desplazan cada vez que recorto".
- Sin importación manual. Coloca los clips
.movrenderizados directamente en una pista de video. - Mejor elección de momentos. Ver dónde caen los cortes ya significa que las superposiciones pueden aterrizar con la edición en lugar de luchar contra ella.
- También puede iniciar el renderizado cuando las superposiciones están listas.
Reglas para usarlo:
- Lee antes de escribir. Confirma primero el nombre del proyecto, nombre de la línea de tiempo, código de tiempo de inicio y frecuencia de cuadros, y díselos al usuario.
- Nunca escribas en la línea de tiempo maestra. Duplícala primero y trabaja en la copia, para que un error no cueste nada.
- Evita
execute_resolve_codea menos que una herramienta normal no pueda hacer el trabajo. Ejecuta Python arbitrario contra los proyectos del usuario. Si es la única forma, muestra el código primero. - Solo sesiones locales. Resolve tiene que estar ejecutándose en este Mac, por lo que esto nunca funciona en una ejecución en la nube o programada. Vuelve al FCPXML allí.
- La ruta FCPXML aún funciona y sigue siendo la alternativa. Sigue generando ambos archivos; cuestan segundos.
Transcripción de audio: algunos servidores MCP de Resolve incluyen su propia transcripción mlx-whisper, pero el paso de whisper.cpp anterior está probado con este pipeline.
Tipos de clips
Todo lo siguiente está retirado para videos nuevos (se mantiene para que los videos antiguos puedan re-cortarse). Los tipos de fotograma completo pintan un fondo de tinta sólida con un brillo de acento suave, por lo que ocultan completamente al orador. Los chips se sientan en la parte inferior izquierda.
| tipo | úsalo para | data |
|---|---|---|
full-dots | retirado, usa dots | lit, pct, count |
full-flow | retirado, usa flow | label, steps[], good |
full-compare | retirado, usa compare | title, left{label,value,sub}, right{...,hi}, vs |
full-bars | retirado, usa bars | title, bars[{label,value,value_label,hi}] |
full-math | retirado, usa math | parts[] (operadores x = + → se renderizan en el color de acento), sub |
full-pack | retirado, usa bars o side | name, note, size 0-1, hi |
full-stat | retirado, usa pair | label, value, sub, hi |
full-title | retirado, usa pair o type | title (se permite <br>), size, sub |
full-list | retirado, usa list2 | title, items[], step segundos |
chip | retirado, usa side o num | num, title, sub, hi |
chip-stack | retirado, usa list2 | items[] |
Los tipos retirados aún se renderizan, para re-cortar videos antiguos.
Tipos en escena (el predeterminado)
Sin tarjetas, el texto vive en la habitación junto a la cabeza del orador, y los títulos grandes van alrededor de la cabeza.
| tipo | úsalo para | data |
|---|---|---|
pair | un título de remate: línea delineada sobre la cabeza, línea sólida a la altura de la frente | top, bottom, outline (top/bottom) |
side | 2-3 líneas cortas al lado de la cabeza, cada una aparece en su palabra | lines[], accent (índice, grande + color de acento), at[] |
type | una cita o pregunta que se escribe palabra por palabra a medida que se dice | words[{w,t,hi,br}] |
define | retirado: oscurece la toma. Usa side o pair | word, sub |
num | número delineado que aparece, palabras a su lado ("01 planifica la semana") | num, lines[] |
list2 | elementos que se construyen a la izquierda y/o derecha de la cabeza | left[], right[], at[] (en orden, primero a la izquierda) |
Diagramas en escena (misma colocación en la pared y máscara):
| tipo | úsalo para | data |
|---|---|---|
dots | una proporción sobre 100, cuadrícula que se llena al lado del orador | lit, pct, count |
flow | un proceso, pasos encadenados de arriba a abajo, el último en el color de acento | label, steps[], at[] opcional |
bars | dos o tres números por tamaño | title, bars[{label,value,value_label,hi}] |
compare | dos opciones, una a cada lado de la cabeza | left{label,value,sub}, right{...,hi} |
math | una ecuación que se lee hacia abajo en la pared, los operadores encabezan cada línea | parts[], sub |
at y t son segundos desde el inicio del clip; constrúyelos a partir de las marcas de tiempo de las palabras para que cada línea aparezca en su palabra.
Estos necesitan FOOTAGE= , el corte sin superposiciones (renderiza la línea de tiempo desde Resolve con los clips de superposición deshabilitados). El renderizador lee la caja facial y una cuadrícula de brillo de fondo para cada clip, para que el texto encuentre la pared vacía, cambie de lado o se encoja si se saldría del encuadre, y cambie a tinta sobre fondo claro y texto claro sobre fondo oscuro. Enmascara al orador de cada fotograma de cada clip en escena (segmentación de personas Apple Vision, person.swift), para que los títulos se sienten detrás de la cabeza y las manos pasen delante de los gráficos. Compila una vez: swiftc -O person.swift -o person.
Resolución de problemas
| síntoma | probable causa | solución |
|---|---|---|
| el clip se ve negro | falta FOOTAGE= o la ruta del metraje es incorrecta | pasa el corte renderizado sin superposiciones |
| el texto se sale del encuadre | la caja facial se leyó mal (cámara movida, orador de espaldas) | ejecuta person <frame.png> out.png para ver la caja; ajusta la escala del texto en scene.html |
pair oculta la palabra | la línea tiene una sola palabra | agrégale una palabra más ("UNA HORA" → "UNA HORA COMPLETA") y vuelve a ejecutar |
| el código de tiempo de colocación está en 00:00:00 | PLACEMENT.txt usó el desplazamiento de error 01 | resta 86400 de recordFrame, o usa el archivo starts-00 |
| Resolve no coloca todo | la línea de base sin superposiciones tiene una pista de video diferente | verifica que el trackIndex coincida con la pista esperada |
Todo el pipeline de un vistazo
- Preparar: verificar que el corte esté bloqueado, pedir el archivo de video o conectarse a Resolve.
- Transcribir:
ffmpega mono 16kHz,whisper-clicon marcas de tiempo. - Planificar: leer la transcripción, elegir ~1 clip/15s, escribir
clips.json. - Renderizar corte limpio: desde Resolve, sin superposiciones, a 1080p.
- Renderizar superposiciones:
node render.mjsconFOOTAGE=yCLIPS=. - Verificar:
node proof.mjspara ver una muestra de fotogramas. - Colocar: importar en Resolve o usar el MCP para colocar directamente.
- Vista previa: construir un
PREVIEW.mp4sobre el metraje real. - Entregar: dar los pasos de importación y el archivo de proyecto.