/dubbing
Une compétence qui double automatiquement des vidéos via l'API Perso AI Dubbing.
Règles principales (à suivre impérativement)
- Seul le worker voit la clé brute. Ne jamais ouvrir la clé avec
Read, l'afficher ou la passer en argument de ligne de commande. Les workers l'envoient uniquement dans l'en-têteXP-API-KEY. - Une commande = tout le travail.
scripts/dubbing.mjsgère lui-même le téléchargement, le découpage, la traduction, la fusion et la sauvegarde. Ne jamais exécuterprepare_input.mjsouprobe_split.mjsavant — ce sont des outils de débogage, etprobe_split.mjseffectue un vrai téléchargement (la vidéo serait téléchargée deux fois). - Toujours exécuter
dubbing.mjsen arrière-plan. Les travaux prennent de quelques minutes à plusieurs heures ; un délai d'attente du shell au premier plan tuerait l'exécution en cours de route. - Langues non spécifiées → source
auto, cibleen. - Plusieurs langues en une seule commande (
--target en,zh,ja) — le téléchargement/découpage a lieu une fois et est réutilisé par langue. Ne jamais exécuter une fois par langue (cela retéléchargerait la source à chaque fois). - Relayer fidèlement la progression. Afficher les lignes
[progress]stdout du worker dans le chat, textuellement ou résumées — et ne pas annoncer des étapes qui n'apparaissent pas (le découpage/fusion n'existent que pour les vidéos dépassant la limite). Les logs détaillés de stderr en retrait n'ont pas besoin d'être relayés. - Tenir l'utilisateur informé pendant l'exécution. Le doublage, le lip-sync et la séparation audio affichent tous un battement de cœur
[status](étape + nombre d'éléments, par ex.ja 4/6 · en 1/6ouseparating — file 2/5, avec un indicenext check ~Nm), et chaque élément terminé est diffusé sous forme de ligne[progress]. Vérifier la sortie en arrière-plan environ 3 minutes après le lancement, puis à l'intervalle indiqué (10 min pour les médias ≤10 min, 30 min pour les plus longs) : relayer immédiatement toute nouvelle ligne[progress], sinon publier la dernière ligne[status]comme mise à jour d'une seule ligne. Ne jamais inventer de pourcentages, de temps écoulé ou d'ETAs — ne relayer que ce que disent les lignes. Les arrêts nécessitant l'utilisateur ([space-select],[split-confirm],[free-limit],[resume-check],[credit-check], crédit épuisé, achèvement) terminent le processus, donc ils apparaissent sans sondage. - Nommer votre runtime pour la télémétrie (silencieuse). Ajouter
--host <claude-code|codex|cursor|antigravity|gemini>à la commande du worker, en nommant l'agent que vous exécutez, si vous pouvez le dire. Cela ne fait que taguer la télémétrie d'utilisation — ne jamais le montrer à l'utilisateur et ne jamais lui demander. Omettre si vous n'êtes pas sûr. - Les formats non pris en charge sont ignorés automatiquement et le reste continue le traitement — relayer l'avis d'ignorance.
- Une pause n'est pas un échec. Une ligne
[space-select],[split-confirm],[free-limit],[credit-check]ou[resume-check]signifie que l'exécution s'est arrêtée pour vous demander quelque chose — elle se termine avec le code 0 (donc elle ne ressemble jamais à une erreur), mais le travail n'est pas terminé. Chaque fois que l'une de ces lignes est présente, ne jamais signaler l'exécution comme terminée : la relayer et agir en conséquence. Les lignes sont le signal, pas le code de sortie.
Configuration (paresseuse — aucune étape préalable)
- Clé API — pas une étape à exécuter en premier. Le worker se bloque lui-même au moment où une commande nécessitant l'API Perso s'exécute : lors de cette première exécution, il ouvre une page de navigateur (connexion, un clic — la clé est stockée chiffrée), ou imprime des instructions basées sur un fichier si aucun navigateur ne peut s'ouvrir — les relayer à l'utilisateur. Sur une machine headless/SSH, le worker ignore complètement le flux du navigateur et commence par l'enregistrement par fichier/
--import(la page du navigateur ne peut délivrer une clé qu'à la machine sur laquelle elle s'exécute) — relayez ces instructions de la même manière. Ne jamais déclencher vous-même l'enregistrement de la clé : le worker lanceconnect.mjs/resolve_key.mjs --watchquand nécessaire, donc ne les exécutez pas, et n'exécutez pasresolve_key.mjs --checkcomme étape préalable (uniquement si l'utilisateur demande explicitement si une clé est enregistrée — une clé manquante n'est pas une erreur). Ne jamais coller la clé dans le chat. Obtenir une clé : https://developers.perso.ai/api-keys - ffmpeg/ffprobe — installé automatiquement uniquement lorsqu'une vidéo dépasse la limite du plan et doit être découpée (approuver si une autorisation est demandée). Vérification manuelle :
node scripts/check_deps.mjs.
Exécution
Collecter l'entrée (chemin local ou URL — redemander si manquant) et exécuter en arrière-plan — le worker effectue lui-même le blocage de la clé lors du premier besoin ; relayer simplement ses instructions imprimées si l'enregistrement démarre :
- Simple :
node scripts/dubbing.mjs "<fichier|URL>" [--source auto] [--target en] [--space "nom de l'espace"] [--out resultat.mp4] [--lipsync] [--no-save] - Multilingue / multi-entrée (une ou plusieurs entrées ; URLs et fichiers peuvent être mélangés) :
node scripts/dubbing.mjs "<URL>" "<fichier>" --target en,ja— une sortie par entrée × langue, sauvegardée à côté de chaque source (--out <dossier>les rassemble). - Dossier (par lots) :
node scripts/dubbing.mjs "<dossier>" [--target en,zh] [--recursive] [--out dossier-sortie]
Codes de langue — --target prend un code de langue (en, ja) ou une balise régionale. Les variantes régionales existent uniquement pour l'anglais (en = US par défaut, en-GB = Royaume-Uni), le portugais (pt = Brésil par défaut, pt-PT = Portugal) et l'espagnol (es = Mexique par défaut, es-ES = Espagne) ; en-US, pt-BR et es-MX sont des orthographes acceptées de ces valeurs par défaut. Chaque autre langue a une variante unique, et --source n'a besoin que de la langue (une balise régionale y est traitée comme la langue simple).
- Région nommée dans la requête → utiliser cette balise exacte. Une région sans variante (par exemple l'anglais australien) → dire à l'utilisateur que seules les options listées existent et le laisser choisir avant d'exécuter.
- Les jetons de deux lettres sont des codes de langue, pas des pays :
ukest ukrainien — l'anglais britannique esten-GB. Même piège pourca(catalan),be(biélorusse),eu(basque). Si le libellé de l'utilisateur peut signifier l'un ou l'autre, demander lequel il veut dire avant d'exécuter. - Langue nommée sans région → la variante par défaut. Pour ces trois langues, nommer la valeur par défaut appliquée dans la note de livraison (par exemple "Espagnol (Mexique) — utilisez
es-ESpour l'Espagne").
Emplacement de sauvegarde — --out prend soit un dossier (un répertoire existant, ou un chemin se terminant par /) qui rassemble les résultats à l'intérieur, soit un chemin de fichier pour une seule entrée (l'extension réelle est ajoutée si le chemin n'en a pas). La valeur par défaut est à côté de chaque fichier source ; ne pas demander. Pour le premier travail de la session, l'indiquer dans la ligne de lancement (par exemple "Les résultats seront sauvegardés à côté des originaux — dites-moi maintenant si vous souhaitez un dossier différent") et commencer immédiatement, sans attendre de réponse. Si l'utilisateur nomme un dossier — avec cette demande ou à tout moment ultérieur de cette session — exécuter avec --out "<dossier>" et continuer à utiliser ce dossier pour chaque travail ultérieur jusqu'à ce qu'il le change. S'il redirige après qu'une exécution a déjà commencé, déplacer les fichiers terminés là-bas une fois l'exécution terminée. Exception : lorsque chaque entrée est une URL (pas de dossier source local), demander où sauvegarder avant de commencer.
Sélection de l'espace — avec plusieurs espaces de travail, le worker s'arrête avant le téléchargement, imprime les lignes [space-select] (nom | (plan) | crédits restants) et s'arrête. Montrer à l'utilisateur UNIQUEMENT ces options (pas de chiffres internes), demander laquelle, et réexécuter avec --space "<nom de l'espace>". Un espace → pas de question ; PERSO_SPACE_SEQ le fixe.
Confirmation du découpage — si l'entrée dépasse la limite de durée ou de taille et doit être automatiquement découpée et fusionnée (doublage, dub+lip-sync, ou séparation audio), le worker s'arrête et imprime les lignes [split-confirm]. Les relayer et demander à l'utilisateur : cela dépasse la limite de durée/taille, donc cela nécessite un découpage automatique → traitement → fusion, qui peut donner un résultat moins soigné que s'ils le découpaient eux-mêmes — procéder automatiquement ? Sur un oui, réexécuter la même commande avec --allow-split (rien n'est facturé jusqu'à ce point, donc la réexécution est gratuite). Exécutions par lots : --allow-split autorise chaque découpage de l'exécution.
Ne pas sauvegarder (serveur uniquement) — lorsque l'utilisateur veut que la vidéo soit doublée mais pas sauvegardée en fichier local, ajouter --no-save : le worker laisse le résultat dans l'espace de travail Perso de l'utilisateur et ignore le téléchargement, en imprimant Conservé sur le serveur, non sauvegardé : … → projet <seq> (la ligne [project-ref] est toujours émise, donc le doublage peut être lip-syncé ou récupéré plus tard). Vidéos simples/non découpées uniquement — le fichier fusionné d'une vidéo découpée nécessite un téléchargement local, donc il est sauvegardé normalement et le worker le dit. Ne peut pas être combiné avec --lipsync (la vidéo lip-syncée doit être téléchargée).
Plan gratuit (les résultats restent sur Perso) — un espace de travail Gratuit peut doubler, lip-syncer et séparer normalement, mais le serveur refuse tous les téléchargements, donc le worker n'y sauvegarde jamais de fichier. Il imprime Plan gratuit — <nom>: … prêt sur Perso → <url> (les téléchargements nécessitent un plan payant) — relayer ce lien comme le résultat livré : l'exécution est terminée, rien n'a échoué et il n'y a rien à reprendre. Si l'utilisateur veut des fichiers réels, cela nécessite un plan payant — proposer le chemin de mise à niveau (voir Mise à niveau du plan et crédits), jamais une réexécution.
Pauses [free-limit] du plan gratuit — deux d'entre elles, toutes deux avant que quoi que ce soit ne soit facturé :
- Aperçu de 30 secondes. Une entrée de doublage/lip-sync plus longue que 30 s s'arrête avec des lignes
[free-limit]: sur le plan Gratuit, seules les 30 premières secondes seraient doublées, le reste laissé de côté. Relayer la question, et sur un oui, réexécuter la même commande avec--allow-preview(le worker découpe les 30 premières secondes localement et ne soumet que cela). Un lien de plateforme importé par Perso ne peut pas être découpé — là, la réponse est un fichier local, un clip plus court ou une mise à niveau. - Dépassement de la limite du plan. Les médias dépassant la limite de durée/taille du plan ne reçoivent aucune offre de découpage sur le plan Gratuit (
--allow-splitne s'applique pas) : les lignes[free-limit]disent qu'un fichier plus court ou une mise à niveau est la seule option. Les relayer et s'arrêter.
Pendant son exécution (pour expliquer l'attente) :
- Découpage : le fichier entier est téléchargé en premier ; seul un rejet de limite de plan installe ffmpeg et découpe sans perte. Les URLs externes (YouTube·TikTok·Drive·Vimeo) sont traitées côté serveur.
- File d'attente : toutes les entrées × parties × langues partagent un même pool ; une file d'attente pleine est revérifiée toutes les 5 minutes. Une erreur de moteur sur une partie annule les autres langues de cette partie ; une partie silencieuse transmet l'original ; une garde d'inactivité empêche les blocages infinis.
- Sauvegarde : les parties sont fusionnées en un seul fichier par (entrée × langue). Une sortie non découpée conserve le nom de fichier Perso ; une sortie fusionnée est
<nom-original>.doublée.<langue>.<ext>; les collisions obtiennent_2,_3….
Lip-sync
Le lip-sync (bouche synchronisée avec l'audio doublé) s'exécute après le doublage, sur le projet de doublage terminé. Vidéo uniquement — les entrées audio sont rejetées. C'est un travail long : exécuter en arrière-plan et dire à l'utilisateur d'avance que cela prend considérablement plus de temps que le doublage. Crédits (la facturation du serveur est faisant autorité) : doublage ≈ secondes ×1 · lip-sync ≈ ×2 · les deux ≈ ×3 — doublage maintenant + lip-sync plus tard coûte le même montant que les deux à la fois. Sources 4K+ : chaque tarif ×3 sur les plans pro/business/entreprise (par exemple, une vidéo 4K de 1 min dub+lip-sync ≈ 60×3×3 = 540) — le mentionner lorsque la vidéo est en 4K.
Choisir le flux en fonction de ce qui existe :
- Nouvelle vidéo + lip-sync — une commande exécute toute la chaîne (dub → lip-sync → sauvegarde) ; avertir que les deux étapes sont facturées :
node scripts/dubbing.mjs "<fichier|URL>" --target en --lipsyncSi les lignes[credit-check]s'impriment et qu'il s'arrête, l'estimation dépasse les crédits restants : afficher ces lignes (URL de recharge incluse), et réexécuter avec--forceuniquement après que l'utilisateur a rechargé ou approuvé la poursuite quand même. - Doublé plus tôt dans cette session — chaque exécution terminée imprime une ligne
[project-ref] {...}. La conserver ; ne jamais la montrer à l'utilisateur. Lip-sync sans re-doublage (×2, pas de frais de doublage) :node scripts/dubbing.mjs --lipsync-only '<ce [project-ref] JSON>' - Pas de [project-ref] dans cette session — demander si l'utilisateur connaît le numéro de projet depuis le portail Perso (
--lipsync-only <numéro>, ×2). Sinon, la vidéo doit être doublée à nouveau (--lipsync, ×3) — confirmer avant de re-doubler.
Règles :
- Répéter le lip-sync sur le même projet facture à nouveau (pas de déduplication côté serveur). Si cette session l'a déjà lip-syncé, pointer vers le fichier existant et réexécuter uniquement sur confirmation explicite.
- Si le lip-sync échoue, le worker sauvegarde la vidéo doublée à la place et le dit dans le rapport final — le relayer clairement ; les crédits de doublage ne sont pas gaspillés.
- Crédits épuisés entre le doublage et le lip-sync : les vidéos doublées sont sauvegardées et la suite termine uniquement le lip-sync — relayer l'URL de recharge, et continuer via le chemin
[resume-state]une fois payé (voir Interruption et reprise).
Locuteur (projets de doublage uniquement)
Pour attribuer un nouveau locuteur à une ou plusieurs phrases d'un projet déjà doublé, exécuter :
node scripts/speaker.mjs "<URL du projet|projectSeq>" --at "5s,22s,98s" (timecodes/plages séparés par des virgules)
node scripts/speaker.mjs "<URL du projet|projectSeq>" --text "première ligne|deuxième ligne" (séparés par des pipes, texte entre guillemets)
node scripts/speaker.mjs "<URL du projet|projectSeq>" --sentence 1234,1288 (réexécution après une question [sentence-select])
node scripts/speaker.mjs "<URL du projet|projectSeq>" --list (parcourir les phrases du projet pour trouver la bonne cible)
Projets de doublage uniquement — le worker rejette les projets de sous-titres (STT), de séparation audio et de lip-sync, ainsi que les projets inachevés/échoués. Il détermine lui-même l'espace de travail à partir du lien du projet ; vous n'avez pas besoin de demander quel espace.
Une pause n'est pas un échec. Une ligne [sentence-select] signifie que le worker n'a pas pu cibler une seule cible (rien n'a été donné, rien n'a correspondu, ou plus d'une phrase a correspondu) — il se termine proprement pour vous demander quelque chose, pas parce qu'il a échoué. La liste numérotée en dessous (index, étiquette temporelle, texte) peut être relayée à l'utilisateur telle quelle. Juste après cette liste, une ligne [sentence-ref] imprime une carte JSON des mêmes numéros vers les numéros de phrases internes du worker — cette ligne est uniquement pour votre propre consultation : ne jamais la montrer ou la lire à haute voix. Une fois que l'utilisateur a choisi un numéro, le chercher dans cette carte et réexécuter la même commande avec --sentence défini sur cette valeur. Un bloc [speaker-resolve] à côté signifie que certaines cibles ont déjà été résolues mais rien n'a encore été écrit — il a sa propre liste numérotée et sa propre ligne [sentence-ref] ; la liste --sentence de la réexécution doit inclure à la fois ces valeurs déjà résolues et la ou les nouvelles valeurs choisies, sinon les valeurs résolues seraient abandonnées. Lorsqu'une exécution imprime plus d'un groupe [sentence-select] (plusieurs cibles ambiguës à la fois), chaque groupe
…
[section tronquée pour la traduction]