subtitles: corregir captions de Premiere Pro
Configuración única: ver INSTALL.md en la raíz del kit. Con el plugin, la línea de configuración es
bash "${CLAUDE_PLUGIN_ROOT}/install.sh" --data "${CLAUDE_PLUGIN_DATA}"; con una instalación manual (las cuatro carpetas skills/
copiadas una al lado de la otra en ~/.claude/skills/), ejecute bash install.sh desde la raíz del kit.
Los scripts viven en la carpeta scripts/ de esta habilidad. Esta habilidad apunta a la otra como ../rough-cut/....
Qué hace la habilidad
Premiere genera una transcripción automática (exportación .json) que a menudo está llena de errores tipográficos, sobre todo en
nombres propios (el nombre de un fotógrafo convertido en dos palabras sin relación). whisper.cpp transcribe mucho mejor.
Esta habilidad transcribe el vídeo nuevamente de forma local, luego reconstruye un .json en el formato exacto de Premiere
(reimportable tal cual): nombres corregidos con la ayuda de un guión de referencia, puntuación y mayúsculas restauradas,
títulos de obras entre comillas. Finalmente realinea los tiempos sobre el audio mediante alineación forzada (los marcas de tiempo de whisper se desvían).
- SÍ: transcripción local, corrección de nombres, puntuación y mayúsculas, comillas alrededor de títulos, realineación de tiempos sobre el audio (alineación forzada), un formato idéntico al de Premiere, listo para reimportar.
- NO: cortar o edición preliminar del vídeo (eso es
rough-cut), traducir, resincronizar un archivo de captions que ya está bien, subtítulos.srt/.vtt(el formato aquí es el JSON nativo de Premiere).
Entradas esperadas
- El vídeo exportado de Premiere (
.mp4/.mov): contiene el audio a transcribir. - El
.jsonde captions exportado de Premiere: proporciona el formato de destino (claves, UUID delspeaker, idioma) a clonar. - El guión de referencia del vídeo, en cualquier forma legible (texto plano, Markdown, un Google Doc, una página de Notion, un texto pegado): la autoridad para la ortografía de los nombres propios (personas, obras, lugares, marcas). Si no hay, pídalo, o corrija solo los errores tipográficos obvios y mantenga la ortografía de whisper para cualquier nombre incierto.
Idioma
Tipografía (espaciado alrededor de la puntuación), letras acentuadas, conjuntos de puntuación, marcas de comillas y normalizaciones ortográficas viven en languages/<code>.json (fr y en están incluidos), nunca en el código. El idioma se elige mediante --lang <code>, si no el idioma del JSON de Premiere, si no el registrado en la transcripción de whisper, si no en. Cómo funcionan los archivos y cómo agregar un idioma: ../rough-cut/references/languages.md (mismo mecanismo; esta habilidad tiene su propia carpeta languages/).
Requisitos (una vez por máquina)
whisper-cli + ffmpeg + el modelo ~/Models/whisper/ggml-large-v3-turbo.bin. Ver ../rough-cut/references/whisper-setup.md (configuración compartida). Verificar: which whisper-cli ffmpeg && ls ~/Models/whisper/.
Alineación forzada (realineación de tiempos sobre el audio, paso 6b):
pip3 install --break-system-packages ctc-forced-aligner torch torchaudio unidecode
Un modelo ONNX de aproximadamente 1,2 GB se descarga en la primera ejecución (caché ~/ctc_forced_aligner/). Verificar: python3 -c "import ctc_forced_aligner".
Flujo de trabajo
Los comandos
scripts/...a continuación asumen que está en la carpeta de la habilidad. El directorio de trabajo se puede restablecer entre dos llamadas al shell: ya sea haciendocda la carpeta de la habilidad una vez, o anteponiendo a cada script su ruta absoluta. Los archivos de trabajo (audio, transcripción, correcciones) van en una carpeta temporal fuera de cualquier carpeta sincronizada.
1. Examinar el vídeo y el formato de entrada
PRIMERO: tomar una instantánea del original. Premiere puede REESCRIBIR el .json de entrada en una re-exportación, y es la única verdad de sincronización para verificar los tiempos en el paso 7:
cp "<Reel.json>" /tmp/subs_original_premiere.json
ffprobe -v error -show_entries format=duration:stream=codec_type,r_frame_rate \
-of default=noprint_wrappers=1 "<video>"
Anote la duración (la última palabra debe caer dentro de ella) y los fps. Luego inspeccione el JSON de Premiere de entrada para confirmar el formato:
python3 scripts/inspect_format.py "<Reel.json>"
Imprime las claves (top/segment/word), el UUID del speaker, el idioma, el número de segmentos y el texto actual segmento por segmento (= la lista de errores tipográficos a corregir). El formato esperado está documentado en references/premiere-format.md: léalo si la inspección muestra claves inusuales.
2. Transcripción local (whisper.cpp)
python3 scripts/extract_audio.py "<video>" /tmp/subs_audio.wav
python3 scripts/transcribe.py /tmp/subs_audio.wav /tmp/subs_transcript.json --model large-v3-turbo --language <code>
Aproximadamente 8 s para 3 min en Apple Silicon. La transcripción tiene segmentos con text (puntuado, con mayúsculas/minúsculas) y words con marcas de tiempo.
2b. Si whisper NO puntuó (entrega continua): volver a puntuar
En monólogos rápidos, whisper a veces devuelve un flujo sin puntuación ni mayúsculas (los segmentos se rompen en medio de una oración, sin ./,). Síntoma claro: build_subs.py reporta eos: 0. Sin puntuación, los subtítulos son ilegibles. Verificación rápida: python3 scripts/words_of.py /tmp/subs_transcript.json 0 1 2 --plain.
Si es así, vuelva a puntuar usted mismo (es una tarea de LLM, no de whisper): reescriba el text de cada segmento con puntuación y mayúsculas de oración, manteniendo las palabras de whisper sin cambios: agregue SOLO puntuación y mayúsculas/minúsculas; las correcciones léxicas (nombres, acrónimos) van en corrections.json. La puntuación SIEMPRE unida a la palabra (so?, no so ?): un signo precedido de un espacio se convierte en un token aislado que la alineación elimina silenciosamente (una vez se perdieron 114 ?/! a la vez). build_subs.py restaura el espaciado del idioma (para francés, un espacio de no separación) por sí mismo. Coloque el resultado en un JSON {index: "texto puntuado"}, luego:
python3 scripts/repunctuate.py --transcript /tmp/subs_transcript.json --punct /tmp/punct.json --output /tmp/subs_transcript_punct.json
El script se niega si agregó, eliminó o cambió una palabra (muestra el diff): eso es lo que mantiene la alineación palabra-tiempo intacta. Tenga cuidado con el flujo entre segmentos: un segmento que continúa una oración comienza en minúscula, uno que abre una oración lleva mayúscula. Use esta transcripción puntuada para el resto (--transcript en el paso 6).
También elimine cualquier alucinación de final de clip: en el silencio final, whisper a menudo inventa una línea como un crédito de subtítulo o "Gracias por ver" (las líneas típicas de cada idioma se enumeran bajo whisper_hallucinations en languages/<code>.json). Elimínela con una seq_rule de N a 0. Otra alucinación: segmentos consecutivos con texto idéntico cuyas palabras tienen una duración casi nula (start==end), repeticiones inventadas sobre un silencio ("here we go" x3). Detección: segmentos i e i+1 con el mismo texto y palabras de duración 0. Solución: elimínelos de la transcripción antes de la construcción (del segs[i:j]).
3. Leer la transcripción de whisper y el guión de referencia
Imprima la transcripción segmento por segmento (text) y compárela con el texto de Premiere de entrada (paso 1). whisper ya corrige muchas cosas, pero mantiene errores tipográficos en los nombres. Lea el guión de referencia para obtener la ortografía canónica de los nombres propios y de los títulos de las obras que cita.
4. Construir corrections.json (el núcleo inteligente)
Aquí es donde se decide la calidad. Construya un archivo de correcciones comparando lo que whisper escuchó con los nombres del guión. Inspeccione la división palabra por palabra de los segmentos afectados para escribir reglas exactas:
python3 scripts/words_of.py /tmp/subs_transcript.json 2 9 38 # tokens indexados de los segmentos a examinar
La salida indexa cada palabra (9:Smyth 10:Jones): eso es lo que le permite escribir reglas exactas sin adivinar la tokenización (cuántos tokens ocupa un nombre, guiones conservados, etc.).
Formato de corrections.json (todas las claves opcionales):
{
"quote_char": "\"",
"normalize_cotes": true,
"seq_rules": [
[["jon","smyth"], ["John","Smith"]],
[["train","park"], ["Trent","Parke"]],
[["arthus","bertrand"], ["Arthus-Bertrand"]]
],
"word_rules": {"macdum": "Magnum", "scholler": "Schoeller"},
"titles": [
{"match": ["the","decisive","moment"], "canonical": ["The","Decisive","Moment"]},
{"match": ["errance"], "canonical": ["Errance"], "sep_comma": true}
]
}
seq_rules: nombres de varias palabras, o de 1 a N / de N a 1. La coincidencia se realiza sobre las palabras en minúsculas, sin puntuación. Úselo para cualquier nombre que abarque 2 o más palabras de whisper, o cuando el número de palabras cambie. Mayúsculas/minúsculas: las palabras de destino se usan LITERALMENTE: escriba las mayúsculas/minúsculas finales que desee, incluida la posición en la oración (["Hoy","nosotros",...]si la palabra abre la oración, no["hoy",...]). Misma longitud fuente/destino: la puntuación (comas internas) y los tiempos se conservan por índice; longitudes diferentes: los tiempos se redistribuyen y solo sobrevive la puntuación de la primera/última palabra (las comas internas de las palabras reemplazadas se pierden: relea esos segmentos en el paso 7). Eliminación: un destino vacío[["uh"], []]elimina la palabra (resérvelo para ruidos aislados reales que whisper duplicó; para una palabra mal transcrita, corríjala en lugar de eliminarla, o perderá parte de una oración).word_rules: un nombre de una palabra, mayúsculas/minúsculas forzadas (clave en minúsculas al valor exacto).titles: obras citadas (libros, series, fotografías tituladas) obtienen comillas automáticas (apertura en la primera palabra, cierre en la última) más uncanonicalpara las mayúsculas/minúsculas correctas del título.sep_comma: trueagrega una coma después del título (útil cuando varios títulos se suceden). Ponga obras entre comillas, no nombres de personas o marcas.normalize_cotes: aplica las normalizaciones ortográficas del archivo de idioma (normalize_words), para variantes que whisper escribe de manera inconsistente. Desactivado a menos que lo configure.french_typography: el interruptor para el espaciado tipográfico del idioma (el nombre es histórico; se aplica a cualquier idioma en uso). El valor predeterminado se toma del archivo de idioma: para francés, un espacio de no separación (U+00A0) antes de los signos de puntuación dobles y la comilla de cierre, y después de la comilla de apertura; para inglés, ninguno. Establezcafalsesolo si desea puntuación adjunta. Cuando vuelva a puntuar en el paso 2b, la puntuación DEBE estar adjunta (Flop:);build_subs.pyrestaura el espaciado por sí mismo.
5. Resolver incertidumbres CON el usuario (nunca inventar)
whisper a veces produce una palabra no nombre ambigua o ininteligible. No adivine en silencio. Enumere estos puntos con su código de tiempo y pregunte al usuario qué se dice realmente, ofreciendo una hipótesis si el contexto o el guión sugieren una. Distinga:
- Corregir directamente: nombres propios confirmados por el guión; errores tipográficos obvios y ciertos.
- Preguntar primero: palabras no nombre ambiguas, o cualquier corrección que cambie el significado (por ejemplo, whisper "quien siente" contra el guión "sin": una inversión de significado, márquelo incluso si el guión lo resuelve).
No reescriba el estilo hablado (jerga, frases de relleno, repeticiones): los subtítulos deben seguir el audio.
Qué escribir en el archivo mientras espera la respuesta (no necesita bloquear la generación): produzca una primera versión que aplique todas las correcciones seguras más su mejor hipótesis para las palabras ambiguas comunes (y márquelas), pero mantenga la ortografía original de whisper para un NOMBRE incierto que no esté en el guión: nunca invente un nombre propio. Entregue esa versión con la lista de puntos a validar (códigos de tiempo), luego regenerar después de la respuesta actualizando corrections.json. Ese es el flujo normal: generar, presentar con las preguntas, refinar.
6. Generar el archivo corregido
python3 scripts/build_subs.py \
--transcript /tmp/subs_transcript.json \
--original "<Reel.json>" \
--corrections /tmp/corrections.json \
--output "<folder>/<name>_corrige.json" [--lang <code>]
El script clona el formato del original (UUID del speaker, idioma, claves ordenadas, UTF-8 minimizado), restaura puntuación y mayúsculas/minúsculas, aplica las correcciones, agrega las comillas, recalcula eos e imprime un informe de validación.
Escriba <name>_corrige.json JUNTO AL original, nunca sobrescriba la exportación original (es la fuente del usuario). El sufijo _corrige es un nombre de archivo en el que confían los scripts: manténgalo.
6a. Verificar las 2 trampas recurrentes de build_subs.py (cada vez)
Dos errores vistos en muchos vídeos, a verificar después de CADA construcción:
- Números decimales duplicados. whisper divide
6.6en 2 tokens (6+.6; igualmente1,33, o4 AMen4+AM); la reinyección de puntuación los duplica en la salida (1.4 .4,6.6. .6), yseq_rulescoinciden mal (la normalización no elimina un./,interno). Detección: grep en la salida final los patrones\d[.,]?\s+[.,]\d. Solución: cirugía en la transcripción ANTES de la construcción: fusionar los 2 tokens en uno ({'text':'1.4','start':a.start,'end':b.end,...}; esquema de palabra de whisper =text/start/end/confidence, NOduration), luego reconstruir. - Puntuación aislada de final de oración eliminada. Un
?/!/:presente en eltextdel segmento de whisper pero sin un token de palabra propio desaparece (¿Cómo lo hicimos ?pierde su?). Detección: comparecount('?')(y!,:) entre el texto de whisper y la salida. Solución: edite posteriormente el JSON de salida: vuelva a agregar?(con el espaciado del idioma) al token anterior.
Si se detecta la trampa 1, corrija la transcripción y reconstruya (paso 6). Si es la trampa 2, parche la salida directamente.
6b. Realinear los tiempos sobre el audio: alineación forzada (SIEMPRE)
Los tiempos de las palabras de whisper SE DESVÍAN (medido hasta 7 s antes, no lineal) con monólogos rápidos y de múltiples tomas: whisper aplana los silencios entre tomas, por lo que la voz llega DESPUÉS del subtítulo. Queja típica: "cuando aparece el 'Ah' ya escucho la siguiente oración". DTW (-dtw) no cambia nada (mismos desplazamientos), y una deformación temporal sobre anclas dispersas distorsiona las zonas que ya estaban bien. El texto de whisper es bueno, solo sus tiempos son incorrectos. Solución: alineación forzada con ctc-forced-aligner:
python3 scripts/realign_timings.py \
--subs "<folder>/<name>_corrige.json" \
--audio /tmp/subs_audio.wav \
--output "<folder>/<name>_corrige_alignee.json" \
--lang <code>
La salida tiene un sufijo _alignee (añadido al archivo de entrada, antes de .json). El script produce un informe de desplazamiento (/tmp/realign_report.json) que muestra las correcciones hechas. La alineación forzada se ejecuta SÓLO en este paso final, sobre el JSON ya corregido (puntuación, mayúsculas/minúsculas, nombres). NO la ejecute sobre la transcripción sin procesar: los tiempos de corrección + puntuación deben estar finalizados primero.
7. Validar el resultado
Compare el JSON generado con el original (instantánea del paso 1) en Premiere: impórtelos uno al lado del otro y reproduzca. Verifique:
- Cronometraje: cada palabra aparece cuando se dice (sin desfase).
- Puntuación: los subtítulos tienen sentido (puntos, comas, signos de interrogación).
- Nombres: la ortografía coincide con el guión de referencia.
- Comillas: los títulos de obras (libros, series, fotografías) están entre comillas.
- Sin alucinaciones: sin líneas finales inventadas ni repeticiones.
Si se detecta un error tipográfico: actualice corrections.json y regenere (paso 6).
8. Reimportar en Premiere
En Premiere, elimine los captions existentes de la secuencia. Luego:
- Archivo → Importar → seleccione el archivo
<name>_corrige_alignee.json. - Arrástrelo a la línea de tiempo de captions.
- Verifique que los captions aparezcan en los momentos correctos con el texto correcto.
9. Limpieza
Elimine los archivos temporales:
rm -rf /tmp/subs_*