$SKILLSci-dessous est le dossier qui contient le dossier de cette compétence : le parent du répertoire de base affiché lorsque la compétence se charge (le dossierskills/du plugin).
Zoom dynamique
Pourquoi cette compétence existe
Ajouter un zoom « dynamique » à une vidéo coupée d'une personne qui parle est étonnamment facile à mal faire – les défauts n'apparaissent que lorsqu'on regarde le résultat, pas seulement à partir des chiffres. Sur un cas réel, la même vidéo a nécessité plusieurs rounds de correction après retour d'expérience avant d'être juste – chacun a appris quelque chose qui mérite de ne pas être réappris de zéro :
-
Une approche naïve « zoom à chaque coupe » semblait bon marché et gadget – un vrai jugement de montage est plus rare et plus délibéré.
-
Un recadrage centré symétriquement autour du niveau des yeux, au lieu d'être ancré sur le sommet réel de la tête, laissait un excès d'espace vide au-dessus de la tête une fois ré-échelonné (~17 % d'espace au-dessus quand ~9 % semblait correct).
-
Réutiliser la position mesurée de la tête d'UN segment pour le calcul du recadrage d'AUTRES segments du même enregistrement mettait le sujet visiblement décentré dans les segments où la personne s'était déplacée dans son siège quelques minutes plus tard – même s'il s'agit d'un seul enregistrement continu.
-
Laisser les plans « larges »/non zoomés complètement non recadrés, tout en corrigeant uniquement les plans zoomés, faisait que chaque coupe entre un plan large et un plan zoomé montrait encore un saut visible du sujet latéralement – les plans larges avaient besoin du même recentrage et traitement d'espace au-dessus, juste à un niveau de zoom bien plus faible, pour que les coupes entre eux donnent l'impression d'une seule caméra cohérente.
-
Un seul niveau de zoom par clip, puis un punch-in mécaniquement placé au milieu de chaque phrase, semblaient ALÉATOIRES pour l'utilisateur. Le zoom doit suivre le script : chaque changement tombe sur le mot ou la phrase spécifique qui le justifie (une révélation, un terme clé, une question personnelle, la chute), et les retraits tombent sur des mots d'échelle (« universum », « milijarde godina »). Les changements se produisent au milieu d'un clip, parfois plusieurs fois par clip.
-
L'utilisateur souhaite DÉCIDER ensemble du plan de zoom, mot par mot, à l'aide de l'outil plan-editor (Étape 1). Utilisez-le toujours – pour tout script.
-
L'utilisateur a choisi les étapes de zoom : 1,0x, 1,5x, 1,7x, 2,0x, 2,2x (niveaux de plan 0‑4). « 1,0x » (niveau 0) n'est PAS le cadre brut : rendu comme le cadre brut non recadré, il laissait le sujet décentré avec beaucoup trop d'espace au-dessus de la tête, et l'utilisateur a demandé qu'il soit centré avec le même espace au-dessus que tous les autres plans. Donc le niveau 0 = le recadrage
--baseline(recadrage le plus large respectant les 9 % d'espace au-dessus, recentré ; ~1,2x en pratique) –build_zoom_selections.pyle fait automatiquement. Conservez le libellé « 1,0x » dans l'outil ; cela signifie « le plus large, centré ».
Étape 1 : Planifier le zoom ENSEMBLE dans l'outil plan-editor
Obligatoire pour chaque vidéo : chargez et suivez la compétence plan-editor ($SKILLS/plan-editor/SKILL.md). C'est l'outil standard de clic sur un mot de l'utilisateur, où il décide du zoom avec vous et, via « Izmeni tekst », corrige le texte des sous-titres dans les mêmes phrases. Ne rendez jamais un zoom à partir d'un plan que l'utilisateur n'a pas façonné là.
Votre travail dans cette étape est la proposition de départ : construisez-la à partir de la courbe d'énergie du contenu – plan large pour l'établissement/le calme, un rebondissement ou une chute mérite une entrée sur SON MOT CLÉ, un temps explicatif recule pour reprendre son souffle, le climax obtient le cadre le plus serré ; différences de niveau décisives (pas de dérive subtile) ; au moins un vrai retour au large après l'ouverture – et donnez la raison de chaque changement. La réponse de l'utilisateur (PLAN_JSON, SENTENCES_JSON) est le plan confirmé ; rendez-le exactement à l'Étape 5.
L'application du plan nécessite les horodatages des mots (Étape 5) et les positions des sujets par clip (Étape 2).
examples/sample/ contient un ensemble réel confirmé (phrases, plan de l'utilisateur, positions, coupes) pour une vidéo tournée de côté et pivotée avec transpose=1 – une référence pour les formats de fichier et pour le type de plan que cet utilisateur choisit.
Étape 2 : Mesurer la position du sujet – PAR SEGMENT, à chaque fois
Ne réutilisez jamais la position mesurée de la tête d'un segment pour le calcul du recadrage d'un autre segment, même dans le même enregistrement continu – une personne peut se déplacer dans son siège au fil des minutes d'une longue prise, et un recadrage calculé à partir d'une image d'un segment précoce peut décentrer, ou même couper, le sujet dans un segment ultérieur qui semble similaire mais n'est pas cadré de la même façon.
Pour chaque segment que vous allez recadrer (y compris les plans « larges » – voir Étape 4) :
ffmpeg -nostdin -y -ss <horodatage_milieu_de_ce_segment> -i source.mp4 -frames:v 1 ref.png
ffmpeg -nostdin -y -i ref.png -vf "drawgrid=w=54:h=96:t=1:[email protected]" grid.png
(54x96 donne une grille 20x20 sur une image 1080x1920 – adaptez proportionnellement pour d'autres résolutions.) Regardez grid.png et lisez :
cx– le x du centre du visage/nezhead_top_y– le y du sommet des cheveux (pas les yeux – l'espace au-dessus se mesure à partir du sommet de la tête)
Étape 3 : Calculer le recadrage
python3 scripts/compute_crop.py --cx <x_mesuré> --head-top-y <y_sommet_tête> --zoom 1.7
python3 scripts/compute_crop.py --cx <x_mesuré> --head-top-y <y_sommet_tête> --baseline
--zoom Zpour un niveau de punch-in explicite (par ex.1.7pour un plan à 170 %).--baselinepour les plans « larges » : il trouve le recadrage LE PLUS LARGE qui satisfait toujours l'objectif d'espace au-dessus et recentre horizontalement – c'est ce qui fait que les plans larges sont seulement modérément recadrés (pas littéralement 100 %/intacts) mais partagent exactement le même centre optique et le même espace au-dessus que les plans zoomés, ce qui empêche réellement les coupes de donner l'impression que le sujet saute. Ne sautez pas le recadrage des plans larges – les laisser bruts tout en ne corrigeant que les plans zoomés était la plus grande source de « quelque chose cloche » lors des tests, plus que de se tromper sur un niveau de zoom individuel.
L'espace au-dessus par défaut est de 9 % de la hauteur du cadre recadré, ce qui semblait correct lors des tests – en dessous, cela paraissait serré, et ce n'est pas simplement esthétique : une plateforme verticale (Instagram/TikTok/Shorts) superpose l'UI (nom d'utilisateur, légende, fondu) tout en haut du cadre, donc cette marge est un espace fonctionnel que la plateforme couvrira, pas un espace libre. Remplacez par --headroom si un projet spécifique demande autre chose, mais ne descendez pas trop en dessous de ~9 % pour tout ce qui est destiné à ces plateformes.
Tout le raisonnement ci-dessus se trouve dans le docstring de scripts/compute_crop.py – il est court, lisez-le si quelque chose n'est pas clair sur un cas spécifique.
Étape 4 : Vérifier le rendu réel avant le rendu complet
Un recadrage qui semble correct sur une seule image fixe peut toujours couper ou décentrer le sujet une fois que vous vérifiez le mouvement. Pour chaque segment (surtout celui qui montrait le plus de mouvement corporel – se pencher, incliner la tête – lorsque vous choisissiez l'image de référence) :
- Rendez un seul segment avec son recadrage appliqué.
- Échantillonnez 3+ images réparties sur sa durée réelle (pas seulement celle que vous avez mesurée), par ex.
ffmpeg -i clip.mp4 -vf fps=3 check_%02d.png. - Regardez-les. Si la tête est jamais coupée ou trop près d'un bord, desserrez le zoom de ce segment ou élargissez sa marge avant de continuer.
Ce n'est qu'après cette vérification que vous devez rendre tous les segments et concaténer.
Étape 5 : Transformer le plan confirmé en coupes ancrées sur les mots, puis assembler
- Obtenez les horodatages des mots à partir d'un rendu de la même coupe (une coupe simple, ou un rendu zoom précédent – le padding doit correspondre), et notez les durées par clip de ce rendu (ffprobe chaque
clip_NNN.mp4dans son répertoire de travail) :python3 scripts/word_times.py cut_render.mp4 words.json --language sr - Construisez le fichier de sélections.
cuts.txt= lesstart<TAB>endnon paddés de la compétence de base par clip ;pos.json=[[cx, head_top_y], ...]par clip depuis l'Étape 2 (mesuré sur l'image pivotée si--pre-filterpivote) :
Lisez le rapport de correspondance qu'il imprime : chaque mot planifié devrait indiquerpython3 scripts/build_zoom_selections.py --cuts cuts.txt --positions pos.json \ --plan plan.json --sentences sentences.txt --words words.json \ --durations 6.28,6.08,... [--pre-filter transpose=1] > selections.txtexact(ou une variante orthographiquefuzzy~raisonnable – Whisper peut épeler les noms différemment ou en cyrillique). Toute ligneproportional (no match!)doit être corrigée manuellement avant le rendu. Le changement se produit 0,08 s avant le mot (--lead). Les clips avec plusieurs niveaux sont rendus comme un seul clip continu (split + overlay temporisé), donc un zoom en milieu de phrase n'ajoute jamais de coupe ni de jointure audio. - Vérifiez ponctuellement les images 0,2 s avant et après chaque changement de zoom (logique de l'Étape 4 : tête jamais coupée, changement clairement visible), puis rendez :
scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4 "$PWD/build"
Passez le répertoire de travail comme un chemin ABSOLU – avec un chemin relatif, l'étape finale de concaténation ne trouve pas concat_list.txt.
Les durées des clips rendus peuvent différer de quelques centièmes de seconde par rapport au rendu précédent, donc si les sous-titres (compétence subtitle) étaient synchronisés sur un rendu antérieur, décalez chaque sous-titre de son nouveau début de clip avant de les incruster.
Lisez le bloc de commentaires en haut de cut_and_concat.sh avant de l'adapter – il documente les pièges ffmpeg/bash (vol de stdin dans les boucles, fondus qui mangent la dernière syllabe d'une coupe, pourquoi les clips doivent être ré-encodés individuellement avant concaténation) qui sont faciles à réintroduire silencieusement.
Exemple de fichier de sélections mélangeant un segment large (baseline) et deux punch-ins :
63.82 69.57 crop=908:1614:152:304,scale=1080:1920
80.37 85.90 crop=636:1130:288:348,scale=1080:1920
97.25 100.81 crop=908:1614:152:304,scale=1080:1920
Si la source nécessite également une correction de rotation/orientation, cela est traité séparément (ne fait pas partie de cette compétence, car ce n'est pas toujours nécessaire) – ajoutez vous-même le filtre de rotation convenu à la chaîne de filtres de chaque ligne si cela s'applique ici.
Livraison
Si la sortie maître dépasse la limite de taille de fichier d'un outil, faites une copie compressée secondaire 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
Les objets que le présentateur montre doivent rester entiers dans chaque zoom
Sur un vrai montage, le présentateur montre des photos sur une tablette à côté de lui ; les recadrages centrés sur le visage coupaient son bord gauche ("ne sme tablet da bude isečen ovako nigde"). Avant de rendre, listez tout ce qui se trouve dans le cadre et qui est IMPORTANT dans la vidéo (une tablette, un produit, une page tenue en l'air) et mesurez son cadre sur la grille. Chaque recadrage de zoom doit contenir ce cadre avec ~30 px d'air : déplacez le x/y du recadrage vers lui (le visage peut être décentré, jusqu'à ~70 % sur le côté), et si le visage touche alors le bord, utilisez un niveau plus large pour ce changement (1,7x → 1,6x sur un vrai montage) plutôt que de couper l'un ou l'autre. Regardez une image de chaque plage de zoom pour confirmer que les deux sont entiers.