Communitygithub.com

calesthio/OpenMontage

Compétence de traduction et doublage vidéo via l'API HeyGen, avec synchronisation labiale et prise en charge multilingue. Fait partie du studio vidéo agentique 61k★.

Qu'est-ce que OpenMontage ?

Cette compétence, baptisée OpenMontage, s'intègre à HeyGen pour traduire et doubler des vidéos dans plusieurs langues tout en préservant la synchronisation labiale et les caractéristiques vocales du locuteur d'origine. Elle s'utilise via la commande POST /v2/video_translate en fournissant une URL de vidéo ou un identifiant HeyGen, et peut être polluée jusqu'à la fin du traitement (statut completed). La compétence prend en charge les options audio uniquement (plus rapide, sans synchronisation labiale), les vidéos à plusieurs locuteurs, la préservation de termes spécifiques (vocabulaire), les sous-titres SRT personnalisés et le traitement par lots vers plusieurs langues en parallèle. Elle est compatible avec les agents (Claude Code, Cursor, Codex) grâce à une documentation détaillée en curl, TypeScript et Python, et offre des fonctionnalités avancées comme le contrôle de la piste musicale, l'amélioration de la qualité audio et la traduction de plusieurs langues en un seul appel API. L'authentification se fait via la clé HEYGEN_API_KEY, et les meilleures pratiques recommandent d'utiliser des vidéos de haute qualité, un audio clair et un seul locuteur pour des résultats optimaux.

Compatible avec~Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add https://github.com/calesthio/OpenMontage/tree/main/.agents/skills/video-translate

Demander à votre IA préférée

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

Documentation

Traduction Vidéo (HeyGen)

Traduisez et doublez des vidéos existantes en plusieurs langues, en conservant la synchronisation labiale et les intonations naturelles. Fournissez une URL de vidéo ou un identifiant de vidéo HeyGen — pas besoin de créer la vidéo sur HeyGen au préalable.

Authentification

Toutes les requêtes nécessitent l'en-tête X-Api-Key. Définissez la variable d'environnement HEYGEN_API_KEY.

curl -X POST "https://api.heygen.com/v2/video_translate" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"video_url": "https://exemple.com/video.mp4", "output_language": "es-ES"}'

Flux de travail par défaut

  1. Fournissez une URL de vidéo ou un identifiant de vidéo HeyGen
  2. Appelez POST /v2/video_translate avec la langue cible
  3. Interrogez GET /v2/video_translate/{translate_id} jusqu'à ce que le statut soit completed
  4. Téléchargez la vidéo traduite depuis l'URL renvoyée

Création d'une tâche de traduction

Champs de la requête

ChampTypeOblig.Description
video_urlstringO*URL de la vidéo à traduire (*ou video_id)
video_idstringO*Identifiant HeyGen de la vidéo (*ou video_url)
output_languagestringOCode de la langue cible (p. ex., "es-ES")
titlestringNom de la vidéo traduite
translate_audio_onlybooleanAudio uniquement, pas de synchronisation labiale (plus rapide)
speaker_numnumberNombre de locuteurs dans la vidéo
callback_idstringIdentifiant personnalisé pour le suivi webhook
callback_urlstringURL de notification de fin

Il faut fournir video_url ou video_id.

curl

curl -X POST "https://api.heygen.com/v2/video_translate" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_url": "https://exemple.com/video-originale.mp4",
    "output_language": "es-ES",
    "title": "Version Espagnole"
  }'

TypeScript

interface VideoTranslateRequest {
  video_url?: string;
  video_id?: string;
  output_language: string;
  title?: string;
  translate_audio_only?: boolean;
  speaker_num?: number;
  callback_id?: string;
  callback_url?: string;
}

interface VideoTranslateResponse {
  error: null | string;
  data: {
    video_translate_id: string;
  };
}

async function translateVideo(config: VideoTranslateRequest): Promise<string> {
  const response = await fetch("https://api.heygen.com/v2/video_translate", {
    method: "POST",
    headers: {
      "X-Api-Key": process.env.HEYGEN_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(config),
  });

  const json: VideoTranslateResponse = await response.json();

  if (json.error) {
    throw new Error(json.error);
  }

  return json.data.video_translate_id;
}

Python

import requests
import os

def translate_video(config: dict) -> str:
    response = requests.post(
        "https://api.heygen.com/v2/video_translate",
        headers={
            "X-Api-Key": os.environ["HEYGEN_API_KEY"],
            "Content-Type": "application/json"
        },
        json=config
    )

    data = response.json()
    if data.get("error"):
        raise Exception(data["error"])

    return data["data"]["video_translate_id"]

Langues prises en charge

LangueCodeRemarques
Anglais (États-Unis)en-USSource par défaut
Espagnol (Espagne)es-ESEspagnol européen
Espagnol (Mexique)es-MXAmérique latine
Françaisfr-FRStandard
Allemandde-DEStandard
Italienit-ITStandard
Portugais (Brésil)pt-BRPortugais brésilien
Japonaisja-JPStandard
Coréenko-KRStandard
Chinois (Mandarin)zh-CNChinois simplifié
Hindihi-INStandard
Arabear-SAArabe standard moderne

Options de traduction

Traduction de base (avec synchronisation labiale)

const config = {
  video_url: "https://exemple.com/original.mp4",
  output_language: "es-ES",
  title: "Traduction Espagnole",
};

Traduction audio uniquement (plus rapide, sans synchronisation labiale)

const config = {
  video_url: "https://exemple.com/original.mp4",
  output_language: "es-ES",
  translate_audio_only: true,
};

Vidéos à plusieurs locuteurs

const config = {
  video_url: "https://exemple.com/interview.mp4",
  output_language: "fr-FR",
  speaker_num: 2,
};

Options avancées (API v4)

Pour un contrôle plus poussé de la traduction :

interface VideoTranslateV4Request {
  input_video_id?: string;
  google_url?: string;
  output_languages: string[];        // Plusieurs langues en un seul appel
  name: string;
  srt_key?: string;                  // Sous-titres SRT personnalisés
  instruction?: string;
  vocabulary?: string[];             // Termes à conserver tels quels
  brand_voice_id?: string;
  speaker_num?: number;
  keep_the_same_format?: boolean;
  input_language?: string;
  enable_video_stretching?: boolean;
  disable_music_track?: boolean;
  enable_speech_enhancement?: boolean;
  srt_role?: "input" | "output";
  translate_audio_only?: boolean;
}

Plusieurs langues de sortie

const config = {
  input_video_id: "original_video_id",
  output_languages: ["es-ES", "fr-FR", "de-DE"],
  name: "Traductions multilingues",
};

Vocabulaire personnalisé (conserver des termes spécifiques)

const config = {
  video_url: "https://exemple.com/demo-produit.mp4",
  output_language: "ja-JP",
  vocabulary: ["SuperWidget", "Pro Max", "TechCorp"],
};

Sous-titres SRT personnalisés

const config = {
  video_url: "https://exemple.com/video.mp4",
  output_language: "es-ES",
  srt_key: "chemin/vers/sous-titres-personnalises.srt",
  srt_role: "input",
};

Vérification du statut de la traduction

curl

curl -X GET "https://api.heygen.com/v2/video_translate/{translate_id}" \
  -H "X-Api-Key: $HEYGEN_API_KEY"

TypeScript

interface TranslateStatusResponse {
  error: null | string;
  data: {
    id: string;
    status: "pending" | "processing" | "completed" | "failed";
    video_url?: string;
    message?: string;
  };
}

async function getTranslateStatus(translateId: string): Promise<TranslateStatusResponse["data"]> {
  const response = await fetch(
    `https://api.heygen.com/v2/video_translate/${translateId}`,
    { headers: { "X-Api-Key": process.env.HEYGEN_API_KEY! } }
  );

  const json: TranslateStatusResponse = await response.json();

  if (json.error) {
    throw new Error(json.error);
  }

  return json.data;
}

Interrogation jusqu'à la fin

Les traductions prennent plus de temps que la génération vidéo standard — prévoyez jusqu'à 30 minutes.

async function waitForTranslation(
  translateId: string,
  maxWaitMs = 1800000,
  pollIntervalMs = 30000
): Promise<string> {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWaitMs) {
    const status = await getTranslateStatus(translateId);

    switch (status.status) {
      case "completed":
        return status.video_url!;
      case "failed":
        throw new Error(status.message || "Échec de la traduction");
      default:
        console.log(`Statut : ${status.status}...`);
        await new Promise((r) => setTimeout(r, pollIntervalMs));
    }
  }

  throw new Error("Délai d'attente de la traduction dépassé");
}

Flux de travail complet

async function translateAndDownload(
  videoUrl: string,
  targetLanguage: string
): Promise<string> {
  console.log(`Début de la traduction vers ${targetLanguage}...`);
  const translateId = await translateVideo({
    video_url: videoUrl,
    output_language: targetLanguage,
  });
  console.log(`ID de traduction : ${translateId}`);

  console.log("Traitement de la traduction...");
  const translatedVideoUrl = await waitForTranslation(translateId);
  console.log(`Traduction terminée : ${translatedVideoUrl}`);

  return translatedVideoUrl;
}

const spanishVideo = await translateAndDownload(
  "https://exemple.com/ma-video.mp4",
  "es-ES"
);

Traduction par lots

Traduire en plusieurs langues en parallèle :

async function translateToMultipleLanguages(
  sourceVideoUrl: string,
  targetLanguages: string[]
): Promise<Record<string, string>> {
  const results: Record<string, string> = {};

  const translatePromises = targetLanguages.map(async (lang) => {
    const translateId = await translateVideo({
      video_url: sourceVideoUrl,
      output_language: lang,
    });
    return { lang, translateId };
  });

  const translationJobs = await Promise.all(translatePromises);

  for (const job of translationJobs) {
    try {
      const videoUrl = await waitForTranslation(job.translateId);
      results[job.lang] = videoUrl;
    } catch (error) {
      results[job.lang] = `erreur : ${error.message}`;
    }
  }

  return results;
}

const translations = await translateToMultipleLanguages(
  "https://exemple.com/original.mp4",
  ["es-ES", "fr-FR", "de-DE", "ja-JP"]
);

Fonctionnalités

  • Synchronisation labiale — Ajuste automatiquement les mouvements des lèvres du locuteur pour correspondre à l'audio traduit
  • Clonage vocal — L'audio traduit correspond aux caractéristiques vocales du locuteur d'origine
  • Contrôle de la piste musicale — Option pour supprimer la musique de fond avec disable_music_track: true
  • Amélioration de la parole — Améliore la qualité audio avec enable_speech_enhancement: true

Bonnes pratiques

  1. La qualité de la source compte — Utilisez des vidéos sources de haute qualité pour de meilleurs résultats
  2. Audio clair — Les vidéos avec une parole claire se traduisent mieux
  3. Un seul locuteur — Meilleurs résultats avec un seul locuteur
  4. Rythme modéré — Un débit de parole très rapide peut affecter la qualité
  5. Testez d'abord — Essayez avec des clips courts avant de traduire des vidéos longues
  6. Prévoyez du temps supplémentaire — La traduction prend plus de temps que la génération vidéo (jusqu'à 30 minutes)

Gestion des erreurs

Erreurs courantes et comment les gérer :

async function safeTranslate(
  videoUrl: string,
  targetLanguage: string
): Promise<{ success: boolean; result?: string; error?: string }> {
  try {
    const url = await translateAndDownload(videoUrl, targetLanguage);
    return { success: true, result: url };
  } catch (error) {
    if (error.message.includes("quota")) {
      return { success: false, error: "Crédits insuffisants" };
    }
    if (error.message.includes("duration")) {
      return { success: false, error: "Vidéo trop longue" };
    }
    if (error.message.includes("format")) {
      return { success: false, error: "Format vidéo non pris en charge" };
    }
    return { success: false, error: error.message };
  }
}

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills associés