captions
Génère un fichier de sous-titres ASS à partir de la transcription mot à mot et l'incruste dans le rendu.
Arguments : slug $project, $preset un nom provenant de presets/captions/*.json. La requête complète est dans $ARGUMENTS. Un espace réservé $ vide signifie que la compétence s'est déclenchée automatiquement plutôt que d'être invoquée par slash : prendre le projet de PIPELINE_STATE dans le contexte de session et indiquer lequel a été choisi. Les noms de verbes suivent le contrat de construction ; python engine/ve.py --help fait foi.
Deux règles spécifiques à l'allemand qui déterminent si cela fonctionne
1. Ne jamais grouper par max_chars
Les noms composés allemands sont des tokens uniques insécables qui dépassent toute limite de caractères. Geschwindigkeitsbegrenzung fait 26 caractères et occupe environ 60 % d'un cadre 1920 à 90 px. Une règle max_chars: 22 ne peut pas le couper, donc soit il dépasse du cadre, soit le groupeur émet silencieusement une ligne qu'il avait promis de ne pas faire.
Grouper plutôt par un budget de largeur de pixel, avec une règle de réduction automatique pour un mot seul qui dépasse le budget. estimate_text_width_px() gère la mesure ; group_words() gère le budget. Les détails se trouvent dans docs/fonts.md et dans le docstring du module engine/vedit/captions.py.
C'est l'échec le plus courant des sous-titres allemands et il est totalement évitable.
2. La police doit être un fichier statique à poids unique, nommé par son nameID 1 exact
Les polices variables ne peuvent pas être sélectionnées par poids depuis ASS. InterVariable.ttf rend octet pour octet identique à Arial via libass - même md5 - tout en enregistrant qu'il a chargé le fichier. Exit 0, aucun avertissement. Style: D,Inter,...,Bold=-1 vous donne DirectWrite Arial et personne ne le remarque jusqu'à ce que l'export soit sur YouTube.
Utilisez uniquement celles-ci, avec la chaîne Fontname exacte et la valeur Bold :
| fichier | 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/ ne contient que des polices. libass charge chaque fichier qu'il y trouve et génère une erreur sur tout autre chose - un README.md égaré produit Error opening memory font 'README.md'. Ce répertoire est une permission ask dans les paramètres exactement pour cette raison ; si un document doit accompagner les polices, il va dans docs/fonts.md.
Étape 1 - choisir un préréglage
presets/captions/*.json contient le style, le budget de groupement, la position et le comportement de surbrillance. Choisir par output.platform, pas par goût :
- Les plateformes verticales veulent de grandes, centrées, 1-3 mots, hautes dans la zone sécurisée - le tiers inférieur est sous l'interface utilisateur de la plateforme elle-même.
- YouTube format long veut deux lignes, ancrées en bas, dimensionnées pour qu'un téléspectateur sur téléphone puisse les lire.
Confirmer le choix avec l'utilisateur avant de générer, et nommer ce qui change.
Étape 2 - générer l'ASS
python engine/ve.py captions $project --preset $preset
Deux exigences structurelles, toutes deux échouant silencieusement en cas de violation :
- La ligne
Style: Format:V4+ à 23 champs (Name..Encoding, la propre liste de champs de libass - le 24e champ,AlphaLevel, est SSA v4 et pas ASS V4+) et la ligneFormat:Events à 10 champs doivent être canoniques, et la ligne sous chacune doit avoir exactement ce nombre de colonnes. Une discordance de comptage fait que libass exit 0, prépare une virgule indésirable dans le texte rendu, et supprime silencieusement le contour et le gras. Reproduit avec un Format à 5 champs contre un Dialogue à 6 champs.captions.validate_ass()vérifie les deux comptes avant toute incrustation ; ce n'est pas optionnel etburn_in()refuse en cas d'échec. - Le fichier est en UTF-8. Les diacritiques allemands passent correctement via libass quand c'est le cas -
Größe Straße Übermäßig äöüÄÖÜßa été vérifié de bout en bout.
Le fichier sidecar est écrit dans projects/<slug>/out/captions.ass et référencé depuis captions.sidecar_path.
Le timing provient de la même liste de mots que les coupes, mappée via les positions de la timeline dans _computed. Les sous-titres sont donc invalidés par tout changement de coupe - régénérer après un recoupage plutôt que de corriger.
Étape 3 - corriger le texte, pas la transcription
Les nombres sont le problème prévisible. --suppress_numerals est obligatoire à la transcription, donc la transcription dit zwanzig là où le locuteur a dit "20". Corriger dans captions.overrides[] comme {word_index, text}, jamais en éditant la transcription - la transcription est l'ancre pour chaque plage de mots du projet et la réécrire désynchronise les coupes.
Idem pour les noms propres, les noms de produits et les abréviations de type GmbH. Les collecter en un seul passage en recherchant les mots à faible score : le score d'alignement est inutile comme signal de remplissage mais c'est un bon signal "l'aligneur n'était pas sûr de ce mot", ce qui est exactement à quoi ressemble un nom propre.
Étape 4 - incruster
python engine/ve.py render $project
L'incrustation se fait dans le mux final, une fois, sur la vidéo concaténée - jamais par segment. Une incrustation par segment re-rend le même sous-titre à chaque limite et pose une ligne partielle de chaque côté d'une coupe.
L'échappement de subtitles= et fontsdir= est exact et non évident. Des barres obliques partout, et la valeur citée avec un deux-points de lecteur échappé simple :
subtitles='C\:/Users/me/.../out/captions.ass':fontsdir='C\:/Users/me/.../assets/fonts'
escape_filter_path() dans vedit/ffmpeg.py gère cela et retourne la chaîne y compris ses guillemets - ne pas ajouter les vôtres. Les guillemets, pas la profondeur d'imbrication, sont ce qui décide : la forme simple échappée nue échoue avec No option name near '/Users/...', et seule la forme citée survit à une virgule, un point-virgule ou un crochet dans le chemin, tous légaux sous Windows. escape_filter_path_nested() retourne la variante doublée non citée C\\:/... pour ceux qui en ont besoin, avec cette limitation documentée.
Tout passe par une liste argv depuis Python. Le même chemin construit dans Git Bash devient C\;C:\Program Files\Git\Users\... - reproduit en direct, pas théorique.
Définir captions.enabled et captions.burn_in, et mettre stage à "captions".
Étape 5 - prouver que la police a réellement été chargée
Le contrôle qualité (e) est obligatoire et c'est le seul moyen de détecter un repli silencieux. Rendre une image de sous-titre avec le style du projet et une avec un style de contrôle Arial, et affirmer que les hachages diffèrent. Des hachages identiques signifient que libass est tombé en repli et que l'export sera livré avec la mauvaise police de caractères.
Ensuite, vérifier aussi : les comptes de champs sur les lignes Format:, aucune ligne dépassant le budget de pixel, et aucun sous-titre ne traversant une limite de segment d'une manière qui laisse un fragment.
Modes de défaillance
| Symptôme | Cause | Correctif |
|---|---|---|
| Les sous-titres ressemblent à Arial | Police variable, ou mauvais Fontname | Fichier statique + chaîne exacte nameID 1 ; exécuter QC (e) |
| Virgule indésirable dans le texte, pas de contour | Ligne Format: malformée | Émettre des lignes canoniques 24/10 champs |
Error opening memory font 'README.md' | Fichier non-police dans assets/fonts/ | Polices uniquement dans ce répertoire |
| Un mot long dépasse du cadre | Groupé par max_chars | Budget de largeur de pixel plus réduction automatique pour mot seul |
No option name near '/Users/...' | fontsdir échappé simple | Doubler l'antislash avant le deux-points |
| Les sous-titres épellent les nombres | --suppress_numerals, comme conçu | Entrées captions.overrides |
| Les sous-titres se désynchronisent après un recoupage | ASS obsolète | Régénérer ; ne jamais corriger les timings à la main |