ssml · Rust · no network · no credentials · XML only

ssml

ssml writes Speech Synthesis Markup Language, the XML that tells Azure Speech which voice to use and how to deliver a line. Text comes from an argument or stdin, delivery from flags, and one well-formed document goes to stdout. It makes no network calls and reads no credentials, and the same input always produces the same bytes.

ssml --model MAI-Voice-2.1 --voice en-US-Iris --emotion cheerful --degree 1.2 'Good morning.'
<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts' xml:lang='en-GB'><voice name='en-US-Iris:MAI-Voice-2.1'><lang xml:lang='en-GB'><mstts:express-as style='cheerful' styledegree='1.20'><prosody rate='+0.00%' pitch='+0.00%' volume='100.00'>Good morning.</prosody></mstts:express-as></lang></voice></speak>

Flags to markup

Which flag writes what Where each ssml flag lands in the document. --model and --voice choose the voice element's name: a MAI model alone, a named voice qualified with the model, or with --model azure a standard Azure voice name. --embedding adds an mstts:ttsembedding with the speaker profile ID. --lang sets xml:lang on speak and on lang. --style, --degree and --role add an mstts:express-as, and a degree or a role without a style is refused. --speed, --pitch and --volume fill the prosody element as a rate percentage, a pitch percentage and a volume. --model · --voice --embedding --lang --style · --degree · --role --speed · --pitch · --volume <voice name='…'>Iris → en-US-Iris:MAI-Voice-2.1 <mstts:ttsembedding speakerProfileId='…'> xml:lang on <speak> and <lang> <mstts:express-as style styledegree role> <prosody rate pitch volume>--speed 1.5 → rate='+50.00%'
One flag, one place. --style, --emotion and --express-as are aliases, as are --embedding and --speaker-profile-id.
A voice from a speaker profile The document ssml --embedding 00000000-0000-0000-0000-000000000000 --speed 1.5 'A cloned voice, faster.' prints, as nested elements: speak, then voice MAI-Voice-2.1-Flash, then mstts:ttsembedding with the speaker profile ID, then lang en-GB, then prosody with rate +50%, pitch +0% and volume 100 around the text. <speak xml:lang"en-GB" > <voice name"MAI-Voice-2.1-Flash" > <mstts:ttsembedding speakerProfileId"00000000-0000-0000-0000-000000000000" > <lang xml:lang"en-GB" > <prosody rate"+50.00%" pitch"+0.00%" volume"100.00" > A cloned voice, faster.
A speaker profile wraps the line. With --embedding, the voice is a cloned profile rather than a named persona (the ID here is a placeholder).

Escaping and validation

Text is text

Input is literal plain text, never SSML and never a prompt to a model. Everything is escaped:

ssml 'Fish & chips <3 "quoted"'
…<prosody rate='+0.00%' pitch='+0.00%' volume='100.00'>Fish &amp; chips &lt;3 &quot;quoted&quot;</prosody>…

Invalid is an error

A degree or a role only means something with a style, so asking for one without the other fails with exit code 2 rather than emitting markup a service would quietly ignore:

ssml --model MAI-Voice-2.1 --voice en-US-Iris --degree 1.2 'Role without style'
ssml: role/degree require style
Ranges
Speed0.5–3, a multiplier
Pitch−50 to +50 percent
Volume0–100
Style degree0.01–2
Inputone argument, - or stdin, at most 1 MiB

Defaults are MAI-Voice-2.1-Flash in British English at normal speed, with no embedding or emotion. Named MAI voices become name:model; already qualified voices are left alone; --model azure takes standard Azure voice names. Exit codes: 0 for success (a closed output pipe included), 1 for I/O, 2 for invalid arguments.

Whether a deployment accepts a combination is the service's business: syntactically valid SSML is not a promise that a model supports every style, role or prosody value.

Where it is used

The same document comes out of three tools. aztts links the Rust library to escape and wrap text before sending it to Azure. The Read Aloud Chrome extension builds SSML with its own JavaScript builder, and ssml --config speech.json accepts that extension's Speech settings object in place of flags, so the same settings object describes a voice in the browser and in a shell.