Skip to content

ElevenLabs: translate SSML input into the model-matched dialect - #39

Merged
willwade merged 3 commits into
mainfrom
feat/elevenlabs-ssml-speak
Sep 9, 2026
Merged

willwade merged 3 commits into
mainfrom
feat/elevenlabs-ssml-speak

Conversation

@willwade

@willwade willwade commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the last ElevenLabs TODO item. tts_speak_ssml (and any <speak>-wrapped input) on ElevenLabs was stripped to plain text — every break, whisper and style lost. Now it round-trips SSML → SpeechMarkdown → dialect (speechmarkdown-rust 0.4.16, which adds amazon:effect whisper + mstts:express-as translations — AACTools/speechmarkdown-rust#6, v0.4.16 published):

  • <break time="2s"/> → <break time="2s"/> (pre-v3) / [long pause] (v3)
  • <prosody rate="slow"> → [drawn out] (v3)
  • <amazon:effect name="whispered"> → [whispers]
  • <mstts:express-as style="cheerful"> → [cheerful]
  • Parse failures degrade to the old plain-text strip

Test plan

  • Unit: elevenlabs_ssml_to_dialect across both dialects + plain-text passthrough
  • Offline mock-server end-to-end: SSML input → request body contains the audio-tag dialect, no raw XML
  • Full suite + CI-parity clippy/fmt clean
  • CI green per-commit

ElevenLabs parses no SSML, so SSML input (the tts_speak_ssml path and
SpeechMarkdown that expands to <speak>-wrapped markup) was stripped to
plain text — every break, whisper and style lost. Now it round-trips
through SpeechMarkdown into the model-matched dialect: breaks become
pause tiers, prosody becomes tempo tags, amazon:effect whispered
becomes [whispers], mstts:express-as styles pass through as audio tags
(speechmarkdown-rust 0.4.16). Parse failures fall back to stripping.

Verified end-to-end with the offline mock server: the request body for
SSML input on the default eleven_v3 model contains the audio-tag
dialect and no raw XML.
…ck tests

Review caught an estimated-boundary regression: with SSML input on the
default eleven_v3 model (which lacks /with-timestamps), the estimator
was tokenizing the translated dialect — boundary words like "[long"
and "pause]". Boundary search text for translated ElevenLabs SSML is
now the plain spoken text, restoring pre-translation semantics; pinned
by a degraded-path integration test asserting exactly the plain words.

Also: malformed-SSML → strip fallback and <voice>-dropped behavior
pinned in unit tests; README FFI table corrected (ElevenLabs translates
SSML via SpeechMarkdown rather than bypassing it); cbindgen header +
vendored Swift copy synced.
@willwade
willwade merged commit cdd97e7 into main Sep 9, 2026
22 checks passed
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.

1 participant