Pinch Voice Translation (Dubbing)
Dubbing That Keeps the Speaker's Voice — and Never Touches Your GPU
- job_id
- output_path
- status
- subtitles_original
- subtitles_translated
The honest pitch
Dubbing is the one audio job the local stack quietly gives up on. Text-to-speech and voice cloning you can run on a consumer card all day - Kokoro, Chatterbox, the whole lineup. But take a finished video in Spanish and hand back the same video in English, in the same speaker's voice, with the background music intact? There's no open model for that you just download and run. This node skips the problem entirely: it hands your media to Pinch's cloud dubbing API and drops the dubbed result into your ComfyUI output folder.
Read the display name carefully: "Pinch Voice Translation (Dubbing)". It's an API wrapper, not a local model. Nothing loads into VRAM, nothing lands in your models/ folder, and the only Python dependency is requests. Internet connection plus a Pinch API key and you're done.
How it works
Give it either a public URL or a local file path. If you give a local path, the node validates it (must exist, 500 MB max), asks Pinch for a pre-signed upload URL, PUTs the file straight to S3, and swaps in the returned URL. Then it follows their API: create the dubbing job, poll /api/dubbing/jobs/{id} every poll_interval seconds, and when the job reads completed it calls the result endpoint to confirm a download URL actually exists before pulling the file.
The output lands in ComfyUI/output/ as pinch_dubbed_<job_id>.<ext>. If subtitles came back you also get pinch_dubbed_<job_id>_original.srt and _translated.srt sitting right next to it. The whole thing happens inside one node execution - you queue it, watch the console logs, and it blocks until the job finishes or times out.
Pinch's side does voice-preserving dubbing: transcribe, translate with natural phrasing instead of word-for-word, synthesize in the original speaker's voice, and keep the background audio. Their whole pitch is "it still sounds like them," not a generic replacement voice.
The inputs that matter
Three required, everything else optional:
api_key- from your startpinch.com dashboard. It's stored in the workflow as plain text, so scrub it before sharing workflow files.target_languageandsource_language- dropdowns of ten languages (en, es, fr, de, it, pt, ru, ja, ko, zh); source defaults toautodetection.
Then exactly one of media_url (a public http(s) URL) or local_file_path (absolute path, max 500 MB). The node refuses both or neither, which is a nice touch.
The tuning knobs all default to sensible values: reduce_accent, translation_lag_time (0–5 s delay before dubbed speech starts), original_speech_volume (mix the original under the dub, 0–1), and poll_interval (5–60 s between status checks). The two volume/lag fields are for the "listen to both tracks" use case - language learning, broadcast-style mixing.
Outputs: job_id, output_path, status, plus subtitles_original and subtitles_translated as SRT strings (empty if the API returned none). In a real workflow these mostly matter for logging; wire output_path into a preview or save node, or just read the console.
Install
cd ComfyUI/custom_nodes/
git clone https://github.com/pinch-eng/comfyui-pinch-voice-translation
pip install -r comfyui-pinch-voice-translation/requirements.txt
Restart ComfyUI. Or search "Pinch Voice Translation" in ComfyUI Manager - same result. No model files, no CUDA requirements; requests is probably already in your venv.
The cost and the gotchas
Sign-up at startpinch.com gets you $5 in free credits - roughly ten minutes of dubbing - with no card. After that it's $0.50 per minute of input media, no subscription, no watermarks. For a one-off localization job that's cheap; for a long backlog it adds up, which is the standard API tradeoff: metered per call, and your data leaves the machine.
Where people get burned:
- Jobs time out at 30 minutes. The node polls for up to 30 minutes, then returns just the
job_idso you can recover with the pack's Status node. Long files plus a busy queue is exactly when this bites. - Provide one source, not both. Filling
media_urlandlocal_file_pathis an error by design. - The URL must be publicly reachable.
http://localhost/...won't work from their servers; use the local file path instead. - It's an API node holding a key. Your media goes to a server you don't control, subject to its logging and moderation. Fine for content you own; think twice about anything sensitive.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| target_language | COMBO | es | 10 options: en, es, fr, de, it, pt, +4 |
| source_language | COMBO | auto | 11 options: auto, en, es, fr, de, it, +5 |
| api_key | STRING | — | |
| media_urlopt | STRING | — | |
| local_file_pathopt | STRING | — | |
| reduce_accentopt | BOOLEAN | false | — |
| translation_lag_timeopt | FLOAT | 0.00–5 | — |
| original_speech_volumeopt | FLOAT | 0.000–1 | — |
| poll_intervalopt | INT | 105–60 | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| job_id | STRING | — |
| output_path | STRING | — |
| status | STRING | — |
| subtitles_original | STRING | — |
| subtitles_translated | STRING | — |