$SKILLSa 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 carpetaskills/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:
-
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.
-
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).
-
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.
-
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.
-
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.
-
El usuario quiere DECIDIR el plan de zoom juntos, palabra por palabra, usando la herramienta plan-editor (Paso 1). Úsala siempre, para cualquier guion.
-
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.pyhace 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/narizhead_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 Zpara un nivel de zoom explícito (ej.1.7para una toma al 170%).--baselinepara 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):
- Renderiza solo ese segmento con su recorte aplicado.
- 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. - 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
- 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.mp4en su directorio de trabajo):python3 scripts/word_times.py cut_render.mp4 words.json --language sr - Construye el archivo de selecciones.
cuts.txt= elinicio<TAB>finsin 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-filterrota):
Lee el informe de coincidencias que imprime: cada palabra planificada debería decirpython3 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.txtexact(o una variante ortográficafuzzy~razonable; Whisper puede deletrear nombres de manera diferente o en cirílico). Cualquier líneaproportional (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. - 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.