Communitygithub.com

Istok-Media-Tool/subtitle

Gravez des sous-titres stylisés et lisibles dans une vidéo en utilisant un fichier ASS réel et le rendu libass de ffmpeg (sans astuces drawtext). Utilisez cette compétence chaque fois que l'utilisateur souhaite ajouter ou graver des sous-titres/légendes dans une vidéo, superposer du texte synchronisé à la parole, ou modifier la police/taille/position/retours à la ligne des sous-titres d'une vidéo existante, même s'il ne mentionne pas explicitement "ASS" ou "libass" (par exemple, des instructions comme "ajoute des sous-titres lisibles").

Qu'est-ce que subtitle ?

Cette compétence offre un workflow robuste et professionnel pour graver des sous-titres stylisés dans des vidéos, en tirant parti du rendu libass de ffmpeg via un fichier ASS (Advanced SubStation Alpha). Contrairement aux solutions basées sur `drawtext`, cette approche garantit un rendu précis de la typographie, des polices et des boîtes d'arrière-plan. Conçue pour les plateformes comme Instagram, elle gère des contraintes réelles : largeur des marges pour éviter le recadrage, ancrage vertical bas-maintenant-haut pour empêcher les sous-titres de sauter, et espacement temporel entre les dialogues pour une meilleure lisibilité. Le processus comprend : la vérification que ffmpeg prend en charge les sous-titres, la génération d'un fichier ASS avec un style par défaut (Inter Bold, texte blanc, boîte noire opaque, position centrale basse), un découpage manuel des lignes aux endroits de pause naturelle (via des `\N` explicites), un rendu de trames de test pour vérifier le débordement, et enfin la gravure complète. Elle intègre également des correctifs de ponctuation (suppression des points finaux) et un flux de correction itératif via un plan-editor, permettant de modifier le texte sans retoucher le timing. Les leçons clés incluent : utiliser une version complète de ffmpeg (`ffmpeg-full`), ancrer les légendes en haut (Aligment 8) pour une stabilité visuelle, et laisser un vide de 0,12 s entre les dialogues pour signaler un changement de sous-titre.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Istok-Media-Tool/video-edit-skills/tree/HEAD/video-edit/skills/subtitle

Demander à votre IA préférée

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

Documentation

Gravure de sous-titres

Pourquoi cette compétence existe

Obtenir un rendu correct des sous-titres a nécessité plusieurs corrections itératives sur un projet réel, chacune valant la peine de ne pas tout réapprendre de zéro :

  1. Le ffmpeg par défaut de Homebrew n'a aucun filtre drawtext/subtitles (non compilé avec libass/freetype) – graver des sous-titres stylisés nécessite une version différente de ffmpeg, pas une solution de contournement.
  2. Couper les lignes de sous-titres pour équilibrer le nombre de caractères produit du non-sens – une coupure doit tomber là où une personne ferait naturellement une pause en parlant, jamais au milieu d'une phrase.
  3. Les lignes trop proches des bords gauche/droit du cadre peuvent être coupées par le recadrage d'Instagram après publication – une marge réelle est nécessaire, pas seulement "ça tient dans le cadre".
  4. La taille de police et la position verticale sont subjectives par projet et méritent d'être confirmées, mais faites-les vérifier visuellement avant un rendu complet – une vérification sur une image fixe détecte les débordements/coupures en quelques secondes, un rendu complet ne le fait pas.
  5. Les sous-titres ne doivent pas bouger d'un écran à l'autre. La première version les ancrait en BAS (Alignement 2, MarginV 500), donc un sous-titre de 3 lignes commençait plus haut qu'un sous-titre de 2 ou 1 ligne, et les sous-titres chevauchant le précédent de quelques centièmes de seconde étaient poussés vers le haut par la détection de collision de libass pendant toute leur durée – l'utilisateur voyait les titres "monter et descendre". Règle fixe : ancrer en HAUT (Alignement 8, MarginV = y de la première ligne ; 1215 sur 1080x1920 maintient la ligne la plus basse d'un sous-titre de 3 lignes là où l'ancien ancrage bas la plaçait) pour que la première ligne de chaque sous-titre soit au même endroit et que les lignes supplémentaires poussent vers le bas, et ne jamais laisser deux événements Dialogue se chevaucher dans le temps. De plus, laissez un court instant VIDE entre les sous-titres consécutifs : terminez chaque événement 0,12 s (3 images à 25 ips) avant le début du suivant. L'utilisateur l'a demandé explicitement – le court blanc indique au cerveau que le sous-titre a changé ; des sous-titres consécutifs se lisent comme un texte unique qui scintille. Raccourcissez la fin du sous-titre précédent, ne retardez jamais le début suivant (celui-ci est synchronisé avec la parole).
  6. Le texte des sous-titres n'est pas obligé d'être une transcription textuelle – corrigez les problèmes de grammaire/orthographe (un mot mal compris, une construction maladroite) et supprimez ou ajoutez des mots que l'utilisateur demande de changer, comme vous le feriez pour une relecture de texte que quelqu'un lira.

Étape 1 : Vérifier que ffmpeg peut graver des sous-titres

ffmpeg -hide_banner -filters 2>&1 | grep -E "subtitles|^\s*\S*\s+ass\b"

Si rien ne correspond, le ffmpeg standard ne peut pas faire cela. Installez la version complète :

brew install ffmpeg-full

ffmpeg-full est en mode keg-only (non lié dans PATH) – utilisez-le explicitement pour l'étape de gravure : /opt/homebrew/opt/ffmpeg-full/bin/ffmpeg.

Piège qui s'est réellement produit : l'installation de ffmpeg-full a importé une version plus récente de x265 partagée et a cassé le ffmpeg/ffprobe standard présent dans PATH (erreur dyld "Library not loaded: libx265.*.dylib"), car ils étaient liés à une version plus ancienne. Immédiatement après avoir installé ffmpeg-full, exécutez :

ffmpeg -version && ffprobe -version

Si l'une ou l'autre commande échoue, corrigez avec brew reinstall ffmpeg (la formule standard, pas -full) et revérifiez que les deux commandes fonctionnent avant de continuer – ne supposez pas que ffmpeg standard a survécu à l'installation sans dommage.

Étape 2 : Obtenir le texte exact des sous-titres et le timing

Si la vidéo provient du workflow de coupe d'une compétence basic, vous connaissez déjà le texte exact conservé par segment et ses horodatages source d'origine – mais une fois les segments coupés et concaténés, le timing se réinitialise sur la ligne de temps de la nouvelle vidéo. Calculez la position de chaque segment dans la vidéo FINALE en additionnant les durées réelles des clips rendus dans l'ordre :

ffprobe -v error -show_entries format=duration -of csv=p=0 clip_01.mp4

...et accumulez. Dans la fenêtre de chaque segment, décalez légèrement du début/fin du clip pour correspondre au début/fin réel de la parole (si les clips ont un pré/post padding de l'étape de coupe, soustrayez-le) pour que les sous-titres n'apparaissent/disparaissent pas en silence.

Étape 3 : Écrire le fichier ASS

Utilisez ce modèle de départ – c'est le style exact confirmé sur un projet réel (Inter Bold, texte blanc, boîte noire opaque, position milieu-bas, marges latérales généreuses) :

[Script Info]
Title: <nom de la vidéo> - titlovi
ScriptType: v4.00+
WrapStyle: 2
ScaledBorderAndShadow: yes
PlayResX: 1080
PlayResY: 1920

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Inter,70,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,3,16,0,8,120,120,1215,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.15,0:00:05.93,Default,,0,0,0,,Zamisli univerzum koji postoji\Ntoliko dugo da su sve zvezde\Nodavno ugašene

Avant de graver, faites en sorte que chaque événement se termine 0,12 s avant le début du suivant (définissez la Fin de l'événement précédent sur Début suivant - 0,12 ; ne déplacez jamais un Début) – voir leçon 5.

Ajustez PlayResX/PlayResY pour correspondre à la résolution réelle de la vidéo (cet exemple est en 1080x1920 vertical). WrapStyle: 2 signifie que libass NE va PAS revenir automatiquement à la ligne – chaque saut de ligne doit être un \N explicite dans le champ Texte. C'est intentionnel : cela vous oblige à choisir chaque coupure délibérément (voir Étape 4) au lieu de laisser le rendu deviner, et cela permet aussi de détecter une ligne qui déborde comme un bord coupé plutôt qu'une ligne supplémentaire ajoutée silencieusement.

Police/taille/couleur/position sont tous des valeurs par défaut pour COMMENCER, pas des règles fixes – confirmez avec l'utilisateur si c'est un nouveau projet, ou réutilisez ses dernières valeurs confirmées si vous les connaissez déjà pour cet utilisateur.

Convention de ponctuation confirmée sur un projet réel

Les points finaux ET les virgules finales à la fin de la DERNIÈRE ligne d'un écran de sous-titres sont supprimés (les sous-titres n'ont pas besoin de point final comme la prose – cela se lit comme une surcharge visuelle à cette taille). Cela s'applique même lorsque la phrase continue sur l'écran de sous-titres suivant : le changement d'écran lui-même se lit déjà comme la pause, donc "svoje znanje kroz praksu," est affiché comme "svoje znanje kroz praksu". L'utilisateur l'a demandé explicitement (2026-11-05, "isto kao za tačku"). Les points d'interrogation et les points d'exclamation restent. Les virgules À L'INTÉRIEUR d'un écran (au milieu d'une ligne, ou à la fin d'une ligne qui n'est pas la dernière de l'écran) restent. Appliquez-le par défaut sur chaque vidéo, y compris à la ligne en langue seconde des sous-titres bilingues, et au texte des sous-titres que l'utilisateur modifie dans le plan-editor.

Étape 4 : Couper chaque ligne là où une personne ferait réellement une pause en parlant

C'est la partie la plus importante à bien faire et la plus facile à bâcler par paresse. Lisez la phrase à voix haute et coupez aux frontières naturelles de respiration/de phrase – limites de proposition, après une pensée complète, aux virgules qui représentent une pause réelle dans la parole. Ne coupez jamais parce qu'une ligne "doit" être plus courte si le point de coupure naturel est ailleurs – trouvez plutôt une coupure naturelle PLUS COURTE (il y a presque toujours plus d'un point de pause valide dans une phrase plus longue), ne forcez pas une coupure non naturelle juste pour atteindre une cible de largeur.

Exemple concret d'une correction réelle – mauvais (équilibré par le nombre de caractères, coupe au milieu d'une phrase) :

Zamisli univerzum koji postoji toliko
dugo da su sve zvezde odavno ugasene

Bon (coupes aux pauses naturelles de la parole) :

Zamisli univerzum koji postoji
toliko dugo da su sve zvezde
odavno ugašene

1, 2 ou 3 lignes sont toutes acceptables – il n'y a pas de maximum fixe. Ce qui compte, c'est que chaque coupure soit un endroit où un locuteur ferait réellement une pause, et que chaque ligne tienne dans les marges (étape suivante).

Étape 5 : Vérifier la largeur par rapport aux MARGES LATÉRALES réelles, pas à la largeur brute du cadre

MarginL/MarginR dans le style (120 px chacun sur un cadre de 1080 de large dans le modèle ci-dessus – environ 11 % de chaque côté) existent parce que la plateforme vers laquelle la vidéo est destinée (Instagram, etc.) applique son propre léger recadrage après la publication, et un texte trop proche du bord brut peut être coupé par celui-ci, pas seulement par la limite du cadre. Traitez PlayResX - MarginL - MarginR comme la largeur réelle utilisable pour juger si une ligne est trop longue, pas la largeur totale du cadre.

Puisque WrapStyle: 2 désactive le retour automatique à la ligne, une ligne réellement trop large ne se coupera pas automatiquement – elle dépassera la marge, ce qui se lira comme un texte coupé/tronqué. Détectez cela à l'Étape 6, avant que cela ne se retrouve dans un rendu complet.

Étape 6 : Rendre des trames de test avant de graver toute la vidéo

Choisissez un horodatage dans chacun des 2-3 sous-titres les plus longs/risqués et rendez juste cette trame :

/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg -nostdin -y -i video.mp4 -ss <t> -update 1 \
  -vf "subtitles=captions.ass" -frames:v 1 check.png -loglevel error

Examinez chacun pour : texte dépassant les marges, plus de lignes que prévu (signe qu'une ligne est plus large que prévu et que quelque chose en amont doit être reformulé/recoupé), et la position verticale se lisant vraiment comme "milieu-bas" plutôt que trop proche du bord inférieur ou assez haute pour couvrir le visage. Corrigez et revérifiez avant de passer à la suite – cette boucle est rapide (quelques secondes par trame) et attrape exactement les erreurs qu'un rendu complet perdrait sinon des minutes à reproduire.

Étape 7 : Graver la vidéo finale

/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg -nostdin -y -i video.mp4 \
  -vf "subtitles=captions.ass" -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \
  -c:a copy output_titlovi.mp4 -loglevel error

L'audio est inchangé (-c:a copy) car les sous-titres ne concernent que la vidéo. Si la sortie doit passer par un outil avec une limite de taille de fichier, faites une copie compressée secondaire plutôt que de compresser le master – même modèle que les compétences basic et zoom :

ffmpeg -nostdin -y -i output_titlovi.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k output_titlovi_share.mp4 -loglevel error

Itérer sur les retours

Le travail de sous-titrage passe généralement par plusieurs séries de petites corrections spécifiques (taille de police, position, un mot erroné, ponctuation). Chaque série ne doit toucher que ce qui a changé – modifiez directement le fichier .ass (c'est du texte brut), relancez la vérification rapide de l'Étape 6 sur ce qui a été affecté, puis regravez avec l'Étape 7. Il n'est pas nécessaire de refaire les Étapes 1-2 sauf si la vidéo sous-jacente elle-même a changé.

Correction des mots erronés via l'outil interactif

L'utilisateur préfère corriger lui-même le texte des sous-titres dans le même outil plan-editor (compétence plan-editor – l'éditeur standard de l'utilisateur) : avec le bouton "Izmeni tekst", chaque phrase devient directement éditable. Chaque fois que des sous-titres existent, montrez cet outil avec --ass captions.ass --durations ... (voir la compétence plan-editor) au lieu de demander des corrections en prose – également lorsque seuls les sous-titres nécessitent une vérification et qu'aucun zoom n'est prévu (alors commencez simplement chaque clip en grand et laissez le plan de zoom intact).

La réponse contient CAPTIONS_JSON: {"<index du dialogue>": "ligne 1\nligne 2"} avec uniquement les écrans modifiés. Appliquez-le :

python3 scripts/apply_caption_edits.py captions.ass caption_edits.json captions_fixed.ass

Cela ne modifie que le texte (le timing et le style restent), transformant chaque ligne en un saut \N explicite. Relancez ensuite la vérification de l'Étape 6 sur les écrans modifiés (un mot plus long peut déborder des marges) et regravez avec l'Étape 7.

Individual skills in this repo

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

Skills associés