Communitygithub.com

Istok-Media-Tool/basic

Corta una grabación en bruto de una sola toma (voz en off con ensayos, falsos arranques y tartamudeos) para obtener una edición final limpia que sigue un guion, utilizando transcripción de Whisper cotejada con detección de silencios para puntos de corte precisos, y luego ensamblada con ffmpeg.

¿Qué es basic?

**Istok-Media-Tool/basic** es una habilidad especializada para editores de video que trabajan con agentes de IA como Claude Code, diseñada para transformar grabaciones continuas y llenas de fallos (como tomas de voz en off con múltiples reinicios y pausas) en un video final limpio y sincronizado con un guion. La habilidad utiliza **Whisper** para transcripción pero, crucialmente, no confía en sus marcas de tiempo; en su lugar, emplea detección de silencios a nivel de muestra para identificar los límites reales del habla, transcribe cada segmento de habla de forma aislada para revelar falsos arranques ocultos, y luego construye un plan de edición que el usuario debe confirmar antes de cortar. Los cortes se ensamblan con ffmpeg, con ajustes de precisión como rellenos personalizados y control de fotogramas exactos, y se verifica cada renderizado con un script de control para detectar problemas como fotogramas congelados o desviaciones de audio. La habilidad está optimizada para macOS y maneja formatos como .rtf y .docx para la lectura del guion, y los resultados pueden integrarse con otras habilidades (zoom, subtítulos) a través del plan-editor para una edición interactiva final.

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

Preguntar en tu IA favorita

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

Documentación

Edición básica de video (cortando tomas en bruto para que coincidan con un guion)

Por qué existe esta habilidad

Una sola toma continua llena de ensayos es engañosamente difícil de cortar correctamente solo a partir de una transcripción. La primera vez que este flujo de trabajo se hizo a mano, confiando en las marcas de tiempo de los propios segmentos de Whisper de una sola pasada de archivo completo se produjo un plan de corte donde una oración parecía haber tomado 22 segundos en decirse. En realidad tomó 5.75 segundos; Whisper había fusionado 16 segundos de silencio y dos falsos arranques en ese segmento. Confiar en esa transcripción para los cortes habría enviado un video lleno de silencios.

Ten en cuenta esa lección: el objetivo de este flujo de trabajo son puntos de corte precisos, y Whisper por sí solo no te los da de manera confiable en material con pausas, ensayos o líneas repetidas. Todo lo siguiente existe para compensar eso.

Flujo de trabajo

1. Analizar la fuente

Examina el video para obtener datos básicos antes de hacer cualquier otra cosa:

ffprobe -v error -show_entries format=duration,size -show_entries stream=width,height,codec_name,codec_type,avg_frame_rate -of default=noprint_wrappers=0 "<video>"

Extrae audio mono de 16kHz para transcripción (Whisper lo requiere, y es mucho más pequeño/rápido de procesar que el video completo):

ffmpeg -y -i "<video>" -vn -ac 1 -ar 16000 audio.wav -loglevel error

Si el usuario te proporcionó un archivo de guion/escena (txt, rtf, docx, etc.), léelo ahora para tener el contenido previsto con el que comparar. Un .rtf a menudo necesita textutil -convert txt -stdout file.rtf (macOS) para obtener texto limpio en lugar de marcado RTF sin procesar.

2. Obtener una visión general aproximada con una primera transcripción

whisper audio.wav --language <lang> --model medium --output_format txt --output_format srt --output_dir .

Usa esto SOLO para tener una idea del contenido general y aproximadamente qué tan avanzadas en la grabación están las diferentes líneas. No uses sus tiempos de inicio/fin de segmento como puntos de corte reales todavía; consulta "Por qué existe esta habilidad" más arriba. Si la grabación es larga, esto puede ejecutarse en paralelo con (o saltarse en favor de) el paso 3.

3. Encontrar límites reales del habla con detección de silencio

Este es el paso que realmente importa. Ejecuta:

python3 scripts/detect_bursts.py audio.wav > bursts.txt

Esto proporciona "ráfagas" de habla precisas a nivel de muestra (el complemento del silencio detectado), filtradas para excluir parpadeos de menos de 0.5 s que casi siempre son ruido de respiración, no palabras. Confía en estas marcas de tiempo, no en las de Whisper.

Verificación de cordura: el habla natural fluye aproximadamente a 2-3 palabras por segundo. Si la duración de una ráfaga parece desproporcionadamente grande en relación con la cantidad de texto que esperarías que contenga, algo está mal.

4. Transcribir cada ráfaga de forma aislada

python3 scripts/transcribe_bursts.py audio.wav bursts.txt --language <lang> --model medium

Esto carga el modelo Whisper una vez, luego transcribe cada ráfaga como su propio clip corto con condition_on_previous_text=False y temperature=0. Esa combinación es lo que realmente saca a la luz los ensayos ocultos: una ráfaga que una pasada de archivo completo etiqueta como una línea limpia puede resultar, una vez aislada, contener un falso arranque seguido inmediatamente por la entrega real, completamente invisible en la transcripción de primera pasada, que había fusionado o suprimido silenciosamente la repetición.

Espera que una grabación con ensayos notables produzca más ráfagas que líneas de guion, a menudo varios fragmentos de falso arranque por línea, especialmente en la línea con la que el hablante tuvo más dificultades. Eso es normal; es exactamente para lo que sirve este paso.

5. Construir el plan de edición y obtener confirmación antes de cortar cualquier cosa

Agrupa las ráfagas transcritas por la línea/idea del guion a la que pertenecen. Para cada línea, normalmente verás uno de:

  • Una ráfaga limpia y completa - fácil, esa es la toma.
  • Un falso arranque seguido de una ráfaga completa - usa la completa, descarta el fragmento.
  • Varias tomas completas con la misma redacción - estas son duplicados genuinos de interpretación; no puedes juzgar la entrega/emoción solo a partir de una transcripción, así que corta con un valor predeterminado razonable (por ejemplo, el intento posterior, ya que las personas a menudo se asientan en una toma) y deja que el usuario elija en el modo Dublovi (a continuación).
  • Varias tomas completas con redacción genuinamente diferente (el hablante improvisó de manera diferente cada vez) - esta es una elección de contenido real, no solo de interpretación. Presenta las alternativas al usuario explícitamente en lugar de elegir en silencio; pueden preferir su propia frase improvisada sobre la del guion, o viceversa.

Para ambos tipos de tomas duplicadas, el usuario decide junto contigo, viéndolas: las tomas van al modo Dublovi del editor de planes (dos tomas lado a lado, izquierda / derecha, "Ovaj je bolji"), en CADA video. No descripciones de tomas en el chat y no preguntes "dubl 1 ili 2" allí. Corta con tu selección predeterminada, lista cada duplicado en takes_src.json (habilidad editor de planes, "Dublovi") y abre el editor; intercambia la toma en el corte si eligen otra.

Luego escribe el plan completo como un mensaje para el usuario: el texto exacto que se conserva para cada línea, su rango de marcas de tiempo exacto, y qué se está cortando y por qué (silencio, falsos arranques, tomas alternativas descartadas). Espera confirmación explícita antes de ejecutar cualquier comando de corte. Esto no es una formalidad: editar un video se siente destructivo y es difícil de verificar rápidamente sin mirar la salida, por lo que el usuario necesita ver el plan para detectar problemas (toma incorrecta elegida, una línea que no suena natural cosida junto a otra) antes de que se dedique tiempo de renderizado a ello, no después.

Si el usuario pregunta sobre la duración total, simplemente suma las duraciones de los segmentos conservados, pero advierte que la duración final real será un poco más larga una vez que se agregue el relleno entre los cortes (paso 6).

6. Cortar y ensamblar

Escribe las selecciones confirmadas (un par inicio<TAB>fin por línea, en orden de salida) en un archivo, luego:

scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4

Lee el bloque de comentarios en la parte superior de cut_and_concat.sh antes de ejecutarlo manualmente o adaptarlo; documenta problemas (ffmpeg robando stdin de un bucle bash, fundidos de salida que se comen la última sílaba de una palabra, por qué cada clip necesita re-codificación individual antes de la concatenación) que son fáciles de reintroducir silenciosamente si se reescribe desde cero en lugar de usar el script tal cual.

Valores predeterminados: 0.15 s de relleno antes de cada corte de entrada, 0.35 s después de cada corte de salida, un fundido de entrada de 5 ms solo (sin fundido de salida), h264 re-codificado (crf 18) + aac 192k por clip, cada clip cortado a un número exacto de fotogramas con su audio recortado a esa misma duración, unidos con el FILTRO de concatenación de ffmpeg (nunca el demuxer de concatenación; consulta "Verificar cada renderizado" a continuación). Anula mediante las variables de entorno PRE_PAD, POST_PAD, CRF, FADE_IN si un corte específico necesita un manejo diferente (por ejemplo, una línea con mucho ruido de respiración final podría necesitar más POST_PAD).

Si la última letra de una oración aún suena recortada después de una primera pasada, eso casi siempre significa que POST_PAD es demasiado ajustado para ese corte específico (la consonante final del hablante se extiende más allá del umbral del detector de silencio); auméntalo para ese segmento en lugar de usar un fundido, que solo empeora un corte ajustado, no lo mejora.

Ritmo ajustado: el estándar del usuario

El relleno predeterminado (0.15 s / 0.35 s) deja una pausa de ~0.5 s en cada corte. El usuario descubrió que rompía el ritmo ("mora biti sve zbijenije i tečnije"), por lo que para sus videos ajusta cada corte antes de ensamblar:

python3 scripts/tighten_cuts.py audio.wav selections.txt tight.txt
PRE_PAD=0.04 POST_PAD=0.08 scripts/cut_and_concat.sh "<source_video>" tight.txt final_output.mp4 <abs workdir>

Mueve cada borde a donde realmente comienza/termina el habla (-42 dB) y divide cualquier pausa interna superior a 0.30 s. En un reel real, esto redujo la duración de 1:48 a 1:41 con cada palabra intacta. Después, verifica transcribiendo el resultado: cada palabra del texto conservado debe seguir ahí. Las temporizaciones de cualquier elemento colocado en el corte anterior (primeros planos, subtítulos, título) deben re-mapearse a través del tiempo de origen, no desplazarse por una constante. Cuando dos partes cortadas por separado se unen (por ejemplo, principal + final), toma el punto de unión del recuento real de fotogramas de la primera parte (nb_read_frames / fps), no de su duración en el contenedor: en un reel real, la diferencia de 0.04 s dejó un fotograma suelto del plano general entre un primer plano y el final, y el primer subtítulo del final apareció un fotograma antes. Una superposición que termina en la unión debe ejecutarse hasta ese fotograma.

Verificar cada renderizado, antes de enviar cualquier cosa

En un reel real, el usuario detectó un fotograma suelto del plano general en 1:05 ("to ne sme da nam se dešava"). La causa real era mayor: el antiguo cut_and_concat.sh unía clips con el demuxer de concatenación, que coloca cada clip según su duración en el contenedor y el relleno/priming AAC, no según sus fotogramas reales. Eso dejó un agujero de 80 ms (2 fotogramas) en 10 de los cortes: un fotograma congelado en pantalla después de la re-codificación, y el audio se desviaba lentamente por delante de la imagen, por lo que cada superposición y subtítulo basado en él aterrizaba un fotograma o dos fuera. El script corregido (problema #4 en su encabezado) corta cada clip a un número exacto de fotogramas, rellena/recorta su audio exactamente a esa duración y lo une con el filtro de concatenación.

Reglas que se derivan de ello:

  • Ejecuta el verificador en CADA renderizado (corte, zoom, subtítulos, final) antes de enviarlo al usuario:

    python3 scripts/check_video.py final.mp4 --ass captions.ass --edges edges.txt --cuts joins.txt
    

    joins.txt = cada unión de la lista de cortes en la línea de tiempo final (sumas acumulativas de las durs.txt de cada parte, más los límites de las partes). Pásalo siempre: un corte de salto dentro del mismo encuadre puntúa muy por debajo del umbral de escena, por lo que sin él el verificador pasó por alto un cambio de zoom que aterrizó 3 fotogramas antes de una unión en un reel real (1:15) - un destello de 3 fotogramas que el usuario vio.

    Informa agujeros en marcas de tiempo, fotogramas congelados solitarios, duración de audio vs. video, tomas de solo unos pocos fotogramas de duración, subtítulos de menos de 0.4 s o que comienzan/ terminan 1-3 fotogramas de un corte de imagen, y bordes de superposición (edges.txt: un inicio o final de primer plano / B-roll por línea, en segundos) a 2-5 fotogramas de un corte. Código de salida 1 = se encontró algo: arréglalo y renderiza de nuevo; solo "OK - nothing found" se envía.

  • Toma cada posición de clip de los recuentos de fotogramas (nb_frames / fps, o el archivo durs.txt que escribe el script), nunca de format=duration.

  • Cuando el corte cambie, mueve los primeros planos, subtítulos y el título a través del tiempo de ORIGEN (línea de tiempo anterior -> origen -> nueva línea de tiempo), nunca por una constante.

  • Una superposición que termina en un corte (un primer plano antes del final) se ejecuta exactamente hasta el fotograma de ese corte; un subtítulo termina con su clip.

  • Ajusta cada cambio de zoom, final de primer plano y borde de subtítulo que caiga dentro de 6 fotogramas de una unión SOBRE ESA unión (un inicio de primer plano solo cuando la unión está justo antes de él). Dos cambios de imagen separados por unos pocos fotogramas siempre se leen como un fallo; en un fotograma se leen como un solo corte.

  • Dale al último segmento una pequeña cola (~0.5 s) para que el último subtítulo y palabra no se corten.

7. Entrega

La salida maestra de cut_and_concat.sh tiene calidad completa (re-codificada a crf 18). Si necesita pasar por una herramienta con un límite de tamaño de archivo, haz una copia comprimida secundaria para ese propósito 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

Si el usuario también quiere recortes de acercamiento/zoom dinámicos, consulta la habilidad zoom; puede tomar la salida de esta habilidad y volver a cortarla, o (más eficientemente) puedes combinar ambas en una sola pasada escribiendo las cadenas de filtro por segmento de la habilidad zoom directamente en la tercera columna de selections.txt antes de ejecutar cut_and_concat.sh una vez. La rotación a vertical, si es necesaria, se maneja por separado (no es parte de ninguna habilidad): pregúntale al usuario, ya que no siempre se desea.

Notas de plataforma

  • macOS no tiene timeout de GNU de forma predeterminada: no envuelvas las llamadas de ffmpeg en uno; son rápidas para clips cortos, y el manejo de fondo/tiempo de espera de la herramienta que llama cubre la pasada de archivo completo de larga duración en el paso 2.
  • Cada vez que escribas un bucle while read ...; done < file tú mismo (en lugar de usar los scripts incluidos) y llames a ffmpeg dentro de él, añade -nostdin a la llamada de ffmpeg y redirige su stdin desde /dev/null; de lo contrario, ffmpeg consume las líneas destinadas a la read del bucle, corrompiendo qué marca de tiempo se aplica a qué iteración sin un error obvio.

Después del corte: el zoom y los subtítulos pasan por el editor de planes

Cuando el video cortado también recibirá zoom/recortes (habilidad zoom) y/o subtítulos (habilidad subtítulos), el estándar del usuario es la habilidad plan-editor: una herramienta interactiva de clic en una palabra donde deciden el zoom contigo y corrigen el texto de los subtítulos en las mismas oraciones ("Izmeni tekst"). Mantén el texto conservado por clip del plan de esta habilidad; se convierte en el archivo sentences.txt de esa herramienta (una línea por clip cortado, mismo orden que selections.txt).

Individual skills in this repo

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

Skills relacionados