speaker_helper.engine module
Backend-agnostic text-to-speech engine interface and registry.
Module summary
speaker-helper’s value is a small, typed façade over a local TTS engine. The
concrete engine is deliberately an implementation detail: the
rest of the package — Speaker, the REST API,
the CLI, the evaluation layer — talks only to the TTSEngine protocol
defined here. Swapping or adding a backend is therefore a local change: write a
class with four coroutines and register it.
Four backends ship in-tree:
kokoro— the default. In this process, no server, 54 built-in voices.openvoice,chatterbox— cloning, from a reference recording.mock—MockEngine, a deterministic, dependency-light synthesiser that needs no server and no model. It makes the test suite and the evaluation layer runnable in CI, and it proves the abstraction: the same code paths drive any backend, narration included.
A fifth used to ship — a REST client for a desktop application on a fixed port. It was removed once the default ran in-process: an engine you have to install, launch and keep alive, to do what a 300 MB file does here, is not a second option but a second way to fail.
create_engine() dispatches on settings.backend. Third parties can add
their own backend at runtime with register_backend().
Usage example
>>> from speaker_helper.config import Settings
>>> from speaker_helper.engine import create_engine
>>> eng = create_engine(Settings.from_mapping({"backend": "mock"}))
>>> type(eng).__name__
'MockEngine'
- class speaker_helper.engine.MockEngine(settings)[source]
Bases:
objectA deterministic, serverless TTS backend for tests and evaluation.
It produces a real (decodable) WAV whose duration is proportional to the input length at a fixed speaking rate, and reports a compute time derived from a configured real-time factor. No audio model runs and no network call is made, so behaviour is reproducible and fast — ideal for CI, for exercising the evaluation layer’s anomaly checks, and for proving that engine-agnostic code paths work against any backend.
- Parameters:
settings (Settings) – Used for
languageandenginelabels and for themockknobs (rtf,chars_per_sec,sample_rate,amplitude,frequency_hz) read viaSettings.mock.
Notes
The audio is a quiet sine tone (not silence) so evaluation’s “empty audio” and “clipping” checks see a realistic, non-degenerate signal. Set
mock.amplitudeabove 1.0 to deliberately synthesise a clipping signal for testing the anomaly detector.- async clone_voice(name, samples, *, language=None)[source]
Pretend to clone a voice; return a deterministic id (no audio model).
Validates that at least one sample is provided (matching the real engine’s contract) but performs no synthesis-model work, so cloning is testable without a server.
- Parameters:
name (str)
samples (list[VoiceSample])
language (str | None)
- Return type:
- async synthesize(text, *, language=None, voice=None, speed=1.0)[source]
Synthesise
textinto a deterministic sine-tone WAV.Honouring
speedis what makes this backend worth narrating with: a pace offasthas to shorten the audio, or the cues it produces are not the ones a real engine would produce and the timeline tests pass against a shape nothing ships.- Parameters:
text (str) – Text to “speak” (must be non-empty). Only its length drives the output duration; content is not spoken.
language (str or None) – Override the configured language label on the result.
voice (str or None) – Reported back on the result; nothing here varies with it.
speed (float) – Divides the duration, the way a faster reading would.
- Returns:
A decodable WAV plus duration and a compute time equal to
duration * rtf(soresult.rtfreproduces the configured RTF).- Return type:
- Raises:
ValueError – If
textis empty or whitespace-only.
- class speaker_helper.engine.TTSEngine(*args, **kwargs)[source]
Bases:
ProtocolThe minimal surface every speaker-helper backend must provide.
A backend is any object exposing these four coroutines. Everything else in the package is written against this protocol, never against a concrete engine, so the backend stays swappable.
Notes
@runtime_checkableletsisinstance(obj, TTSEngine)verify the method names are present (not their signatures) — handy in tests and factories.- async clone_voice(name, samples, *, language=None)[source]
Clone a voice from reference
samplesand return its id.Implementations should be idempotent on
name(reuse an existing clone rather than duplicating). A backend that cannot clone may raiseNotImplementedError.- Parameters:
name (str)
samples (list[VoiceSample])
language (str | None)
- Return type:
- async synthesize(text, *, language=None, voice=None, speed=1.0)[source]
Synthesise
textinto oneAudioResult.voiceandspeedarrived with narration, where a document picks a voice once and varies the pace block by block. They are keyword-only and both have defaults, so a backend written against the original two-argument signature still satisfies this protocol —narrate()offers them and withdraws them when the backend does not declare them.- Parameters:
- Return type:
- speaker_helper.engine.available_backends()[source]
Return the sorted names of all registered backends.
- speaker_helper.engine.create_engine(settings)[source]
Instantiate the backend named by
settings.backend.- Parameters:
settings (Settings) – Configuration; its
backendfield selects the engine.- Returns:
A ready-to-use engine instance.
- Return type:
- Raises:
ValueError – If
settings.backendnames no registered backend.
- speaker_helper.engine.register_backend(name, factory)[source]
Register (or override) a backend factory under
name.- Parameters:
name (str) – Backend identifier used in
settings.backend(case-insensitive).factory (callable) –
Settings -> TTSEngine. Called lazily bycreate_engine().
- Return type:
None