Skip to content

feat(speechify): stream with timestamps via /v1/audio/stream/with-timestamps - #6926

Open
luke-speechify wants to merge 2 commits into
livekit:mainfrom
luke-speechify:feat/speechify-sync-8fba9f4
Open

feat(speechify): stream with timestamps via /v1/audio/stream/with-timestamps#6926
luke-speechify wants to merge 2 commits into
livekit:mainfrom
luke-speechify:feat/speechify-sync-8fba9f4

Conversation

@luke-speechify

Copy link
Copy Markdown
Contributor

Summary

SynthesizeStream now streams audio with word-level marks from the real streaming endpoint instead of issuing one batch request per sentence.

For streaming-native models (simba-3.0, simba-3.2, or no model given, which the server resolves to simba-3.0) the stream uses /v1/audio/stream/with-timestamps via the SDK's stream_with_timestamps. Each speech.chunk event pushes its PCM audio as it arrives and converts its speech_marks into a timed transcript, so audio and aligned word timestamps are emitted while the audio is still landing. speech.done ends the sentence, speech.error surfaces as APIStatusError.

The legacy simba-english and simba-multilingual models do not serve that route, the server rejects them with 400 speech_marks_unsupported. They keep the per-sentence /v1/audio/speech path unchanged.

Changes

  • tts.py: route selection in _synthesize, _stream_request_kwargs (output_format=pcm_24000), _supports_streaming_marks, shared _marks_to_timed, rewired docstring
  • pyproject.toml + uv.lock: speechify-api floor >=3.0.1 to >=4.0.0 (first release with stream_with_timestamps)
  • tts.py: warning when a curated simba-3.2 voice (id suffix _32, e.g. dominic_32) is paired with any other model, including NOT_GIVEN
  • tests/test_plugin_speechify.py: new hermetic unit tests

Why it's not breaking

  • Public API unchanged: no constant, function, or TTS method removed or altered in signature
  • Legacy models issue byte-identical requests on the wire, verified by spying the raw SDK call from both the unchanged synthesize() path and the new stream() legacy branch
  • Output contract identical: same 24 kHz mono PCM frames, same USERDATA_TIMED_TRANSCRIPT userdata
  • The speechify-api floor bump only resolves forward; the methods the plugin uses exist in both versions

Test plan

  • mypy strict: clean on plugin and tests
  • ruff format + lint: clean
  • pytest tests/test_plugin_speechify.py: 7 tests, covering the streaming route, legacy fallback, NOT_GIVEN model, marks conversion, error propagation, and the voice/model warning
  • Live E2E against the real API: simba-3.2 streamed 3.13s of audio with 7 word marks in order, legacy fallback 2.96s with marks, synthesize() multi-sentence offset accumulation correct

…estamps

Use the streaming-native SSE endpoint (speechify-api >= 4.0.0) for
SynthesizeStream, emitting audio chunks and aligned word marks as they
become final. Legacy models (simba-english, simba-multilingual) fall
back to one /v1/audio/speech request per sentence.
…odels

Voices whose id carries a '_32' suffix (e.g. dominic_32) are part of the
curated simba-3.2 roster. Log a warning when one is paired with any other
model (including NOT_GIVEN, which the server resolves to simba-3.0).
@luke-speechify
luke-speechify requested a review from a team as a code owner August 20, 2026 14:26
@CLAassistant

CLAassistant commented Aug 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants