soprosynth 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.
soprosynth/__init__.py ADDED
@@ -0,0 +1,103 @@
1
+ """Dependency-light public API for the Sopro speech-synthesis runtime."""
2
+
3
+ try:
4
+ from ._version import version as __version__
5
+ except ImportError:
6
+ from importlib.metadata import PackageNotFoundError, version
7
+
8
+ try:
9
+ __version__ = version("soprosynth")
10
+ except PackageNotFoundError:
11
+ __version__ = "0.0.0"
12
+
13
+ from .api import SoproRuntime
14
+ from .api_contract import REQUEST_API_VERSION, RequestApiContract, request_api_contract
15
+ from .config import (
16
+ GenerationConfig,
17
+ SoproOutputConfig,
18
+ SoproOutputMode,
19
+ VoiceLevelApplication,
20
+ VoiceLevelConfig,
21
+ VoiceLevelMode,
22
+ VoiceLevelSource,
23
+ )
24
+ from .discovery import discover_bundles, runtime_identity
25
+ from .errors import (
26
+ AssetAccessError,
27
+ AssetCacheError,
28
+ AssetDownloadError,
29
+ AssetError,
30
+ BundleNotFoundError,
31
+ CatalogUnavailableError,
32
+ EmptyTextError,
33
+ InvalidGenerationConfigError,
34
+ InvalidLanguageError,
35
+ InvalidRequestError,
36
+ InvalidVoiceError,
37
+ ModelInferenceError,
38
+ OfflineAssetError,
39
+ OptionalDependencyError,
40
+ RuntimeCapabilityError,
41
+ RuntimeClosedError,
42
+ SessionCreationError,
43
+ SoproSynthError,
44
+ UnsupportedBundleError,
45
+ UnsupportedFeatureError,
46
+ )
47
+ from .types import (
48
+ DiscoveredBundle,
49
+ PreparedVoice,
50
+ RequestMeasure,
51
+ RuntimeCapabilities,
52
+ RuntimeDiagnostics,
53
+ SynthesisChunk,
54
+ SynthesisRequest,
55
+ SynthesisResult,
56
+ WordTiming,
57
+ )
58
+
59
+ __all__ = [
60
+ "REQUEST_API_VERSION",
61
+ "AssetAccessError",
62
+ "AssetCacheError",
63
+ "AssetDownloadError",
64
+ "AssetError",
65
+ "BundleNotFoundError",
66
+ "CatalogUnavailableError",
67
+ "DiscoveredBundle",
68
+ "EmptyTextError",
69
+ "GenerationConfig",
70
+ "InvalidGenerationConfigError",
71
+ "InvalidLanguageError",
72
+ "InvalidRequestError",
73
+ "InvalidVoiceError",
74
+ "ModelInferenceError",
75
+ "OfflineAssetError",
76
+ "OptionalDependencyError",
77
+ "PreparedVoice",
78
+ "RequestApiContract",
79
+ "RequestMeasure",
80
+ "RuntimeCapabilities",
81
+ "RuntimeCapabilityError",
82
+ "RuntimeClosedError",
83
+ "RuntimeDiagnostics",
84
+ "SessionCreationError",
85
+ "SoproOutputConfig",
86
+ "SoproOutputMode",
87
+ "SoproRuntime",
88
+ "SoproSynthError",
89
+ "SynthesisChunk",
90
+ "SynthesisRequest",
91
+ "SynthesisResult",
92
+ "UnsupportedBundleError",
93
+ "UnsupportedFeatureError",
94
+ "VoiceLevelApplication",
95
+ "VoiceLevelConfig",
96
+ "VoiceLevelMode",
97
+ "VoiceLevelSource",
98
+ "WordTiming",
99
+ "__version__",
100
+ "discover_bundles",
101
+ "request_api_contract",
102
+ "runtime_identity",
103
+ ]
soprosynth/__main__.py ADDED
@@ -0,0 +1,178 @@
1
+ """SoproSynth command-line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from collections.abc import Mapping, Sequence
8
+ from dataclasses import fields, is_dataclass
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from . import __version__, discover_bundles, request_api_contract, runtime_identity
13
+ from .api import SoproRuntime
14
+ from .config import GenerationConfig, SoproOutputConfig, VoiceLevelConfig
15
+
16
+
17
+ def _json_value(value: Any) -> Any:
18
+ if is_dataclass(value) and not isinstance(value, type):
19
+ return {field.name: _json_value(getattr(value, field.name)) for field in fields(value)}
20
+ if isinstance(value, Mapping):
21
+ return {str(key): _json_value(item) for key, item in value.items()}
22
+ if isinstance(value, (tuple, list)):
23
+ return [_json_value(item) for item in value]
24
+ if isinstance(value, Path):
25
+ return str(value)
26
+ if value is None or isinstance(value, (str, int, float, bool)):
27
+ return value
28
+ return str(value)
29
+
30
+
31
+ def _print_json(value: Any) -> None:
32
+ print(json.dumps(_json_value(value), indent=2, sort_keys=True))
33
+
34
+
35
+ def _contract_summary() -> dict[str, Any]:
36
+ contract = request_api_contract()
37
+ return {
38
+ "version": contract.version,
39
+ "request_type": contract.request_type.__name__,
40
+ "result_type": contract.result_type.__name__,
41
+ "synthesis_method": contract.synthesis_method,
42
+ "measurement_method": contract.measurement_method,
43
+ "supports_reference_voice": contract.supports_reference_voice,
44
+ "supports_voice_level": contract.supports_voice_level,
45
+ "supports_sopro_output_policy": contract.supports_sopro_output_policy,
46
+ "caller_owns_text_boundaries": contract.caller_owns_text_boundaries,
47
+ "supports_request_measurement": contract.supports_request_measurement,
48
+ "supports_streaming": contract.supports_streaming,
49
+ "supports_word_timings": contract.supports_word_timings,
50
+ }
51
+
52
+
53
+ def _build_parser() -> argparse.ArgumentParser:
54
+ parser = argparse.ArgumentParser(
55
+ prog="soprosynth",
56
+ description="SoproSynth speech synthesis using ONNXVoice's Sopro runtime",
57
+ )
58
+ parser.add_argument("--version", action="version", version=__version__)
59
+ subparsers = parser.add_subparsers(dest="command", required=True)
60
+
61
+ speak = subparsers.add_parser("speak", help="synthesize speech")
62
+ speak.add_argument("text", help="text to synthesize")
63
+ speak.add_argument("--ref", required=True, help="reference voice WAV path")
64
+ speak.add_argument("--out", default="out.wav", help="output mono PCM16 WAV path")
65
+ speak.add_argument("--language", choices=("en", "pt", "fr", "de", "auto"))
66
+ speak.add_argument("--model", default="sopro:v2-turbo")
67
+ speak.add_argument("--profile", default="wasm-uint8")
68
+ speak.add_argument("--provider", choices=("cpu",), default="cpu")
69
+ speak.add_argument(
70
+ "--output-mode",
71
+ choices=("native", "raw"),
72
+ default="native",
73
+ help="Sopro-compatible output conditioning or raw ONNX waveform",
74
+ )
75
+ speak.add_argument("--temperature", type=float)
76
+ speak.add_argument("--top-p", type=float)
77
+ speak.add_argument("--top-k", type=int)
78
+ speak.add_argument("--max-seconds", type=float)
79
+ speak.add_argument("--min-seconds", type=float)
80
+ speak.add_argument("--voice-level", choices=("off", "calibrated"), default="off")
81
+ speak.add_argument("--gain-db", type=float, help="explicit static gain override")
82
+
83
+ subparsers.add_parser(
84
+ "info", help="show API contract and runtime provenance without loading a model"
85
+ )
86
+
87
+ discover = subparsers.add_parser("discover", help="list ONNXVoice Sopro bundle metadata")
88
+ discover.add_argument(
89
+ "--offline", action="store_true", help="use catalog data available offline"
90
+ )
91
+ discover.add_argument(
92
+ "--refresh", action="store_true", help="refresh ONNXVoice catalog metadata"
93
+ )
94
+ discover.add_argument("--cache-dir", type=Path)
95
+
96
+ inspect = subparsers.add_parser(
97
+ "inspect",
98
+ help="open a local ONNXVoice session and print diagnostics; does not synthesize",
99
+ )
100
+ inspect.add_argument("directory", type=Path)
101
+ inspect.add_argument("--profile", default="wasm-uint8")
102
+ return parser
103
+
104
+
105
+ def _speak(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
106
+ if args.gain_db is not None and args.voice_level != "calibrated":
107
+ parser.error("--gain-db requires --voice-level calibrated")
108
+ config = GenerationConfig(
109
+ temperature=args.temperature,
110
+ top_p=args.top_p,
111
+ top_k=args.top_k,
112
+ max_seconds=args.max_seconds,
113
+ min_seconds=args.min_seconds,
114
+ )
115
+ voice_level = VoiceLevelConfig(mode=args.voice_level, gain_db=args.gain_db)
116
+ output_config = SoproOutputConfig(mode=args.output_mode)
117
+ with SoproRuntime.from_pretrained(
118
+ model=args.model,
119
+ profile=args.profile,
120
+ providers=args.provider,
121
+ ) as runtime:
122
+ result = runtime.synthesize_text(
123
+ args.text,
124
+ id="cli-speak",
125
+ voice=args.ref,
126
+ language=args.language,
127
+ config=config,
128
+ voice_level=voice_level,
129
+ output=output_config,
130
+ )
131
+ output_path = result.save_wav(args.out)
132
+ print(output_path)
133
+ _print_json(
134
+ {
135
+ "sopro_output": result.metadata.get("sopro_output"),
136
+ "voice_level_application": result.metadata.get("voice_level_application"),
137
+ }
138
+ )
139
+ return 0
140
+
141
+
142
+ def _discover(args: argparse.Namespace) -> int:
143
+ bundles = discover_bundles(
144
+ offline=args.offline,
145
+ refresh=args.refresh,
146
+ cache_dir=args.cache_dir,
147
+ )
148
+ _print_json(bundles)
149
+ return 0
150
+
151
+
152
+ def _inspect(args: argparse.Namespace) -> int:
153
+ print("Developer inspection only; this command does not run synthesis.")
154
+ with SoproRuntime.from_onnx_local(args.directory, profile=args.profile) as runtime:
155
+ _print_json(runtime.diagnostics)
156
+ return 0
157
+
158
+
159
+ def main(argv: Sequence[str] | None = None) -> int:
160
+ parser = _build_parser()
161
+ args = parser.parse_args(argv)
162
+ if args.command == "speak":
163
+ return _speak(args, parser)
164
+ if args.command == "info":
165
+ _print_json(
166
+ {"version": __version__, "api": _contract_summary(), "runtime": runtime_identity()}
167
+ )
168
+ return 0
169
+ if args.command == "discover":
170
+ return _discover(args)
171
+ if args.command == "inspect":
172
+ return _inspect(args)
173
+ parser.error(f"unknown command: {args.command}")
174
+ return 2
175
+
176
+
177
+ if __name__ == "__main__":
178
+ raise SystemExit(main())