captions
Gere um arquivo de legenda ASS a partir da transcrição em nível de palavra e incorpore-o na renderização.
Argumentos: slug $project, $preset — um nome de presets/captions/*.json. Requisição completa em
$ARGUMENTS. Um espaço reservado vazio $-significa que a skill foi acionada automaticamente, em vez de ser invocada por barra:
pegue o projeto de PIPELINE_STATE no contexto da sessão e informe qual você escolheu.
Os nomes de verbos seguem o contrato de build; python engine/ve.py --help vence.
Duas regras específicas do alemão que determinam se isto funciona
1. Nunca agrupe por max_chars
Substantivos compostos alemães são tokens únicos e indivisíveis que estouram qualquer limite de caracteres.
Geschwindigkeitsbegrenzung tem 26 caracteres e ocupa aproximadamente 60% de um quadro de 1920 a
90 px. Uma regra max_chars: 22 não consegue quebrá-lo, então ou ele transborda o quadro ou o
agrupador emite silenciosamente uma linha que prometeu não emitir.
Agrupe por um orçamento de largura de pixel, com uma regra de redução automática para uma única palavra que
exceda o orçamento sozinha. estimate_text_width_px() é responsável pela medição;
group_words() é responsável pelo orçamento. Os detalhes estão em docs/fonts.md e no docstring do módulo de engine/vedit/captions.py.
Esta é a falha de legenda alemã mais comum e é totalmente evitável.
2. A fonte deve ser um arquivo estático de peso único, nomeado pelo seu nome exato nameID 1
Fontes variáveis não podem ser selecionadas por peso a partir do ASS. InterVariable.ttf renderiza
byte-idêntico ao Arial através do libass — mesmo md5 — enquanto registra que carregou o arquivo.
Exit 0, sem aviso. Style: D,Inter,...,Bold=-1 resulta em DirectWrite Arial e ninguém nota
até que a exportação esteja no YouTube.
Use apenas estas, com a string Fontname exata e o valor Bold:
| arquivo | 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/ contém apenas fontes. O libass carrega todos os arquivos que encontra lá e gera erro em
qualquer outra coisa — um README.md perdido produz Error opening memory font 'README.md'. Esse
diretório é uma permissão ask nas configurações exatamente por esse motivo; se um documento pertencer
às fontes, ele vai em docs/fonts.md.
Etapa 1 — escolha um preset
presets/captions/*.json contém estilo, orçamento de agrupamento, posição e comportamento de destaque.
Escolha por output.platform, não por gosto:
- Plataformas verticais querem legendas grandes, centralizadas, de 1 a 3 palavras, no alto da área segura — o terço inferior fica sob a interface da própria plataforma.
- YouTube longform quer duas linhas, ancoradas na parte inferior, dimensionadas para que um espectador de celular consiga ler.
Confirme a escolha com o usuário antes de gerar e nomeie o que muda.
Etapa 2 — gere o ASS
python engine/ve.py captions $project --preset $preset
Dois requisitos estruturais, ambos falham silenciosamente quando violados:
- A linha
Style: Format:de 23 campos V4+ (Name..Encoding, a própria lista de campos do libass — o 24º campo,AlphaLevel, é SSA v4 e não ASS V4+) e a linhaEvents Format:de 10 campos devem ser canônicas, e a linha abaixo de cada uma deve ter exatamente esse número de colunas. Uma contagem incompatível faz o libass sair com exit 0, prefixar uma vírgula extra no texto renderizado e descartar silenciosamente o contorno e o negrito. Reproduzido com um Format de 5 campos contra um Dialogue de 6 campos.captions.validate_ass()verifica ambas as contagens antes de qualquer queima; não é opcional eburn_in()recusa em caso de falha. - O arquivo é UTF-8. Diacríticos alemães passam corretamente pelo libass quando está em UTF-8 —
Größe Straße Übermäßig äöüÄÖÜßfoi verificado de ponta a ponta.
O sidecar é escrito em projects/<slug>/out/captions.ass e referenciado a partir de
captions.sidecar_path.
O timing vem da mesma lista de palavras de onde vieram os cortes, mapeada pelas posições da linha do tempo
em _computed. As legendas são, portanto, invalidadas por qualquer alteração de corte — regenere
após recortar, em vez de corrigir.
Etapa 3 — corrija o texto, não a transcrição
Números são o problema previsível. --suppress_numerals é obrigatório na transcrição, então
a transcrição diz zwanzig onde o locutor disse "20". Corrija em
captions.overrides[] como {word_index, text}, nunca editando a transcrição — a
transcrição é a âncora para cada intervalo de palavras no projeto e reescrevê-la dessincroniza
os cortes.
O mesmo para nomes próprios, nomes de produtos e abreviações do tipo GmbH. Colete-os em uma passada
examinando palavras com score baixo: a pontuação de alinhamento é inútil como sinal de preenchimento, mas é
um bom sinal de "o alinhador não tinha certeza sobre o que era esta palavra", que é exatamente o que um nome
próprio parece.
Etapa 4 — queime (burn-in)
python engine/ve.py render $project
A queima acontece na multiplexação final, uma vez, sobre o vídeo concatenado — nunca por segmento. Uma queima por segmento re-renderiza a mesma legenda em cada fronteira e coloca uma linha parcial em cada lado de um corte.
O escape de subtitles= e fontsdir= é exato e não óbvio. Barras normais
em todos os lugares, e o valor entre aspas com dois-pontos de unidade escapado uma vez:
subtitles='C\:/Users/me/.../out/captions.ass':fontsdir='C\:/Users/me/.../assets/fonts'
escape_filter_path() em vedit/ffmpeg.py é responsável por isso e retorna a string incluindo suas
aspas — não adicione as suas. Aspas, não profundidade de aninhamento, é o que decide: a forma
escapada uma vez sem aspas falha com No option name near '/Users/...', e apenas a forma entre aspas
sobrevive a uma vírgula, ponto e vírgula ou colchete no caminho, todos legais no Windows.
escape_filter_path_nested() retorna a variante duplicada sem aspas C\\:/... para quem
precisar, com essa limitação documentada.
Tudo passa por uma lista de argumentos (argv) do Python. O mesmo caminho construído no Git Bash se torna
C\;C:\Program Files\Git\Users\... — reproduzido ao vivo, não teórico.
Defina captions.enabled e captions.burn_in, e defina stage como "captions".
Etapa 5 — prove que a fonte realmente foi carregada
A verificação de qualidade (QC) (e) é obrigatória e esta é a única maneira de detectar fallback silencioso. Renderize um
quadro de legenda com o estilo do projeto e um com um estilo de controle Arial, e afirme que os hashes
diferem. Hashes idênticos significam que o libass fez fallback e a exportação será enviada com o tipo de letra
errado.
Em seguida, confirme também: contagens de campos nas linhas Format:, nenhuma linha excedendo o orçamento de pixel,
e nenhuma legenda cruzando um limite de segmento de forma que deixe um fragmento.
Modos de falha
| Sintoma | Causa | Correção |
|---|---|---|
| As legendas parecem Arial | Fonte variável, ou Fontname errado | Arquivo estático + string nameID 1 exata; execute QC (e) |
| Vírgula extra no texto, sem contorno | Linha Format: malformada | Emita linhas canônicas de 24/10 campos |
Error opening memory font 'README.md' | Arquivo não-fonte em assets/fonts/ | Apenas fontes nesse diretório |
| Uma palavra longa sai do quadro | Agrupado por max_chars | Orçamento de largura de pixel + redução automática de palavra única |
No option name near '/Users/...' | fontsdir escapado uma vez sem aspas | Duplique a barra invertida antes dos dois-pontos |
| As legendas escrevem números por extenso | --suppress_numerals, conforme projetado | Entradas em captions.overrides |
| Legendas dessincronizam após um recorte | ASS desatualizado | Regenerar; nunca corrigir timings manualmente |