Quick Start

This guide will help you get started with SSMD quickly.

Canonical SSMD 0.9 input

New standalone documents declare their dialect. Use canonical fenced directives and attribute names:

---
ssmd_version: "0.9"
---
:::{voice="host"}
Hello *world*!
:::

[Bonjour]{lang="fr"}
[urgent]{volume="loud" rate="fast" pitch="high"}

Unversioned files retain legacy compatibility behavior. Run ssmd migrate FILE --to 0.9 for an explicit semantic-equivalence-checked upgrade.

Basic Conversion

The convenience-function samples use short body fragments for clarity. Add the 0.9 front matter shown above when saving source as a standalone document. For machine-driven authoring, use the root-level JSON interface and check both the process exit status and command-specific result fields:

ssmd --json create draft.ssmd.md -o output.ssmd.md --fail-on-warn
ssmd --json lint output.ssmd.md --roundtrip --fail-on-warn
ssmd --json inspect output.ssmd.md --spans
ssmd --json to-ssml output.ssmd.md -o output.ssml
ssmd --json text output.ssmd.md

Creation is complete only when result.created == true, bytes_written is nonzero, and the output exists; linting is complete only when result.passed == true.

The simplest way to use SSMD is with the convenience functions:

SSMD to SSML

import ssmd

# Convert SSMD markup to SSML
ssml = ssmd.to_ssml("Hello *world*!")
print(ssml)
# Output: <speak>Hello <emphasis>world</emphasis>!</speak>

# More complex example
ssml = ssmd.to_ssml("""
# Welcome
Hello *world*!
This is a ...500ms pause.
[Bonjour]{lang="fr"} everyone!
""")

Strip Markup

Remove all SSMD markup to get plain text:

import ssmd

plain = ssmd.to_text("Hello *world* @marker!")
print(plain)
# Output: Hello world!

SSML to SSMD

Convert SSML back to SSMD format:

import ssmd

ssml = '<speak><emphasis>Hello</emphasis> world</speak>'
ssmd_text = ssmd.from_ssml(ssml)
print(ssmd_text)
# Default output is a complete SSMD 0.9 document:
# ---
# ssmd_version: '0.9'
# ---
# *Hello* world

Using the Document API

For building and managing TTS content, use the Document class:

Sentence/list operations such as split(), sentences(), indexing, and len() are legacy APIs for unversioned or SSMD 0.8 documents. SSMD 0.9 documents, including results from Document.from_ssml(), use a structural model and reject those sentence-level operations. Use ssmd.parse_structure() or ssmd.parse_spans() for 0.9 structure, and supply explicit sentence spans to conversion when needed.

Creating Documents

from ssmd import Document

# Create an empty document
doc = Document()

# Create with initial content
doc = Document("Hello *world*!")

# Create with configuration
doc = Document(
    config={'auto_sentence_tags': True},
    capabilities='pyttsx3'
)

Building Documents

from ssmd import Document

# Build incrementally
doc = Document()
doc.add_sentence("Hello and *welcome*!")
doc.add_sentence("This is SSMD.")
doc.add_paragraph("Starting a new paragraph.")

# Method chaining
doc = Document() \
    .add("Hello ") \
    .add("*world*!") \
    .add_sentence("Next sentence.")

Exporting Documents

from ssmd import Document

doc = Document("Hello *world*!")

# Export to different formats
ssml = doc.to_ssml()      # SSML XML
markdown = doc.to_ssmd()  # SSMD markdown
text = doc.to_text()      # Plain text

# Access properties
print(doc.ssmd)           # Raw SSMD content
print(len(doc))           # Number of sentences
print(len(list(doc.paragraphs())))  # Number of paragraphs

TTS Streaming

Iterate through unversioned or SSMD 0.8 documents sentence-by-sentence for TTS. SSMD 0.9 documents require structural parsing and explicit sentence spans instead:

from ssmd import Document

# Create document
doc = Document(config={'auto_sentence_tags': True})

# Build content
doc.add_paragraph("# Chapter 1")
doc.add_sentence("Welcome to SSMD!")
doc.add_sentence("This is the first sentence.")
doc.add_sentence("This is the second sentence.")
doc.add_paragraph("# Chapter 2")
doc.add_sentence("Here's another chapter.")

# Iterate through sentences
for i, sentence in enumerate(doc.sentences(), 1):
    print(f"Sentence {i}: {sentence}")
    # Your TTS engine here:
    # tts_engine.speak(sentence)

# Access specific sentences
sentence_count = len(list(doc.sentences()))
print(f"Total sentences: {sentence_count}")
print(f"Total paragraphs: {len(list(doc.paragraphs()))}")
print(f"First: {doc[0]}")
print(f"Last: {doc[-1]}")

TTS pipeline integration

For an application that normalizes text before sentence splitting, parse SSMD structurally first:

import ssmd

parsed = ssmd.parse_spans(source)
prepared_text = normalize(parsed.clean_text, parsed.annotations)
sentence_spans = split_with_offsets(prepared_text)
ssml = ssmd.to_ssml(source, sentence_spans=sentence_spans)

External spans use offsets in the structural clean-text space after the caller has remapped them through normalization. SSMD does not perform number normalization, abbreviation expansion, language detection, or G2P. Explicit ph and ipa annotations should be marked protected by the downstream normalizer.

Document Editing

Documents support sentence/list editing methods for unversioned or SSMD 0.8 content. These methods are not available on SSMD 0.9 documents.

from ssmd import Document

doc = Document("First. Second. Third.")

# Edit sentences
doc[0] = "Modified first sentence."
del doc[1]  # Remove second sentence

# String operations
doc.replace("sentence", "line")

# Insert content
doc.insert(0, "New opening sentence.")

# Clear all content
doc.clear()

Advanced Document Operations

import ssmd
from ssmd import Document

# SSML import creates a complete 0.9 document.
doc09 = Document.from_ssml('<speak><emphasis>Hello</emphasis></speak>')
structure = ssmd.parse_structure(doc09.ssmd, dialect="0.9")
print(structure.clean_text)

# Use the structural parser for 0.9 documents, not sentence/list methods.

# Sentence-level operations remain available for unversioned legacy documents.
legacy_doc = Document("First sentence. Second sentence.")
sentences = legacy_doc.split()

for sent_doc in sentences:
    ssml = sent_doc.to_ssml()
    ssmd_text = sent_doc.to_ssmd()

Working with TTS Engines

Filter output based on engine capabilities:

Using Presets

from ssmd import Document

# Adapt output using the provider's declared capability profile
source = '---\nssmd_version: "0.9"\n---\n*Hello* [world]{lang="fr"}!'
doc = Document(source, capabilities='espeak')
ssml = doc.to_ssml(target="provider", loss_policy="warn")
diagnostics = doc.render_diagnostics  # inspect any adaptations or losses

# A second provider preset applies its own capability profile
doc = Document(source, capabilities='google')
ssml = doc.to_ssml(target="provider", loss_policy="warn")
diagnostics = doc.render_diagnostics

Available presets:

  • minimal - Plain text only

  • pyttsx3 - Limited prosody and paragraph support

  • espeak - Breaks, language, prosody, and phoneme support

  • polly / amazon - Provider-specific feature profile

  • google / azure - Provider-specific capability adaptation; feature support varies

  • full - Enable all features implemented by SSMD; not a guarantee of universal SSML support

Custom Capabilities

from ssmd import Document, TTSCapabilities

# Define exactly what your TTS engine supports
caps = TTSCapabilities(
    emphasis=True,
    break_tags=True,
    prosody=False,  # No prosody support
    language=True
)

doc = Document("*Hello* world!", capabilities=caps)

Common Patterns

Emphasis and Stress

ssmd.to_ssml("This is *important*!")
ssmd.to_ssml("This is **very important**!")

Pauses and Breaks

# Specific duration (required - bare ... is not a break)
ssmd.to_ssml("Hello ...500ms world")
ssmd.to_ssml("Hello ...2s world")
ssmd.to_ssml("Hello ...1s world")  # 1 second

# Strength-based
ssmd.to_ssml("Hello ...c world")  # comma
ssmd.to_ssml("Hello ...s world")  # sentence
ssmd.to_ssml("Hello ...p world")  # paragraph
ssmd.to_ssml("Hello ...n world")  # none/no break

Voice Control

# Volume
ssmd.to_ssml('[loud]{volume="loud"}')
ssmd.to_ssml('[very loud]{volume="x-loud"}')
ssmd.to_ssml('[soft]{volume="soft"}')

# Speed
ssmd.to_ssml('[fast]{rate="fast"}')
ssmd.to_ssml('[very fast]{rate="x-fast"}')
ssmd.to_ssml('[slow]{rate="slow"}')

# Pitch
ssmd.to_ssml('[high]{pitch="high"}')
ssmd.to_ssml('[very high]{pitch="x-high"}')
ssmd.to_ssml('[low]{pitch="low"}')

Language Switching

ssmd.to_ssml('[Bonjour]{lang="fr"} everyone!')
ssmd.to_ssml('[Hola]{lang="es-MX"} amigos!')
ssmd.to_ssml('[Hello]{lang="en-GB"} there!')

Phonetic Pronunciation

ssmd.to_ssml('[tomato]{ph="təˈmeɪtoʊ"}')
ssmd.to_ssml('[hello]{ipa="həˈloʊ"}')

Next Steps