-
Stefy Lanza (nextime / spora ) authored
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
c33f92ba