ask a backend for its voices the way backends actually expose them
XTTS reported no voices after seventeen successful syntheses, and the reason was
that the loaded and remembered paths had never worked for any engine since they
were written. Every backend declares `voices` as a METHOD; both callers read it
as a sequence:
[str(v) for v in (getattr(backend, "voices", None) or [])]
A bound method is truthy, so the `or []` never fired, iterating it raised
TypeError, and both callers caught that and fell back to "nothing known". No
voice was ever recorded or returned, and the cache file had never been written
once. Kokoro and bark hid it behind their static lists; XTTS and parler, having
none, published zero for ever.
The tests passed because their fake sets `voices` as a list attribute — a shape
no real backend has. A new test asserts the contract against the real classes
through inspect rather than against a fixture, which is the only version of this
test that could have failed.
With that fixed, 58 XTTS voices were recorded and the listing still showed none:
they were written under "XTTS-v2", the id the request used, and looked up under
"coqui/XTTS-v2", the id the listing publishes. The lookup now falls back to
matching the last path segment — the idiom the coqui speaker lookup already uses
— with an exact key still winning and distinct models still distinct.
The existing never-breaks-synthesis test then caught a gap in the first fix: a
backend may expose `voices` as a PROPERTY, so merely reading the attribute runs
its code and can raise. The getattr is inside the guard for that reason.
Live after this: XTTS-v2 58 voices, Kokoro 54, bark 130, parler 0 with
voice_selection "described", which is the honest answer for an engine steered by
a text description.
Co-Authored-By:
Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T3rZozi1Ehk96NG1WesgC
Showing
Please
register
or
sign in
to comment