Skip to content

feat(media-use): add VoiceStudio as an explicit-choice TTS provider - #5658

Closed
leokung4325 wants to merge 1 commit into
heygen-com:mainfrom
leokung4325:feat/voicestudio-tts-provider
Closed

leokung4325 wants to merge 1 commit into
heygen-com:mainfrom
leokung4325:feat/voicestudio-tts-provider

Conversation

@leokung4325

Copy link
Copy Markdown

Summary

Adds VoiceStudio (a local, fully
open-source ElevenLabs-alternative TTS/voice-cloning server) as a provider in
skills/media-use/audio/scripts/lib/tts.mjs, alongside HeyGen, ElevenLabs,
Kokoro, and Gemini — gives users real voice cloning with no cloud dependency
and no account.

Why explicit-choice-only, not auto-detected

Deliberately not added to the silent auto-detect fallback chain, for two
reasons:

  1. Mechanical: the existing chain (heygenAvailable(), elevenlabsAvailable())
    is synchronous by design. VoiceStudio is a local server with no credential
    to check — "is it running" can only be answered with a network call.
  2. Licensing, the more important one: VoiceStudio's default engine
    (omnivoice, backed by k2-fsa/OmniVoice) ships CC-BY-NC with
    commercial_inference: restricted per VoiceStudio's own model registry.
    Silently auto-picking VoiceStudio would silently inherit whichever engine
    the user's backend happens to have active, with real licence consequences
    for anyone generating narration for monetized content. This PR pins
    VOICESTUDIO_ENGINE to VoxCPM2 (Apache-2.0, still supports real voice
    cloning — unlike the other commercially-safe engines, Kokoro/KittenTTS,
    which are fixed-preset only) by default, but that choice should always be
    deliberate, never silent. gemini already follows this same
    explicit-only pattern for its own reasons; this PR follows the precedent.

What's in scope

The simple synthesize-one-line path (/v1/audio/speech, OpenAI-compatible),
matching what every other provider does in this file. No word timestamps in
the response (same as ElevenLabs/Kokoro) — caller chains transcribeWav().

Out of scope, left as a follow-up: voice cloning itself (VoiceStudio's
native /generate endpoint with a ref_audio reference sample) is a
per-project provisioning step, closer to heygen-voice.mjs's
clone/list/delete pattern than to this per-line synthesis engine. Keeping
this PR focused on the provider-chain addition.

Verification

  • Live, against a real backend: ran VoiceStudio 0.5.7 natively on macOS
    (Apple Silicon, MPS acceleration — the published Docker image is
    linux/amd64-only with no GPU access in that container, so native is the
    right path there), installed VoxCPM2, and generated real audio through the
    exact /v1/audio/speech payload shape this function sends. Confirmed
    playable (ffprobe-verified wav, correct duration).
  • Unit tests: 20/20 pass (14 existing + 6 new) —
    node --test skills/media-use/audio/scripts/lib/tts.test.mjs. New tests
    cover: pickProvider accepting the explicit choice, pickProvider never
    auto-selecting it into the fallback chain, a successful synth writing wav
    bytes directly (no transcode step, unlike HeyGen), a model-licence refusal
    being surfaced verbatim (not swallowed — the actual error VoiceStudio
    returns when a model's licence isn't accepted), a plain HTTP failure, and a
    connection failure naming the backend URL + the bun run dev:api hint.
  • Lint/format: oxlint and oxfmt --check both clean on both changed
    files.

Test plan

  • node --test skills/media-use/audio/scripts/lib/tts.test.mjs — 20/20 pass
  • bunx oxlint skills/media-use/audio/scripts/lib/tts.mjs skills/media-use/audio/scripts/lib/tts.test.mjs — clean
  • bunx oxfmt --check on both files — clean
  • Live generation against a real VoiceStudio backend, output verified playable

🤖 Generated with Claude Code

VoiceStudio (github.com/debpalash/VoiceStudio) is a local, fully open-source
TTS/voice-cloning server — adds a free/local option alongside HeyGen,
ElevenLabs, and Kokoro for users who want real voice cloning without a cloud
dependency.

Verified live against a running VoiceStudio 0.5.7 backend (native macOS
process on Apple Silicon, MPS acceleration): generated real audio through
the exact /v1/audio/speech payload shape this function sends, confirmed
playable. 20/20 unit tests pass (14 existing + 6 new), oxlint/oxfmt clean.

Deliberately explicit-choice-only (--provider voicestudio), same as Gemini,
never added to the silent auto-detect chain:

- VoiceStudio has no credential to probe synchronously (it's a local server,
  not a cloud API) — the existing chain (heygenAvailable/elevenlabsAvailable)
  is sync-only by design, and "is the backend running" can only be answered
  with a network call.
- More importantly: VoiceStudio's default engine (omnivoice, backed by
  k2-fsa/OmniVoice) ships CC-BY-NC with restricted commercial inference.
  Silently auto-picking VoiceStudio would silently inherit whichever engine
  happens to be active on the user's backend, with real licence consequences.
  This PR pins VOICESTUDIO_ENGINE to VoxCPM2 (Apache-2.0, still supports
  voice cloning) by default, but that choice should always be explicit.

Scope: this is the simple synthesize-one-line path, matching what the other
providers already do here. Voice cloning (VoiceStudio's /generate endpoint
with a ref_audio sample) is a separate per-project provisioning step, closer
to heygen-voice.mjs's clone/list/delete pattern than to this per-line engine
— a natural follow-up, kept out of this PR to stay focused.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, and for keeping VoiceStudio out of the automatic fallback chain. The reasoning in the description (no credential to check synchronously, and engine licences that matter for commercial use) is sound, and the provider follows the existing Gemini pattern closely.

One thing to fix before this can go in:

  • tts.test.mjs:237 ("pickProvider never auto-picks voicestudio or gemini") depends on the machine it runs on. It clears five env vars, but heygenCredential() also reads HEYGEN_ACCESS_TOKEN, HEYGEN_API_BASE, HEYGEN_CONFIG_DIR and ~/.heygen/credentials (heygen.mjs:109-126). On a machine that is signed in to HeyGen, the test fails with expected 'kokoro', got 'heygen'; I reproduced that at this head, and it also fails with just HEYGEN_ACCESS_TOKEN set. Pointing HEYGEN_CONFIG_DIR at an empty temp dir and clearing those two variables (like the "unreadable credentials path" test a few lines up) fixes it.

Smaller suggestions:

  • references/tts.md (the provider table and the Gemini note) and the request comment at audio.mjs:18 still list only the existing four providers. Agents choose providers from those docs, so without an entry there VoiceStudio is hard to find, and the "pin a commercially usable engine" guidance only lives in a code comment.
  • The synthesizeVoicestudio tests all pass lang: null, but audio.mjs always passes a language ("en" by default), so the request body that is actually sent, with language, is never asserted. One case with lang: "en" would cover it.
  • The note about verifying against 0.5.7 natively on macOS and the Docker image (around tts.mjs:393) reads as history; CONTRIBUTING's "Comments" section asks for that kind of detail in the PR description instead.

CI has not run on this head yet, so I'm going on reading the code and running the test file locally.

Verdict: REQUEST_CHANGES
Reasoning: A new test fails on any machine signed in to HeyGen (reproduced at this head); the provider code itself looks fine once that and the docs gap are addressed.

@jrusso1020

Copy link
Copy Markdown
Collaborator

Thanks for putting this together. We're keeping the TTS providers in the skills to ones we maintain or officially support, so we're not going to recommend VoiceStudio here. Closing, but it would make a good community skill in heygen-com/hyperframes-community-skills.

@jrusso1020 jrusso1020 closed this Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants