ssmd 0.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ssmd/__init__.py ADDED
@@ -0,0 +1,189 @@
1
+ """SSMD - Speech Synthesis Markdown to SSML converter.
2
+
3
+ SSMD provides a lightweight markdown-like syntax for creating SSML
4
+ (Speech Synthesis Markup Language) documents. It's designed to be
5
+ more human-friendly than raw SSML while maintaining full compatibility.
6
+
7
+ Example:
8
+ Basic usage::
9
+
10
+ import ssmd
11
+
12
+ # Create and build a document
13
+ doc = ssmd.Document()
14
+ doc.add_sentence("Hello *world*!")
15
+ doc.add_sentence("This is SSMD.")
16
+
17
+ # Export to different formats
18
+ ssml = doc.to_ssml()
19
+ text = doc.to_text()
20
+
21
+ # Or use convenience functions for one-off conversions
22
+ ssml = ssmd.to_ssml("Hello *world*!")
23
+
24
+ Advanced usage with streaming::
25
+
26
+ # Create parser with custom config
27
+ doc = ssmd.Document(
28
+ capabilities='pyttsx3',
29
+ config={'auto_sentence_tags': True}
30
+ )
31
+
32
+ # Build document incrementally
33
+ doc.add_paragraph("# Welcome")
34
+ doc.add_sentence("Hello and *welcome* to SSMD!")
35
+
36
+ # Stream to TTS
37
+ for sentence in doc.sentences():
38
+ tts_engine.speak(sentence)
39
+ """
40
+
41
+ from typing import Any
42
+
43
+ from ssmd.document import Document
44
+ from ssmd.ssml_parser import SSMLParser
45
+ from ssmd.capabilities import (
46
+ TTSCapabilities,
47
+ get_preset,
48
+ ESPEAK_CAPABILITIES,
49
+ PYTTSX3_CAPABILITIES,
50
+ GOOGLE_TTS_CAPABILITIES,
51
+ AMAZON_POLLY_CAPABILITIES,
52
+ AZURE_TTS_CAPABILITIES,
53
+ MINIMAL_CAPABILITIES,
54
+ FULL_CAPABILITIES,
55
+ )
56
+ from ssmd.parser import (
57
+ parse_sentences,
58
+ parse_segments,
59
+ parse_voice_blocks,
60
+ )
61
+ from ssmd.parser_types import (
62
+ SSMDSegment,
63
+ SSMDSentence,
64
+ VoiceAttrs,
65
+ ProsodyAttrs,
66
+ BreakAttrs,
67
+ SayAsAttrs,
68
+ AudioAttrs,
69
+ PhonemeAttrs,
70
+ )
71
+ from ssmd.segment import Segment
72
+ from ssmd.sentence import Sentence
73
+ from ssmd.types import (
74
+ HeadingConfig,
75
+ DEFAULT_HEADING_LEVELS,
76
+ )
77
+ from ssmd.formatter import format_ssmd
78
+ from ssmd.utils import escape_ssmd_syntax, unescape_ssmd_syntax
79
+
80
+ try:
81
+ from ssmd._version import version as __version__
82
+ except ImportError:
83
+ __version__ = "unknown"
84
+
85
+
86
+ # ═══════════════════════════════════════════════════════════
87
+ # CONVENIENCE FUNCTIONS
88
+ # ═══════════════════════════════════════════════════════════
89
+
90
+
91
+ def to_ssml(ssmd_text: str, **config: Any) -> str:
92
+ """Convert SSMD to SSML (convenience function).
93
+
94
+ Creates a temporary Document and converts to SSML.
95
+ For repeated conversions with the same config, create a Document instance.
96
+
97
+ Args:
98
+ ssmd_text: SSMD markdown text
99
+ **config: Optional configuration parameters
100
+
101
+ Returns:
102
+ SSML string
103
+
104
+ Example:
105
+ >>> ssmd.to_ssml("Hello *world*!")
106
+ '<speak>Hello <emphasis>world</emphasis>!</speak>'
107
+ """
108
+ return Document(ssmd_text, config).to_ssml()
109
+
110
+
111
+ def to_text(ssmd_text: str, **config: Any) -> str:
112
+ """Convert SSMD to plain text (convenience function).
113
+
114
+ Strips all SSMD markup, returning plain text.
115
+
116
+ Args:
117
+ ssmd_text: SSMD markdown text
118
+ **config: Optional configuration parameters
119
+
120
+ Returns:
121
+ Plain text with markup removed
122
+
123
+ Example:
124
+ >>> ssmd.to_text("Hello *world* @marker!")
125
+ 'Hello world!'
126
+ """
127
+ return Document(ssmd_text, config).to_text()
128
+
129
+
130
+ def from_ssml(ssml_text: str, **config: Any) -> str:
131
+ """Convert SSML to SSMD format (convenience function).
132
+
133
+ Args:
134
+ ssml_text: SSML XML string
135
+ **config: Optional configuration parameters
136
+
137
+ Returns:
138
+ SSMD markdown string
139
+
140
+ Example:
141
+ >>> ssml = '<speak><emphasis>Hello</emphasis> world</speak>'
142
+ >>> ssmd.from_ssml(ssml)
143
+ '*Hello* world'
144
+ """
145
+ parser = SSMLParser(config)
146
+ return parser.to_ssmd(ssml_text)
147
+
148
+
149
+ __all__ = [
150
+ "Document",
151
+ "to_ssml",
152
+ "to_text",
153
+ "from_ssml",
154
+ "SSMLParser",
155
+ "TTSCapabilities",
156
+ "get_preset",
157
+ # Capability presets
158
+ "ESPEAK_CAPABILITIES",
159
+ "PYTTSX3_CAPABILITIES",
160
+ "GOOGLE_TTS_CAPABILITIES",
161
+ "AMAZON_POLLY_CAPABILITIES",
162
+ "AZURE_TTS_CAPABILITIES",
163
+ "MINIMAL_CAPABILITIES",
164
+ "FULL_CAPABILITIES",
165
+ # Parser functions
166
+ "parse_sentences",
167
+ "parse_segments",
168
+ "parse_voice_blocks",
169
+ "format_ssmd",
170
+ # Utility functions
171
+ "escape_ssmd_syntax",
172
+ "unescape_ssmd_syntax",
173
+ # New core classes
174
+ "Segment",
175
+ "Sentence",
176
+ # Types
177
+ "VoiceAttrs",
178
+ "ProsodyAttrs",
179
+ "BreakAttrs",
180
+ "SayAsAttrs",
181
+ "AudioAttrs",
182
+ "PhonemeAttrs",
183
+ "HeadingConfig",
184
+ "DEFAULT_HEADING_LEVELS",
185
+ # Backward compatibility aliases
186
+ "SSMDSegment",
187
+ "SSMDSentence",
188
+ "__version__",
189
+ ]
ssmd/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.5.3'
32
+ __version_tuple__ = version_tuple = (0, 5, 3)
33
+
34
+ __commit_id__ = commit_id = None
ssmd/capabilities.py ADDED
@@ -0,0 +1,277 @@
1
+ """TTS capability definitions and presets.
2
+
3
+ This module defines which SSML features are supported by various TTS engines
4
+ and provides capability-based filtering for SSMD processing.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+
10
+ class TTSCapabilities:
11
+ """Define TTS engine capabilities.
12
+
13
+ This class allows you to specify which SSML features your TTS engine
14
+ supports. Unsupported features will be automatically stripped to plain text.
15
+
16
+ Example:
17
+ >>> # Basic TTS with minimal support
18
+ >>> caps = TTSCapabilities(
19
+ ... emphasis=False,
20
+ ... break_tags=True,
21
+ ... prosody=False
22
+ ... )
23
+ >>>
24
+ >>> parser = SSMD(capabilities=caps)
25
+ >>> ssml = parser.to_ssml("Hello *world*!")
26
+ >>> # Output: <speak><p>Hello world!</p></speak>
27
+ >>> # (emphasis stripped because not supported)
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ # Core features
33
+ emphasis: bool = True,
34
+ break_tags: bool = True,
35
+ paragraph: bool = True,
36
+ # Language & pronunciation
37
+ language: bool = True,
38
+ phoneme: bool = True,
39
+ substitution: bool = True,
40
+ # Prosody (volume, rate, pitch)
41
+ prosody: bool = True,
42
+ prosody_volume: bool = True,
43
+ prosody_rate: bool = True,
44
+ prosody_pitch: bool = True,
45
+ # Advanced features
46
+ say_as: bool = True,
47
+ audio: bool = True,
48
+ mark: bool = True,
49
+ # Extensions (platform-specific)
50
+ extensions: dict[str, bool] | None = None,
51
+ # Sentence and heading support
52
+ sentence_tags: bool = True,
53
+ heading_emphasis: bool = True,
54
+ ):
55
+ """Initialize TTS capabilities.
56
+
57
+ Args:
58
+ emphasis: Support for <emphasis> tags
59
+ break_tags: Support for <break> tags
60
+ paragraph: Support for <p> tags
61
+ language: Support for <lang> tags
62
+ phoneme: Support for <phoneme> tags
63
+ substitution: Support for <sub> tags
64
+ prosody: Support for <prosody> tags (general)
65
+ prosody_volume: Support for volume attribute
66
+ prosody_rate: Support for rate attribute
67
+ prosody_pitch: Support for pitch attribute
68
+ say_as: Support for <say-as> tags
69
+ audio: Support for <audio> tags
70
+ mark: Support for <mark> tags
71
+ extensions: Dict of extension names and their support
72
+ sentence_tags: Support for <s> tags
73
+ heading_emphasis: Support for heading emphasis
74
+ """
75
+ self.emphasis = emphasis
76
+ self.break_tags = break_tags
77
+ self.paragraph = paragraph
78
+ self.language = language
79
+ self.phoneme = phoneme
80
+ self.substitution = substitution
81
+ self.prosody = prosody
82
+ self.prosody_volume = prosody_volume and prosody
83
+ self.prosody_rate = prosody_rate and prosody
84
+ self.prosody_pitch = prosody_pitch and prosody
85
+ self.say_as = say_as
86
+ self.audio = audio
87
+ self.mark = mark
88
+ self.extensions = extensions or {}
89
+ self.sentence_tags = sentence_tags
90
+ self.heading_emphasis = heading_emphasis
91
+
92
+ def to_config(self) -> dict[str, Any]:
93
+ """Convert capabilities to SSMD config.
94
+
95
+ Returns:
96
+ Configuration dict for SSMD converter
97
+ """
98
+ config: dict[str, Any] = {
99
+ "skip": [],
100
+ "capabilities": self,
101
+ }
102
+
103
+ # Skip processors for unsupported features
104
+ if not self.emphasis:
105
+ config["skip"].append("emphasis")
106
+ if not self.break_tags:
107
+ config["skip"].append("break")
108
+ if not self.paragraph:
109
+ config["skip"].append("paragraph")
110
+ if not self.mark:
111
+ config["skip"].append("mark")
112
+
113
+ # Prosody is handled specially (selective attributes)
114
+ if not self.prosody:
115
+ config["skip"].append("prosody")
116
+
117
+ # Headings handled by modifying heading_levels
118
+ if not self.heading_emphasis:
119
+ config["heading_levels"] = {} # No heading processing
120
+
121
+ return config
122
+
123
+ def supports_extension(self, extension_name: str) -> bool:
124
+ """Check if an extension is supported.
125
+
126
+ Args:
127
+ extension_name: Name of the extension
128
+
129
+ Returns:
130
+ True if supported
131
+ """
132
+ return self.extensions.get(extension_name, False)
133
+
134
+
135
+ # Preset capability definitions for common TTS engines
136
+ ESPEAK_CAPABILITIES = TTSCapabilities(
137
+ emphasis=False, # eSpeak doesn't support emphasis
138
+ break_tags=True,
139
+ paragraph=False, # eSpeak treats paragraphs as plain text
140
+ language=True,
141
+ phoneme=True, # eSpeak has good phoneme support
142
+ substitution=False,
143
+ prosody=True,
144
+ prosody_volume=True,
145
+ prosody_rate=True,
146
+ prosody_pitch=True,
147
+ say_as=False,
148
+ audio=False, # No audio file support
149
+ mark=False,
150
+ sentence_tags=False,
151
+ heading_emphasis=False,
152
+ )
153
+
154
+ PYTTSX3_CAPABILITIES = TTSCapabilities(
155
+ emphasis=False, # pyttsx3 has minimal SSML support
156
+ break_tags=False,
157
+ paragraph=False,
158
+ language=False, # Voice selection, not SSML
159
+ phoneme=False,
160
+ substitution=False,
161
+ prosody=True, # Via properties, not SSML
162
+ prosody_volume=True,
163
+ prosody_rate=True,
164
+ prosody_pitch=False,
165
+ say_as=False,
166
+ audio=False,
167
+ mark=False,
168
+ sentence_tags=False,
169
+ heading_emphasis=False,
170
+ )
171
+
172
+ GOOGLE_TTS_CAPABILITIES = TTSCapabilities(
173
+ emphasis=True,
174
+ break_tags=True,
175
+ paragraph=True,
176
+ language=True,
177
+ phoneme=True,
178
+ substitution=True,
179
+ prosody=True,
180
+ prosody_volume=True,
181
+ prosody_rate=True,
182
+ prosody_pitch=True,
183
+ say_as=True,
184
+ audio=True,
185
+ mark=True,
186
+ sentence_tags=True,
187
+ heading_emphasis=True,
188
+ )
189
+
190
+ AMAZON_POLLY_CAPABILITIES = TTSCapabilities(
191
+ emphasis=True,
192
+ break_tags=True,
193
+ paragraph=True,
194
+ language=True,
195
+ phoneme=True,
196
+ substitution=True,
197
+ prosody=True,
198
+ prosody_volume=True,
199
+ prosody_rate=True,
200
+ prosody_pitch=True,
201
+ say_as=True,
202
+ audio=False, # Limited audio support
203
+ mark=True,
204
+ extensions={"whisper": True, "drc": True}, # Amazon-specific
205
+ sentence_tags=True,
206
+ heading_emphasis=True,
207
+ )
208
+
209
+ AZURE_TTS_CAPABILITIES = TTSCapabilities(
210
+ emphasis=True,
211
+ break_tags=True,
212
+ paragraph=True,
213
+ language=True,
214
+ phoneme=True,
215
+ substitution=True,
216
+ prosody=True,
217
+ prosody_volume=True,
218
+ prosody_rate=True,
219
+ prosody_pitch=True,
220
+ say_as=True,
221
+ audio=True,
222
+ mark=True,
223
+ sentence_tags=True,
224
+ heading_emphasis=True,
225
+ )
226
+
227
+ # Minimal fallback (plain text only)
228
+ MINIMAL_CAPABILITIES = TTSCapabilities(
229
+ emphasis=False,
230
+ break_tags=False,
231
+ paragraph=False,
232
+ language=False,
233
+ phoneme=False,
234
+ substitution=False,
235
+ prosody=False,
236
+ say_as=False,
237
+ audio=False,
238
+ mark=False,
239
+ sentence_tags=False,
240
+ heading_emphasis=False,
241
+ )
242
+
243
+ # Full SSML support (reference)
244
+ FULL_CAPABILITIES = TTSCapabilities()
245
+
246
+
247
+ # Preset lookup
248
+ PRESETS: dict[str, TTSCapabilities] = {
249
+ "espeak": ESPEAK_CAPABILITIES,
250
+ "pyttsx3": PYTTSX3_CAPABILITIES,
251
+ "google": GOOGLE_TTS_CAPABILITIES,
252
+ "polly": AMAZON_POLLY_CAPABILITIES,
253
+ "amazon": AMAZON_POLLY_CAPABILITIES,
254
+ "azure": AZURE_TTS_CAPABILITIES,
255
+ "microsoft": AZURE_TTS_CAPABILITIES,
256
+ "minimal": MINIMAL_CAPABILITIES,
257
+ "full": FULL_CAPABILITIES,
258
+ }
259
+
260
+
261
+ def get_preset(name: str) -> TTSCapabilities:
262
+ """Get a preset capability configuration.
263
+
264
+ Args:
265
+ name: Preset name (espeak, pyttsx3, google, polly, azure, minimal, full)
266
+
267
+ Returns:
268
+ TTSCapabilities instance
269
+
270
+ Raises:
271
+ ValueError: If preset not found
272
+ """
273
+ if name.lower() not in PRESETS:
274
+ available = ", ".join(PRESETS.keys())
275
+ raise ValueError(f"Unknown preset '{name}'. Available: {available}")
276
+
277
+ return PRESETS[name.lower()]