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>
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.
-
Clone the IndexTTS repo on disk:
git clone https://github.com/index-tts/index-tts.git -
Install the editable package into a fresh venv. Use
uv pip install -e .— neveruv sync --all-extras, which would overwrite OmniVoice's lock file withtransformers<5and break the parent process:cd index-tts uv venv .venv uv pip install -e . -
Download the model weights (~6 GB). Either:
hf download IndexTeam/IndexTTS-2 --local-dir=checkpointsor let HuggingFace cache them on first synthesize call (the parent forwards
HF_HOME/HF_HUB_CACHEto the sidecar so the cache is shared with the rest of OmniVoice's downloads). -
Set the
OMNIVOICE_INDEXTTS_DIRenvironment variable to the repo root (the directory that containscheckpoints/andpyproject.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") -
Restart OmniVoice. IndexTTS-2 will appear in Settings → Engines with
available: trueandisolation_mode: subprocess.
Venv resolution order
OmniVoice probes for a usable IndexTTS Python interpreter in this
priority order (see backend/engines/indextts/bootstrap.py):
${OMNIVOICE_INDEXTTS_DIR}/.venv/— your existing clone's venv. Highest priority, so v0.2.7 users who already ranuv pip install -e .get zero migration cost on the upgrade to v0.3.x.backend/engines/indextts/.venv/— OmniVoice's own venv, created on demand by step 3.- Lazy bootstrap — if neither venv exists, OmniVoice runs
uv venv backend/engines/indextts/.venvanduv pip install --python <python> -e ${OMNIVOICE_INDEXTTS_DIR}on first launch. RequiresOMNIVOICE_INDEXTTS_DIRto 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.