Montage vidéo de base (coupe des prises brutes pour correspondre à un script)
Pourquoi ce skill existe
Une seule prise continue pleine de répétitions est étonnamment difficile à couper correctement à partir d'une simple transcription. La première fois que ce workflow a été fait à la main, en se fiant aux horodatages des segments de Whisper issus d'un seul passage sur le fichier complet, on obtenait un plan de coupe où une phrase semblait prendre 22 secondes à être dite. En réalité, elle prenait 5,75 secondes ; Whisper avait fusionné 16 secondes de silence et deux faux départs dans ce seul segment. Se fier à cette transcription pour les coupes aurait livré une vidéo pleine de blancs.
Gardez cette leçon en mémoire : l'objectif de ce workflow est d'obtenir des points de coupe précis, et Whisper seul ne les donne pas de manière fiable sur des séquences avec des pauses, des répétitions ou des redites. Tout ce qui suit existe pour compenser cela.
Workflow
1. Analyser la source
Sondez la vidéo pour obtenir les informations de base avant toute autre action :
ffprobe -v error -show_entries format=duration,size -show_entries stream=width,height,codec_name,codec_type,avg_frame_rate -of default=noprint_wrappers=0 "<video>"
Extrayez l'audio mono 16 kHz pour la transcription (Whisper attend ce format, et c'est beaucoup plus petit/rapide à traiter que la vidéo complète) :
ffmpeg -y -i "<video>" -vn -ac 1 -ar 16000 audio.wav -loglevel error
Si l'utilisateur vous a fourni un fichier de script/scénario (txt, rtf, docx, etc.), lisez-le maintenant pour avoir le contenu prévu à comparer. Un fichier .rtf nécessite souvent textutil -convert txt -stdout file.rtf (macOS) pour obtenir un texte propre au lieu du balisage RTF brut.
2. Obtenir un aperçu grossier avec une première transcription
whisper audio.wav --language <lang> --model medium --output_format txt --output_format srt --output_dir .
Utilisez ceci UNIQUEMENT pour avoir une idée du contenu global et approximativement où se trouvent les différentes lignes dans l'enregistrement. N'utilisez pas ses heures de début/fin de segment comme de vrais points de coupe pour le moment - voir "Pourquoi ce skill existe" ci-dessus. Si l'enregistrement est long, cela peut être exécuté en parallèle (ou être ignoré au profit de l'étape 3).
3. Trouver les vraies limites de la parole par détection de silence
C'est l'étape qui compte vraiment. Exécutez :
python3 scripts/detect_bursts.py audio.wav > bursts.txt
Cela produit des "bursts" de parole précis à l'échantillon (le complément du silence détecté), filtrés pour exclure les micro-épisodes de moins de 0,5 s qui sont presque toujours du bruit de respiration, pas des mots. Faites confiance à ces horodatages, pas à ceux de Whisper.
Vérification de l'échelle : la parole naturelle tourne autour de 2-3 mots par seconde. Si la durée d'un burst semble disproportionnée par rapport à la quantité de texte que vous vous attendez à y trouver, quelque chose ne va pas.
4. Transcrire chaque burst isolément
python3 scripts/transcribe_bursts.py audio.wav bursts.txt --language <lang> --model medium
Cela charge le modèle Whisper une fois, puis transcrit chaque burst comme son propre clip court avec condition_on_previous_text=False et temperature=0. Cette combinaison révèle réellement les répétitions cachées : un burst qu'un passage sur le fichier complet étiquette comme une ligne propre peut s'avérer, une fois isolé, contenir un faux départ immédiatement suivi de la vraie prise - totalement invisible dans la transcription de premier passage, qui avait silencieusement fusionné ou supprimé la répétition.
Attendez-vous à ce qu'un enregistrement avec des répétitions notables produise plus de bursts que de lignes de script - souvent plusieurs fragments de faux départ par ligne, surtout sur la ligne avec laquelle le locuteur a le plus peiné. C'est normal ; c'est exactement le but de cette étape.
5. Construire le plan de montage - et le faire confirmer avant de couper quoi que ce soit
Regroupez les bursts transcrits par ligne/ idée du script auquel ils appartiennent. Pour chaque ligne, vous verrez généralement l'un des cas suivants :
- Un seul burst propre et complet - facile, c'est la prise.
- Un faux départ suivi d'un burst complet - utilisez le complet, jetez le fragment.
- Plusieurs prises complètes et identiques avec le même texte - ce sont de véritables doublons de performance ; vous ne pouvez pas juger de la livraison/émotion à partir d'une seule transcription, donc coupez avec un choix par défaut raisonnable (par exemple la tentative la plus tardive, car les gens trouvent souvent leur rythme) et laissez l'utilisateur choisir dans le mode Dublovi (ci-dessous).
- Plusieurs prises complètes avec un texte vraiment différent (le locuteur a improvisé différemment à chaque fois) - c'est un vrai choix de contenu, pas seulement de performance. Présentez les alternatives à l'utilisateur explicitement plutôt que de choisir silencieusement - il peut préférer sa propre phrase improvisée à celle du script, ou vice versa.
Pour les deux types de prises en double, l'utilisateur décide avec vous en les regardant : les prises vont dans le mode Dublovi de l'éditeur de plan (deux prises côte à côte, gauche / droite, "Ovaj je bolji"), sur CHAQUE vidéo. Ne décrivez pas les prises dans le chat et ne demandez pas "dubl 1 ou 2" là. Coupez avec votre choix par défaut, listez chaque doublon dans takes_src.json (skill plan-editor, "Dublovi") et ouvrez l'éditeur ; échangez la prise dans la coupe s'il en choisit une autre.
Ensuite, écrivez le plan complet dans un message à l'utilisateur : le texte exact conservé pour chaque ligne, sa plage horaire exacte, et ce qui est coupé et pourquoi (silences, faux départs, prises alternatives écartées). Attendez une confirmation explicite avant d'exécuter une commande de coupe. Ce n'est pas une formalité - éditer une vidéo donne une impression de destruction et il est difficile de vérifier sans regarder le résultat, donc l'utilisateur doit voir le plan pour détecter les problèmes (mauvaise prise choisie, une ligne qui ne sonne pas naturelle cousue à côté d'une autre) avant que le temps de rendu ne soit consacré, pas après.
Si l'utilisateur demande la durée totale, additionnez simplement les durées des segments conservés - mais signalez que la durée finale réelle sera un peu plus longue une fois les marges entre les coupes ajoutées (étape 6).
6. Couper et assembler
Écrivez les sélections confirmées (une paire début<TAB>fin par ligne, dans l'ordre de sortie) dans un fichier, puis :
scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4
Lisez le bloc de commentaire en haut de cut_and_concat.sh avant de l'exécuter manuellement ou de l'adapter - il documente les pièges (ffmpeg qui vole stdin d'une boucle bash, les fondus qui mangent la dernière syllabe d'un mot, pourquoi chaque clip a besoin d'un ré-encodage individuel avant la concaténation) qui sont faciles à réintroduire silencieusement si on réécrit tout depuis zéro au lieu d'utiliser le script tel quel.
Valeurs par défaut : 0,15 s de marge avant chaque cut-in, 0,35 s après chaque cut-out, un fondu d'entrée de 5 ms uniquement (pas de fondu de sortie), ré-encodage h264 (crf 18) + aac 192 kbps par clip, chaque clip coupé sur un nombre exact de trames avec son audio ajusté à cette même longueur, assemblé avec le FILTRE concat de ffmcfg (jamais le démultiplexeur concat - voir "Vérifier chaque rendu" ci-dessous). Remplacez via les variables d'environnement PRE_PAD, POST_PAD, CRF, FADE_IN si une coupe spécifique nécessite un traitement différent (par exemple une ligne avec beaucoup de bruit de respiration en fin de phrase pourrait nécessiter plus de POST_PAD).
Si la dernière lettre d'une phrase semble encore coupée après un premier passage, cela signifie presque toujours que POST_PAD est trop serré pour cette coupe spécifique (la consonne finale du locuteur s'étend plus loin que le seuil du détecteur de silence ne l'a capté) - augmentez-le pour ce segment plutôt que d'utiliser un fondu, ce qui ne ferait qu'empirer une coupe serrée, pas l'améliorer.
Rythme serré - le standard de l'utilisateur
Les marges par défaut (0,15 s / 0,35 s) laissent une pause d'environ 0,5 s à chaque coupe. L'utilisateur a trouvé que cela cassait le rythme ("mora biti sve zbijenije i tečnije"), donc pour ses vidéos, resserrez chaque coupe avant l'assemblage :
python3 scripts/tighten_cuts.py audio.wav selections.txt tight.txt
PRE_PAD=0.04 POST_PAD=0.08 scripts/cut_and_concat.sh "<source_video>" tight.txt final_output.mp4 <abs workdir>
Cela déplace chaque bordure là où la parole commence/se termine réellement (-42 dB) et coupe toute pause interne de plus de 0,30 s. Sur une vraie bobine, cela a réduit la durée de 1:48 à 1:41 avec chaque mot intact. Ensuite, vérifiez en transcrivant le résultat : chaque mot du texte conservé doit toujours être présent. Les timings de tout ce qui est placé sur l'ancienne coupe (gros plans, sous-titres, titre) doivent être remappés via le temps source, pas décalés d'une constante. Lorsque deux parties coupées séparément sont jointes (par exemple partie principale + outro), prenez le point de jonction à partir du nombre réel de trames de la première partie (nb_read_frames / fps), pas de sa durée de conteneur : sur une vraie bobine, la différence de 0,04 s a laissé une trame parasite du plan large entre un gros plan et l'outro, et le premier sous-titre de l'outro est apparu une trame trop tôt. Une incrustation qui se termine à la jonction doit courir jusqu'à cette trame.
Vérifier chaque rendu - avant d'envoyer quoi que ce soit
Sur une vraie bobine, l'utilisateur a repéré une trame parasite du plan large à 1:05 ("to ne sme da nam se dešava"). La vraie cause était plus grande : l'ancien cut_and_concat.sh assemblé les clips avec le démultiplexeur concat, qui place chaque clip par sa durée de conteneur et le padding/amorçage AAC, pas par ses trames réelles. Cela a laissé un trou de 80 ms (2 trames) à 10 des coupes - une image figée à l'écran après ré-encodage - et l'audio a lentement dérivé en avance sur l'image, donc chaque incrustation et sous-titre basé sur ce timing arrivait une ou deux trames trop tôt. Le script corrigé (piège n°4 dans son en-tête) coupe chaque clip sur un nombre exact de trames, pad/ajuste son audio à exactement cette longueur et joint avec le filtre concat.
Règles qui en découlent :
-
Exécutez le vérificateur sur CHAQUE rendu (coupe, zoom, sous-titres, final) avant de l'envoyer à l'utilisateur :
python3 scripts/check_video.py final.mp4 --ass captions.ass --edges edges.txt --cuts joins.txtjoins.txt= chaque jonction de la liste de coupe sur la timeline finale (sommes cumulées des durées de chaque partie, plus les limites des parties). Passez-le toujours : un cut à l'intérieur du même cadrage est bien en dessous du seuil de changement de plan, donc sans lui le vérificateur a manqué un changement de zoom arrivant 3 trames avant une jonction sur une vraie bobine (1:15) - un flash de 3 trames que l'utilisateur a vu.Il signale les trous d'horodatage, les images figées uniques, les différences audio vs vidéo, les plans d'une durée de seulement quelques trames, les sous-titres de moins de 0,4 s ou commençant/se terminant à 1-3 trames d'une coupe image, et les bords d'incrustation (
edges.txt: un début ou fin de gros plan / B-roll par ligne, en secondes) à 2-5 trames d'une coupe. Sortie 1 = quelque chose a été trouvé : corrigez et rendez à nouveau ; seul "OK - rien trouvé" est envoyé. -
Prenez chaque position de clip à partir du nombre de trames (
nb_frames / fps, ou ledurs.txtécrit par le script), jamais deformat=duration. -
Lorsque la coupe change, déplacez les gros plans, sous-titres et le titre à travers le temps SOURCE (ancienne timeline -> source -> nouvelle timeline), jamais par une constante.
-
Une incrustation qui se termine à une coupe (un gros plan avant l'outro) s'arrête exactement à la trame de cette coupe ; un sous-titre se termine avec son clip.
-
Accrochez chaque changement de zoom, fin de gros plan et bord de sous-titre qui tombe dans les 6 trames d'une jonction SUR cette jonction (un début de gros plan seulement quand la jonction est juste avant). Deux changements d'image à quelques trames d'intervalle sont toujours perçus comme un défaut ; sur une même trame, ils sont lus comme une seule coupe.
-
Donnez au tout dernier segment une petite queue (~0,5 s) pour que le dernier sous-titre et mot ne soient pas coupés court.
7. Livraison
La sortie maître de cut_and_concat.sh est de pleine qualité (ré-encodée à crf 18). Si elle doit passer par un outil avec une limite de taille de fichier, faites une copie compressée secondaire à cet effet plutôt que de compresser le maître :
ffmpeg -nostdin -y -i final_output.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k final_output_share.mp4 -loglevel error
Si l'utilisateur souhaite également des recadrages/zooms dynamiques pour le dynamisme, consultez le skill zoom - il peut soit prendre la sortie de ce skill et la re-couper, soit (plus efficacement) combiner les deux en un seul passage en écrivant les chaînes de filtres par segment du skill zoom directement dans la troisième colonne de selections.txt avant d'exécuter cut_and_concat.sh une seule fois. La rotation en vertical, si nécessaire, est traitée séparément (ne fait partie d'aucun de ces skills) - demandez à l'utilisateur, car ce n'est pas toujours souhaité.
Notes sur les plateformes
- macOS n'a pas
timeoutGNU par défaut - n'enveloppez pas les appels ffmpeg avec ; ils sont rapides pour les petits clips, et la gestion de l'arrêt/délai de l'outil appelant couvre le long passage sur fichier complet de l'étape 2. - Chaque fois que vous écrivez une boucle
while read ...; done < fichiervous-même (plutôt que d'utiliser les scripts fournis) et que vous appelezffmpegà l'intérieur, ajoutez-nostdinà l'appel ffmpeg et redirigez son stdin depuis/dev/null- sinon ffmpeg consomme les lignes destinées aureadde la boucle, corrompant quel horodatage s'applique à quelle itération sans erreur apparente.
Après la coupe : zoom et sous-titres passent par l'éditeur de plan
Lorsque la vidéo coupée recevra aussi des zooms/recadrages (skill zoom) et/ou des sous-titres (skill sous-titre), le standard de l'utilisateur est le skill plan-editor : un outil interactif où l'on clique sur un mot pour décider du zoom avec l'utilisateur et corriger le texte des sous-titres dans les mêmes phrases ("Izmeni tekst"). Conservez le texte gardé par clip du plan de ce skill - il deviendra le sentences.txt de cet outil (une ligne par clip coupé, dans le même ordre que selections.txt).