Spans
SSMD spans report offsets in the cleaned text returned by parse_spans. The coordinate
system matches ParseSpansResult.clean_text after markup is removed and placeholders
are unescaped.
TTS pipeline integration
Use parse_spans() as the primary integration boundary when another TTS pipeline
prepares and segments text:
SSMD parse -> semantic preparation/normalization -> sentence splitting -> G2P
parsed = ssmd.parse_spans(source)
# Pass parsed.clean_text and parsed.annotations to the downstream normalizer.
parse_spans() performs structural parsing only. It does not expand numbers or
abbreviations, infer a document language, phonemize text, or run sentence detection.
Annotation offsets refer to structural clean_text; after normalization changes text
length, the orchestrator remaps those offsets. Phoneme (ph/ipa) annotations expose
exact source ranges that can be passed to the normalizer as protected spans.
Semantic and pronunciation language scopes
An inline language annotation is semantic by default:
[Bonjour]{lang="fr"}
Use scope="pronunciation" to mark a language run for pronunciation/G2P only:
[File]{lang="en" scope="pronunciation"}
[Manpower]{lang="en" scope="pronunciation"}diskussion
ge[cancel]{lang="en" scope="pronunciation"}t
[download]{lang="en" scope="pronunciation"}en
parse_spans() and parse_structure() preserve the generic annotation attributes as
lang, scope, and tag="lang". AnnotationSpan.language_scope defaults to
semantic, and is_pronunciation_language identifies the stronger scope. SSMD does not
infer language or enforce how a downstream consumer routes pronunciation.
When normalization is enabled, source-adjacent inline pieces remain adjacent in clean
text, so the first example yields Manpowerdiskussion and the annotated Manpower span
remains [0:8].
A portable header can also provide a routing hint:
language_detection:
mode: auto
languages: [de, en]
The header is returned separately and validated, but SSMD does not perform detection.
Structure-only parsing
parse_structure() is the sentence-neutral companion to parse_spans(). It returns the
same clean-text and annotation concepts plus zero-width StructuralEvent values for
breaks, marks, and paragraph boundaries:
parsed = ssmd.parse_structure("Hello ...500ms @chapter world")
assert parsed.clean_text == "Hello world"
assert parsed.events[0].pos == 5 # a boundary, not the last character index
Events and annotations are calculated in the returned clean-text coordinate system.
Events use anchor="before" for content that follows the boundary and anchor="after"
for content that precedes it. Break attributes use time or semantic strength; mark
attributes use name. Final breaks and marks are flushed rather than discarded.
Paragraph events are structural and carry no pause duration.
The result also exposes YAML front matter as header, separately from clean_text.
parse_structure() does not detect language, normalize written language into spoken
language, phonemize, or invoke sentence detection. Those operations remain owned by the
downstream consumer.
Coordinate system
Offsets refer to character indices in
clean_textonly.Markup like
*,[text]{...}, and canonical:::voice directives is removed before offsets are computed. Legacy raw<div>voice blocks are also recognized in compatibility mode.Escaping via
escape_ssmd_syntax()is reversible but not length-preserving; do not use offsets from escaped text.
Examples
import ssmd
result = ssmd.parse_spans("Hello [world]{lang='en'}")
print(result.clean_text) # "Hello world"
print(result.annotations[0])
Sentence offsets
Use iter_sentences_spans() to align sentence text with clean_text:
for sentence, start, end in ssmd.iter_sentences_spans("Hello *world*. Next."):
print(sentence, start, end)