openspeechapi 0.1.0__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 (118) hide show
  1. openspeech/__init__.py +75 -0
  2. openspeech/__main__.py +5 -0
  3. openspeech/cli.py +413 -0
  4. openspeech/client/__init__.py +4 -0
  5. openspeech/client/client.py +145 -0
  6. openspeech/config.py +212 -0
  7. openspeech/core/__init__.py +0 -0
  8. openspeech/core/base.py +75 -0
  9. openspeech/core/enums.py +39 -0
  10. openspeech/core/models.py +61 -0
  11. openspeech/core/registry.py +37 -0
  12. openspeech/core/settings.py +8 -0
  13. openspeech/demo.py +675 -0
  14. openspeech/dispatch/__init__.py +0 -0
  15. openspeech/dispatch/context.py +34 -0
  16. openspeech/dispatch/dispatcher.py +661 -0
  17. openspeech/dispatch/executors/__init__.py +0 -0
  18. openspeech/dispatch/executors/base.py +34 -0
  19. openspeech/dispatch/executors/in_process.py +66 -0
  20. openspeech/dispatch/executors/remote.py +64 -0
  21. openspeech/dispatch/executors/subprocess_exec.py +446 -0
  22. openspeech/dispatch/fanout.py +95 -0
  23. openspeech/dispatch/filters.py +73 -0
  24. openspeech/dispatch/lifecycle.py +178 -0
  25. openspeech/dispatch/watcher.py +82 -0
  26. openspeech/engine_catalog.py +236 -0
  27. openspeech/engine_registry.yaml +347 -0
  28. openspeech/exceptions.py +51 -0
  29. openspeech/factory.py +325 -0
  30. openspeech/local_engines/__init__.py +12 -0
  31. openspeech/local_engines/aim_resolver.py +91 -0
  32. openspeech/local_engines/backends/__init__.py +1 -0
  33. openspeech/local_engines/backends/docker_backend.py +490 -0
  34. openspeech/local_engines/backends/native_backend.py +902 -0
  35. openspeech/local_engines/base.py +30 -0
  36. openspeech/local_engines/engines/__init__.py +1 -0
  37. openspeech/local_engines/engines/faster_whisper.py +36 -0
  38. openspeech/local_engines/engines/fish_speech.py +33 -0
  39. openspeech/local_engines/engines/sherpa_onnx.py +56 -0
  40. openspeech/local_engines/engines/whisper.py +41 -0
  41. openspeech/local_engines/engines/whisperlivekit.py +60 -0
  42. openspeech/local_engines/manager.py +208 -0
  43. openspeech/local_engines/models.py +50 -0
  44. openspeech/local_engines/progress.py +69 -0
  45. openspeech/local_engines/registry.py +19 -0
  46. openspeech/local_engines/task_store.py +52 -0
  47. openspeech/local_engines/tasks.py +71 -0
  48. openspeech/logging_config.py +607 -0
  49. openspeech/observe/__init__.py +0 -0
  50. openspeech/observe/base.py +79 -0
  51. openspeech/observe/debug.py +44 -0
  52. openspeech/observe/latency.py +19 -0
  53. openspeech/observe/metrics.py +47 -0
  54. openspeech/observe/tracing.py +44 -0
  55. openspeech/observe/usage.py +27 -0
  56. openspeech/providers/__init__.py +0 -0
  57. openspeech/providers/_template.py +101 -0
  58. openspeech/providers/stt/__init__.py +0 -0
  59. openspeech/providers/stt/alibaba.py +86 -0
  60. openspeech/providers/stt/assemblyai.py +135 -0
  61. openspeech/providers/stt/azure_speech.py +99 -0
  62. openspeech/providers/stt/baidu.py +135 -0
  63. openspeech/providers/stt/deepgram.py +311 -0
  64. openspeech/providers/stt/elevenlabs.py +385 -0
  65. openspeech/providers/stt/faster_whisper.py +211 -0
  66. openspeech/providers/stt/google_cloud.py +106 -0
  67. openspeech/providers/stt/iflytek.py +427 -0
  68. openspeech/providers/stt/macos_speech.py +226 -0
  69. openspeech/providers/stt/openai.py +84 -0
  70. openspeech/providers/stt/sherpa_onnx.py +353 -0
  71. openspeech/providers/stt/tencent.py +212 -0
  72. openspeech/providers/stt/volcengine.py +107 -0
  73. openspeech/providers/stt/whisper.py +153 -0
  74. openspeech/providers/stt/whisperlivekit.py +530 -0
  75. openspeech/providers/stt/windows_speech.py +249 -0
  76. openspeech/providers/tts/__init__.py +0 -0
  77. openspeech/providers/tts/alibaba.py +95 -0
  78. openspeech/providers/tts/azure_speech.py +123 -0
  79. openspeech/providers/tts/baidu.py +143 -0
  80. openspeech/providers/tts/coqui.py +64 -0
  81. openspeech/providers/tts/cosyvoice.py +90 -0
  82. openspeech/providers/tts/deepgram.py +174 -0
  83. openspeech/providers/tts/elevenlabs.py +311 -0
  84. openspeech/providers/tts/fish_speech.py +158 -0
  85. openspeech/providers/tts/google_cloud.py +107 -0
  86. openspeech/providers/tts/iflytek.py +209 -0
  87. openspeech/providers/tts/macos_say.py +251 -0
  88. openspeech/providers/tts/minimax.py +122 -0
  89. openspeech/providers/tts/openai.py +104 -0
  90. openspeech/providers/tts/piper.py +104 -0
  91. openspeech/providers/tts/tencent.py +189 -0
  92. openspeech/providers/tts/volcengine.py +117 -0
  93. openspeech/providers/tts/windows_sapi.py +234 -0
  94. openspeech/server/__init__.py +1 -0
  95. openspeech/server/app.py +72 -0
  96. openspeech/server/auth.py +42 -0
  97. openspeech/server/middleware.py +75 -0
  98. openspeech/server/routes/__init__.py +1 -0
  99. openspeech/server/routes/management.py +848 -0
  100. openspeech/server/routes/stt.py +121 -0
  101. openspeech/server/routes/tts.py +159 -0
  102. openspeech/server/routes/webui.py +29 -0
  103. openspeech/server/webui/app.js +2649 -0
  104. openspeech/server/webui/index.html +216 -0
  105. openspeech/server/webui/styles.css +617 -0
  106. openspeech/server/ws/__init__.py +1 -0
  107. openspeech/server/ws/stt_stream.py +263 -0
  108. openspeech/server/ws/tts_stream.py +207 -0
  109. openspeech/telemetry/__init__.py +21 -0
  110. openspeech/telemetry/perf.py +307 -0
  111. openspeech/utils/__init__.py +5 -0
  112. openspeech/utils/audio_converter.py +406 -0
  113. openspeech/utils/audio_playback.py +156 -0
  114. openspeech/vendor_registry.yaml +74 -0
  115. openspeechapi-0.1.0.dist-info/METADATA +101 -0
  116. openspeechapi-0.1.0.dist-info/RECORD +118 -0
  117. openspeechapi-0.1.0.dist-info/WHEEL +4 -0
  118. openspeechapi-0.1.0.dist-info/entry_points.txt +3 -0
openspeech/factory.py ADDED
@@ -0,0 +1,325 @@
1
+ """High-level factory — create providers by string name, no class imports needed."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from openspeech.core.base import SpeechProvider
9
+ from openspeech.exceptions import ProviderNotFoundError
10
+
11
+ # Lazy registry: name → (module_path, class_name, settings_class_name)
12
+ _PROVIDER_MAP: dict[str, tuple[str, str, str]] = {
13
+ # STT
14
+ "openai-stt": (
15
+ "openspeech.providers.stt.openai",
16
+ "OpenAISTT",
17
+ "OpenAISTTSettings",
18
+ ),
19
+ "faster-whisper": (
20
+ "openspeech.providers.stt.faster_whisper",
21
+ "FasterWhisperSTT",
22
+ "FasterWhisperSTTSettings",
23
+ ),
24
+ "whisper": (
25
+ "openspeech.providers.stt.whisper",
26
+ "WhisperSTT",
27
+ "WhisperSTTSettings",
28
+ ),
29
+ "deepgram": (
30
+ "openspeech.providers.stt.deepgram",
31
+ "DeepgramSTT",
32
+ "DeepgramSTTSettings",
33
+ ),
34
+ "elevenlabs-stt": (
35
+ "openspeech.providers.stt.elevenlabs",
36
+ "ElevenLabsSTT",
37
+ "ElevenLabsSTTSettings",
38
+ ),
39
+ "deepgram-tts": (
40
+ "openspeech.providers.tts.deepgram",
41
+ "DeepgramTTS",
42
+ "DeepgramTTSSettings",
43
+ ),
44
+ "whisperlivekit-stt": (
45
+ "openspeech.providers.stt.whisperlivekit",
46
+ "WhisperLiveKitSTT",
47
+ "WhisperLiveKitSTTSettings",
48
+ ),
49
+ "sherpa-onnx-stt": (
50
+ "openspeech.providers.stt.sherpa_onnx",
51
+ "SherpaOnnxSTT",
52
+ "SherpaOnnxSTTSettings",
53
+ ),
54
+ # TTS
55
+ "openai-tts": (
56
+ "openspeech.providers.tts.openai",
57
+ "OpenAITTS",
58
+ "OpenAITTSSettings",
59
+ ),
60
+ "elevenlabs": (
61
+ "openspeech.providers.tts.elevenlabs",
62
+ "ElevenLabsTTS",
63
+ "ElevenLabsTTSSettings",
64
+ ),
65
+ "piper": (
66
+ "openspeech.providers.tts.piper",
67
+ "PiperTTS",
68
+ "PiperTTSSettings",
69
+ ),
70
+ "coqui": (
71
+ "openspeech.providers.tts.coqui",
72
+ "CoquiTTS",
73
+ "CoquiTTSSettings",
74
+ ),
75
+ "cosyvoice": (
76
+ "openspeech.providers.tts.cosyvoice",
77
+ "CosyVoiceTTS",
78
+ "CosyVoiceTTSSettings",
79
+ ),
80
+ "fish-speech": (
81
+ "openspeech.providers.tts.fish_speech",
82
+ "FishSpeechTTS",
83
+ "FishSpeechTTSSettings",
84
+ ),
85
+ "minimax": (
86
+ "openspeech.providers.tts.minimax",
87
+ "MinimaxTTS",
88
+ "MinimaxTTSSettings",
89
+ ),
90
+ "macos-say": (
91
+ "openspeech.providers.tts.macos_say",
92
+ "MacOSSayTTS",
93
+ "MacOSSaySettings",
94
+ ),
95
+ "macos-stt": (
96
+ "openspeech.providers.stt.macos_speech",
97
+ "MacOSSpeechSTT",
98
+ "MacOSSpeechSettings",
99
+ ),
100
+ # Windows Native
101
+ "windows-tts": (
102
+ "openspeech.providers.tts.windows_sapi",
103
+ "WindowsSapiTTS",
104
+ "WindowsSapiSettings",
105
+ ),
106
+ "windows-stt": (
107
+ "openspeech.providers.stt.windows_speech",
108
+ "WindowsSpeechSTT",
109
+ "WindowsSpeechSettings",
110
+ ),
111
+ # Cloud STT
112
+ "google-stt": (
113
+ "openspeech.providers.stt.google_cloud",
114
+ "GoogleCloudSTT",
115
+ "GoogleCloudSTTSettings",
116
+ ),
117
+ "azure-stt": (
118
+ "openspeech.providers.stt.azure_speech",
119
+ "AzureSpeechSTT",
120
+ "AzureSpeechSTTSettings",
121
+ ),
122
+ "assemblyai-stt": (
123
+ "openspeech.providers.stt.assemblyai",
124
+ "AssemblyAISTT",
125
+ "AssemblyAISTTSettings",
126
+ ),
127
+ "volcengine-stt": (
128
+ "openspeech.providers.stt.volcengine",
129
+ "VolcengineSTT",
130
+ "VolcengineSTTSettings",
131
+ ),
132
+ "alibaba-stt": (
133
+ "openspeech.providers.stt.alibaba",
134
+ "AlibabaSTT",
135
+ "AlibabaSTTSettings",
136
+ ),
137
+ "tencent-stt": (
138
+ "openspeech.providers.stt.tencent",
139
+ "TencentSTT",
140
+ "TencentSTTSettings",
141
+ ),
142
+ "baidu-stt": (
143
+ "openspeech.providers.stt.baidu",
144
+ "BaiduSTT",
145
+ "BaiduSTTSettings",
146
+ ),
147
+ "iflytek-stt": (
148
+ "openspeech.providers.stt.iflytek",
149
+ "IflytekSTT",
150
+ "IflytekSTTSettings",
151
+ ),
152
+ # Cloud TTS
153
+ "google-tts": (
154
+ "openspeech.providers.tts.google_cloud",
155
+ "GoogleCloudTTS",
156
+ "GoogleCloudTTSSettings",
157
+ ),
158
+ "azure-tts": (
159
+ "openspeech.providers.tts.azure_speech",
160
+ "AzureSpeechTTS",
161
+ "AzureSpeechTTSSettings",
162
+ ),
163
+ "volcengine-tts": (
164
+ "openspeech.providers.tts.volcengine",
165
+ "VolcengineTTS",
166
+ "VolcengineTTSSettings",
167
+ ),
168
+ "alibaba-tts": (
169
+ "openspeech.providers.tts.alibaba",
170
+ "AlibabaTTS",
171
+ "AlibabaTTSSettings",
172
+ ),
173
+ "tencent-tts": (
174
+ "openspeech.providers.tts.tencent",
175
+ "TencentTTS",
176
+ "TencentTTSSettings",
177
+ ),
178
+ "baidu-tts": (
179
+ "openspeech.providers.tts.baidu",
180
+ "BaiduTTS",
181
+ "BaiduTTSSettings",
182
+ ),
183
+ "iflytek-tts": (
184
+ "openspeech.providers.tts.iflytek",
185
+ "IflytekTTS",
186
+ "IflytekTTSSettings",
187
+ ),
188
+ }
189
+
190
+
191
+ # Platform-generic aliases that resolve to the correct native provider.
192
+ _NATIVE_ALIASES: dict[str, dict[str, str]] = {
193
+ "native-tts": {"darwin": "macos-say", "win32": "windows-tts"},
194
+ "native-stt": {"darwin": "macos-stt", "win32": "windows-stt"},
195
+ }
196
+
197
+
198
+ def _resolve_native(alias: str) -> str:
199
+ """Map a ``native-*`` alias to the concrete provider name for this OS."""
200
+ platform_map = _NATIVE_ALIASES.get(alias)
201
+ if platform_map is None:
202
+ raise ProviderNotFoundError(f"Unknown native alias '{alias}'")
203
+ concrete = platform_map.get(sys.platform)
204
+ if concrete is None:
205
+ supported = ", ".join(sorted(platform_map.keys()))
206
+ raise ProviderNotFoundError(
207
+ f"No native provider for platform '{sys.platform}'. "
208
+ f"Supported: {supported}"
209
+ )
210
+ return concrete
211
+
212
+
213
+ def _resolve(name: str) -> tuple[type, type]:
214
+ """Lazily import and return (ProviderClass, SettingsClass).
215
+
216
+ Raises ``ProviderNotFoundError`` with a ``pip install`` hint when the
217
+ provider's optional extras are not installed.
218
+ """
219
+ import importlib
220
+
221
+ # Handle native aliases
222
+ if name in _NATIVE_ALIASES:
223
+ name = _resolve_native(name)
224
+
225
+ entry = _PROVIDER_MAP.get(name)
226
+ if entry is None:
227
+ available = ", ".join(sorted(_PROVIDER_MAP))
228
+ raise ProviderNotFoundError(
229
+ f"Unknown provider '{name}'. Available: {available}"
230
+ )
231
+
232
+ module_path, cls_name, settings_name = entry
233
+ try:
234
+ mod = importlib.import_module(module_path)
235
+ except ImportError as e:
236
+ raise ProviderNotFoundError(
237
+ f"Provider '{name}' requires optional dependencies that are not installed. "
238
+ f"Install with: pip install 'openspeech[{name}]' "
239
+ f"(missing: {e.name or e})"
240
+ ) from e
241
+ return getattr(mod, cls_name), getattr(mod, settings_name)
242
+
243
+
244
+ def _resolve_from_config(prov_cfg) -> tuple[type, type]:
245
+ """Resolve provider class from a ProviderConfig.
246
+
247
+ If the config specifies ``module`` and ``provider_class``, use importlib
248
+ to load the class dynamically. Otherwise fall back to the built-in
249
+ ``_PROVIDER_MAP`` via ``_resolve()``.
250
+ """
251
+ import importlib
252
+
253
+ if prov_cfg.module and prov_cfg.provider_class:
254
+ mod = importlib.import_module(prov_cfg.module)
255
+ provider_cls = getattr(mod, prov_cfg.provider_class)
256
+ settings_cls = getattr(provider_cls, "settings_cls", None)
257
+ return provider_cls, settings_cls
258
+
259
+ return _resolve(prov_cfg.provider)
260
+
261
+
262
+ def _ensure_plugins_dir(project_root: Path | None = None) -> None:
263
+ """Add ``<project_root>/plugins`` to *sys.path* if the directory exists."""
264
+ if project_root is None:
265
+ project_root = Path.cwd()
266
+ plugins_dir = project_root / "plugins"
267
+ if plugins_dir.is_dir():
268
+ plugins_str = str(plugins_dir)
269
+ if plugins_str not in sys.path:
270
+ sys.path.insert(0, plugins_str)
271
+
272
+
273
+ def create_provider(name: str, **settings: Any) -> SpeechProvider:
274
+ """Create a provider instance by name.
275
+
276
+ Usage::
277
+
278
+ tts = create_provider("openai-tts", api_key="sk-...")
279
+ stt = create_provider("faster-whisper", model_size="tiny")
280
+
281
+ """
282
+ provider_cls, settings_cls = _resolve(name)
283
+ s = settings_cls(**settings)
284
+ return provider_cls(settings=s)
285
+
286
+
287
+ def list_providers() -> list[str]:
288
+ """Return all registered provider names."""
289
+ return sorted(_PROVIDER_MAP)
290
+
291
+
292
+ def create_default_registry() -> "ProviderRegistry":
293
+ """Build a ProviderRegistry pre-populated with all known providers.
294
+
295
+ Resolves all entries in ``_PROVIDER_MAP`` and ``_NATIVE_ALIASES``,
296
+ silently skipping any whose dependencies are missing.
297
+ """
298
+ from openspeech.core.registry import ProviderRegistry
299
+
300
+ from openspeech.logging_config import logger
301
+
302
+ _ensure_plugins_dir()
303
+ registry = ProviderRegistry()
304
+ skipped: list[str] = []
305
+ for name in _PROVIDER_MAP:
306
+ try:
307
+ provider_cls, _ = _resolve(name)
308
+ registry.register(name, provider_cls)
309
+ except ProviderNotFoundError as e:
310
+ skipped.append(name)
311
+ logger.debug(f"skip provider '{name}': {e}")
312
+ except Exception as e:
313
+ logger.debug(f"skip provider '{name}' (unexpected): {e}")
314
+ for alias in _NATIVE_ALIASES:
315
+ try:
316
+ provider_cls, _ = _resolve(alias)
317
+ registry.register(alias, provider_cls)
318
+ except Exception as e:
319
+ logger.debug(f"skip native alias '{alias}': {e}")
320
+ if skipped:
321
+ logger.info(
322
+ f"{len(skipped)} provider(s) unavailable due to missing optional deps: "
323
+ f"{', '.join(skipped)}. Install e.g. `pip install 'openspeech[<name>]'`."
324
+ )
325
+ return registry
@@ -0,0 +1,12 @@
1
+ """Local engine manager exports."""
2
+
3
+ from openspeech.local_engines.manager import EngineManager
4
+ from openspeech.local_engines.models import EngineAction, EngineStatus, RuntimeConfig, TaskStatus
5
+
6
+ __all__ = [
7
+ "EngineAction",
8
+ "EngineManager",
9
+ "EngineStatus",
10
+ "RuntimeConfig",
11
+ "TaskStatus",
12
+ ]
@@ -0,0 +1,91 @@
1
+ """Resolve model paths from AIM CLI output."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ import subprocess
7
+ from typing import Any
8
+
9
+
10
+ def _run_aim(args: list[str]) -> str:
11
+ proc = subprocess.run(
12
+ ["aim", *args],
13
+ capture_output=True,
14
+ text=True,
15
+ check=False,
16
+ )
17
+ if proc.returncode != 0:
18
+ raise RuntimeError((proc.stderr or proc.stdout or "").strip() or f"aim {' '.join(args)} failed")
19
+ return (proc.stdout or "").strip()
20
+
21
+
22
+ def _parse_aim_resolve_json(raw: str) -> dict[str, Any]:
23
+ data = json.loads(raw)
24
+ if not isinstance(data, dict):
25
+ return {}
26
+ return data
27
+
28
+
29
+ def resolve_aim_model_paths(
30
+ *,
31
+ model_ids: list[str],
32
+ provision_engine: str = "whisper",
33
+ kind: str = "any",
34
+ ) -> list[str]:
35
+ """Return absolute model paths for requested model IDs from AIM CLI.
36
+
37
+ Args:
38
+ model_ids: model IDs in priority order.
39
+ provision_engine: target engine filter.
40
+ kind: "file", "dir", or "any".
41
+ """
42
+ ids = [x.strip() for x in model_ids if str(x).strip()]
43
+ if not ids:
44
+ return []
45
+
46
+ out: list[str] = []
47
+ seen: set[str] = set()
48
+ for mid in ids:
49
+ try:
50
+ resolved = _parse_aim_resolve_json(
51
+ _run_aim(["resolve", mid, "--engine", provision_engine, "--json"])
52
+ )
53
+ except Exception:
54
+ continue
55
+ engines = resolved.get("engines", [])
56
+ if isinstance(engines, list) and engines:
57
+ if provision_engine and provision_engine not in {str(x).strip() for x in engines}:
58
+ continue
59
+
60
+ resolved_path = str(resolved.get("path", "")).strip()
61
+ if not resolved_path:
62
+ continue
63
+ abs_path = Path(resolved_path).expanduser()
64
+ abs_str = str(abs_path)
65
+ if abs_str not in seen:
66
+ seen.add(abs_str)
67
+ out.append(abs_str)
68
+
69
+ resolved_file = str(resolved.get("resolved_file", "") or "").strip()
70
+ if resolved_file:
71
+ rf = str(Path(resolved_file).expanduser())
72
+ if rf not in seen:
73
+ seen.add(rf)
74
+ out.append(rf)
75
+
76
+ # whisper-style canonical directory often contains .pt/.bin payload files.
77
+ if abs_path.is_dir():
78
+ for p in sorted(abs_path.iterdir()):
79
+ if p.is_file() and p.suffix.lower() in {".pt", ".bin"}:
80
+ f = str(p)
81
+ if f not in seen:
82
+ seen.add(f)
83
+ out.append(f)
84
+
85
+ if kind == "any":
86
+ return out
87
+ if kind == "file":
88
+ return [x for x in out if Path(x).is_file() or Path(x).suffix.lower() in {".pt", ".bin"}]
89
+ if kind == "dir":
90
+ return [x for x in out if Path(x).is_dir() or (not Path(x).suffix and not x.endswith(".pt"))]
91
+ return out
@@ -0,0 +1 @@
1
+ """Runtime backend implementations."""