Communitygithub.com

calesthio/OpenMontage

使用 HeyGen 影片翻譯技能,透過 /v2/video_translate 將現有影片進行多語言配音並同步嘴型。屬於 61k★ 代理影片工作室的一部分。

OpenMontage 是什麼?

這個技能允許你直接透過 HeyGen API 翻譯現有影片,無需先在 HeyGen 平台上建立影片。你只需提供影片 URL 或 HeyGen 影片 ID,即可翻譯成多種語言,並保留原始講者的語音特徵與嘴型同步。支援的語言包括英、西、法、德、義、葡、日、韓、中、印地語與阿拉伯語等主要語言。你可以選擇標準的嘴型同步翻譯,或更快速的純音訊翻譯。進階功能包括自訂詞彙保留、多講者影片支援、背景音樂移除、語音增強,以及批次翻譯功能。該技能使用 HeyGen 的 V2 與 V4 API,並提供 curl、TypeScript 與 Python 的完整程式碼範例。

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

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

影片翻譯 (HeyGen)

將現有影片翻譯並配音成多種語言,保留嘴型同步與自然的語音模式。提供影片 URL 或 HeyGen 影片 ID — 無需先在 HeyGen 上建立影片。

驗證

所有請求都需要 X-Api-Key 標頭。設定 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"}'

預設工作流程

  1. 提供影片 URL 或 HeyGen 影片 ID
  2. 呼叫 POST /v2/video_translate 搭配目標語言
  3. 輪詢 GET /v2/video_translate/{translate_id} 直到狀態為 completed
  4. 從回傳的 URL 下載翻譯後的影片

建立翻譯工作

請求欄位

欄位類型必填說明
video_urlstring是*要翻譯的影片 URL(*或 video_id)
video_idstring是*HeyGen 影片 ID(*或 video_url)
output_languagestring是目標語言代碼(例如 "es-ES")
titlestring翻譯後影片的名稱
translate_audio_onlyboolean僅音訊,無嘴型同步(更快)
speaker_numnumber影片中的講者人數
callback_idstring用於 Webhook 追蹤的自訂 ID
callback_urlstring完成通知的 URL

必須提供 video_url 或 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": "Spanish Version"
  }'

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

支援的語言

語言代碼備註
英文(美國)en-US預設來源語言
西班牙文(西班牙)es-ES歐洲西班牙語
西班牙文(墨西哥)es-MX拉丁美洲
法文fr-FR標準法語
德文de-DE標準德語
義大利文it-IT標準義大利語
葡萄牙文(巴西)pt-BR巴西葡萄牙語
日文ja-JP標準日語
韓文ko-KR標準韓語
中文(普通話)zh-CN簡體中文
印地語hi-IN標準印地語
阿拉伯語ar-SA現代標準阿拉伯語

翻譯選項

基本翻譯(含嘴型同步)

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

僅音訊翻譯(更快,無嘴型同步)

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

多講者影片

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

進階選項(V4 API)

如需更精細的翻譯控制:

interface VideoTranslateV4Request {
  input_video_id?: string;
  google_url?: string;
  output_languages: string[];        // 單次呼叫多種語言
  name: string;
  srt_key?: string;                  // 自訂 SRT 字幕
  instruction?: string;
  vocabulary?: string[];             // 保留不變的詞彙
  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;
}

多種輸出語言

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

自訂詞彙(保留特定術語)

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

自訂 SRT 字幕

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

檢查翻譯狀態

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

輪詢等待完成

翻譯比標準影片生成花費更長時間 — 請預留最多 30 分鐘。

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

  throw new Error("Translation timed out");
}

完整工作流程

async function translateAndDownload(
  videoUrl: string,
  targetLanguage: string
): Promise<string> {
  console.log(`Starting translation to ${targetLanguage}...`);
  const translateId = await translateVideo({
    video_url: videoUrl,
    output_language: targetLanguage,
  });
  console.log(`Translation ID: ${translateId}`);

  console.log("Processing translation...");
  const translatedVideoUrl = await waitForTranslation(translateId);
  console.log(`Translation complete: ${translatedVideoUrl}`);

  return translatedVideoUrl;
}

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

批次翻譯

同時翻譯成多種語言:

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

  return results;
}

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

功能

  • 嘴型同步 — 自動調整講者的嘴部動作以匹配翻譯後的音訊
  • 語音複製 — 翻譯後的音訊會符合原始講者的聲音特徵
  • 音樂軌道控制 — 可選用 disable_music_track: true 移除背景音樂
  • 語音增強 — 使用 enable_speech_enhancement: true 改善音訊品質

最佳實務

  1. 來源品質很重要 — 使用高品質的來源影片以獲得更好的結果
  2. 清晰的音訊 — 語音清晰的影片翻譯效果更佳
  3. 單一講者 — 單一講者內容的效果最好
  4. 適中的說話速度 — 非常快的語速可能會影響品質
  5. 先測試 — 在翻譯長影片前先用較短片段測試
  6. 預留額外時間 — 翻譯比影片生成花費更長時間(最多 30 分鐘)

錯誤處理

常見錯誤與處理方式:

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: "Insufficient credits" };
    }
    if (error.message.includes("duration")) {
      return { success: false, error: "Video too long" };
    }
    if (error.message.includes("format")) {
      return { success: false, error: "Unsupported video format" };
    }
    return { success: false, error: error.message };
  }
}

Individual skills in this repo

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

相關技能