speaker_helper package
Subpackages
- speaker_helper.backends package
- Submodules
- speaker_helper.backends.chatterbox module
- speaker_helper.backends.kokoro module
- Module summary
- Two things pip cannot give you
- Usage example
- Author
BUILTIN_VOICESCACHE_CANDIDATESCACHE_ROOTDEFAULT_VOICE_BY_LANGFALLBACK_VOICEKokoroEngineLANG_TO_ESPEAKMODEL_FILENAMESAMPLE_RATE_HZVOICE_PREFIX_LANGcheck_install()detect_language()espeak_config()language_for()model_paths()normalise_language()pcm_to_wav_bytes()voice_for_language()
- speaker_helper.backends.openvoice module
- Module contents
- Submodules
- speaker_helper.eval package
- Submodules
- Module contents
Submodules
- speaker_helper.api module
- speaker_helper.cli module
- speaker_helper.click_cli module
- speaker_helper.cloning module
- speaker_helper.config module
- speaker_helper.engine module
- speaker_helper.install module
- speaker_helper.mcp module
- speaker_helper.narration module
- Module summary
- Usage example
- Author
NarrationResultNarrationResult.compute_sNarrationResult.cuesNarrationResult.detected_languageNarrationResult.duration_sNarrationResult.ignored_hintsNarrationResult.languageNarrationResult.language_fallbackNarrationResult.rtfNarrationResult.sample_rateNarrationResult.to_vtt()NarrationResult.voice_idNarrationResult.wav_bytes
PACE_SPEEDPUNCTUATION_PAUSE_MSSegmentSpeechCueTimelinecheck_timeline_matches_audio()coerce_segments()cues_to_vtt()format_timestamp()resolve_spoken_math()
- speaker_helper.profiles module
- speaker_helper.router module
- Module summary
- Usage example
- Author
OperatingPointRouteDecisionRouteDecision.apply()RouteDecision.backendRouteDecision.candidates_consideredRouteDecision.conditionRouteDecision.confidenceRouteDecision.engineRouteDecision.first_chunk_sentencesRouteDecision.justificationRouteDecision.mean_rtfRouteDecision.modeRouteDecision.pareto_sizeRouteDecision.qualityRouteDecision.quality_sourceRouteDecision.rtf_sourceRouteDecision.stream_concurrencyRouteDecision.to_dict()
RouteRequestdefault_operating_points()route()route_settings()
- speaker_helper.sources module
- speaker_helper.speaker module
- speaker_helper.speech_text module
- speaker_helper.text module
- speaker_helper.transcription module
- speaker_helper.types module
- speaker_helper.voices module
Module contents
speaker-helper — professional text-to-speech, offline and streaming.
Module summary
speaker-helper is the inverse of vocal-helper: it turns text into
speech. A small, typed API over three jobs — say one sentence,
stream a reply as it is written, and narrate a document with the
timed track that lets a page follow along — plus a CLI, a REST API, an MCP
surface and a skill.
The default backend is kokoro: 82M parameters through ONNX, Apache-2.0 in
code and weights, 54 voices, running in this process. Nothing to launch,
nothing to connect to, and a time-to-first-audio that does not include an HTTP
round trip. openvoice and chatterbox clone a voice from a recording;
mock is deterministic and serverless, for tests.
Usage example
>>> from speaker_helper import Speaker, Settings
>>> spk = Speaker(Settings.from_mapping({"backend": "mock", "language": "fr"}))
>>> # spk.save("Bonjour le monde.", "hello.wav")
See EXAMPLES.md for a runnable cookbook.
- class speaker_helper.AudioResult(wav_bytes, sample_rate, duration_s, compute_s, text, voice_id, language)[source]
Bases:
objectA synthesised audio payload and its measured metadata.
- Parameters:
wav_bytes (bytes) – The complete WAV file contents (RIFF header + PCM), ready to write to disk or stream to a client.
sample_rate (int) – Sample rate of the audio in Hz.
duration_s (float) – Audio duration in seconds.
compute_s (float) – Wall-clock seconds spent producing this audio (network + synthesis).
text (str) – The text that was synthesised.
voice_id (str) – The voice used.
language (str) – The language used.
Notes
rtf(real-time factor) is derived, not stored:compute_s / duration_s. A value below1.0means synthesis was faster than real time — the operating point speaker-helper aims for.
- class speaker_helper.LanguageProfile(language, engine='kokoro', voice_id='', normalize=True, first_chunk_sentences=1, stream_concurrency=1, measured_rtf=None, measured_quality=None)[source]
Bases:
objectHyperparameters and a measured operating point for one language.
- Parameters:
language (str) – ISO-639-1 code this profile targets.
engine (str) – Engine/model id to use for this language.
voice_id (str) – Preset voice id; empty means “auto-pick a voice for the language”.
normalize (bool) – Ask the engine to loudness-normalise the output.
first_chunk_sentences (int) – Streaming producer granularity — sentences in the first emitted chunk.
1minimises time-to-first-audio.stream_concurrency (int) – Streaming consumer parallelism — chunks synthesised at once.
1is a strict pipeline (lowest first-audio latency).measured_rtf (float or None) – Mean real-time factor measured for this language, or
Noneif untuned.measured_quality (float or None) – Quality score in
[0, 1]measured for this language, orNone.
- apply(settings=None)[source]
Return
settings(or fresh defaults) with this profile applied.- Parameters:
settings (Settings or None) – Base configuration to derive from;
Noneuses defaults.- Returns:
A copy carrying this profile’s language, engine, voice, and producer granularity.
stream_concurrencyis aSpeakerargument, not aSettingsfield, so pass it separately (seefrom_profile()).- Return type:
- with_measurement(report)[source]
Return a copy with
measured_rtf/measured_qualityfrom a report.- Parameters:
report (EvalReport) – A report for this language (duck-typed: needs
mean_rtfandquality).- Returns:
A copy carrying the measured operating point.
- Return type:
- class speaker_helper.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.NarrationResult(wav_bytes, sample_rate, duration_s, compute_s, cues=<factory>, voice_id='', language='', detected_language='', language_fallback=False, ignored_hints=<factory>)[source]
Bases:
objectA narrated document: the audio, and where every block landed in it.
- Parameters:
wav_bytes (bytes) – The complete WAV file (RIFF header + PCM).
sample_rate (int) – Sample rate in Hz.
duration_s (float) – Length of the audio.
compute_s (float) – Wall-clock seconds spent synthesising.
cues (list of SpeechCue) – Where each segment is spoken. Free to produce — the offsets were already computed to lay the audio out — and the only thing that lets a page follow a narration instead of merely playing it.
voice_id (str) – The voice that read it.
language (str) – The language it was phonemised in. Getting this wrong is not an accent: French read as
en-usis an American reading French spelling aloud, letter patterns and all.detected_language (str) – What the text turned out to be written in, when nobody named a voice. Empty when the caller chose one — this is the detector’s answer, not a restatement of the request.
language_fallback (bool) –
Truewhen the text is in a language the backend has no voice for, so it is being read by a voice that does not speak it. Said out loud, because the audio alone will not make it obvious to whoever generated it and does not speak the language either.ignored_hints (list of str) – Dials the segments asked for and this backend does not have. Empty on a plain narration; non-empty means the caller should know which engine does have them.
- class speaker_helper.OperatingPoint(engine, quality, mean_rtf, quality_source='prior', rtf_source='unknown', backend='kokoro', language=None)[source]
Bases:
objectOne engine’s position on the quality↔speed plane, with provenance.
- Parameters:
engine (str) – Engine/model id (e.g.
"kokoro").quality (float) – Quality score in
[0, 1]to maximise (measured chrF or a prior).mean_rtf (float or None) – Mean real-time factor to minimise;
Nonewhen never measured (the engine then cannot be routed for the online condition).quality_source (str) –
"measured_chrf"or"prior"— howqualitywas obtained.rtf_source (str) –
"measured","estimate", or"unknown"— howmean_rtfwas obtained.backend (str) – The backend that serves this engine (default
"kokoro").language (str or None) – Language the measurement pertains to, when language-specific.
- class speaker_helper.RouteDecision(condition, engine, backend, mode, first_chunk_sentences, stream_concurrency, quality, mean_rtf, quality_source, rtf_source, confidence, justification, candidates_considered, pareto_size)[source]
Bases:
objectA concrete, justified operating point the caller can act on.
- Parameters:
condition (str) – The condition this decision answers.
engine (str) – The chosen engine and the backend that serves it.
backend (str) – The chosen engine and the backend that serves it.
mode (str) –
"streaming"(online) or"offline".first_chunk_sentences (int) – Streaming producer/consumer knobs implied by the condition (only meaningful when
mode == "streaming").stream_concurrency (int) – Streaming producer/consumer knobs implied by the condition (only meaningful when
mode == "streaming").quality (float) – The chosen point’s quality score.
mean_rtf (float or None) – The chosen point’s mean RTF (
Noneonly in the offline, RTF-unknown fallback).quality_source (str) – Provenance of the two axes (see
OperatingPoint).rtf_source (str) – Provenance of the two axes (see
OperatingPoint).confidence (str) –
"high"(both axes measured),"medium"(one measured), or"low"(both from priors/estimates).justification (str) – A human-readable, number-citing explanation of why this point won.
candidates_considered (int) – How many candidates were weighed and how many survived to the Pareto front — for auditability.
pareto_size (int) – How many candidates were weighed and how many survived to the Pareto front — for auditability.
- apply(settings=None)[source]
Return
settings(or fresh defaults) reconfigured for this decision.- Parameters:
settings (Settings or None) – Base configuration to derive from;
Noneuses defaults.- Returns:
A copy carrying the chosen backend, engine, mode, and (for streaming) the first-chunk granularity.
stream_concurrencyis aSpeakerargument, not aSettingsfield, so read it off this decision separately (seefrom_route()).- Return type:
- class speaker_helper.RouteRequest(condition, language='fr', rtf_ceiling=0.8, rtf_budget=None, quality_floor=None, require_measured_rtf=True)[source]
Bases:
objectWhat the caller needs; the router turns it into a justified choice.
- Parameters:
condition (str) –
"online_realtime"(delay-bounded streaming) or"offline"(throughput / quality). SeeCONDITIONS.language (str) – ISO-639-1 target language (drives the default catalogue and profile).
rtf_ceiling (float) – Online only: the hard
mean_rtfbound an engine must beat to be feasible. Defaults toDEFAULT_RTF_CEILING(0.8).rtf_budget (float or None) – Offline only: optional patience cap on
mean_rtf(None= no cap, quality is all that matters).quality_floor (float or None) – Reject any candidate below this quality, in both conditions (
None= no floor).require_measured_rtf (bool) – Online only: refuse engines whose RTF was never measured (default
True— do not claim an untimed engine keeps up).
- class speaker_helper.Segment(text, say='', pause_before_ms=0.0, pause_after_ms=0.0, pace='normal', emotion='', intensity='')[source]
Bases:
objectOne block of a document, as the narrator receives it.
- Parameters:
text (str) – The page’s words. This is what the cue carries, and therefore what a read-along highlights. Never substituted.
say (str) – What the engine should actually pronounce, when that differs — a formula read as a sentence, a footnote marker dropped. Empty means “say
text”, which is the ordinary case.pause_before_ms (float) – Silence the document’s structure asks for around this block — the gap that sets a heading apart from the paragraph before it. Requested by the caller, applied here.
pause_after_ms (float) – Silence the document’s structure asks for around this block — the gap that sets a heading apart from the paragraph before it. Requested by the caller, applied here.
pace (str) –
"slow","normal"or"fast". SeePACE_SPEED.emotion (str) – Asked for by some documents, honoured by some engines. A backend that has no such dial reports the name in
NarrationResult.ignored_hintsrather than pretending.intensity (str) – Asked for by some documents, honoured by some engines. A backend that has no such dial reports the name in
NarrationResult.ignored_hintsrather than pretending.
Notes
pause_before_msis skipped on the first segment: a “pause before” with nothing before it is dead air at the head of the file.- classmethod from_mapping(raw)[source]
Build a segment from a plain dict, ignoring keys it does not know.
The wire format between a document tool and this one is JSON, and the document side is free to carry extra keys (block ids, heading levels) that are none of a narrator’s business. Unknown keys are dropped here rather than rejected, so adding one upstream is not a breaking change.
- class speaker_helper.Settings(backend='kokoro', engine='kokoro', voice='', voice_sample='', voice_id='', language='fr', normalize=True, mode='offline', first_chunk_sentences=1, mock=<factory>, clone=<factory>)[source]
Bases:
objectTop-level speaker-helper configuration.
- Parameters:
backend (str) – Which TTS backend to drive.
"kokoro"(default) runs in this process and needs no server;"openvoice"and"chatterbox"clone a voice from a recording;"mock"is deterministic and serverless, for tests and the evaluation layer. The backend is an implementation detail — the rest of the package talks only to theTTSEngineprotocol. Seespeaker_helper.engine.available_backends().engine (str) – A label carried on results, for callers that record which model produced what. It does not select the backend;
backenddoes.voice (str) – Which built-in voice reads. Empty (default) means “read the text, detect the language, and pick the voice that speaks it” — which is not a convenience: the detected language is what the phonemiser works from, and French read as English is not French in an American accent, it is an American spelling the words out.
voice_sample (str) – Path to a reference recording, for the cloning backends. Kokoro refuses one rather than ignoring it.
voice_id (str) – Deprecated spelling of
voice, kept because configuration files in the wild use it.voicewins when both are set.language (str) – ISO-639-1 target language (e.g.
"fr").normalize (bool) – Loudness-normalise the output where the backend can.
mode (str) –
"offline"or"streaming".first_chunk_sentences (int) – Streaming only: sentences in the first emitted chunk (1 → lowest time-to-first-audio, which is the number a conversation feels).
Notes
Precedence, lowest to highest: dataclass defaults →
settings.yaml→SPEAKER_HELPER_*environment variables.- classmethod from_mapping(mapping)[source]
Build settings from a plain mapping (unknown keys ignored).
- class speaker_helper.Speaker(settings=None, *, engine=None, stream_concurrency=1)[source]
Bases:
objectTurn text into speech via a pluggable TTS engine, offline or streaming.
The concrete engine (kokoro, chatterbox, mock, …) is selected by
settings.backendand reached only through theTTSEngineprotocol, soSpeakernever depends on a specific backend.- Parameters:
settings (Settings or None) – Configuration; when
None,Settings.load()is used (reads./settings.yamlif present, thenSPEAKER_HELPER_*env vars).engine (TTSEngine or None) – Inject a ready backend instance (e.g. a mock in tests). When
None(default), one is built fromsettingsviacreate_engine().stream_concurrency (int) – Maximum number of chunks synthesised in parallel in streaming mode. The default of
1(a strict pipeline) gives the lowest time to first audio: the first chunk gets the whole engine to itself, and because kokoro synthesises faster than real time (RTF < 1) each later chunk is ready before playback of the previous one finishes. Raise it only to favour total throughput over first-audio latency.
Examples
>>> from speaker_helper import Speaker, Settings >>> spk = Speaker(Settings.from_mapping({"engine": "kokoro", "language": "fr"})) >>> spk.settings.engine 'kokoro'
- property client: TTSEngine
Backward-compatible alias for
engine.Earlier releases exposed the backend as
speaker.client(it was always an HTTP client). It is now anyTTSEngine; this alias keeps old call sites working.
- async clone_voice(name=None, samples=None, *, language=None)[source]
Clone a voice and make this speaker synthesise with it.
- Parameters:
name (str or None) – Profile name for the clone (idempotency key). Defaults to the configured clone name, or
"ref-fr-female".samples (list of VoiceSample or None) – Reference recordings. When
None, they are resolved fromsettings.clone— falling back to the bundled ref-fr-female reference. A sample without a transcript is transcribed withvocal-helper.language (str or None) – Language of the cloned voice (defaults to the configured language).
- Returns:
The cloned voice/profile id.
- Return type:
Examples
>>> from speaker_helper import Speaker, Settings, VoiceSample >>> spk = Speaker(Settings.from_mapping({"backend": "mock"})) >>> # give only the audio; the transcript is derived automatically: >>> # await spk.clone_voice("my-voice", [VoiceSample("ref.wav", "")])
- classmethod from_profile(profile, settings=None, *, engine=None)[source]
Build a
Speakerfrom a per-language operating profile.Applies the profile’s language/engine/voice/producer settings and its
stream_concurrency(a consumer-parallelism argument, not aSettingsfield).- Parameters:
profile (LanguageProfile) – The profile to apply (see
speaker_helper.profiles).settings (Settings or None) – Base configuration to derive from;
Noneuses defaults.engine (TTSEngine or None) – Optional injected backend (e.g. a mock in tests).
- Returns:
A speaker configured for the profile’s language.
- Return type:
- classmethod from_route(condition, *, language='fr', settings=None, engine=None, **route_kwargs)[source]
Build a
Speakerfrom a routed operating point.Asks
speaker_helper.router.route()to choose an engine + mode from measured quality↔speed evidence for the given condition ("online_realtime"or"offline"), applies that choice, and forwards the decision’sstream_concurrency.- Parameters:
condition (str) –
"online_realtime"(delay-bounded streaming) or"offline".language (str) – Target language driving the candidate catalogue.
settings (Settings or None) – Base configuration to reconfigure;
Noneuses defaults.engine (TTSEngine or None) – Optional injected backend (e.g. a mock in tests); when given, the routing choice still sets language/mode but this engine is used.
**route_kwargs – Extra
RouteRequestfields (rtf_ceiling,rtf_budget,quality_floor,require_measured_rtf).
- Returns:
A speaker configured for the routed operating point. The chosen
RouteDecisionis attached asspeaker.routefor inspection / logging.- Return type:
- async narrate(segments, *, language=None, voice=None)[source]
Read a document aloud and say where every block landed in the audio.
The difference from
stream()is not the engine, it is the bookkeeping: a document has structure — pauses its headings ask for, blocks whose spoken form differs from their printed form — and a caller that wants to follow the reading needs to know which stretch of audio speaks which block. Seespeaker_helper.narration.- Parameters:
segments (list) –
Segmentobjects, plain dicts, or bare strings — seecoerce_segments().language (str or None) – Override the configured ones for this reading.
voice (str or None) – Override the configured ones for this reading.
- Returns:
The audio, the cues, and what the backend could not honour.
- Return type:
- Raises:
ValueError – When no segment holds anything speakable, or when the laid-out audio and the timeline disagree.
Notes
Synthesis happens first, for every speakable block at once (bounded by
stream_concurrency), and the layout second. Two passes rather than one because the sample rate is the backend’s answer, not ours, and the timeline cannot start counting before it knows what a sample is worth.
- save(text, path, *, language=None)[source]
Synthesise
textand write the WAV topath(blocking).- Parameters:
- Returns:
The synthesised audio (also written to
path).- Return type:
- async say(text, *, language=None)[source]
Synthesise
textinto oneAudioResult(offline mode).- Parameters:
- Returns:
The synthesised audio and its metadata.
- Return type:
- say_sync(text, *, language=None)[source]
Blocking wrapper around
say()for non-async callers.- Parameters:
- Returns:
The synthesised audio.
- Return type:
- Raises:
RuntimeError – If called from within a running event loop (use
await say).
- async stream(text, *, language=None)[source]
Yield audio chunks as they are synthesised (streaming mode).
- Parameters:
- Yields:
StreamChunk – Chunks in order. The first carries
ttfa_s(time to first audio); the last hasis_final=True.- Return type:
Notes
Synthesis is pipelined up to
stream_concurrencychunks at a time so later chunks are produced while earlier ones are being consumed, but emission order matches the text.
- async voices(engine=None)[source]
List preset voices for an engine (defaults to the configured one).
- async warmup()[source]
Prime the engine so the first real request is not the slow one.
Synthesises a tiny throwaway utterance, which forces backend-specific one-time costs (profile bootstrap, model download / load) to happen now rather than on a user’s first call. Failures are logged and swallowed — warm-up is best-effort and must never break startup.
- Return type:
None
- class speaker_helper.SpeechCue(index, text, start_seconds, end_seconds)[source]
Bases:
objectOne narrated block, with the slice of audio that speaks it.
- Parameters:
index (int) – Position in the original segment list, not in the audio. The two differ: a segment whose text is empty after cleaning is never synthesised, so counting emitted pieces drifts from counting segments. Carrying the original index is what lets a page map a cue back to the block it came from.
start_seconds (float) – Where this block is spoken, from the head of the file.
end_seconds (float) – Where this block is spoken, from the head of the file.
- class speaker_helper.StreamChunk(seq, audio, ttfa_s=None, is_final=False)[source]
Bases:
objectOne chunk of a streaming synthesis (one sentence-sized unit).
- Parameters:
seq (int) – Zero-based index of this chunk within the stream.
audio (AudioResult) – The synthesised audio for this chunk.
ttfa_s (float | None) – Time-to-first-audio in seconds, set only on the first chunk (
seq == 0);Noneafterwards.is_final (bool) –
Truefor the last chunk of the stream.
- audio: AudioResult
- class speaker_helper.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:
- class speaker_helper.Timeline(sample_rate_hz)[source]
Bases:
objectAccumulates durations while a backend accumulates audio.
Two jobs, one object: apply the pauses the segments ask for, and record where every segment landed. Both need the same running sample count, and keeping them apart is how they drift.
Deliberately numpy-free: it counts samples and the caller holds the arrays, so this module imports with nothing but the standard library — which is what lets a caller build and inspect a narration plan without the ONNX runtime anywhere near it.
Examples
>>> timeline = Timeline(1000) >>> timeline.silence(500) 500 >>> timeline.speech(2000, index=0, text="Hello.") >>> timeline.silence(250) 250 >>> [(c.start_seconds, c.end_seconds) for c in timeline.cues] [(0.5, 2.5)] >>> timeline.total_seconds 2.75
- Parameters:
sample_rate_hz (int)
- silence(milliseconds)[source]
Advance the clock by
milliseconds; return the samples to append.Returning the count rather than an array is what keeps this module numpy-free. A caller that ignores the return value desynchronises the timeline from its own audio, which is exactly what
check_timeline_matches_audio()exists to catch.
- class speaker_helper.Voice(voice_id, name, language, gender='', engine='')[source]
Bases:
objectA preset voice offered by a backend.
- Parameters:
voice_id (str) – Engine-specific identifier (e.g.
"ff_siwis"for kokoro French).name (str) – Human-readable display name.
language (str) – ISO-639-1 language code the voice speaks (e.g.
"fr","en").gender (str) – Reported voice gender, or
""when the engine does not expose one.engine (str) – Engine the voice belongs to (e.g.
"kokoro").
- class speaker_helper.VoiceList(engine, voices=<factory>)[source]
Bases:
objectVoices available for an engine.
- Parameters:
- class speaker_helper.VoiceSample(audio, reference_text)[source]
Bases:
objectOne reference recording used to clone a voice.
- Parameters:
Notes
Keep samples short and clean (a few seconds of clear speech). Provide several for a more robust clone.
- filename()[source]
Return a filename to send with the upload (from the path, or a default).
- Return type:
- speaker_helper.chunk_for_streaming(text, first_chunk_sentences=1)[source]
Group sentences into synthesis chunks, keeping the first one small.
- Parameters:
- Returns:
Chunks of text to synthesise in order. The first chunk holds
first_chunk_sentencessentences; every remaining sentence is its own chunk so downstream audio keeps flowing steadily.- Return type:
Examples
>>> chunk_for_streaming("A. B. C.", first_chunk_sentences=1) ['A.', 'B.', 'C.'] >>> chunk_for_streaming("A. B. C.", first_chunk_sentences=2) ['A. B.', 'C.']
- speaker_helper.coerce_segments(raw)[source]
Accept segments as dicts, as
Segment, or as bare strings.Three shapes because three callers: a document tool writes JSON, a Python caller builds dataclasses, and somebody at a REPL has a list of sentences and no interest in any of this.
- speaker_helper.complexity(latex)[source]
How much structure a formula carries.
A rough count of the things that make a formula hard to follow by ear: commands, relations, nesting, sub- and superscripts.
Rscores zero and needs no help; a nested fraction of sums scores high and a symbol-by-symbol reading of it is sound without meaning.
- speaker_helper.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.cues_to_vtt(cues)[source]
Render narration cues as WebVTT.
Each cue carries its segment index as the WebVTT identifier — the line before the timings — which is what lets a page highlight the right block without matching on text. The text is collapsed onto one line because a blank line ends a cue, so a hard-wrapped payload would silently become two cues and shift every index after it.
- speaker_helper.default_operating_points(language='fr')[source]
Build the fallback candidate set for
languagefrom profiles + priors.The per-language profile supplies the measured kokoro operating point (RTF from native-MLX tuning; quality is the engine prior until a round-trip recalibrates it). Every other engine in the priors table contributes a quality-only candidate with an unknown RTF — enough to reason about offline quality, but deliberately not enough to claim it keeps up online.
- Parameters:
language (str) – Language whose profile seeds the measured kokoro point.
- Returns:
One candidate per known engine, provenance flagged on both axes.
- Return type:
- speaker_helper.profile_for(language)[source]
Return the profile for
language(a default profile if none is defined).- Parameters:
language (str)
- Return type:
- speaker_helper.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
- speaker_helper.resolve_spoken_math(segments, lang, *, narrator=None)[source]
Fill each segment’s
saywith a readable form of its notation.A convenience over
speaker_helper.speech_text.speakable(), which is where the work actually happens. Separate fromSegmenton purpose: a segment whosesaywas set deliberately — a pronunciation override, a formula a human already phrased — must not be overwritten, so this only fills the empty ones.- Parameters:
segments (list of Segment) – Left untouched; a new list is returned.
lang (str) – The language the page is written in. Not optional and not defaulted: a French page read with English operator names is worse than one that says nothing, because the listener hears fluent words in the wrong language and assumes they misheard.
narrator (callable or None) –
(latex, lang) -> str | None, asked about formulas too complex to spell out. Optional — without one every formula gets the deterministic reading, which is the baseline, not a degraded mode.
- Returns:
The same blocks, with
sayfilled where it was empty and the notation differs from the words.- Return type:
- speaker_helper.route(request, *, points=None, reports=None)[source]
Choose a justified engine + mode for
requestfrom the evidence.- Parameters:
request (RouteRequest) – The condition and its constraints.
points (sequence of OperatingPoint or None) – Explicit candidate operating points. When
Noneandreportsis alsoNone, the built-in catalogue forrequest.languageis used.reports (sequence of EvalReport or None) – Measured reports to derive candidates from (highest confidence). Takes precedence over
pointswhen both are given.
- Returns:
The chosen operating point, mode, streaming knobs, and a number-citing justification.
- Return type:
- Raises:
ValueError – If
request.conditionis unknown, or no candidate satisfies the condition’s constraints (e.g. no engine keeps up online).
Examples
>>> route(RouteRequest(condition="offline", language="fr")).mode 'offline'
- speaker_helper.route_settings(condition, *, language='fr', settings=None, **request_kwargs)[source]
Convenience: route for
conditionand return applied settings + decision.- Parameters:
condition (str) –
"online_realtime"or"offline".language (str) – Target language.
settings (Settings or None) – Base settings to reconfigure;
Noneuses defaults.**request_kwargs – Extra
RouteRequestfields (rtf_ceiling,rtf_budget,quality_floor,require_measured_rtf).
- Returns:
Settings reconfigured for the decision, and the decision itself (its
stream_concurrencystill needs forwarding toSpeaker).- Return type:
tuple of (Settings, RouteDecision)
- speaker_helper.speak_math(latex, lang='en')[source]
One formula, as a person would read it out.
R = T_{BA} \circ T_{AB}becomes “R equals T B A composed with T A B”.Subscripts are read as their own letters rather than announced: “T A B” is how somebody says that at a blackboard, and “T subscript A B” is how nobody does. Powers keep their announcement, because “x 2” and “x squared” are different claims.
- speaker_helper.speakable(text, lang='en', *, narrator=None)[source]
The sentence as the voice should say it.
Only the notation changes; every other word is the page’s.
narratoris an optional(latex, lang) -> str | None. It is asked about formulas whosecomplexity()is aboveNARRATE_ABOVE, and its answer is used when it gives one. This is the difference between spelling a formula and reading it: “the sum over i of x i, all over n” is a transliteration, “the average of the x values” is a sentence, and only the second survives being heard once with no page in front of you.It stays an injected callable rather than an import so this module needs no model, no network and no optional extra to be used or tested. Without one, every formula gets the deterministic reading — which is the baseline, not a degraded mode.
- speaker_helper.split_sentences(text)[source]
Split text into sentence-sized, non-empty, stripped units.
- Parameters:
text (str) – Arbitrary text, possibly multi-sentence and multi-line.
- Returns:
Sentences in order, each stripped of surrounding whitespace, with their terminating punctuation preserved. Text without any terminator returns a single-element list (the whole stripped input); empty or whitespace-only input returns an empty list.
- Return type:
Examples
>>> split_sentences("Un. Deux. Trois.") ['Un.', 'Deux.', 'Trois.'] >>> split_sentences("no terminator here") ['no terminator here'] >>> split_sentences(" ") []
- async speaker_helper.tune_profiles(base_settings, languages, **eval_kwargs)[source]
Measure a set of languages and return profiles carrying the results.
A thin convenience over
run_multilang_eval(): it runs the evaluation per language and folds each report’s RTF/quality into the correspondingLanguageProfile.- Parameters:
- Returns:
Mapping
language -> LanguageProfilewith measured RTF/quality.- Return type:
- speaker_helper.voice_for(language, gender='', *, index=0)[source]
Resolve a
(language, gender)pair to a concrete kokoro voice id.- Parameters:
- Returns:
A real kokoro
voice_id. Falls back to the first voice of the language (ignoring gender), then toff_siwis/am_michael-style defaults, so the result is always a usable id.- Return type: