Files
VoiceStudio/docs/engines/indextts.md
T
Palash DebnathandClaude Opus 4.7 c3695e1668 Phase 2 Plan 02-03: IndexTTS on SubprocessBackend (closes #42) (#98)
Migrates IndexTTS-2 off the in-process import path and onto the
SubprocessBackend primitive shipped in Plan 02-01. Closes issue #42 with
a structural fix — the parent's transformers>=5.3 and IndexTTS's
transformers<5 now live in separate OS processes and can never collide.

* New: backend/engines/indextts/ — sidecar package (__init__.py hosts
  IndexTTS2Backend, main.py is the sidecar entrypoint, bootstrap.py owns
  the 3-step venv probe + lazy uv-based bootstrap).
* services.tts_backend: IndexTTS2Backend's in-process body removed;
  registry resolves the class lazily via a _LazyRegistry indirection +
  PEP 562 __getattr__ re-export. This breaks the import cycle that
  arose when both subprocess_backend and tts_backend tried to import
  each other at module load.
* docs/engines/indextts.md: install walkthrough + venv resolution order
  + common errors (linked from is_available()'s unavailable message).
* tests:
  - test_indextts_backward_compat.py (8) — probe priority, no-spawn
    discipline, HF cache marker preservation (ENGINE-07).
  - test_indextts_sidecar.py (17) — subclass shape, isolation_mode,
    parent-side emotion arbitration (vector/audio/text/description),
    coexist-with-OmniVoice (headline #42 closure), env forwarding.
  - tests/fixtures/mock_indextts_sidecar.py — stdlib-only sidecar
    mimicking the production wire protocol; emits 1 s sine wave.
  - test_issue_fixes.py: two obsolete in-process-conflict tests rewritten
    to assert the new subprocess contract (no indextts.* import in the
    parent).

Hard constraints honored: backend/services/sonitranslate.py and
gpu_sandbox.py are untouched (D1 / D4). Existing v0.2.7 users with
OMNIVOICE_INDEXTTS_DIR and a populated HF cache reach a working
generation with zero re-download and zero re-install.

44 tests pass across the four exercised files. Full suite: 391 passed,
10 skipped, 13 xfailed, 1 xpassed in 57 s. Smoke: 4 passed.

Closes #42. Requirements: ENGINE-02, ENGINE-03, ENGINE-04, ENGINE-07.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:26:46 +05:30

4.8 KiB

OmniVoice Studio — IndexTTS-2 Engine

IndexTTS-2 (Bilibili) is OmniVoice's emotion-controlled zero-shot TTS engine. It runs in its own subprocess + dedicated Python venv with transformers<5, isolated from the OmniVoice parent process which pins transformers>=5.3. This isolation is the resolution of #42 — the canonical OffloadedCache ImportError that resulted from loading both libraries inside one Python interpreter.

Install

IndexTTS-2 is not bundled with OmniVoice — the model weights are ~6 GB and the package itself pins a conflicting transformers version. OmniVoice ships with a sidecar runner that loads IndexTTS into an isolated venv on demand.

  1. Clone the IndexTTS repo on disk:

    git clone https://github.com/index-tts/index-tts.git
    
  2. Install the editable package into a fresh venv. Use uv pip install -e . — never uv sync --all-extras, which would overwrite OmniVoice's lock file with transformers<5 and break the parent process:

    cd index-tts
    uv venv .venv
    uv pip install -e .
    
  3. Download the model weights (~6 GB). Either:

    hf download IndexTeam/IndexTTS-2 --local-dir=checkpoints
    

    or let HuggingFace cache them on first synthesize call (the parent forwards HF_HOME / HF_HUB_CACHE to the sidecar so the cache is shared with the rest of OmniVoice's downloads).

  4. Set the OMNIVOICE_INDEXTTS_DIR environment variable to the repo root (the directory that contains checkpoints/ and pyproject.toml):

    # macOS / Linux
    echo 'export OMNIVOICE_INDEXTTS_DIR=$HOME/code/index-tts' >> ~/.zshrc
    source ~/.zshrc
    
    # Windows PowerShell
    [Environment]::SetEnvironmentVariable("OMNIVOICE_INDEXTTS_DIR","$env:USERPROFILE\code\index-tts","User")
    
  5. Restart OmniVoice. IndexTTS-2 will appear in Settings → Engines with available: true and isolation_mode: subprocess.

Venv resolution order

OmniVoice probes for a usable IndexTTS Python interpreter in this priority order (see backend/engines/indextts/bootstrap.py):

  1. ${OMNIVOICE_INDEXTTS_DIR}/.venv/ — your existing clone's venv. Highest priority, so v0.2.7 users who already ran uv pip install -e . get zero migration cost on the upgrade to v0.3.x.
  2. backend/engines/indextts/.venv/ — OmniVoice's own venv, created on demand by step 3.
  3. Lazy bootstrap — if neither venv exists, OmniVoice runs uv venv backend/engines/indextts/.venv and uv pip install --python <python> -e ${OMNIVOICE_INDEXTTS_DIR} on first launch. Requires OMNIVOICE_INDEXTTS_DIR to be set; raises a clear error otherwise.

The cache marker test (tests/backend/services/test_indextts_backward_compat.py::test_hf_home_marker_present_after_bootstrap) proves that the bootstrap path never mutates $HF_HOME/hub/models--IndexTeam--IndexTTS-2/ — so the 6 GB model weights survive the upgrade byte-for-byte.

Common errors

IndexTTS-2 venv not found. Set OMNIVOICE_INDEXTTS_DIR ...

You haven't pointed OmniVoice at an IndexTTS clone yet. Follow the Install steps above.

uv is required to bootstrap the IndexTTS-2 venv but was not found on PATH

The bootstrap path needs a working uv binary. Either install uv into your PATH (https://docs.astral.sh/uv/) or pre-create the venv manually with uv venv and uv pip install -e as in step 2.

IndexTTS bootstrap completed but import indextts.infer_v2 still fails

The clone at OMNIVOICE_INDEXTTS_DIR is missing the indextts package. Verify with:

ls "$OMNIVOICE_INDEXTTS_DIR/pyproject.toml"   # should exist
ls "$OMNIVOICE_INDEXTTS_DIR/indextts/"        # should exist

If the directory is correct but the import still fails, delete backend/engines/indextts/.venv/ and re-launch — OmniVoice will re-bootstrap from scratch.

Why a subprocess?

IndexTTS-2 pins transformers<5. OmniVoice pins transformers>=5.3. The two cannot share a Python interpreter — at import time, one of them blows up trying to find a class the other moved or removed (the canonical failure is OffloadedCache from transformers.cache_utils, which v5 renamed). Running IndexTTS in its own subprocess + its own venv lets both libraries coexist in the same OmniVoice session.

This is the structural fix for issue #42; the previous graceful-degradation wrap (which simply detected the conflict and disabled IndexTTS) is replaced by a real isolation primitive (backend/services/subprocess_backend.py::SubprocessBackend, shipped in Plan 02-01).

License

IndexTTS-2 ships under a custom Bilibili research license — free for research / non-commercial use. Commercial use requires contacting indexspeech@bilibili.com. See the upstream README for the full terms.