All skills
heygen-com avatar

/heygen-translate

@1bd5e4d
by HeyGenheygen-com/skills460 stars
78

Translate and dub a video into another language with voice cloning and lip-sync, powered by HeyGen Video Translation. The presenter keeps their face, their voice is cloned into the target language, and lips re-sync to the new audio — viewers see the same person speaking natively. Use when: (1) localizing an existing video into one or more languages ("translate this video to Spanish", "make this in French and German", "dub this into Japanese", "I need this in 10 languages for a launch"), (2) the user has a finished video and wants the SAME presenter speaking another language (not a new presenter — that's heygen-video), (3) podcast / audio-only translation ("translate this podcast", "dub the audio but keep my video"), (4) high-stakes translations where the user wants to review/edit subtitles before final render (the proofreads workflow), (5) "translate my video", "dub this", "localize this clip", "make a multilingual version", "subtitle and dub". Returns the translated video URL (or audio file for audio-only mode), one per target language. Chain signal: if the user wants to CREATE a new video in another language (no source video exists yet), route to heygen-video and write the script in the target language — do not use heygen-translate. Use heygen-translate only when there is an existing source video to localize. NOT for: creating new videos from scratch (use heygen-video), avatar creation (use heygen-avatar), TTS-only synthesis (use heygen-video with audio-only output), or text-only translation.

Use this Skill: https://skilld.dev/gh/heygen-com/skills/heygen-translate

This session only. Nothing lands on disk.

referencestroubleshooting.md

≈2.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Troubleshooting & Polling

Polling

Don't poll in foreground. Long translations (5–30 min) will block the session and burn approval prompts on every status check. Background-poll instead.

CLI mode polling

For a SINGLE language, prefer --wait:

heygen video-translate create -d '...' \
  --output-languages "Spanish (Spain)" \
  --mode precision \
  --enable-speech-enhancement \
  --enable-caption \
  --enable-dynamic-duration \
  --keep-the-same-format \
  --wait \
  --timeout 30m

--wait blocks until success or failure. The CLI uses a server-side polling strategy with sane backoff. Exit code 0 = succeeded; 4 = timeout (job is still running, just exceeded --timeout); 1 = failed; check stderr for the error envelope.

For BATCH translations (multiple languages), you can't --wait on all of them together. Strategy: submit without --wait, capture the array of video_translation_ids, then run one backgrounded poll per ID.

# Submit
heygen video-translate create -d '...' \
  --output-languages "Spanish (Spain)" "Japanese (Japan)" "German (Germany)" \
  --mode precision --enable-speech-enhancement --enable-caption \
  --enable-dynamic-duration --keep-the-same-format \
  > /tmp/translation-batch.json

# Extract IDs
jq -r '.data.video_translation_ids[]' /tmp/translation-batch.json

For each ID, run a backgrounded poll loop in a single shell invocation so the user only approves once per language:

ID="<video-translation-id>"
LANG="Spanish (Spain)"
while true; do
  resp=$(heygen video-translate get "$ID" 2>/dev/null)
  status=$(echo "$resp" | jq -r '.data.status')
  case "$status" in
    completed)
      url=$(echo "$resp" | jq -r '.data.video_url // empty')
      echo "DONE $LANG $url"
      break
      ;;
    failed)
      msg=$(echo "$resp" | jq -r '.data.failure_message // "unknown"')
      echo "FAILED $LANG $msg"
      break
      ;;
  esac
  sleep 30
done

Polling cadence: 30 s for the first 3 minutes, then 60 s. Hard timeout: 60 min.

Harness-specific polling notes

  • Claude Code: Backgrounded Bash calls inherit session permissions cleanly. Subagents spawned with Agent do NOT inherit Bash approvals — every heygen call inside a subagent will fail on permission denial. Use backgrounded bash, not subagents, for translation polling.
  • OpenClaw: Use exec with background: true and rely on automatic completion wake. The runtime announces completion when the loop exits. Don't tight-poll the process from the main session.
  • Cursor / generic CDP harnesses: Backgrounded tasks may be killed when the parent turn ends. Prefer webhook callbacks (--callback-url) for these environments.

MCP mode polling

If mcp__heygen__get_video_translation is exposed in your toolset, use it. If not, fall through to CLI for the polling step specifically — that's the one acceptable place to mix MCP for submission and CLI for status checks.

Webhook callbacks (skip polling)

If the user has a webhook endpoint, set --callback-url and --callback-id on the create call. HeyGen POSTs to the URL on completion. This is the cleanest path for production pipelines.

heygen video-translate create -d '...' \
  --callback-url "https://example.com/heygen-webhooks" \
  --callback-id "session-12345" \
  --output-languages "Spanish (Spain)" \
  --mode precision

Errors → Action Mapping

Auth errors

exit 3 from CLI / 401 from API: HEYGEN_API_KEY is missing, expired, or wrong. Action:

  1. Confirm the env var: echo "${HEYGEN_API_KEY:0:8}…"
  2. Test with a known-good call: heygen user me get
  3. If that fails, regenerate the key at https://app.heygen.com/api

heygen auth login worked but a call says unauthorized: Token cached but rotated server-side. Run heygen auth logout && heygen auth login.

Source video errors

400 "video URL not accessible" / "Could not fetch video": The URL requires auth, returned HTML, returned wrong MIME, or 404'd. Run a quick sanity check:

curl -sI "<URL>" | head -5

Expect a 200 + Content-Type: video/.... If you see 403, 404, text/html, or a redirect chain to a login page, ask the user for a public URL or a local file. For local files, route through heygen asset create --file.

400 "asset_id not found": The asset_id was for a different account, was deleted, or doesn't exist. Re-upload via heygen asset create --file <path>.

File >32 MB: HeyGen asset upload caps at 32 MB. Options:

  1. Re-encode the source at lower bitrate (often a 32 MB file is over-encoded).
  2. Host the file at a public URL (S3, Bucket, etc.) and pass as {type:"url"}.
  3. Use a presigned URL if it's behind auth — make sure the URL is reachable without additional headers.

Language errors

400 "language not supported": The string didn't match the canonical list exactly. Re-run:

heygen video-translate languages list | jq -r '.data.languages[]' > /tmp/heygen-langs.txt
grep -i "<user-said>" /tmp/heygen-langs.txt

Present the closest matches. Common mismatches:

User said Canonical
"Spanish" "Spanish (Spain)"
"Mexican Spanish" "Spanish (Mexico)"
"Chinese" / "Mandarin" "Chinese (Mandarin, Simplified)"
"Cantonese" "Chinese (Cantonese, Traditional)"
"Portuguese" "Portuguese (Portugal)" or "Portuguese (Brazil)" — ASK
"Arabic" "Arabic" or one of 19 region variants — ASK
"Japanese" "Japanese (Japan)" or just "Japanese"
"French" "French (France)"
"German" "German (Germany)"
"English" "English (United States)" or just "English"

Submission errors

failed with failure_message: "Your video's audio is missing or corrupted, please try with another video" (verified empirically): The source has no audible speech, very corrupted audio, or unsupported codec. Fails fast (~30 s after submit). Verify by previewing the source. If the user says speech is present, try re-encoding to a clean MP4 with AAC audio (ffmpeg -i input.mp4 -c:v libx264 -c:a aac -b:a 128k output.mp4). For animation / b-roll / silent sources, video translation is the wrong tool — route to a different workflow.

failed with failure_message: "Failed to download video from url, please check the url is valid or the video is public" (verified empirically): Fails almost instantly on submit. Source URL was 401/403/404, returned HTML instead of a video, redirected to a login page, or had a presigned URL that already expired. HEAD-check first with curl -sI "$URL" — expect 200 + Content-Type: video/.... Ask the user for a public URL or fall back to local-file upload.

failed with failure_message: "speaker detection": speaker_num mismatched the actual speakers, or audio was too noisy for diarization. Re-submit with correct speaker count and enable_speech_enhancement: true.

failed with failure_message: "lip-sync": Face was not detected or detection was unstable. Likely cause: occlusion, profile shots, low resolution, or fast cuts. Either:

  1. Re-submit as translate_audio_only: true (the user gets translated audio, composites manually).
  2. Tell the user the source isn't viable for visual translation.

Stuck jobs

Stuck >30 min in running: Most translations finish in 5–15 min; some legitimately take 30–45 min for long source videos or batched languages. Beyond 60 min, treat as stuck:

  1. heygen video-translate get <id> and capture the response.
  2. Surface to the user: "This translation has been running for over an hour. Backend may be stuck. Want me to delete the job and re-submit, or wait another 30 minutes?"
  3. If they re-submit, heygen video-translate delete <id> first.

SRT upload (proofreads workflow)

Preferred: upload the edited SRT as an asset. heygen asset create accepts srt (supported types: png, jpeg, mp4, webm, mp3, wav, pdf, srt). Upload the file, capture .data.asset_id, and pass it to srt update:

ASSET_ID=$(heygen asset create --file /path/to/proofread-edited.srt | jq -r '.data.asset_id')
heygen video-translate proofreads srt update <proofread-id> \
  -d "{\"srt\":{\"type\":\"asset_id\",\"asset_id\":\"$ASSET_ID\"}}"

Fallback: public URL route. If you'd rather host the SRT, use the URL form:

heygen video-translate proofreads srt update <proofread-id> \
  -d '{"srt":{"type":"url","url":"https://example.com/proofread-edited.srt"}}'

Working host options: GitHub gists (raw URL), GitHub repo files (raw URL), S3/GCS with public-read or a presigned URL ≥2 h, Vercel/static hosts. The URL must serve application/x-subrip or text/plain with the SRT body — verify with curl -sI. (Non-subtitle content-type mismatches still error on upload.)

Output / delivery errors

completed but video_url is null: Race condition between job completion and CDN propagation. Retry the get call after 30 seconds.

video_url returns 403 or expired: Presigned URLs from HeyGen expire. Re-fetch with heygen video-translate get <id> to get a fresh URL.

Captions in wrong direction (RTL languages): Burned-in captions for Arabic / Hebrew / Urdu / Persian render right-to-left and may collide with source video graphics in the lower-third. Switch to the proofreads workflow and use the SRT as a sidecar caption file.

Lip-sync quality issues

Output looks fine in some shots but bad in others: Source face conditions (occlusion, profile, fast cuts) are degrading lip-sync in those segments. Set expectations upfront in Phase 2 next time. For this output, options:

  1. Accept it (lip-sync was always going to be best-effort with this source).
  2. Re-edit the source to use cleaner shots, then re-translate.
  3. Switch to audio-only translation for use in a different video.

Voice clone sounds off (wrong gender, accent, age): Almost always a speaker_num issue — the engine separated voices wrong and clipped a different speaker's audio into the dub. Re-submit with corrected speaker count, or use proofreads to scrub the SRT and re-dub.


Debug Checklist (run before escalating)

  1. Auth: heygen user me get returns user info ✅ / fails ❌
  2. CLI version: heygen --version returns v0.0.6 or newer
  3. Languages list: heygen video-translate languages list | jq '.data.languages | length' returns >0
  4. Source URL reachable: curl -sI "<URL>" | head -1 returns 200
  5. Source MIME: curl -sI "<URL>" | grep -i content-type shows video/...
  6. Recent jobs: heygen video-translate list to see if past jobs succeeded

If all six pass and the translation still fails: capture the failure_message from heygen video-translate get <id> and surface it — that's HeyGen-side, not something the skill can resolve.

Source: SKILL.md on GitHub

1 warning16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill enables video translation using HeyGen's services. It provides instructions to install the HeyGen CLI from their official site and uses it to manage assets and dubbing. All identified behaviors, such as remote script installation and API key handling, are consistent with the vendor's official documentation and intended use of the service.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 2 issues

Signed by skilld at 1bd5e4d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Steadyupdated 3 months ago
What it can do
Runs commands Network Reads files Edits files
MCP servers
heygen
version
3.2.0
argument-hint
[video_url_or_path] [--to language]
homepage
https://docs.heygen.com/reference/video-translate
All 5 allowed tools
BashWebFetchReadWritemcp__heygen__*
Other metadata
metadata
{
  "openclaw": {
    "requires": {
      "env": [
        "HEYGEN_API_KEY"
      ]
    },
    "primaryEnv": "HEYGEN_API_KEY"
  }
}

README badge

README badge for heygen-com/skills/heygen-translate