Communitygithub.com

businessbaum/vedit

Compétence pour générer des sous-titres ASS, appliquer la règle de groupement par largeur de pixel pour l'allemand, gérer la table des polices et incruster les sous-titres via libass.

Qu'est-ce que vedit ?

Cette compétence génère un fichier de sous-titres ASS à partir d'une transcription mot à mot et l'incruste dans le rendu vidéo final. Elle est conçue pour les environnements d'édition automatisée (comme Claude Code, Cursor, Codex) et prend en charge les sous-titres mot à mot, le karaoké, la correction de texte, et le burn-in via libass. Elle implémente deux règles spécifiques à l'allemand : le groupement par budget de largeur de pixel (au lieu de `max_chars`) pour gérer les noms composés longs, et l'utilisation de polices statiques mono-poids pour garantir un rendu correct. La compétence valide la structure du fichier ASS (formatages 23+1 champs et 10 champs) et vérifie que la police est bien chargée par libass via un contrôle qualité de hachage. Elle gère également les nombres et les noms propres via des surcharges sans modifier la transcription originale, et évite les problèmes de désynchronisation en régénérant les sous-titres après chaque changement de coupe.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/businessbaum/vedit/tree/HEAD/.agents/skills/captions

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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 :

fichierASS FontnameBold
Inter-Regular.ttfInter0
Inter-Bold.ttfInter-1
Inter-SemiBold.ttfInter SemiBold0
Inter-ExtraBold.ttfInter ExtraBold0
Montserrat-Bold.ttfMontserrat-1
Montserrat-ExtraBold.ttfMontserrat ExtraBold0

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 ligne Format: 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 et burn_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ômeCauseCorrectif
Les sous-titres ressemblent à ArialPolice variable, ou mauvais FontnameFichier statique + chaîne exacte nameID 1 ; exécuter QC (e)
Virgule indésirable dans le texte, pas de contourLigne 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 cadreGroupé par max_charsBudget de largeur de pixel plus réduction automatique pour mot seul
No option name near '/Users/...'fontsdir échappé simpleDoubler l'antislash avant le deux-points
Les sous-titres épellent les nombres--suppress_numerals, comme conçuEntrées captions.overrides
Les sous-titres se désynchronisent après un recoupageASS obsolèteRégénérer ; ne jamais corriger les timings à la main

Individual skills in this repo

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

businessbaum/vedit

This skill should be used when the user asks for mechanical tightening of material that is already selected - "mach den Rohschnitt", "bau den Rohschnitt", "schneid die Ähms raus", "nimm die Füllwörter raus", "entferne die Pausen", "kürz die Pausen", "schneid die Versprecher raus", "mach den Radioschnitt", "straff das", "build the rough cut", "cut the fillers", "remove the ums", "tighten the pauses", "trim the dead air", "cut the retakes", "radio edit". Turns kept story blocks into enabled segments in project.json. Does not decide what the video is about - that is cut-story.

businessbaum/vedit

This skill should be used when the user asks to decide what the video is actually about before any mechanical cutting - "bau die Story", "was soll ins Video", "was kann raus", "welche Themen bleiben", "kürz das auf 6 Minuten", "mach daraus ein kurzes Video", "wo ist der Hook", "sortier die Themen", "worum geht es hier eigentlich", "build the story", "decide what stays", "structure this", "cut this down to N minutes", "find the hook", "reorder the topics". This is the CONTENT-selection pass that writes the "story" block in project.json; it runs after transcribe and before cut-rough.

businessbaum/vedit

This skill should be used when the user asks for the finished video file - "exportier das", "render das fertig", "mach das final", "renderte das Video", "mach den Endexport", "bau das Video zusammen", "mach es fertig für YouTube", "gib mir die MP4", "export the final", "render the final cut", "make the master", "export for TikTok", "give me the mp4". Covers segment rendering with the cache, guarded concat, the one-pass audio, loudness normalisation and the single mux.

Skills associés