captions
Genera un archivo de subtítulos ASS a partir de la transcripción palabra por palabra y lo incrusta en el renderizado.
Argumentos: slug de $project, $preset un nombre de presets/captions/*.json. Solicitud completa en
$ARGUMENTS. Un marcador $ vacío significa que la skill se activó automáticamente en lugar de ser
invocada por barra: toma el proyecto de PIPELINE_STATE en el contexto de la sesión e indica cuál
seleccionaste. Los nombres de verbo siguen el contrato de compilación; python engine/ve.py --help prevalece.
Dos reglas específicas del alemán que determinan si esto funciona
1. Nunca agrupar por max_chars
Los sustantivos compuestos alemanes son tokens únicos e indivisibles que superan cualquier límite de caracteres.
Geschwindigkeitsbegrenzung tiene 26 caracteres y ocupa aproximadamente el 60% de un fotograma de 1920 a
90 px. Una regla de max_chars: 22 no puede dividirlo, por lo que desborda el fotograma o el
agrupador emite silenciosamente una línea que prometió no emitir.
Agrupar por un presupuesto de ancho de píxel en su lugar, con una regla de reducción automática para una sola palabra que
supere el presupuesto por sí misma. estimate_text_width_px() es la encargada de la medición;
group_words() es la encargada del presupuesto. Los detalles están en docs/fonts.md y en el docstring del módulo engine/vedit/captions.py.
Este es el error de subtítulos en alemán más común y es completamente evitable.
2. La fuente debe ser un archivo estático de un solo peso, nombrado por su nombre exacto nameID 1
Las fuentes variables no se pueden seleccionar por peso desde ASS. InterVariable.ttf se renderiza
byte-idéntico a Arial a través de libass - mismo md5 - mientras registra que cargó el archivo.
Salida 0, sin advertencia. Style: D,Inter,...,Bold=-1 obtiene DirectWrite Arial y nadie se da cuenta
hasta que la exportación está en YouTube.
Usar únicamente estas, con la cadena exacta de Fontname y el valor de Bold:
| archivo | ASS Fontname | Bold |
|---|---|---|
Inter-Regular.ttf | Inter | 0 |
Inter-Bold.ttf | Inter | -1 |
Inter-SemiBold.ttf | Inter SemiBold | 0 |
Inter-ExtraBold.ttf | Inter ExtraBold | 0 |
Montserrat-Bold.ttf | Montserrat | -1 |
Montserrat-ExtraBold.ttf | Montserrat ExtraBold | 0 |
assets/fonts/ contiene solo fuentes. libass carga cada archivo que encuentra allí y da error en
cualquier otra cosa: un README.md suelto produce Error opening memory font 'README.md'. Ese
directorio tiene un permiso de ask en la configuración precisamente por esta razón; si un documento pertenece junto con
las fuentes, va en docs/fonts.md.
Paso 1 - elegir un preset
presets/captions/*.json contiene el estilo, el presupuesto de agrupación, la posición y el comportamiento de resaltado.
Elegir por output.platform, no por preferencia:
- Las plataformas verticales quieren texto grande, centrado, de 1 a 3 palabras, alto en el área segura - el tercio inferior está debajo de la propia interfaz de la plataforma.
- El contenido largo de YouTube quiere dos líneas, ancladas abajo, con un tamaño que un espectador de móvil pueda leer.
Confirmar la elección con el usuario antes de generar y mencionar qué cambia.
Paso 2 - generar el ASS
python engine/ve.py captions $project --preset $preset
Dos requisitos estructurales, ambos fallan silenciosamente si se violan:
- La línea V4+
Style: Format:de 23 campos (Name..Encoding, la propia lista de campos de libass - el campo 24,AlphaLevel, es SSA v4 y no ASS V4+) y la líneaFormat:de 10 campos de Eventos deben ser canónicas, y la fila debajo de cada una debe tener exactamente esa cantidad de columnas. Una discrepancia en el recuento hace que libass salga con 0, anteponga una coma extra en el texto renderizado y elimine silenciosamente el contorno y la negrita. Reproducido con un Format de 5 campos contra un Dialogue de 6 campos.captions.validate_ass()verifica ambos recuentos antes de cualquier incrustación; no es opcional yburn_in()se niega en caso de fallo. - El archivo debe ser UTF-8. Los diacríticos alemanes viajan correctamente a través de libass cuando lo es -
Größe Straße Übermäßig äöüÄÖÜßfue verificado de principio a fin.
El archivo secundario se escribe en projects/<slug>/out/captions.ass y se referencia desde
captions.sidecar_path.
El tiempo se obtiene de la misma lista de palabras de la que provienen los cortes, mapeada a través de las posiciones de la línea de tiempo
en _computed. Por lo tanto, los subtítulos se invalidan con cualquier cambio de corte: regenerar
después de volver a cortar en lugar de parchear.
Paso 3 - corregir el texto, no la transcripción
Los números son el problema predecible. --suppress_numerals es obligatorio en la transcripción, por lo que
la transcripción dice zwanzig donde el hablante dijo "20". Corregirlo en
captions.overrides[] como {word_index, text}, nunca editando la transcripción: la
transcripción es el ancla para cada rango de palabras en el proyecto y reescribirla desincroniza los
cortes.
Lo mismo para nombres propios, nombres de productos y abreviaciones tipo GmbH. Recopilarlos en una sola pasada
escaneando palabras con un score bajo: la puntuación de alineación es inútil como señal de relleno, pero es
una buena señal de "el alineador no estaba seguro de qué era esta palabra", que es exactamente lo que parece un nombre propio.
Paso 4 - incrustar
python engine/ve.py render $project
La incrustación ocurre en el multiplexado final, una vez, sobre el vídeo concatenado - nunca por segmento. Una incrustación por segmento vuelve a renderizar el mismo subtítulo en cada límite y coloca una línea parcial a cada lado de un corte.
El escape de las rutas subtitles= y fontsdir= es exacto y no obvio. Barras inclinadas hacia adelante
en todas partes, y el valor entrecomillado con dos puntos de unidad escapados individualmente:
subtitles='C\:/Users/me/.../out/captions.ass':fontsdir='C\:/Users/me/.../assets/fonts'
escape_filter_path() en vedit/ffmpeg.py es la encargada de esto y devuelve la cadena incluyendo sus
comillas - no añadir las propias. Las comillas, no la profundidad de anidamiento, es lo que decide: la forma
simple con un solo escape falla con No option name near '/Users/...', y solo la forma entrecomillada
sobrevive a una coma, punto y coma o corchete en la ruta, todos los cuales son legales en Windows.
escape_filter_path_nested() devuelve la variante sin comillas con doble escape C\\:/... para cualquiera que la
necesite, con esa limitación documentada.
Todo pasa a través de una lista argv desde Python. La misma ruta construida en Git Bash se convierte en
C\;C:\Program Files\Git\Users\... - reproducido en vivo, no teórico.
Establecer captions.enabled y captions.burn_in, y establecer stage a "captions".
Paso 5 - demostrar que la fuente realmente se cargó
La verificación de calidad (e) es obligatoria y esta es la única forma de detectar una fuente de respaldo silenciosa. Renderizar un
fotograma de subtítulo con el estilo del proyecto y otro con un estilo de control Arial, y afirmar que los
hashes difieren. Hashes idénticos significan que libass recurrió al respaldo y la exportación se enviará con el tipo de letra
incorrecto.
Luego también confirmar: recuentos de campos en las líneas Format:, ninguna línea que exceda el presupuesto de píxeles,
y ningún subtítulo que cruce un límite de segmento dejando un fragmento.
Modos de fallo
| Síntoma | Causa | Solución |
|---|---|---|
| Los subtítulos parecen Arial | Fuente variable, o Fontname incorrecto | Archivo estático + cadena exacta nameID 1; ejecutar QC (e) |
| Coma extra en el texto, sin contorno | Línea Format: mal formada | Emitir líneas canónicas de 24/10 campos |
Error opening memory font 'README.md' | Archivo que no es de fuente en assets/fonts/ | Solo fuentes en ese directorio |
| Una palabra larga se sale del fotograma | Agrupado por max_chars | Presupuesto de ancho de píxel más reducción automática de una sola palabra |
No option name near '/Users/...' | Escape simple de fontsdir | Duplicar la barra invertida antes de los dos puntos |
| Los subtítulos escriben números | --suppress_numerals, según lo diseñado | Entradas en captions.overrides |
| Los subtítulos se desincronizan después de un recorte | ASS obsoleto | Regenerar; nunca parchear tiempos manualmente |