Communitygithub.com

calesthio/OpenMontage

Habilidade de tradução de vídeos com sincronia labial usando a API HeyGen /v2/video_translate. Parte do estúdio de vídeo com agente de 61k★.

O que é OpenMontage?

Esta habilidade permite que você traduza e duble vídeos existentes para vários idiomas, preservando a sincronia labial e os padrões naturais de fala — sem a necessidade de criar o vídeo primeiro no HeyGen. Basta fornecer uma URL ou ID do HeyGen. A habilidade oferece suporte a fluxos de trabalho básicos (com sincronia labial) e avançados (API v4), incluindo múltiplos idiomas de saída em uma única chamada, vocabulário personalizado para preservar termos específicos, legendas SRT customizadas e opção de apenas áudio para processamento mais rápido. Você pode verificar o status da tradução via polling e, quando concluída, baixar o vídeo traduzido diretamente. Os principais recursos incluem sincronia labial automática, clonagem de voz e controle da trilha musical. As práticas recomendadas incluem o uso de vídeos de fonte de alta qualidade e áudio claro para obter melhores resultados, além de aguardar até 30 minutos para conclusão. A habilidade também oferece tratamento de erros pronto para uso, lidando com problemas comuns como cotas insuficientes, vídeos muito longos ou formatos não suportados.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Tradução de Vídeo (HeyGen)

Traduza e duble vídeos existentes para vários idiomas, preservando a sincronia labial e os padrões naturais de fala. Forneça uma URL de vídeo ou ID de vídeo do HeyGen — não é necessário criar o vídeo no HeyGen primeiro.

Autenticação

Todas as requisições exigem o cabeçalho X-Api-Key. Defina a variável de ambiente 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://example.com/video.mp4", "output_language": "es-ES"}'

Fluxo de Trabalho Padrão

  1. Forneça uma URL de vídeo ou ID de vídeo do HeyGen
  2. Chame POST /v2/video_translate com o idioma de destino
  3. Faça polling em GET /v2/video_translate/{translate_id} até o status ser completed
  4. Baixe o vídeo traduzido da URL retornada

Criando um Job de Tradução

Campos da Requisição

CampoTipoReqDescrição
video_urlstringS*URL do vídeo a ser traduzido (*ou video_id)
video_idstringS*ID do vídeo do HeyGen (*ou video_url)
output_languagestringSCódigo do idioma de destino (ex.: "es-ES")
titlestringNome para o vídeo traduzido
translate_audio_onlybooleanApenas áudio, sem sincronia labial (mais rápido)
speaker_numnumberNúmero de falantes no vídeo
callback_idstringID personalizado para rastreamento via webhook
callback_urlstringURL para notificação de conclusão

Deve ser fornecido 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://example.com/original-video.mp4",
    "output_language": "es-ES",
    "title": "Versão em Espanhol"
  }'

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"]

Idiomas Suportados

IdiomaCódigoObservações
Inglês (EUA)en-USOrigem padrão
Espanhol (Espanha)es-ESEspanhol europeu
Espanhol (México)es-MXLatino-americano
Francêsfr-FRFrancês padrão
Alemãode-DEAlemão padrão
Italianoit-ITItaliano padrão
Português (Brasil)pt-BRPortuguês brasileiro
Japonêsja-JPJaponês padrão
Coreanoko-KRCoreano padrão
Chinês (Mandarim)zh-CNChinês simplificado
Hindihi-INHindi padrão
Árabear-SAÁrabe moderno padrão

Opções de Tradução

Tradução Básica (com sincronia labial)

const config = {
  video_url: "https://example.com/original.mp4",
  output_language: "es-ES",
  title: "Tradução em Espanhol",
};

Tradução Apenas de Áudio (mais rápida, sem sincronia labial)

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

Vídeos com Múltiplos Falantes

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

Opções Avançadas (API v4)

Para mais controle sobre a tradução:

interface VideoTranslateV4Request {
  input_video_id?: string;
  google_url?: string;
  output_languages: string[];        // Múltiplos idiomas em uma chamada
  name: string;
  srt_key?: string;                  // Legendas SRT personalizadas
  instruction?: string;
  vocabulary?: string[];             // Termos a preservar como estão
  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;
}

Múltiplos Idiomas de Saída

const config = {
  input_video_id: "original_video_id",
  output_languages: ["es-ES", "fr-FR", "de-DE"],
  name: "Traduções em múltiplos idiomas",
};

Vocabulário Personalizado (preservar termos específicos)

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

Legendas SRT Personalizadas

const config = {
  video_url: "https://example.com/video.mp4",
  output_language: "es-ES",
  srt_key: "path/to/custom-subtitles.srt",
  srt_role: "input",
};

Verificando o Status da Tradução

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;
}

Polling pela Conclusão

As traduções demoram mais que a geração padrão de vídeos — aguarde até 30 minutos.

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 || "Tradução falhou");
      default:
        console.log(`Status: ${status.status}...`);
        await new Promise((r) => setTimeout(r, pollIntervalMs));
    }
  }

  throw new Error("Tradução excedeu o tempo limite");
}

Fluxo Completo

async function translateAndDownload(
  videoUrl: string,
  targetLanguage: string
): Promise<string> {
  console.log(`Iniciando tradução para ${targetLanguage}...`);
  const translateId = await translateVideo({
    video_url: videoUrl,
    output_language: targetLanguage,
  });
  console.log(`ID da tradução: ${translateId}`);

  console.log("Processando tradução...");
  const translatedVideoUrl = await waitForTranslation(translateId);
  console.log(`Tradução concluída: ${translatedVideoUrl}`);

  return translatedVideoUrl;
}

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

Tradução em Lote

Traduza para múltiplos idiomas em paralelo:

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] = `erro: ${error.message}`;
    }
  }

  return results;
}

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

Recursos

  • Sincronia Labial — Ajusta automaticamente os movimentos labiais do falante para corresponder ao áudio traduzido
  • Clonagem de Voz — O áudio traduzido corresponde às características da voz do falante original
  • Controle de Trilha Musical — Opcionalmente remova a música de fundo com disable_music_track: true
  • Melhoria de Fala — Melhore a qualidade do áudio com enable_speech_enhancement: true

Melhores Práticas

  1. Qualidade da fonte importa — Use vídeos fonte de alta qualidade para melhores resultados
  2. Áudio claro — Vídeos com fala clara são traduzidos melhor
  3. Falante único — Melhores resultados com conteúdo de um único falante
  4. Ritmo moderado — Fala muito rápida pode afetar a qualidade
  5. Teste primeiro — Experimente com clipes menores antes de traduzir vídeos longos
  6. Reserve tempo extra — A tradução leva mais tempo que a geração de vídeo (até 30 min)

Tratamento de Erros

Erros comuns e como lidar com eles:

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éditos insuficientes" };
    }
    if (error.message.includes("duration")) {
      return { success: false, error: "Vídeo muito longo" };
    }
    if (error.message.includes("format")) {
      return { success: false, error: "Formato de vídeo não suportado" };
    }
    return { success: false, error: error.message };
  }
}

Individual skills in this repo

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

Habilidades Relacionadas