Communitygithub.com

Istok-Media-Tool/zoom

Planifica el zoom junto con el usuario en la herramienta plan-editor (clic en una palabra, consulta la habilidad plan-editor), luego añade recortes de zoom dinámicos a un video de persona hablando ya cortado, centrados correctamente en el sujeto con el espacio superior adecuado, para que los cortes entre distintos niveles de zoom se lean como una elección cinematográfica intencionada en lugar de que el sujeto salte visiblemente por el encuadre.

¿Qué es zoom?

Esta habilidad resuelve el problema de agregar zooms dinámicos a videos editados de una persona hablando, donde los cortes entre tomas de diferentes niveles de zoom a menudo se ven bruscos y poco profesionales. Su metodología asegura que cada cambio de zoom esté justificado por el contenido del diálogo (una palabra clave, un giro, un remate), en lugar de aplicarse mecánicamente. El flujo de trabajo comienza con la planificación colaborativa del zoom usando la herramienta 'plan-editor', donde el usuario y la IA deciden juntos en qué palabras aplicar los cambios. Luego, mide con precisión la posición del sujeto en cada segmento de video (incluso en tomas 'abiertas') para calcular recortes que mantengan un espacio superior constante del ~9% y centren correctamente al sujeto, evitando saltos visuales. La habilidad soporta la edición con herramientas como ffmpeg y scripts Python (compute_crop, word_times, build_zoom_selections) para generar un archivo de selecciones que define filtros de recorte y escala. Finalmente, verifica que el recorte funcione en movimiento (no solo en un fotograma) y que ningún objeto importante (como una tableta o producto que se muestra) sea cortado, ajustando el encuadre si es necesario.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Istok-Media-Tool/video-edit-skills/tree/HEAD/video-edit/skills/zoom

Preguntar en tu IA favorita

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

Documentación

$SKILLS a continuación es la carpeta que contiene la carpeta de esta habilidad: el padre del directorio base que se muestra cuando se carga la habilidad (la carpeta skills/ del plugin).

Zoom dinámico

Por qué existe esta habilidad

Agregar zoom "dinámico" a un video editado de una persona hablando es engañosamente fácil de hacer mal, de formas que solo se notan al ver el resultado, no solo por los números. En un caso real, el mismo video pasó por varias rondas de corrección con retroalimentación antes de estar bien: cada una enseñó algo que vale la pena no tener que reaprender desde cero:

  1. Un enfoque ingenuo de zoom-en-cada-corte resultó barato y llamativo: el juicio real de edición es más escaso y deliberado que eso.

  2. Un recorte centrado simétricamente alrededor del nivel de los ojos, en lugar de anclado en la parte superior real de la cabeza, dejó un espacio vacío excesivo sobre la cabeza al escalar de nuevo (~17% de espacio superior cuando ~9% se veía bien).

  3. Reutilizar la posición de la cabeza medida de UN segmento para los cálculos de recorte en OTROS segmentos de la misma grabación puso al sujeto visiblemente descentrado en segmentos donde se había movido en su asiento minutos antes o después en la toma, aunque fuera una grabación continua única.

  4. Dejar las tomas "abiertas"/sin zoom completamente sin recortar, mientras solo se corregían las tomas con zoom, significaba que cada corte entre una toma abierta y una con zoom aún hacía que el sujeto saltara visiblemente de lado: las tomas abiertas necesitaban el mismo tratamiento de re-centrado y espacio superior, solo que a un nivel de zoom mucho más bajo, para que los cortes entre ellas se sintieran como una sola cámara coherente.

  5. Un nivel de zoom por clip, y luego un zoom colocado mecánicamente en el punto medio de cada frase, ambos se leían como ALEATORIOS para el usuario. El zoom debe seguir el guion: cada cambio aterriza en la palabra o frase específica que lo justifica (una revelación, un término clave, una pregunta personal, el remate), y las retiradas aterrizan en palabras sobre escala ("universo", "miles de millones de años"). Los cambios ocurren a mitad del clip, a veces más de una vez por clip.

  6. El usuario quiere DECIDIR el plan de zoom juntos, palabra por palabra, usando la herramienta plan-editor (Paso 1). Úsala siempre, para cualquier guion.

  7. El usuario eligió los pasos de zoom: 1.0x, 1.5x, 1.7x, 2.0x, 2.2x (niveles de plan 0-4). "1.0x" (nivel 0) NO es el fotograma original: renderizado como el fotograma original sin recortar, dejaba al sujeto descentrado con demasiado espacio sobre la cabeza, y el usuario pidió que se centrara con el mismo espacio superior que cualquier otra toma. Por lo tanto, el nivel 0 = el recorte --baseline (el recorte más amplio que cumple con el 9% de espacio superior, re-centrado; ~1.2x en la práctica). build_zoom_selections.py hace esto automáticamente. Mantén la etiqueta "1,0x" en la herramienta; significa "el más amplio, centrado".

Paso 1: Planificar el zoom JUNTOS en la herramienta plan-editor

Obligatorio para cada video: carga y sigue la habilidad plan-editor ($SKILLS/plan-editor/SKILL.md). Es la herramienta estándar de clic-en-una-palabra del usuario donde decide el zoom contigo y, a través de "Izmeni tekst", corrige el texto de los subtítulos en las mismas frases. Nunca renderices un zoom a partir de un plan que el usuario no haya moldeado allí.

Tu trabajo en ese paso es la propuesta inicial: constrúyela a partir de la curva de energía del contenido: establecimiento/calma = abierto, un giro o remate merece un zoom en SU PALABRA CLAVE, un beat explicativo se retira para un respiro, el clímax obtiene el encuadre más cerrado; diferencias de nivel decisivas (sin cambios sutiles); al menos un regreso real a lo abierto después de la apertura - y da la razón de cada cambio. La respuesta del usuario (PLAN_JSON, SENTENCES_JSON) es el plan confirmado; renderízalo exactamente en el Paso 5.

Para aplicar el plan se necesitan marcas de tiempo de palabras (Paso 5) y posiciones del sujeto por clip (Paso 2).

examples/sample/ contiene un conjunto real confirmado (frases, plan del usuario, posiciones, cortes) para un video grabado de lado y rotado con transpose=1; una referencia para formatos de archivo y para el tipo de plan que este usuario elige.

Paso 2: Medir la posición del sujeto - POR SEGMENTO, cada vez

Nunca reutilices la posición de la cabeza medida de un segmento para los cálculos de recorte de otro segmento, incluso dentro de la misma grabación continua: alguien puede moverse en su asiento cuando estés varios minutos más adelante en una toma larga, y un recorte calculado a partir de un fotograma temprano puede descentrar, o con zoom más cerrado incluso cortar, al sujeto en un segmento posterior que se ve similar pero no está encuadrado exactamente igual.

Para cada segmento que vayas a recortar (incluyendo los "abiertos" - ver paso 4):

ffmpeg -nostdin -y -ss <mid_timestamp_of_that_segment> -i source.mp4 -frames:v 1 ref.png
ffmpeg -nostdin -y -i ref.png -vf "drawgrid=w=54:h=96:t=1:[email protected]" grid.png

(54x96 da una cuadrícula de 20x20 en un fotograma de 1080x1920; escala proporcionalmente para otras resoluciones). Mira grid.png y lee:

  • cx - la x del centro de la cara/nariz
  • head_top_y - la y de la parte superior del cabello (no los ojos; el espacio superior se mide desde la parte superior de la cabeza)

Paso 3: Calcular el recorte

python3 scripts/compute_crop.py --cx <measured_x> --head-top-y <measured_y> --zoom 1.7
python3 scripts/compute_crop.py --cx <measured_x> --head-top-y <measured_y> --baseline
  • --zoom Z para un nivel de zoom explícito (ej. 1.7 para una toma al 170%).
  • --baseline para las tomas "abiertas": encuentra el recorte MÁS SUELTO que aún cumpla con el objetivo de espacio superior y re-centra horizontalmente; esto es lo que hace que las tomas abiertas solo estén ligeramente recortadas (no literalmente al 100%/intactas) pero compartan exactamente el mismo centro óptico y espacio superior que las tomas con zoom, que es lo que realmente evita que los cortes parezcan que el sujeto salta. No omitas recortar las tomas abiertas: dejarlas sin procesar mientras solo se arreglan las que tienen zoom fue la fuente más grande de "algo se ve raro" en las pruebas, más importante que equivocarse en cualquier nivel de zoom individual.

El espacio superior por defecto es el 9% de la altura del fotograma recortado, que se leyó como correcto en las pruebas; por debajo de eso se veía apretado, y no es solo estético: una plataforma vertical (Instagram/TikTok/Shorts) superpone la interfaz de usuario de nombre/caption/desvanecimiento en la parte superior del fotograma, por lo que ese margen es espacio funcional que la plataforma cubrirá, no espacio sobrante. Sobrescribe con --headroom si un proyecto específico requiere otra cosa, pero no bajes mucho de ~9% para cualquier contenido destinado a esas plataformas.

El razonamiento completo de todo lo anterior está en el docstring de scripts/compute_crop.py; es corto, léelo si algo de esto no está claro en un caso específico.

Paso 4: Verificar la salida renderizada real antes del render completo

Un recorte que se ve bien en un solo fotograma fijo aún puede cortar o descentrar al sujeto una vez que revisas el movimiento. Para cada segmento (especialmente el que mostró más movimiento corporal - inclinarse, inclinación de cabeza - cuando estabas eligiendo el fotograma de referencia):

  1. Renderiza solo ese segmento con su recorte aplicado.
  2. Toma 3+ fotogramas distribuidos a lo largo de su duración real (no solo el que mediste), ej. ffmpeg -i clip.mp4 -vf fps=3 check_%02d.png.
  3. Míralos. Si la cabeza alguna vez se corta o queda demasiado cerca de un borde, afloja el zoom de ese segmento o amplía su margen antes de continuar.

Solo después de que esto pase, debes renderizar todos los segmentos y concatenarlos.

Paso 5: Convertir el plan confirmado en cortes anclados a palabras, luego ensamblar

  1. Obtén las marcas de tiempo de las palabras a partir de un render del mismo corte (un corte simple, o un render de zoom anterior; el padding debe coincidir), y anota las duraciones por clip de ese render (ffprobe cada clip_NNN.mp4 en su directorio de trabajo):
    python3 scripts/word_times.py cut_render.mp4 words.json --language sr
    
  2. Construye el archivo de selecciones. cuts.txt = el inicio<TAB>fin sin padding del skill básico por clip; pos.json = [[cx, head_top_y], ...] por clip del Paso 2 (medido en el fotograma rotado si --pre-filter rota):
    python3 scripts/build_zoom_selections.py --cuts cuts.txt --positions pos.json \
      --plan plan.json --sentences sentences.txt --words words.json \
      --durations 6.28,6.08,... [--pre-filter transpose=1] > selections.txt
    
    Lee el informe de coincidencias que imprime: cada palabra planificada debería decir exact (o una variante ortográfica fuzzy~ razonable; Whisper puede deletrear nombres de manera diferente o en cirílico). Cualquier línea proportional (no match!) debe corregirse manualmente antes de renderizar. El cambio aterriza 0.08s antes de la palabra (--lead). Los clips con varios niveles se renderizan como un solo clip continuo (dividido + superposición temporizada), por lo que un zoom a mitad de frase nunca agrega un corte o una costura de audio.
  3. Verifica fotogramas 0.2s antes y después de cada cambio de zoom (lógica del Paso 4: cabeza nunca cortada, cambio claramente visible), luego renderiza:
scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4 "$PWD/build"

Pasa el directorio de trabajo como una ruta ABSOLUTA; con una relativa, el paso final de concatenación no encuentra concat_list.txt.

Las duraciones de los clips renderizados pueden diferir de las del render anterior por unas centésimas de segundo, así que si los subtítulos (habilidad de subtítulos) se sincronizaron con un render anterior, desplaza cada subtítulo por el nuevo inicio de su clip antes de quemarlos.

Lee el bloque de comentarios en la parte superior de cut_and_concat.sh antes de adaptarlo; documenta errores comunes de ffmpeg/bash (robo de stdin dentro de bucles, desvanecimientos que se comen la última sílaba de un corte, por qué los clips necesitan recodificación individual antes de la concatenación) que son fáciles de reintroducir silenciosamente de otro modo.

Ejemplo de archivo de selecciones que mezcla un segmento base (abierto) y dos zooms:

63.82	69.57	crop=908:1614:152:304,scale=1080:1920
80.37	85.90	crop=636:1130:288:348,scale=1080:1920
97.25	100.81	crop=908:1614:152:304,scale=1080:1920

Si la fuente también necesita una corrección de rotación/orientación, eso se maneja por separado (no es parte de esta habilidad, ya que no siempre es necesario); agrega el filtro de rotación acordado a la cadena de filtros de cada línea tú mismo si aplica aquí.

Entrega

Si la salida maestra excede el límite de tamaño de archivo de una herramienta, haz una copia comprimida secundaria en lugar de comprimir la maestra:

ffmpeg -nostdin -y -i final_output.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k final_output_share.mp4 -loglevel error

Los objetos que muestra el orador deben permanecer completos en cada zoom

En un reel real, el orador muestra fotos en una tableta junto a él; los recortes centrados en la cara cortaban su borde izquierdo ("ne sme tablet da bude isečen ovako nigde"). Antes de renderizar, enumera todo en el fotograma sobre lo que TRATA el video (una tableta, un producto, una página sostenida) y mide su caja en la cuadrícula. Cada recorte de zoom debe contener esa caja con ~30px de aire: desplaza la x/y del recorte hacia ella (la cara puede quedar descentrada, hasta ~70% de ancho), y si la cara entonces toca el borde, usa un nivel más suelto para ese cambio (1.7x -> 1.6x en un reel real) en lugar de cortar cualquiera de los dos. Mira un fotograma de cada rango de zoom para confirmar que ambos están completos.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills relacionados