pipersynth 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.
pipersynth/__init__.py ADDED
@@ -0,0 +1,120 @@
1
+ """Independent synthesis runtime for Piper-compatible ONNX voice models."""
2
+
3
+ from piperg2p import VoiceConfig
4
+ from utterplan import (
5
+ LinguisticsConfig,
6
+ PauseConfig,
7
+ PlannerConfig,
8
+ SSMDConfig,
9
+ UtterancePlan,
10
+ UtterancePlanner,
11
+ )
12
+
13
+ try:
14
+ from ._version import __version__, __version_tuple__
15
+ except ImportError:
16
+ __version__ = "0.1.1"
17
+ __version_tuple__ = (0, 1, 1)
18
+ from .asset_manager import CacheInfo, VoiceAssetManager, list_cached_voices, list_voices
19
+ from .asset_progress import AssetProgressCallback, AssetProgressEvent, ConsoleAssetProgress
20
+ from .assets import VoiceBundle, VoiceMetadata, load_catalog_voice
21
+ from .config import GenerationConfig, PipelineConfig
22
+ from .convenience import synthesize, synthesize_to_wav
23
+ from .diagnostics import RuntimeDiagnostics, TimingDiagnostics
24
+ from .errors import (
25
+ AssetCacheError,
26
+ AssetDownloadError,
27
+ AssetError,
28
+ CatalogUnavailableError,
29
+ ConfigFileNotFoundError,
30
+ InvalidSpeakerError,
31
+ InvalidSynthesisConfigError,
32
+ ModelFileNotFoundError,
33
+ ModelInferenceError,
34
+ ModelLoadError,
35
+ OfflineAssetError,
36
+ OptionalDependencyError,
37
+ PiperSynthError,
38
+ PlanRenderingError,
39
+ PlanSampleRateMismatchError,
40
+ SessionCreationError,
41
+ SynthesisError,
42
+ TextPreparationError,
43
+ UnsupportedModelError,
44
+ UnsupportedPlanDirectiveError,
45
+ UnsupportedPlanLanguageError,
46
+ VoiceBindingError,
47
+ VoiceClosedError,
48
+ VoiceNotFoundError,
49
+ )
50
+ from .pipeline import PiperPipeline, PreparedAudioUnits, build_pipeline
51
+ from .preparation import IdentityTextPreparer, PreparedTextResult, SpokenformTextPreparer
52
+ from .session import ProviderConfig, available_providers
53
+ from .types import AudioChunk, AudioResult, AudioUnitDescriptor, AudioUnitResult, SynthesisConfig
54
+ from .voice import PiperVoice
55
+
56
+ __all__ = [
57
+ "AudioChunk",
58
+ "AudioResult",
59
+ "AudioUnitDescriptor",
60
+ "AudioUnitResult",
61
+ "AssetCacheError",
62
+ "AssetDownloadError",
63
+ "AssetError",
64
+ "AssetProgressCallback",
65
+ "AssetProgressEvent",
66
+ "GenerationConfig",
67
+ "LinguisticsConfig",
68
+ "PauseConfig",
69
+ "PlannerConfig",
70
+ "SSMDConfig",
71
+ "UtterancePlan",
72
+ "UtterancePlanner",
73
+ "CacheInfo",
74
+ "VoiceAssetManager",
75
+ "CatalogUnavailableError",
76
+ "ConsoleAssetProgress",
77
+ "IdentityTextPreparer",
78
+ "PiperPipeline",
79
+ "PiperSynthError",
80
+ "PlanRenderingError",
81
+ "PlanSampleRateMismatchError",
82
+ "ConfigFileNotFoundError",
83
+ "InvalidSpeakerError",
84
+ "InvalidSynthesisConfigError",
85
+ "ModelFileNotFoundError",
86
+ "ModelInferenceError",
87
+ "ModelLoadError",
88
+ "OptionalDependencyError",
89
+ "OfflineAssetError",
90
+ "PiperVoice",
91
+ "PipelineConfig",
92
+ "PreparedAudioUnits",
93
+ "PreparedTextResult",
94
+ "ProviderConfig",
95
+ "RuntimeDiagnostics",
96
+ "SessionCreationError",
97
+ "SynthesisError",
98
+ "TextPreparationError",
99
+ "TimingDiagnostics",
100
+ "UnsupportedModelError",
101
+ "UnsupportedPlanDirectiveError",
102
+ "UnsupportedPlanLanguageError",
103
+ "VoiceBindingError",
104
+ "VoiceClosedError",
105
+ "SpokenformTextPreparer",
106
+ "SynthesisConfig",
107
+ "VoiceBundle",
108
+ "VoiceConfig",
109
+ "VoiceMetadata",
110
+ "VoiceNotFoundError",
111
+ "available_providers",
112
+ "build_pipeline",
113
+ "list_cached_voices",
114
+ "load_catalog_voice",
115
+ "list_voices",
116
+ "synthesize",
117
+ "synthesize_to_wav",
118
+ "__version__",
119
+ "__version_tuple__",
120
+ ]
pipersynth/__main__.py ADDED
@@ -0,0 +1,216 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from collections.abc import Sequence
5
+
6
+ from .asset_manager import VoiceAssetManager
7
+ from .config import GenerationConfig, PipelineConfig
8
+ from .convenience import synthesize_to_wav
9
+ from .pipeline import PiperPipeline
10
+
11
+
12
+ def _speaker(value: str) -> int | str:
13
+ try:
14
+ return int(value)
15
+ except ValueError:
16
+ return value
17
+
18
+
19
+ def _legacy_parser() -> argparse.ArgumentParser:
20
+ parser = argparse.ArgumentParser(
21
+ description="Synthesize speech with a Piper-compatible ONNX voice without the Piper runtime"
22
+ )
23
+ parser.add_argument("model", help="Path to .onnx voice model")
24
+ parser.add_argument("text", help="Text to synthesize")
25
+ parser.add_argument("-o", "--output", default="output.wav")
26
+ parser.add_argument("--config", default=None, help="Path to .onnx.json config")
27
+ parser.add_argument("--speaker", type=_speaker, default=None)
28
+ parser.add_argument("--length-scale", type=float, default=None)
29
+ parser.add_argument("--noise-scale", type=float, default=None)
30
+ parser.add_argument("--noise-w-scale", type=float, default=None)
31
+ parser.add_argument("--noise-w", type=float, default=None, help="Deprecated noise width alias")
32
+ parser.add_argument("--sentence-silence", type=float, default=0.0)
33
+ parser.add_argument("--volume", type=float, default=1.0)
34
+ parser.add_argument(
35
+ "--provider", action="append", default=None, help="ONNX provider; repeat for priority"
36
+ )
37
+ parser.add_argument(
38
+ "--language", default=None, help="Language for optional written-text preparation"
39
+ )
40
+ preparation = parser.add_mutually_exclusive_group()
41
+ preparation.add_argument(
42
+ "--prepare-text", action="store_true", help="Use spokenform preparation"
43
+ )
44
+ preparation.add_argument(
45
+ "--no-prepare-text", action="store_true", help="Use identity preparation"
46
+ )
47
+ parser.add_argument("--no-normalize", action="store_true")
48
+ return parser
49
+
50
+
51
+ def _add_managed_options(parser: argparse.ArgumentParser) -> None:
52
+ parser.add_argument("--cache-dir", default=None)
53
+ parser.add_argument("--offline", action="store_true")
54
+ parser.add_argument("--refresh-catalog", action="store_true")
55
+
56
+
57
+ def _build_parser() -> argparse.ArgumentParser:
58
+ parser = argparse.ArgumentParser(description="Synthesize speech with PiperSynth catalog voices")
59
+ commands = parser.add_subparsers(dest="command", required=True)
60
+
61
+ voices = commands.add_parser("voices")
62
+ voice_commands = voices.add_subparsers(dest="voice_command", required=True)
63
+ listing = voice_commands.add_parser("list")
64
+ _add_managed_options(listing)
65
+ listing.add_argument("--language")
66
+ listing.add_argument("--quality")
67
+ show = voice_commands.add_parser("show")
68
+ _add_managed_options(show)
69
+ show.add_argument("voice")
70
+ for name in ("download", "path", "remove", "license"):
71
+ command = voice_commands.add_parser(name)
72
+ _add_managed_options(command)
73
+ command.add_argument("voice")
74
+ download = voice_commands.choices["download"]
75
+ download.add_argument("--force-download", action="store_true")
76
+
77
+ speak = commands.add_parser("speak")
78
+ _add_managed_options(speak)
79
+ speak.add_argument("--voice", required=True)
80
+ speak.add_argument("text")
81
+ speak.add_argument("-o", "--output", default="output.wav")
82
+ speak.add_argument("--speaker", type=_speaker, default=None)
83
+ speak.add_argument("--length-scale", type=float, default=None)
84
+ speak.add_argument("--noise-scale", type=float, default=None)
85
+ speak.add_argument("--noise-w-scale", type=float, default=None)
86
+ speak.add_argument("--sentence-silence", type=float, default=0.0)
87
+ speak.add_argument("--volume", type=float, default=1.0)
88
+ speak.add_argument("--provider", action="append", default=None)
89
+ speak.add_argument("--prepare-text", action="store_true")
90
+ speak.add_argument("--language", default=None)
91
+ speak.add_argument("--no-normalize", action="store_true")
92
+ speak.add_argument("--force-download", action="store_true")
93
+
94
+ cache = commands.add_parser("cache")
95
+ cache_commands = cache.add_subparsers(dest="cache_command", required=True)
96
+ for name in ("info", "list", "prune", "clear"):
97
+ command = cache_commands.add_parser(name)
98
+ command.add_argument("--cache-dir", default=None)
99
+ cache_commands.choices["clear"].add_argument("--voices", action="store_true")
100
+ return parser
101
+
102
+
103
+ def _run_legacy(args: argparse.Namespace) -> None:
104
+ noise_w_scale = args.noise_w_scale
105
+ if args.noise_w is not None:
106
+ if noise_w_scale is not None:
107
+ raise ValueError("--noise-w and --noise-w-scale cannot both be set")
108
+ noise_w_scale = args.noise_w
109
+ generation = GenerationConfig(
110
+ speaker=args.speaker,
111
+ length_scale=args.length_scale,
112
+ noise_scale=args.noise_scale,
113
+ noise_w_scale=noise_w_scale,
114
+ normalize_audio=not args.no_normalize,
115
+ volume=args.volume,
116
+ sentence_silence=args.sentence_silence,
117
+ )
118
+ config = PipelineConfig(
119
+ model_path=args.model,
120
+ config_path=args.config,
121
+ generation=generation,
122
+ providers=tuple(args.provider) if args.provider is not None else None,
123
+ text_preparation="spokenform" if args.prepare_text else "identity",
124
+ language=args.language,
125
+ )
126
+ with PiperPipeline(config) as pipeline:
127
+ pipeline.run(args.text).save_wav(args.output)
128
+ print(args.output)
129
+
130
+
131
+ def _run_managed(args: argparse.Namespace) -> None:
132
+ offline = True if args.offline else None
133
+ manager = VoiceAssetManager(args.cache_dir, offline=offline)
134
+ if args.command == "voices":
135
+ if args.voice_command == "list":
136
+ for voice in manager.list_voices(
137
+ language=args.language, quality=args.quality, refresh=args.refresh_catalog
138
+ ):
139
+ print(f"{voice.id}\t{voice.name}")
140
+ elif args.voice_command == "show":
141
+ voice = manager.get_voice_metadata(args.voice, refresh=args.refresh_catalog)
142
+ print(f"id: {voice.id}")
143
+ print(f"name: {voice.name}")
144
+ print(f"language: {voice.language_code}")
145
+ print(f"quality: {voice.quality}")
146
+ print(f"speakers: {voice.num_speakers}")
147
+ print(f"aliases: {', '.join(voice.aliases)}")
148
+ elif args.voice_command == "download":
149
+ bundle = manager.resolve_voice(
150
+ args.voice,
151
+ refresh_catalog=args.refresh_catalog,
152
+ force_download=args.force_download,
153
+ )
154
+ print(bundle.directory)
155
+ elif args.voice_command == "path":
156
+ print(manager.voice_cache_path(args.voice))
157
+ elif args.voice_command == "remove":
158
+ manager.remove_voice(args.voice)
159
+ elif args.voice_command == "license":
160
+ print(
161
+ manager.resolve_voice(
162
+ args.voice, refresh_catalog=args.refresh_catalog
163
+ ).model_card_text
164
+ )
165
+ return
166
+ if args.command == "speak":
167
+ output = synthesize_to_wav(
168
+ args.text,
169
+ args.output,
170
+ voice=args.voice,
171
+ speaker=args.speaker,
172
+ length_scale=args.length_scale,
173
+ noise_scale=args.noise_scale,
174
+ noise_w_scale=args.noise_w_scale,
175
+ sentence_silence=args.sentence_silence,
176
+ volume=args.volume,
177
+ normalize_audio=not args.no_normalize,
178
+ providers=args.provider,
179
+ cache_dir=args.cache_dir,
180
+ offline=offline,
181
+ refresh_catalog=args.refresh_catalog,
182
+ force_download=args.force_download,
183
+ text_preparation="spokenform" if args.prepare_text else "identity",
184
+ language=args.language,
185
+ )
186
+ print(output)
187
+ return
188
+ if args.cache_command == "info":
189
+ info = manager.cache_info()
190
+ print(f"directory: {info.directory}")
191
+ print(f"catalog: {info.catalog_path}")
192
+ print(f"cached voices: {', '.join(info.cached_voices)}")
193
+ elif args.cache_command == "list":
194
+ for bundle in manager.cached_voices():
195
+ print(bundle.directory)
196
+ elif args.cache_command == "prune":
197
+ for path in manager.prune():
198
+ print(path)
199
+ elif args.cache_command == "clear":
200
+ manager.clear(voices=args.voices)
201
+
202
+
203
+ def main(argv: Sequence[str] | None = None) -> None:
204
+ arguments = list(argv) if argv is not None else None
205
+ if arguments is None:
206
+ import sys
207
+
208
+ arguments = sys.argv[1:]
209
+ if not arguments or arguments[0] not in {"voices", "speak", "cache"}:
210
+ _run_legacy(_legacy_parser().parse_args(arguments))
211
+ else:
212
+ _run_managed(_build_parser().parse_args(arguments))
213
+
214
+
215
+ if __name__ == "__main__":
216
+ main()
@@ -0,0 +1,317 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Mapping, Sequence
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any, cast
7
+
8
+ from .asset_progress import AssetProgressEvent, AssetProgressPhase
9
+ from .errors import (
10
+ AssetCacheError,
11
+ AssetDownloadError,
12
+ AssetError,
13
+ CatalogUnavailableError,
14
+ ModelInferenceError,
15
+ OptionalDependencyError,
16
+ SessionCreationError,
17
+ UnsupportedModelError,
18
+ VoiceNotFoundError,
19
+ )
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class ResolvedPiperVoice:
24
+ """PiperSynth's read-only view of an OnnxVoice installation."""
25
+
26
+ ref: str | None
27
+ voice_id: str | None
28
+ model_path: Path
29
+ config_path: Path
30
+ model_card_path: Path | None
31
+ language_code: str | None
32
+ source_revision: str | None
33
+ metadata: Mapping[str, Any]
34
+ installation: Any | None = None
35
+
36
+
37
+ def _onnxvoice() -> Any:
38
+ try:
39
+ import onnxvoice
40
+ except ModuleNotFoundError as exc:
41
+ raise OptionalDependencyError(
42
+ "OnnxVoice is required for Piper model loading. Install pipersynth[cpu] or "
43
+ "pipersynth[gpu]."
44
+ ) from exc
45
+ return onnxvoice
46
+
47
+
48
+ def normalize_piper_ref(ref: str) -> str:
49
+ if not isinstance(ref, str) or not ref.strip():
50
+ raise VoiceNotFoundError("Piper voice reference must not be empty")
51
+ value = ref.strip()
52
+ if ":" not in value:
53
+ return f"piper:{value}"
54
+ system, item_id = value.split(":", 1)
55
+ if system.casefold() != "piper" or not item_id:
56
+ raise VoiceNotFoundError(f"Not a Piper voice reference: {ref!r}")
57
+ return f"piper:{item_id}"
58
+
59
+
60
+ def normalize_provider_request(
61
+ providers: Sequence[Any] | str | None,
62
+ provider_options: Mapping[str, Any] | None,
63
+ ) -> tuple[str | Sequence[str] | None, Any | None]:
64
+ """Translate PiperSynth provider compatibility values to OnnxVoice values."""
65
+
66
+ if providers is None:
67
+ return "CPUExecutionProvider", provider_options
68
+ if isinstance(providers, str):
69
+ return providers, provider_options
70
+
71
+ names: list[str] = []
72
+ options: list[dict[str, Any]] = []
73
+ has_explicit_options = False
74
+ for provider in providers:
75
+ if hasattr(provider, "name"):
76
+ name = str(provider.name)
77
+ value = dict(getattr(provider, "options", {}) or {})
78
+ has_explicit_options = has_explicit_options or bool(value)
79
+ elif isinstance(provider, tuple):
80
+ name, raw_options = provider
81
+ value = dict(raw_options or {})
82
+ has_explicit_options = True
83
+ else:
84
+ name = str(provider)
85
+ value = dict(provider_options or {})
86
+ names.append(name)
87
+ options.append(value)
88
+ if not names:
89
+ raise ValueError("providers must not be empty")
90
+ return names, options if has_explicit_options else provider_options
91
+
92
+
93
+ def adapt_progress(
94
+ callback: Callable[[AssetProgressEvent], None] | None,
95
+ ) -> Callable[[Any], None] | None:
96
+ if callback is None:
97
+ return None
98
+
99
+ def emit(event: Any) -> None:
100
+ phase = str(getattr(event, "phase", "asset"))
101
+ phase_map = {
102
+ "catalog_started": "catalog-refresh",
103
+ "catalog_cached": "catalog-load",
104
+ "catalog_completed": "catalog-load",
105
+ "install_started": "voice-resolve",
106
+ "install_completed": "download-complete",
107
+ "download_started": "download-start",
108
+ "download_progress": "download-start",
109
+ "download_completed": "download-complete",
110
+ "artifact_cached": "cache-hit",
111
+ }
112
+ raw_ref = getattr(event, "ref", None)
113
+ voice_id = (
114
+ raw_ref.split(":", 1)[1] if isinstance(raw_ref, str) and ":" in raw_ref else raw_ref
115
+ )
116
+ callback(
117
+ AssetProgressEvent(
118
+ cast(AssetProgressPhase, phase_map.get(phase, "voice-resolve")),
119
+ voice_id=voice_id,
120
+ message=getattr(event, "message", None),
121
+ )
122
+ )
123
+
124
+ return emit
125
+
126
+
127
+ def _map_error(exc: Exception, *, operation: str) -> Exception:
128
+ name = type(exc).__name__
129
+ if name in {"OptionalDependencyError", "ImportError"}:
130
+ return OptionalDependencyError(str(exc))
131
+ if name in {"AssetNotFoundError", "NotInstalledError"}:
132
+ return VoiceNotFoundError(str(exc))
133
+ if name in {"OfflineError"}:
134
+ from .errors import OfflineAssetError
135
+
136
+ return OfflineAssetError(str(exc))
137
+ if name in {"IntegrityError", "ManifestError", "UnsafePathError", "LockError"}:
138
+ return AssetCacheError(str(exc))
139
+ if name in {"CatalogError"}:
140
+ return CatalogUnavailableError(str(exc))
141
+ if name in {"RuntimeContractError", "CapabilityError", "UnsupportedSystemError"}:
142
+ return UnsupportedModelError(str(exc))
143
+ if operation == "infer":
144
+ return ModelInferenceError("ONNX model inference failed")
145
+ if operation == "open":
146
+ return SessionCreationError("Could not open Piper ONNX runtime")
147
+ return AssetDownloadError(str(exc)) if operation == "install" else AssetError(str(exc))
148
+
149
+
150
+ def _call(operation: str, function: Callable[[], Any]) -> Any:
151
+ try:
152
+ return function()
153
+ except (FileNotFoundError, ValueError, TypeError):
154
+ raise
155
+ except Exception as exc:
156
+ mapped = _map_error(exc, operation=operation)
157
+ raise mapped from exc
158
+
159
+
160
+ def open_local_voice(
161
+ model_path: str | Path,
162
+ config_path: str | Path,
163
+ *,
164
+ providers: Sequence[Any] | str | None = None,
165
+ provider_options: Mapping[str, Any] | None = None,
166
+ session_options: Any | None = None,
167
+ ) -> Any:
168
+ requested, options = normalize_provider_request(providers, provider_options)
169
+ module = _onnxvoice()
170
+ return _call(
171
+ "open",
172
+ lambda: module.open_local(
173
+ system="piper",
174
+ model=model_path,
175
+ config=config_path,
176
+ providers=requested,
177
+ provider_options=options,
178
+ session_options=session_options,
179
+ ),
180
+ )
181
+
182
+
183
+ def install_pretrained_voice(
184
+ ref: str,
185
+ *,
186
+ cache_dir: str | Path | None = None,
187
+ offline: bool | None = None,
188
+ refresh_catalog: bool = False,
189
+ force_download: bool = False,
190
+ progress: Callable[[AssetProgressEvent], None] | None = None,
191
+ ) -> ResolvedPiperVoice:
192
+ normalized = normalize_piper_ref(ref)
193
+ module = _onnxvoice()
194
+ manager = module.OnnxVoice(cache_dir=cache_dir, offline=bool(offline))
195
+ installation = _call(
196
+ "install",
197
+ lambda: manager.install(
198
+ normalized,
199
+ refresh=refresh_catalog,
200
+ force=force_download,
201
+ progress=adapt_progress(progress),
202
+ ),
203
+ )
204
+ return installation_to_voice_info(installation, ref=normalized)
205
+
206
+
207
+ def open_installed_voice(
208
+ resolved: ResolvedPiperVoice,
209
+ *,
210
+ providers: Sequence[Any] | str | None = None,
211
+ provider_options: Mapping[str, Any] | None = None,
212
+ session_options: Any | None = None,
213
+ ) -> Any:
214
+ requested, options = normalize_provider_request(providers, provider_options)
215
+ module = _onnxvoice()
216
+ manager = module.OnnxVoice()
217
+ return _call(
218
+ "open",
219
+ lambda: manager.open(
220
+ resolved.installation,
221
+ providers=requested,
222
+ provider_options=options,
223
+ session_options=session_options,
224
+ ),
225
+ )
226
+
227
+
228
+ def installation_to_voice_info(installation: Any, *, ref: str | None = None) -> ResolvedPiperVoice:
229
+ try:
230
+ model = installation.artifact("model").path
231
+ config = installation.artifact("config").path
232
+ except (KeyError, AttributeError) as exc:
233
+ raise UnsupportedModelError(
234
+ "Piper installation must contain model and config artifacts"
235
+ ) from exc
236
+ try:
237
+ model_card = installation.artifact("model_card").path
238
+ except KeyError:
239
+ model_card = None
240
+ raw_metadata = dict(getattr(installation, "metadata", {}) or {})
241
+ language = raw_metadata.get("language") or {}
242
+ language_code = language.get("code") if isinstance(language, Mapping) else None
243
+ return ResolvedPiperVoice(
244
+ ref=ref or getattr(installation, "ref", None),
245
+ voice_id=getattr(installation, "id", None),
246
+ model_path=Path(model),
247
+ config_path=Path(config),
248
+ model_card_path=Path(model_card) if model_card is not None else None,
249
+ language_code=str(language_code) if language_code is not None else None,
250
+ source_revision=(
251
+ str(raw_metadata["source_revision"])
252
+ if raw_metadata.get("source_revision") is not None
253
+ else None
254
+ ),
255
+ metadata=raw_metadata,
256
+ installation=installation,
257
+ )
258
+
259
+
260
+ def runtime_diagnostics(runtime: Any) -> dict[str, Any]:
261
+ installation = getattr(runtime, "installation", None)
262
+ model_path = None
263
+ config_path = None
264
+ if installation is not None:
265
+ try:
266
+ model_path = str(installation.artifact("model").path)
267
+ except (KeyError, AttributeError):
268
+ pass
269
+ try:
270
+ config_path = str(installation.artifact("config").path)
271
+ except (KeyError, AttributeError):
272
+ pass
273
+ session = getattr(runtime, "session", None)
274
+ return {
275
+ "model_path": model_path,
276
+ "config_path": config_path,
277
+ "providers_requested": tuple(getattr(session, "provider_request", ()) or ()),
278
+ "providers_active": tuple(getattr(session, "resolved_providers", ()) or ()),
279
+ "model_inputs": tuple(getattr(session, "input_names", ()) or ()),
280
+ "model_outputs": tuple(getattr(session, "output_names", ()) or ()),
281
+ }
282
+
283
+
284
+ def summarize_array(value: Any) -> dict[str, Any]:
285
+ import numpy as np
286
+
287
+ array = np.asarray(value)
288
+ return {"shape": [int(size) for size in array.shape], "dtype": str(array.dtype)}
289
+
290
+
291
+ def summarize_inference(result: Any) -> tuple[dict[str, Any] | None, dict[str, Any]]:
292
+ timing = getattr(result, "timings", None)
293
+ timings = summarize_array(timing) if timing is not None else None
294
+ outputs = {
295
+ str(name): summarize_array(value)
296
+ for name, value in dict(getattr(result, "outputs", {}) or {}).items()
297
+ }
298
+ return timings, outputs
299
+
300
+
301
+ def available_providers() -> tuple[str, ...]:
302
+ return tuple(_call("open", lambda: _onnxvoice().available_providers()))
303
+
304
+
305
+ __all__ = [
306
+ "ResolvedPiperVoice",
307
+ "adapt_progress",
308
+ "available_providers",
309
+ "installation_to_voice_info",
310
+ "install_pretrained_voice",
311
+ "normalize_piper_ref",
312
+ "normalize_provider_request",
313
+ "open_installed_voice",
314
+ "open_local_voice",
315
+ "runtime_diagnostics",
316
+ "summarize_inference",
317
+ ]
pipersynth/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.1'
22
+ __version_tuple__ = version_tuple = (0, 1, 1)
23
+
24
+ __commit_id__ = commit_id = 'g5927e1531'