Communitygithub.com

localizing-video

Localisez une vidéo en langue étrangère (mandarin/japonais) en anglais avec doublage synchrone, sous-titres et traduction du texte à l'écran, le tout localement et gratuitement sur Apple Silicon.

Qu'est-ce que localizing-video ?

Ce skill automatise la localisation complète d'une vidéo en langue étrangère (notamment mandarin ou japonais) vers l'anglais, en produisant un doublage (dub) synchrone, des sous-titres anglais et une traduction du texte incrusté à l'écran, le tout en local et gratuit sur Apple Silicon. Il s'appuie sur une pipeline éprouvée (yt-dlp, Whisper, Kokoro-onnx TTS, ffmpeg-full, ocrmac) et évite les pièges classiques : dérive temporelle du doublage, sous-titres qui masquent la vidéo, OCR mal configuré, ou Whisper qui « corrige » des jeux de mots homophoniques. Le skill propose une implémentation de référence (dossier `engine/`) avec un jeu de données complet dans `example/`. Il gère le téléchargement, la reconnaissance vocale, la traduction manuelle (par l'agent), la synthèse vocale, le brûlage des sous-titres dans une barre letterbox, l'OCR du texte visuel, et le mixage final. Le point clé est l'ancrage temporel des segments de doublage pour éliminer la dérive, et le ré-étalonnage visuel (freeze-extend des diapositives) pour compenser la densité moindre de l'anglais.

Compatible avec~Claude Code✓Codex CLI✓Cursor✓Antigravity
npx skills add evnchn-agentic/localizing-video

Demander à votre IA préférée

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

Documentation

Que fait localizing-video ?

Transformez une vidéo en langue étrangère en une vidéo anglaise — doublage synchrone + sous-titres anglais + texte à l'écran traduit — 100 % local et gratuit sur Apple Silicon. Un agent compétent pourrait reconstruire le pipeline (yt-dlp, whisper, ffmpeg, scene-detect, mux) ; ce skill existe pour les choix d'outils et les pièges silencieux que la reconstruction manuelle rate, et pour l'implémentation de référence éprouvée.

Implémentation de référence (commencez ici)

Ce dépôt contient la pipeline fonctionnelle : engine/ est le moteur générique et exécutable (build_dub.py — découpage par phrase, sans dérive (voir le piège du doublage), make_ass.py, dense_ocr.py, dedup_spans.py, render_overlay.py, run.sh) ; example/ est un jeu de données complet. Lisez engine/SETUP.md + example/README.md et lancez le moteur — n'écrivez pas de zéro. Pour chaque vidéo, le seul travail nouveau est la production de en.srt + translations.json (l'étape de traduction). Le moteur suppose une source en 1920×1080.

Pipeline + décisions à chaque étape

  1. Téléchargement yt-dlp (essayez --extractor-args "youtube:player_client=web_safari,ios,tv" en cas d'erreur 403 ; le format HLS fonctionne souvent quand le DASH est protégé par DRM).
  2. ASR — mais yt-dlp --list-subs D'ABORD (voir piège) : une piste manuelle/créateur peut surpasser Whisper gratuitement, le tag -orig révèle la vraie langue source (évite de lancer Whisper pour 16 minutes sur une vidéo déjà en anglais), et un sous-titre auto -orig est une deuxième ASR gratuite pour recouper. Ensuite, openai-whisper whisper source.mp4 --model large-v3-turbo --language <zh/ja> --word_timestamps True --output_format all (conservez le JSON). Fixez la langue. (torch n'a pas de wheel py3.14 → exécutez whisper depuis un venv py3.12.)
  3. Traduisez le transcript VOUS-MÊME (l'agent, en boucle) — gratuit, respectueux du ton, cohérent pour le jargon. Conservez la correspondance 1:1 segment↔timestamp. Corrigez les noms propres par votre connaissance (l'ASR les déforme). Préservez la blague, pas seulement les mots — les jeux de mots survivent rarement à une traduction littérale ; trans-créez un jeu de mots anglais qui porte le même mécanisme, et placez la chute dans le sous-titre (le support principal de la blague — le doublage passe par-dessus l'original atténué). Les jeux de mots homophoniques sont invisibles dans l'ASR (voir piège), donc OCRisez le sous-titre incrusté pour les trouver.
  4. Doublage — Kokoro-onnx TTS (voir pièges). Synthèse par phrase, ancrez chaque phrase à son timestamp réel (PAS de rattrapage par segment — ça accumule du décalage), ajustement temporel, mixage. Recalibrez les visuels pour rencontrer le doublage, pas seulement le doublage sur l'horloge — voir Densité temporelle.
  5. Sous-titres — brûlez l'anglais dans une barre letterbox, PAS sur l'image (voir pièges).
  6. OCR visuel — ocrmac (Apple Vision), dense 1 ips, dédoublonné par intervalles de temps.
  7. Traduisez les chaînes visuelles VOUS-MÊME ; superposez l'anglais par-dessus les boîtes chinoises uniquement.
  8. Mixage — ffmpeg-full (libass), superposition → letterbox → brûlage des sous-titres → audio du doublage.

Pièges — la vraie raison d'être de ce skill

PiègeRéalité / correctif
ffmpeg n'a pas le filtre subtitlesle ffmpeg par défaut de brew manque libass. Utilisez /opt/homebrew/opt/ffmpeg-full/bin/ffmpeg. (skill : hardsubbing-video)
TTS = say est plat/robotiquePar défaut, utilisez Kokoro-onnx (gratuit, neural, compatible py3.14 via onnxruntime). Voix masculine am_michael/etc.
Kokoro échoue silencieusement : phontab ... No such filele chemin des données espeak fournies est cassé. brew install espeak-ng, puis dans le script, remplacez espeakng_loader.get_data_path/get_library_path par /opt/homebrew/share/espeak-ng-data + /opt/homebrew/lib/libespeak-ng.dylib.
L'anglais est plus dense à prononcer que le ZH/JA → le doublage prend du retardNE PAS utiliser atempo (>1,25× = artéfacts vocodeur) — resynthétisez avec la vitesse native de Kokoro. NE PAS rattraper le décalage avec un curseur progressif (cursor-start catch-up) : empiriquement, cela accumule ~+5s au milieu de la vidéo pour une parole dense et consécutive. À la place : (a) ancrez chaque énoncé à son vrai début (= la timeline des sous-titres/vidéo) pour que le décalage ne puisse pas s'accumuler ; (b) regroupez les segments ASR adjacents en énoncés de ~phrase pour qu'une ligne anglaise longue emprunte du temps à un voisin court — l'ajustement par segment tronquait 59 % des lignes à une limite de 1,9× ; le regroupement par phrase a réduit cela à ~0. L'ancrage tue le décalage, PAS le dépassement — un segment dont l'audio dépasse son créneau entre toujours en collision avec l'ancre suivante. Ordre de repli en cas de dépassement : (1) traduction plus concise, (2) resynthétisation plus rapide (dans la limite de ~1,9×, "compresser" = resynthèse à vitesse native ou texte plus court — PAS atempo), (3) extension figée de la diapositive en dessous (voir Densité temporelle), (4) emprunt de mou à un voisin de faible densité ; en dernier recours, limite dure + fondu de 30 ms en queue (le sous-titre porte toujours la ligne complète).
Les sous-titres anglais s'affichent ENORME sur la vidéoSRT→libass utilise par défaut PlayResY=288 → la police/marge est agrandie ~4,4×. Générez ASS avec PlayResY explicite = hauteur avec padding. Et ajoutez une letterbox (barre en bas, Alignment=2) pour que l'anglais n'entre pas en conflit avec les sous-titres incrustés de la source (vous ne pouvez pas les supprimer).
L'OCR Vision "nécessite un script personnalisé"Non — pip install ocrmac ; ocrmac.OCR(f, language_preference=["zh-Hans","en-US"]).recognize() renvoie (text, conf, (x,y,w,h)) normalisé, origine en bas à gauche.
Concilier l'ASR avec le sous-titre à l'écranLe sous-titre en bas est la vérité de référence pour la narration — excellent recoupement ASR. MAIS les créateurs placent des sous-titres gag (charabia ≠ ligne parlée) ; ne remplacez jamais aveuglément l'ASR par l'OCR. Signalez les divergences, jugez chaque cas.
L'ASR aplatit silencieusement un jeu de mots homophoniqueLa blague peut être l'homophone : 人工智能 (IA) prononcé 人工痔能 ("technologie hémorroïdaire"), ou 搁置 ("mettre le projet en attente") comme 割痔疮 ("ablation d'hémorroïde"). Whisper "corrige" le mot rare vers le mot courant, donc le gag n'atteint jamais le transcript — et aucune correction de nom propre ne le récupère. Les créateurs écrivent généralement le jeu de mots dans le sous-titre incrusté, donc ici l'OCR n'est pas un recoupement — c'est souvent la seule voie de récupération fiable (pour un homophone écrit, le sous-titre l'emporte, contrairement au cas du sous-titre gag ci-dessus). Indice : une ligne semble étrangement littérale, ou la réaction/le visuel ne correspond pas aux mots → OCRisez ce sous-titre avant de traduire.
Lancer Whisper avant de vérifier les sous-titres YouTubeyt-dlp --list-subs coûte 5s et rapporte trois fois. (1) Le tag <lang>-orig = la vraie langue source de la vidéo ; si c'est déjà votre CIBLE (ex. en-orig), toute la pipeline est une non-opération — arrêtez-vous avant de télécharger + lancer Whisper. (2) Une piste manuelle/créateur (langue source) surpasse souvent Whisper : ponctuation réelle, noms propres corrects, pas de [musique] injecté. (3) Un sous-titre auto -orig est une deuxième ASR gratuite — comparez-le à Whisper pour repérer exactement les lignes nécessitant une correction humaine. Mesuré sur une intervention anglaise de 16 min : 98,2 % d'accord sur les mots, mais les deux moteurs divergent sur Arthur Andersen (YT, correct) vs Anderson (Whisper, faux) et les deux ont écorché un nom de marque (Saily → "Saylite"/"Saley") — le recoupement les signale ; aucun moteur seul ne les détecte. Piège : les 100+ autres langues dans --list-subs sont des auto-TRADUCTIONS YouTube, pas des pistes de créateur — ne prenez pas une ligne zh-Hant pour une vraie source chinoise.
Superposer chaque boîte chinoiseDans les vidéos de commentaire, une grande partie du contenu à l'écran (articles, interventions embarquées, tweets) est déjà en anglais avec du chinois superposé — le recouvrir est redondant et encombrant. Limitez-vous aux éléments uniquement chinois.
Superposition statique sur du texte défilantLa superposition par intervalle (position moyenne) ne peut pas suivre un défilement continu — le chinois reste découvert. Nécessite une OCR image par image + suivi de boîte ; généralement pas rentable (ce contenu est déjà du type anglais).
Garder les extraits anglais originaux audiblesLà où la source diffuse de vrais extraits anglais (interviews/démos), passez l'audio original à plein volume dans les trous ; atténuez-le (~0,1) seulement sous le doublage.
Réutiliser les scripts sur une NOUVELLE vidéoAdaptez, mais vérifiez les longueurs/chemins codés en dur. render_overlay.py dérivait la durée de la superposition d'un nombre d'images codé en dur — sur une vidéo plus longue, la superposition s'arrêtait silencieusement en chemin. Dérivez la durée de len(glob('f1/f_*.png')), pas d'une constante. Retraduisez en.srt + translations.json par vidéo ; l'assertion d'appariement d'index + une vérification côte à côte attrapent un mauvais alignement traduction/clé.

Densité temporelle : recalibrer la timeline, pas seulement le doublage

L'anglais transporte ~1,5–2× moins de bits/seconde que le mandarin/japonais, donc un doublage fidèle lutte toujours pour gagner du temps sur une source dense. Compresser le doublage (vitesse native) est un levier, et il plafonne à ~1,9× avant de paraître précipité. Le levier principal : la timeline visuelle est élastique — recalibrez-la pour rencontrer le doublage. Trois leviers, classés du moins cher au plus coûteux :

  • Traduction plus concise d'abord (dense→plus court). Reformulez les lignes les plus denses pour les raccourcir. Gratuit, ne perd rien que le spectateur voie, et le sous-titre brûlé porte toujours le sens complet si le doublage coupe un cheveu. C'est le levier principal pour les plans de tête parlante (vous ne pouvez pas figer un visage en mouvement sans saccade ; recadrer avec des images de coupe est généralement hors de portée pour une localisation sur place).
  • Figer et étendre les diapositives statiques (gagnez du temps là où la pression est forte). Le doublage accélère le plus exactement quand le narrateur lit une capture d'écran/diapositive — et cette image est statique, donc maintenez-la un peu plus longtemps à une pause naturelle pour gagner du temps de parole. Presque invisible SI vous respectez trois conditions : (i) c'est une modification de timeline — tout en aval est décalé par la prolongation, donc insérez le freeze et régénérez le timing des sous-titres/doublage/superposition sur la timeline prolongée (ou maintenez toutes les pistes ensemble au mixage), ne vous contentez pas d'ancrer au temps original ; (ii) freezez uniquement là où le fond sonore fait aussi une pause — un freeze sur une musique continue/un bruit ambiant/audio original désynchronise le fond (étendez-le/croisez-le, ou freezez uniquement sous le doublage là où le fond est atténué/silencieux) ; (iii) "statique" doit être vraiment statique — un curseur, un caret clignotant, un pointeur laser, ou une incrustation webcam dans l'image brise l'illusion. Faites beaucoup de petits freezes, jamais un long (un freeze de plusieurs secondes ressemble à un blocage/buffer).
  • Récupérez le mou des passages de faible densité (l'inverse — moins dense→dense). Les intros, conclusions, plans lents ont de la marge ; laissez le doublage respirer là, ou empruntez ce mou vers l'avant pour un passage dense.

Pourquoi ça marche : la densité et la possibilité de freeze sont corrélées. La narration est la plus dense parce que le narrateur explique une diapositive — et les diapositives sont exactement ce qui est freezable. Donc la majeure partie de la pression temporelle tend à atterrir là où vous pouvez l'absorber. Mesurez-le, ne le supposez pas : marquez chaque seconde comme statique par le mouvement image par image (mean|image − précédente| sur une échelle de gris réduite) — mais le seuil est un calibrage par vidéo (le bruit du codec, le débit, les fps, le grain le déplacent ; ≈4/255 était une valeur pour une vidéo), et une moyenne sur toute l'image rate les mouvements localisés (curseur/caret/pointeur) → masquez la bande de sous-titres et vérifiez une région statique, pas l'image entière. Puis intersectez le masque statique avec les segments de doublage rapide pour voir combien de pression est freezable. Dans une vidéo explicative de 10 min mesurée, un plafond calme de 1,4× nécessitait ~14s récupérées et 74 % de ce temps se trouvait sur des diapositives statiques freezables (26 % sur une tête parlante non freezable → formulation plus concise) — mais cette répartition est vidéo par vidéo. Dépasser la durée source est acceptable pour un fichier autonome ; rien d'externe ne se synchronise dessus.

Points de décision à soumettre à l'opérateur

  • Étendue visuelle : ciblée (chinois uniquement, recommandé) vs complète (aussi les paragraphes d'article — se dégrade sur le défilement).
  • Voix du doublage : quelle voix Kokoro gratuite ; proposez le payant (ElevenLabs) seulement s'ils relâchent la contrainte de gratuité.
  • Audio : remplacement complet vs garder l'original atténué sous le doublage.
  • Effort de rythme : accepter un doublage rapide (les sous-titres portent le sens complet) vs recalibrer pour un plafond calme (formulation plus concise + freeze-extend des diapositives — coûte une mesure + un nouveau mixage). Proposez le chiffre de la mesure pour qu'ils décident.

Vérifiez (ne faites pas confiance — vérifiez)

Échantillonnez la MP4 finale (lisez les JPG) à : un moment de narration, un moment de diapositive/superposition, un trou d'extrait anglais, et la dernière ligne — confirmez la synchro des sous-titres, le placement de la superposition, la letterbox, et la correspondance des durées A/V. Les pixels brûlent de façon permanente. Échantillonnez spécifiquement un moment de diapositive DENSE (là où le doublage accélère) — c'est là que la synchro/dérive casse en premier ; le sous-titre chinois brûlé à cet instant est votre oracle de synchro. (Vous ne pouvez pas écouter l'audio vous-même — la synchro est structurelle via la timeline ancrée ; dites-le et demandez à l'opérateur une écoute finale.)

Se combine avec

hardsubbing-video (les pièges du brûlage ffmpeg/libass — étape 8 de ce skill).

Skills associés