voicerender 0.1.1__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.
Files changed (39) hide show
  1. voicerender/__init__.py +127 -0
  2. voicerender/__main__.py +3 -0
  3. voicerender/_version.py +24 -0
  4. voicerender/capacity.py +678 -0
  5. voicerender/cli.py +345 -0
  6. voicerender/engines/__init__.py +68 -0
  7. voicerender/engines/api_probe.py +495 -0
  8. voicerender/engines/base.py +425 -0
  9. voicerender/engines/catalog.py +83 -0
  10. voicerender/engines/inflectsynth.py +920 -0
  11. voicerender/engines/kittensynth.py +511 -0
  12. voicerender/engines/kokoro.py +725 -0
  13. voicerender/engines/metadata.py +58 -0
  14. voicerender/engines/piper.py +173 -0
  15. voicerender/engines/pipersynth.py +570 -0
  16. voicerender/engines/pocket.py +169 -0
  17. voicerender/engines/pocketsynth.py +1234 -0
  18. voicerender/engines/registry.py +320 -0
  19. voicerender/engines/selection.py +61 -0
  20. voicerender/engines/supertonicsynth.py +707 -0
  21. voicerender/errors.py +161 -0
  22. voicerender/events.py +43 -0
  23. voicerender/handoff.py +62 -0
  24. voicerender/inputs/__init__.py +21 -0
  25. voicerender/inputs/utterplan_v5.py +897 -0
  26. voicerender/lexicons.py +42 -0
  27. voicerender/manifest.py +750 -0
  28. voicerender/models.py +499 -0
  29. voicerender/py.typed +0 -0
  30. voicerender/render.py +365 -0
  31. voicerender/render_v5.py +1193 -0
  32. voicerender/schemas/render-manifest-v2.schema.json +415 -0
  33. voicerender/storage.py +113 -0
  34. voicerender-0.1.1.dist-info/METADATA +254 -0
  35. voicerender-0.1.1.dist-info/RECORD +39 -0
  36. voicerender-0.1.1.dist-info/WHEEL +5 -0
  37. voicerender-0.1.1.dist-info/entry_points.txt +2 -0
  38. voicerender-0.1.1.dist-info/licenses/LICENSE +201 -0
  39. voicerender-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,495 @@
1
+ """Structured diagnostics for public synthesis-engine API compatibility."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import importlib.metadata
7
+ import json
8
+ import logging
9
+ from collections.abc import Mapping
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Literal
12
+
13
+ logger = logging.getLogger(__name__)
14
+ SUPPORTED_REQUEST_API_VERSION = 1
15
+
16
+ EngineApiStatus = Literal[
17
+ "ready",
18
+ "package_missing",
19
+ "api_incompatible",
20
+ "api_version_incompatible",
21
+ "api_probe_failed",
22
+ ]
23
+ ContractSource = Literal["explicit", "legacy_symbols"]
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class EngineApiProbe:
28
+ """Serializable result of checking one engine's documented public API."""
29
+
30
+ engine: str
31
+ package: str
32
+ compatible: bool
33
+ status: EngineApiStatus
34
+ distribution_version: str | None = None
35
+ module_version: str | None = None
36
+ module_path: str | None = None
37
+ expected_api_version: int | None = None
38
+ api_version: int | None = None
39
+ contract_source: ContractSource | None = None
40
+ missing_symbols: tuple[str, ...] = ()
41
+ missing_methods: tuple[str, ...] = ()
42
+ failed_stage: str | None = None
43
+ failed_symbol: str | None = None
44
+ error_type: str | None = None
45
+ error_message: str | None = None
46
+ warnings: tuple[str, ...] = ()
47
+ details: Mapping[str, Any] = field(default_factory=dict)
48
+
49
+
50
+ def _probe_public_api(
51
+ *,
52
+ engine: str,
53
+ package: str,
54
+ required_symbols: tuple[str, ...],
55
+ required_methods: Mapping[str, tuple[str, ...]],
56
+ expected_api_version: int | None = None,
57
+ require_explicit_contract: bool = False,
58
+ contract_required_symbols: tuple[str, ...] = (),
59
+ contract_required_methods: Mapping[str, tuple[str, ...]] | None = None,
60
+ legacy_required_methods: Mapping[str, tuple[str, ...]] | None = None,
61
+ ) -> EngineApiProbe:
62
+ """Probe a package root and concrete public entry points without opening a runtime."""
63
+ metadata_warning: str | None = None
64
+ try:
65
+ distribution_version = importlib.metadata.version(package)
66
+ except importlib.metadata.PackageNotFoundError:
67
+ distribution_version = None
68
+ except Exception as exc:
69
+ logger.debug(
70
+ "Engine %s distribution metadata probe failed", engine, exc_info=True
71
+ )
72
+ distribution_version = None
73
+ metadata_warning = (
74
+ f"Distribution metadata could not be read: {type(exc).__name__}: {exc}"
75
+ )
76
+ else:
77
+ metadata_warning = None
78
+
79
+ common: dict[str, Any] = {
80
+ "engine": engine,
81
+ "package": package,
82
+ "expected_api_version": expected_api_version,
83
+ "distribution_version": distribution_version,
84
+ }
85
+ warnings: list[str] = []
86
+ if metadata_warning is not None:
87
+ warnings.append(metadata_warning)
88
+
89
+ try:
90
+ module = importlib.import_module(package)
91
+ except ModuleNotFoundError as exc:
92
+ logger.debug("Engine %s package import failed", engine, exc_info=True)
93
+ if exc.name == package:
94
+ return EngineApiProbe(
95
+ **common,
96
+ compatible=False,
97
+ status="package_missing",
98
+ failed_stage="module_import",
99
+ error_type=type(exc).__name__,
100
+ error_message=str(exc),
101
+ warnings=tuple(warnings),
102
+ )
103
+ return _probe_failed(
104
+ common,
105
+ warnings,
106
+ stage="module_import",
107
+ error=exc,
108
+ )
109
+ except Exception as exc:
110
+ logger.debug("Engine %s package import failed", engine, exc_info=True)
111
+ return _probe_failed(common, warnings, stage="module_import", error=exc)
112
+
113
+ try:
114
+ module_version_value = getattr(module, "__version__", None)
115
+ module_path_value = getattr(module, "__file__", None)
116
+ module_version = (
117
+ str(module_version_value) if module_version_value is not None else None
118
+ )
119
+ module_path = str(module_path_value) if module_path_value is not None else None
120
+ except Exception as exc:
121
+ logger.debug(
122
+ "Engine %s module metadata resolution failed", engine, exc_info=True
123
+ )
124
+ return _probe_failed(
125
+ common,
126
+ warnings,
127
+ stage="module_metadata",
128
+ error=exc,
129
+ )
130
+
131
+ if (
132
+ distribution_version is not None
133
+ and module_version is not None
134
+ and distribution_version != module_version
135
+ ):
136
+ warnings.append("Imported module version differs from distribution metadata.")
137
+
138
+ common.update(module_version=module_version, module_path=module_path)
139
+ resolved: dict[str, Any] = {}
140
+ missing_symbols: list[str] = []
141
+ for name in required_symbols:
142
+ try:
143
+ resolved[name] = getattr(module, name)
144
+ except AttributeError:
145
+ missing_symbols.append(name)
146
+ except Exception as exc:
147
+ logger.debug(
148
+ "Engine %s public symbol %s resolution failed",
149
+ engine,
150
+ name,
151
+ exc_info=True,
152
+ )
153
+ return _probe_failed(
154
+ common,
155
+ warnings,
156
+ stage="symbol_resolution",
157
+ error=exc,
158
+ failed_symbol=name,
159
+ missing_symbols=tuple(missing_symbols),
160
+ )
161
+
162
+ missing_methods: list[str] = []
163
+ for owner_name, methods in required_methods.items():
164
+ owner = (
165
+ module
166
+ if owner_name in {"__module__", "module"}
167
+ else resolved.get(owner_name)
168
+ )
169
+ if (
170
+ owner is None
171
+ and owner_name not in {"__module__", "module"}
172
+ and owner_name not in resolved
173
+ ):
174
+ # A missing class is already reported as a missing public symbol.
175
+ continue
176
+ for method_name in methods:
177
+ qualified_name = (
178
+ method_name
179
+ if owner_name in {"__module__", "module"}
180
+ else f"{owner_name}.{method_name}"
181
+ )
182
+ try:
183
+ method = getattr(owner, method_name)
184
+ except AttributeError:
185
+ missing_methods.append(qualified_name)
186
+ continue
187
+ except Exception as exc:
188
+ logger.debug(
189
+ "Engine %s public method %s resolution failed",
190
+ engine,
191
+ qualified_name,
192
+ exc_info=True,
193
+ )
194
+ return _probe_failed(
195
+ common,
196
+ warnings,
197
+ stage="method_resolution",
198
+ error=exc,
199
+ failed_symbol=qualified_name,
200
+ missing_symbols=tuple(missing_symbols),
201
+ missing_methods=tuple(missing_methods),
202
+ )
203
+ if not callable(method):
204
+ missing_methods.append(qualified_name)
205
+
206
+ api_version: int | None = None
207
+ contract_source: ContractSource | None = None
208
+ contract_names = ("REQUEST_API_VERSION", "request_api_contract")
209
+ contract_values: dict[str, Any] = {}
210
+ for name in contract_names:
211
+ try:
212
+ contract_values[name] = getattr(module, name)
213
+ except AttributeError:
214
+ continue
215
+ except Exception as exc:
216
+ logger.debug(
217
+ "Engine %s request API marker %s resolution failed",
218
+ engine,
219
+ name,
220
+ exc_info=True,
221
+ )
222
+ return _probe_failed(
223
+ common,
224
+ warnings,
225
+ stage="contract_resolution",
226
+ error=exc,
227
+ failed_symbol=name,
228
+ missing_symbols=tuple(missing_symbols),
229
+ missing_methods=tuple(missing_methods),
230
+ )
231
+
232
+ has_version = "REQUEST_API_VERSION" in contract_values
233
+ has_contract = "request_api_contract" in contract_values
234
+ if has_version and has_contract:
235
+ contract_source = "explicit"
236
+ version_value = contract_values["REQUEST_API_VERSION"]
237
+ if isinstance(version_value, bool) or not isinstance(version_value, int):
238
+ error = TypeError("REQUEST_API_VERSION must be an integer")
239
+ logger.debug("Engine %s request API version is invalid: %s", engine, error)
240
+ return _probe_failed(
241
+ common,
242
+ warnings,
243
+ stage="contract_version",
244
+ error=error,
245
+ failed_symbol="REQUEST_API_VERSION",
246
+ missing_symbols=tuple(missing_symbols),
247
+ missing_methods=tuple(missing_methods),
248
+ )
249
+ api_version = version_value
250
+ contract_function = contract_values["request_api_contract"]
251
+ if not callable(contract_function):
252
+ error = TypeError("request_api_contract is not callable")
253
+ logger.debug("Engine %s request API contract is invalid: %s", engine, error)
254
+ return _probe_failed(
255
+ common,
256
+ warnings,
257
+ stage="contract_inspection",
258
+ error=error,
259
+ failed_symbol="request_api_contract",
260
+ missing_symbols=tuple(missing_symbols),
261
+ missing_methods=tuple(missing_methods),
262
+ )
263
+ try:
264
+ contract_function()
265
+ except Exception as exc:
266
+ logger.debug(
267
+ "Engine %s request API contract inspection failed",
268
+ engine,
269
+ exc_info=True,
270
+ )
271
+ return _probe_failed(
272
+ common,
273
+ warnings,
274
+ stage="contract_inspection",
275
+ error=exc,
276
+ failed_symbol="request_api_contract",
277
+ missing_symbols=tuple(missing_symbols),
278
+ missing_methods=tuple(missing_methods),
279
+ api_version=api_version,
280
+ contract_source=contract_source,
281
+ )
282
+ elif has_version or has_contract:
283
+ if require_explicit_contract:
284
+ contract_source = "explicit"
285
+ absent_name = (
286
+ "request_api_contract" if has_version else "REQUEST_API_VERSION"
287
+ )
288
+ missing_symbols.append(absent_name)
289
+ else:
290
+ contract_source = "legacy_symbols"
291
+ warnings.append(
292
+ "Incomplete explicit request API contract; using legacy symbol checks."
293
+ )
294
+ elif required_symbols or required_methods:
295
+ if require_explicit_contract:
296
+ contract_source = "explicit"
297
+ missing_symbols.extend(contract_names)
298
+ else:
299
+ contract_source = "legacy_symbols"
300
+
301
+ common.update(api_version=api_version, contract_source=contract_source)
302
+
303
+ conditional_symbols = (
304
+ contract_required_symbols if contract_source == "explicit" else ()
305
+ )
306
+ conditional_methods = (
307
+ contract_required_methods or {}
308
+ if contract_source == "explicit"
309
+ else legacy_required_methods or {}
310
+ )
311
+ for name in conditional_symbols:
312
+ if name in resolved:
313
+ continue
314
+ try:
315
+ resolved[name] = getattr(module, name)
316
+ except AttributeError:
317
+ missing_symbols.append(name)
318
+ except Exception as exc:
319
+ logger.debug(
320
+ "Engine %s public symbol %s resolution failed",
321
+ engine,
322
+ name,
323
+ exc_info=True,
324
+ )
325
+ return _probe_failed(
326
+ common,
327
+ warnings,
328
+ stage="symbol_resolution",
329
+ error=exc,
330
+ failed_symbol=name,
331
+ missing_symbols=tuple(missing_symbols),
332
+ missing_methods=tuple(missing_methods),
333
+ api_version=api_version,
334
+ contract_source=contract_source,
335
+ )
336
+
337
+ for owner_name, methods in conditional_methods.items():
338
+ owner = (
339
+ module
340
+ if owner_name in {"__module__", "module"}
341
+ else resolved.get(owner_name)
342
+ )
343
+ if (
344
+ owner is None
345
+ and owner_name not in {"__module__", "module"}
346
+ and owner_name not in resolved
347
+ ):
348
+ continue
349
+ for method_name in methods:
350
+ qualified_name = (
351
+ method_name
352
+ if owner_name in {"__module__", "module"}
353
+ else f"{owner_name}.{method_name}"
354
+ )
355
+ try:
356
+ method = getattr(owner, method_name)
357
+ except AttributeError:
358
+ missing_methods.append(qualified_name)
359
+ except Exception as exc:
360
+ logger.debug(
361
+ "Engine %s public method %s resolution failed",
362
+ engine,
363
+ qualified_name,
364
+ exc_info=True,
365
+ )
366
+ return _probe_failed(
367
+ common,
368
+ warnings,
369
+ stage="method_resolution",
370
+ error=exc,
371
+ failed_symbol=qualified_name,
372
+ missing_symbols=tuple(missing_symbols),
373
+ missing_methods=tuple(missing_methods),
374
+ api_version=api_version,
375
+ contract_source=contract_source,
376
+ )
377
+ else:
378
+ if not callable(method):
379
+ missing_methods.append(qualified_name)
380
+
381
+ if (
382
+ api_version is not None
383
+ and expected_api_version is not None
384
+ and api_version != expected_api_version
385
+ ):
386
+ return EngineApiProbe(
387
+ **common,
388
+ compatible=False,
389
+ status="api_version_incompatible",
390
+ missing_symbols=tuple(dict.fromkeys(missing_symbols)),
391
+ missing_methods=tuple(dict.fromkeys(missing_methods)),
392
+ warnings=tuple(warnings),
393
+ )
394
+
395
+ if missing_symbols or missing_methods:
396
+ return EngineApiProbe(
397
+ **common,
398
+ compatible=False,
399
+ status="api_incompatible",
400
+ missing_symbols=tuple(dict.fromkeys(missing_symbols)),
401
+ missing_methods=tuple(dict.fromkeys(missing_methods)),
402
+ warnings=tuple(warnings),
403
+ )
404
+
405
+ return EngineApiProbe(
406
+ **common,
407
+ compatible=True,
408
+ status="ready",
409
+ warnings=tuple(warnings),
410
+ )
411
+
412
+
413
+ def probe_public_api(
414
+ *,
415
+ engine: str,
416
+ package: str,
417
+ required_symbols: tuple[str, ...],
418
+ required_methods: Mapping[str, tuple[str, ...]],
419
+ expected_api_version: int | None = None,
420
+ require_explicit_contract: bool = False,
421
+ contract_required_symbols: tuple[str, ...] = (),
422
+ contract_required_methods: Mapping[str, tuple[str, ...]] | None = None,
423
+ legacy_required_methods: Mapping[str, tuple[str, ...]] | None = None,
424
+ ) -> EngineApiProbe:
425
+ """Probe a public package API and emit one compact structured debug event."""
426
+ probe = _probe_public_api(
427
+ engine=engine,
428
+ package=package,
429
+ required_symbols=required_symbols,
430
+ required_methods=required_methods,
431
+ expected_api_version=expected_api_version,
432
+ require_explicit_contract=require_explicit_contract,
433
+ contract_required_symbols=contract_required_symbols,
434
+ contract_required_methods=contract_required_methods,
435
+ legacy_required_methods=legacy_required_methods,
436
+ )
437
+ event = {
438
+ "engine": probe.engine,
439
+ "package": probe.package,
440
+ "distribution_version": probe.distribution_version,
441
+ "module_version": probe.module_version,
442
+ "module_path": probe.module_path,
443
+ "api_version": probe.api_version,
444
+ "expected_api_version": probe.expected_api_version,
445
+ "contract_source": probe.contract_source,
446
+ "compatible": probe.compatible,
447
+ "status": probe.status,
448
+ "missing_symbols": probe.missing_symbols,
449
+ "missing_methods": probe.missing_methods,
450
+ "failed_stage": probe.failed_stage,
451
+ "failed_symbol": probe.failed_symbol,
452
+ "error_type": probe.error_type,
453
+ "error_message": probe.error_message,
454
+ "warnings": probe.warnings,
455
+ }
456
+ logger.debug(
457
+ "engine.api_probe %s", json.dumps(event, sort_keys=True, separators=(",", ":"))
458
+ )
459
+ return probe
460
+
461
+
462
+ def _probe_failed(
463
+ common: Mapping[str, Any],
464
+ warnings: list[str],
465
+ *,
466
+ stage: str,
467
+ error: Exception,
468
+ failed_symbol: str | None = None,
469
+ missing_symbols: tuple[str, ...] = (),
470
+ missing_methods: tuple[str, ...] = (),
471
+ api_version: int | None = None,
472
+ contract_source: ContractSource | None = None,
473
+ ) -> EngineApiProbe:
474
+ return EngineApiProbe(
475
+ **common,
476
+ compatible=False,
477
+ status="api_probe_failed",
478
+ api_version=api_version,
479
+ contract_source=contract_source,
480
+ missing_symbols=missing_symbols,
481
+ missing_methods=missing_methods,
482
+ failed_stage=stage,
483
+ failed_symbol=failed_symbol,
484
+ error_type=type(error).__name__,
485
+ error_message=str(error),
486
+ warnings=tuple(warnings),
487
+ )
488
+
489
+
490
+ __all__ = [
491
+ "ContractSource",
492
+ "EngineApiProbe",
493
+ "EngineApiStatus",
494
+ "probe_public_api",
495
+ ]