prismatoid 0.4.6__cp313-cp313-manylinux_2_34_aarch64.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.
prism/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .core import Backend, BackendId, Context, PrismException
2
+
3
+ __all__ = ["Backend", "BackendId", "Context", "PrismException"]
Binary file
prism/core.py ADDED
@@ -0,0 +1,374 @@
1
+ import sys
2
+ from enum import IntEnum
3
+
4
+ from .lib import ffi, lib
5
+
6
+ if sys.platform == "win32":
7
+ try:
8
+ from win32more.Windows.Win32.Foundation import HWND
9
+ except ImportError:
10
+ HWND = int
11
+ else:
12
+ HWND = int
13
+
14
+
15
+ class BackendId(IntEnum):
16
+ INVALID = 0
17
+ SAPI = 0x1D6DF72422CEEE66
18
+ AV_SPEECH = 0x28E3429577805C24
19
+ VOICE_OVER = 0xCB4897961A754BCB
20
+ SPEECH_DISPATCHER = 0xE3D6F895D949EBFE
21
+ NVDA = 0x89CC19C5C4AC1A56
22
+ JAWS = 0xAC3D60E9BD84B53E
23
+ ONE_CORE = 0x6797D32F0D994CB4
24
+ ORCA = 0x10AA1FC05A17F96C
25
+ ANDROID_SCREEN_READER = 0xD199C175AEEC494B
26
+ ANDROID_TTS = 0xBC175831BFE4E5CC
27
+ WEB_SPEECH = 0x3572538D44D44A8F
28
+ UIA = 0x6238F019DB678F8E
29
+ ZDSR = 0xAF63B44C8601A843
30
+ ZOOM_TEXT = 0xAE439D62DC7B1479
31
+
32
+
33
+ class PrismError(Exception):
34
+ """Base class for all Prism-related errors."""
35
+
36
+ def __init__(self, code: lib.PrismError, message: str | None = None) -> None:
37
+ self.code = code
38
+ self.message = message
39
+ super().__init__(message or f"Prism Error Code: {code}")
40
+
41
+
42
+ class PrismNotInitializedError(PrismError, RuntimeError):
43
+ """PRISM_ERROR_NOT_INITIALIZED"""
44
+
45
+
46
+ class PrismAlreadyInitializedError(PrismError, RuntimeError):
47
+ """PRISM_ERROR_ALREADY_INITIALIZED"""
48
+
49
+
50
+ class PrismInvalidOperationError(PrismError, RuntimeError):
51
+ """PRISM_ERROR_INVALID_OPERATION"""
52
+
53
+
54
+ class PrismInternalError(PrismError, RuntimeError):
55
+ """PRISM_ERROR_INTERNAL"""
56
+
57
+
58
+ class PrismBackendNotAvailableError(PrismError, RuntimeError):
59
+ """PRISM_ERROR_BACKEND_NOT_AVAILABLE"""
60
+
61
+
62
+ class PrismNotImplementedError(PrismError, NotImplementedError):
63
+ """PRISM_ERROR_NOT_IMPLEMENTED"""
64
+
65
+
66
+ class PrismInvalidParamError(PrismError, ValueError):
67
+ """PRISM_ERROR_INVALID_PARAM"""
68
+
69
+
70
+ class PrismRangeError(PrismError, IndexError):
71
+ """PRISM_ERROR_RANGE_OUT_OF_BOUNDS"""
72
+
73
+
74
+ class PrismInvalidUtf8Error(PrismError, UnicodeError):
75
+ """PRISM_ERROR_INVALID_UTF8"""
76
+
77
+
78
+ class PrismNotSpeakingError(PrismError):
79
+ """PRISM_ERROR_NOT_SPEAKING"""
80
+
81
+
82
+ class PrismNotPausedError(PrismError):
83
+ """PRISM_ERROR_NOT_PAUSED"""
84
+
85
+
86
+ class PrismAlreadyPausedError(PrismError):
87
+ """PRISM_ERROR_ALREADY_PAUSED"""
88
+
89
+
90
+ class PrismSpeakError(PrismError, IOError):
91
+ """PRISM_ERROR_SPEAK_FAILURE"""
92
+
93
+
94
+ class PrismNoVoicesError(PrismError):
95
+ """PRISM_ERROR_NO_VOICES"""
96
+
97
+
98
+ class PrismVoiceNotFoundError(PrismError, LookupError):
99
+ """PRISM_ERROR_VOICE_NOT_FOUND"""
100
+
101
+
102
+ class PrismMemoryError(PrismError, MemoryError):
103
+ """PRISM_ERROR_MEMORY_FAILURE"""
104
+
105
+
106
+ class PrismUnknownError(PrismError):
107
+ """PRISM_ERROR_UNKNOWN"""
108
+
109
+
110
+ _ERROR_MAP = {
111
+ lib.PRISM_ERROR_NOT_INITIALIZED: PrismNotInitializedError,
112
+ lib.PRISM_ERROR_INVALID_PARAM: PrismInvalidParamError,
113
+ lib.PRISM_ERROR_NOT_IMPLEMENTED: PrismNotImplementedError,
114
+ lib.PRISM_ERROR_NO_VOICES: PrismNoVoicesError,
115
+ lib.PRISM_ERROR_VOICE_NOT_FOUND: PrismVoiceNotFoundError,
116
+ lib.PRISM_ERROR_SPEAK_FAILURE: PrismSpeakError,
117
+ lib.PRISM_ERROR_MEMORY_FAILURE: PrismMemoryError,
118
+ lib.PRISM_ERROR_RANGE_OUT_OF_BOUNDS: PrismRangeError,
119
+ lib.PRISM_ERROR_INTERNAL: PrismInternalError,
120
+ lib.PRISM_ERROR_NOT_SPEAKING: PrismNotSpeakingError,
121
+ lib.PRISM_ERROR_NOT_PAUSED: PrismNotPausedError,
122
+ lib.PRISM_ERROR_ALREADY_PAUSED: PrismAlreadyPausedError,
123
+ lib.PRISM_ERROR_INVALID_UTF8: PrismInvalidUtf8Error,
124
+ lib.PRISM_ERROR_INVALID_OPERATION: PrismInvalidOperationError,
125
+ lib.PRISM_ERROR_ALREADY_INITIALIZED: PrismAlreadyInitializedError,
126
+ lib.PRISM_ERROR_BACKEND_NOT_AVAILABLE: PrismBackendNotAvailableError,
127
+ lib.PRISM_ERROR_UNKNOWN: PrismUnknownError,
128
+ }
129
+
130
+
131
+ def _check_error(error_code: int) -> None:
132
+ """
133
+ Checks the error code. If it is OK, returns None.
134
+ Otherwise, raises the appropriate PrismError.
135
+ """
136
+ if error_code == 0:
137
+ return
138
+ exc_class = _ERROR_MAP.get(error_code, PrismUnknownError)
139
+ msg_ptr = lib.prism_error_string(error_code)
140
+ msg = ffi.string(msg_ptr).decode("utf-8")
141
+ raise exc_class(error_code, msg)
142
+
143
+
144
+ class Backend:
145
+ _raw = None
146
+
147
+ def __init__(self, raw_ptr: lib.PrismBackend) -> None:
148
+ if raw_ptr == ffi.NULL:
149
+ raise RuntimeError("Backend raw pointer MUST NOT be NULL!")
150
+ self._raw = raw_ptr
151
+ res = lib.prism_backend_initialize(self._raw)
152
+ if res not in {lib.PRISM_OK, lib.PRISM_ERROR_ALREADY_INITIALIZED}:
153
+ _check_error(res)
154
+
155
+ def __del__(self) -> None:
156
+ lib.prism_backend_free(self._raw)
157
+ self._raw = None
158
+
159
+ @property
160
+ def name(self) -> str:
161
+ return ffi.string(lib.prism_backend_name(self._raw)).decode("utf-8")
162
+
163
+ def speak(self, text: str, interrupt: bool = False) -> None:
164
+ if len(text) == 0:
165
+ raise PrismInvalidParamError(
166
+ lib.PRISM_ERROR_INVALID_PARAM,
167
+ "Text MUST NOT be empty",
168
+ )
169
+ return _check_error(
170
+ lib.prism_backend_speak(self._raw, text.encode("utf-8"), interrupt),
171
+ )
172
+
173
+ def speak_to_memory(self, text: str, on_audio_data) -> None:
174
+ if len(text) == 0:
175
+ raise PrismInvalidParamError(
176
+ lib.PRISM_ERROR_INVALID_PARAM,
177
+ "Text MUST NOT be empty",
178
+ )
179
+
180
+ @ffi.callback("void(void *, const float *, size_t, size_t, size_t)")
181
+ def audio_callback_shim(
182
+ _userdata, samples_ptr: int, count, channels, rate
183
+ ) -> None:
184
+ pcm_data = ffi.unpack(samples_ptr, count * channels)
185
+ on_audio_data(pcm_data, channels, rate)
186
+
187
+ self._active_callback = audio_callback_shim
188
+ return _check_error(
189
+ lib.prism_backend_speak_to_memory(
190
+ self._raw,
191
+ text.encode("utf-8"),
192
+ audio_callback_shim,
193
+ ffi.NULL,
194
+ ),
195
+ )
196
+
197
+ def braille(self, text: str) -> None:
198
+ if len(text) == 0:
199
+ raise PrismInvalidParamError(
200
+ lib.PRISM_ERROR_INVALID_PARAM,
201
+ "Text MUST NOT be empty",
202
+ )
203
+ return _check_error(lib.prism_backend_braille(self._raw, text.encode("utf-8")))
204
+
205
+ def output(self, text: str, interrupt: bool = False) -> None:
206
+ if len(text) == 0:
207
+ raise PrismInvalidParamError(
208
+ lib.PRISM_ERROR_INVALID_PARAM,
209
+ "Text MUST NOT be empty",
210
+ )
211
+ return _check_error(
212
+ lib.prism_backend_output(self._raw, text.encode("utf-8"), interrupt),
213
+ )
214
+
215
+ def stop(self) -> None:
216
+ return _check_error(lib.prism_backend_stop(self._raw))
217
+
218
+ def pause(self) -> None:
219
+ return _check_error(lib.prism_backend_pause(self._raw))
220
+
221
+ def resume(self) -> None:
222
+ return _check_error(lib.prism_backend_resume(self._raw))
223
+
224
+ @property
225
+ def speaking(self) -> bool:
226
+ p_speaking = ffi.new("bool*")
227
+ _check_error(lib.prism_backend_is_speaking(self._raw, p_speaking))
228
+ return p_speaking[0]
229
+
230
+ @property
231
+ def volume(self) -> float:
232
+ p_volume = ffi.new("float*")
233
+ _check_error(lib.prism_backend_get_volume(self._raw, p_volume))
234
+ return p_volume[0]
235
+
236
+ @volume.setter
237
+ def volume(self, volume: float) -> None:
238
+ return _check_error(lib.prism_backend_set_volume(self._raw, volume))
239
+
240
+ @property
241
+ def rate(self) -> float:
242
+ p_rate = ffi.new("float*")
243
+ _check_error(lib.prism_backend_get_rate(self._raw, p_rate))
244
+ return p_rate[0]
245
+
246
+ @rate.setter
247
+ def rate(self, rate: float) -> None:
248
+ return _check_error(lib.prism_backend_set_rate(self._raw, rate))
249
+
250
+ @property
251
+ def pitch(self) -> float:
252
+ p_pitch = ffi.new("float*")
253
+ _check_error(lib.prism_backend_get_pitch(self._raw, p_pitch))
254
+ return p_pitch[0]
255
+
256
+ @pitch.setter
257
+ def pitch(self, pitch: float) -> None:
258
+ return _check_error(lib.prism_backend_set_pitch(self._raw, pitch))
259
+
260
+ def refresh_voices(self) -> None:
261
+ return _check_error(lib.prism_backend_refresh_voices(self._raw))
262
+
263
+ @property
264
+ def voices_count(self) -> int:
265
+ out_count = ffi.new("size_t*")
266
+ _check_error(lib.prism_backend_count_voices(self._raw, out_count))
267
+ return out_count[0]
268
+
269
+ def get_voice_name(self, idx: int) -> str:
270
+ pp_name = ffi.new("char **")
271
+ _check_error(lib.prism_backend_get_voice_name(self._raw, idx, pp_name))
272
+ return ffi.string(pp_name[0]).decode("utf-8")
273
+
274
+ def get_voice_language(self, idx: int) -> str:
275
+ pp_lang = ffi.new("char **")
276
+ _check_error(lib.prism_backend_get_voice_language(self._raw, idx, pp_lang))
277
+ return ffi.string(pp_lang[0]).decode("utf-8")
278
+
279
+ @property
280
+ def voice(self) -> int:
281
+ out_voice_id = ffi.new("size_t*")
282
+ _check_error(lib.prism_backend_get_voice(self._raw, out_voice_id))
283
+ return out_voice_id[0]
284
+
285
+ @voice.setter
286
+ def voice(self, idx: int) -> None:
287
+ return _check_error(lib.prism_backend_set_voice(self._raw, idx))
288
+
289
+ @property
290
+ def channels(self) -> int:
291
+ out_channels = ffi.new("size_t*")
292
+ _check_error(lib.prism_backend_get_channels(self._raw, out_channels))
293
+ return out_channels[0]
294
+
295
+ @property
296
+ def sample_rate(self) -> int:
297
+ out_sample_rate = ffi.new("size_t*")
298
+ _check_error(lib.prism_backend_get_sample_rate(self._raw, out_sample_rate))
299
+ return out_sample_rate[0]
300
+
301
+ @property
302
+ def bit_depth(self) -> int:
303
+ out_bit_depth = ffi.new("size_t*")
304
+ _check_error(lib.prism_backend_get_bit_depth(self._raw, out_bit_depth))
305
+ return out_bit_depth[0]
306
+
307
+
308
+ class Context:
309
+ _ctx: lib.PrismContext = None
310
+
311
+ def __init__(self, hwnd: HWND | int | None = None) -> None:
312
+ config = lib.prism_config_init()
313
+ if hwnd is not None:
314
+ config.platform_data = ffi.cast("void*", int(hwnd))
315
+ self._ctx = lib.prism_init(ffi.new("PrismConfig *", config))
316
+ if self._ctx == ffi.NULL:
317
+ raise RuntimeError("Prism could not be initialized")
318
+
319
+ def __del__(self):
320
+ if hasattr(self, "_ctx") and self._ctx:
321
+ lib.prism_shutdown(self._ctx)
322
+ self._ctx = None
323
+
324
+ @property
325
+ def backends_count(self) -> int:
326
+ return lib.prism_registry_count(self._ctx)
327
+
328
+ def id_of(self, index_or_name: int | str) -> BackendId:
329
+ if isinstance(index_or_name, int):
330
+ res = lib.prism_registry_id_at(self._ctx, index_or_name)
331
+ elif isinstance(index_or_name, str):
332
+ res = lib.prism_registry_id(self._ctx, index_or_name.encode("utf-8"))
333
+ else:
334
+ raise TypeError("Expected int or string")
335
+ try:
336
+ return BackendId(res)
337
+ except ValueError as e:
338
+ raise ValueError(f"Prism returned unknown backend ID: {res:#x}") from e
339
+
340
+ def name_of(self, backend_id: BackendId) -> str:
341
+ c_ptr = lib.prism_registry_name(self._ctx, backend_id)
342
+ if c_ptr == ffi.NULL:
343
+ raise ValueError("Backend ID not found")
344
+ return ffi.string(c_ptr).decode("utf-8")
345
+
346
+ def priority_of(self, backend_id: BackendId) -> int:
347
+ return lib.prism_registry_priority(self._ctx, backend_id)
348
+
349
+ def exists(self, backend_id: BackendId) -> bool:
350
+ return bool(lib.prism_registry_exists(self._ctx, backend_id))
351
+
352
+ def create(self, backend_id: BackendId) -> Backend:
353
+ res = lib.prism_registry_create(self._ctx, int(backend_id))
354
+ if res == ffi.NULL:
355
+ raise ValueError("Invalid or unsupported backend")
356
+ return Backend(res)
357
+
358
+ def create_best(self) -> Backend:
359
+ res = lib.prism_registry_create_best(self._ctx)
360
+ if res == ffi.NULL:
361
+ raise ValueError("Invalid or unsupported backend")
362
+ return Backend(res)
363
+
364
+ def acquire(self, backend_id: BackendId) -> Backend:
365
+ res = lib.prism_registry_acquire(self._ctx, int(backend_id))
366
+ if res == ffi.NULL:
367
+ raise ValueError("Invalid or unsupported backend")
368
+ return Backend(res)
369
+
370
+ def acquire_best(self) -> Backend:
371
+ res = lib.prism_registry_acquire_best(self._ctx)
372
+ if res == ffi.NULL:
373
+ raise ValueError("Invalid or unsupported backend")
374
+ return Backend(res)
prism/lib.py ADDED
@@ -0,0 +1,230 @@
1
+ import contextlib
2
+ import importlib
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from cffi import FFI
8
+
9
+
10
+ def _find_native_dir() -> str:
11
+ local_path = Path(__file__).parent / "_native"
12
+ if local_path.exists() and any(local_path.iterdir()):
13
+ return local_path
14
+ relative_path = Path("prism") / "_native"
15
+ for path in sys.path:
16
+ if not path:
17
+ continue
18
+ candidate = Path(path) / relative_path
19
+ if (candidate / "prism.dll").exists():
20
+ return candidate.resolve()
21
+
22
+ return local_path
23
+
24
+
25
+ dll_home = _find_native_dir()
26
+ with contextlib.suppress(AttributeError):
27
+ os.add_dll_directory(str(dll_home))
28
+
29
+
30
+ def _is_android() -> bool:
31
+ with contextlib.suppress(ImportError):
32
+ if importlib.util.find_spec("", "android") is not None:
33
+ return True
34
+ if sys.platform == "linux":
35
+ with contextlib.suppress(FileNotFoundError), Path("/system/build.prop").open():
36
+ return True
37
+ return False
38
+
39
+
40
+ ffi = FFI()
41
+ if sys.platform == "win32" or _is_android():
42
+ ffi.cdef(r"""// SPDX-License-Identifier: MPL-2.0
43
+
44
+ typedef struct PrismContext PrismContext;
45
+ typedef struct PrismBackend PrismBackend;
46
+ typedef uint64_t PrismBackendId;
47
+ typedef struct {
48
+ uint8_t version;
49
+ void* platform_data;
50
+ } PrismConfig;
51
+
52
+ typedef enum PrismError {
53
+ PRISM_OK = 0,
54
+ PRISM_ERROR_NOT_INITIALIZED,
55
+ PRISM_ERROR_INVALID_PARAM,
56
+ PRISM_ERROR_NOT_IMPLEMENTED,
57
+ PRISM_ERROR_NO_VOICES,
58
+ PRISM_ERROR_VOICE_NOT_FOUND,
59
+ PRISM_ERROR_SPEAK_FAILURE,
60
+ PRISM_ERROR_MEMORY_FAILURE,
61
+ PRISM_ERROR_RANGE_OUT_OF_BOUNDS,
62
+ PRISM_ERROR_INTERNAL,
63
+ PRISM_ERROR_NOT_SPEAKING,
64
+ PRISM_ERROR_NOT_PAUSED,
65
+ PRISM_ERROR_ALREADY_PAUSED,
66
+ PRISM_ERROR_INVALID_UTF8,
67
+ PRISM_ERROR_INVALID_OPERATION,
68
+ PRISM_ERROR_ALREADY_INITIALIZED,
69
+ PRISM_ERROR_BACKEND_NOT_AVAILABLE,
70
+ PRISM_ERROR_UNKNOWN,
71
+ PRISM_ERROR_COUNT
72
+ } PrismError;
73
+
74
+ typedef void(PrismAudioCallback)(
75
+ void *userdata, const float *samples, size_t sample_count,
76
+ size_t channels, size_t sample_rate);
77
+
78
+ PrismConfig prism_config_init(void);
79
+ PrismContext *prism_init(PrismConfig* cfg);
80
+ void prism_shutdown(PrismContext *ctx);
81
+ size_t prism_registry_count(PrismContext *ctx);
82
+ PrismBackendId prism_registry_id_at(PrismContext *ctx, size_t index);
83
+ PrismBackendId prism_registry_id(PrismContext *ctx, const char *name);
84
+ const char *prism_registry_name(PrismContext *ctx, PrismBackendId id);
85
+ int prism_registry_priority(PrismContext *ctx, PrismBackendId id);
86
+ bool prism_registry_exists(PrismContext *ctx, PrismBackendId id);
87
+ PrismBackend *prism_registry_get(PrismContext *ctx, PrismBackendId id);
88
+ PrismBackend *prism_registry_create(PrismContext *ctx, PrismBackendId id);
89
+ PrismBackend *prism_registry_create_best(PrismContext *ctx);
90
+ PrismBackend *prism_registry_acquire(PrismContext *ctx, PrismBackendId id);
91
+ PrismBackend *prism_registry_acquire_best(PrismContext *ctx);
92
+ void prism_backend_free(PrismBackend *backend);
93
+ const char *prism_backend_name(PrismBackend *backend);
94
+ PrismError prism_backend_initialize(PrismBackend *backend);
95
+ PrismError prism_backend_speak(PrismBackend *backend, const char *text, bool interrupt);
96
+ PrismError prism_backend_speak_to_memory(
97
+ PrismBackend *backend,
98
+ const char *text,
99
+ PrismAudioCallback callback,
100
+ void *userdata
101
+ );
102
+ PrismError prism_backend_braille(PrismBackend *backend, const char *text);
103
+ PrismError prism_backend_output(PrismBackend *backend, const char *text, bool interrupt);
104
+ PrismError prism_backend_stop(PrismBackend *backend);
105
+ PrismError prism_backend_pause(PrismBackend *backend);
106
+ PrismError prism_backend_resume(PrismBackend *backend);
107
+ PrismError prism_backend_is_speaking(PrismBackend *backend, bool *out_speaking);
108
+ PrismError prism_backend_set_volume(PrismBackend *backend, float volume);
109
+ PrismError prism_backend_get_volume(PrismBackend *backend, float *out_volume);
110
+ PrismError prism_backend_set_rate(PrismBackend *backend, float rate);
111
+ PrismError prism_backend_get_rate(PrismBackend *backend, float *out_rate);
112
+ PrismError prism_backend_set_pitch(PrismBackend *backend, float pitch);
113
+ PrismError prism_backend_get_pitch(PrismBackend *backend, float *out_pitch);
114
+ PrismError prism_backend_refresh_voices(PrismBackend *backend);
115
+ PrismError prism_backend_count_voices(PrismBackend *backend, size_t *out_count);
116
+ PrismError prism_backend_get_voice_name(
117
+ PrismBackend *backend,
118
+ size_t voice_id,
119
+ const char **out_name
120
+ );
121
+ PrismError prism_backend_get_voice_language(
122
+ PrismBackend *backend,
123
+ size_t voice_id,
124
+ const char **out_language
125
+ );
126
+ PrismError prism_backend_set_voice(PrismBackend *backend, size_t voice_id);
127
+ PrismError prism_backend_get_voice(PrismBackend *backend, size_t *out_voice_id);
128
+ PrismError prism_backend_get_channels(PrismBackend *backend, size_t *out_channels);
129
+ PrismError prism_backend_get_sample_rate(PrismBackend *backend, size_t *out_sample_rate);
130
+ PrismError prism_backend_get_bit_depth(PrismBackend *backend, size_t *out_bit_depth);
131
+ const char *prism_error_string(PrismError error);
132
+ """)
133
+ else:
134
+ ffi.cdef(r"""// SPDX-License-Identifier: MPL-2.0
135
+
136
+ typedef struct PrismContext PrismContext;
137
+ typedef struct PrismBackend PrismBackend;
138
+ typedef uint64_t PrismBackendId;
139
+ typedef struct {
140
+ uint8_t version;
141
+ } PrismConfig;
142
+
143
+ typedef enum PrismError {
144
+ PRISM_OK = 0,
145
+ PRISM_ERROR_NOT_INITIALIZED,
146
+ PRISM_ERROR_INVALID_PARAM,
147
+ PRISM_ERROR_NOT_IMPLEMENTED,
148
+ PRISM_ERROR_NO_VOICES,
149
+ PRISM_ERROR_VOICE_NOT_FOUND,
150
+ PRISM_ERROR_SPEAK_FAILURE,
151
+ PRISM_ERROR_MEMORY_FAILURE,
152
+ PRISM_ERROR_RANGE_OUT_OF_BOUNDS,
153
+ PRISM_ERROR_INTERNAL,
154
+ PRISM_ERROR_NOT_SPEAKING,
155
+ PRISM_ERROR_NOT_PAUSED,
156
+ PRISM_ERROR_ALREADY_PAUSED,
157
+ PRISM_ERROR_INVALID_UTF8,
158
+ PRISM_ERROR_INVALID_OPERATION,
159
+ PRISM_ERROR_ALREADY_INITIALIZED,
160
+ PRISM_ERROR_BACKEND_NOT_AVAILABLE,
161
+ PRISM_ERROR_UNKNOWN,
162
+ PRISM_ERROR_COUNT
163
+ } PrismError;
164
+
165
+ typedef void(PrismAudioCallback)(
166
+ void *userdata, const float *samples, size_t sample_count,
167
+ size_t channels, size_t sample_rate);
168
+
169
+ PrismConfig prism_config_init(void);
170
+ PrismContext *prism_init(PrismConfig* cfg);
171
+ void prism_shutdown(PrismContext *ctx);
172
+ size_t prism_registry_count(PrismContext *ctx);
173
+ PrismBackendId prism_registry_id_at(PrismContext *ctx, size_t index);
174
+ PrismBackendId prism_registry_id(PrismContext *ctx, const char *name);
175
+ const char *prism_registry_name(PrismContext *ctx, PrismBackendId id);
176
+ int prism_registry_priority(PrismContext *ctx, PrismBackendId id);
177
+ bool prism_registry_exists(PrismContext *ctx, PrismBackendId id);
178
+ PrismBackend *prism_registry_get(PrismContext *ctx, PrismBackendId id);
179
+ PrismBackend *prism_registry_create(PrismContext *ctx, PrismBackendId id);
180
+ PrismBackend *prism_registry_create_best(PrismContext *ctx);
181
+ PrismBackend *prism_registry_acquire(PrismContext *ctx, PrismBackendId id);
182
+ PrismBackend *prism_registry_acquire_best(PrismContext *ctx);
183
+ void prism_backend_free(PrismBackend *backend);
184
+ const char *prism_backend_name(PrismBackend *backend);
185
+ PrismError prism_backend_initialize(PrismBackend *backend);
186
+ PrismError prism_backend_speak(PrismBackend *backend, const char *text, bool interrupt);
187
+ PrismError prism_backend_speak_to_memory(
188
+ PrismBackend *backend,
189
+ const char *text,
190
+ PrismAudioCallback callback,
191
+ void *userdata
192
+ );
193
+ PrismError prism_backend_braille(PrismBackend *backend, const char *text);
194
+ PrismError prism_backend_output(PrismBackend *backend, const char *text, bool interrupt);
195
+ PrismError prism_backend_stop(PrismBackend *backend);
196
+ PrismError prism_backend_pause(PrismBackend *backend);
197
+ PrismError prism_backend_resume(PrismBackend *backend);
198
+ PrismError prism_backend_is_speaking(PrismBackend *backend, bool *out_speaking);
199
+ PrismError prism_backend_set_volume(PrismBackend *backend, float volume);
200
+ PrismError prism_backend_get_volume(PrismBackend *backend, float *out_volume);
201
+ PrismError prism_backend_set_rate(PrismBackend *backend, float rate);
202
+ PrismError prism_backend_get_rate(PrismBackend *backend, float *out_rate);
203
+ PrismError prism_backend_set_pitch(PrismBackend *backend, float pitch);
204
+ PrismError prism_backend_get_pitch(PrismBackend *backend, float *out_pitch);
205
+ PrismError prism_backend_refresh_voices(PrismBackend *backend);
206
+ PrismError prism_backend_count_voices(PrismBackend *backend, size_t *out_count);
207
+ PrismError prism_backend_get_voice_name(
208
+ PrismBackend *backend,
209
+ size_t voice_id,
210
+ const char **out_name
211
+ );
212
+ PrismError prism_backend_get_voice_language(
213
+ PrismBackend *backend,
214
+ size_t voice_id,
215
+ const char **out_language
216
+ );
217
+ PrismError prism_backend_set_voice(PrismBackend *backend, size_t voice_id);
218
+ PrismError prism_backend_get_voice(PrismBackend *backend, size_t *out_voice_id);
219
+ PrismError prism_backend_get_channels(PrismBackend *backend, size_t *out_channels);
220
+ PrismError prism_backend_get_sample_rate(PrismBackend *backend, size_t *out_sample_rate);
221
+ PrismError prism_backend_get_bit_depth(PrismBackend *backend, size_t *out_bit_depth);
222
+ const char *prism_error_string(PrismError error);
223
+ """)
224
+ if sys.platform == "win32":
225
+ lib_path = (dll_home / "prism.dll").resolve()
226
+ elif sys.platform == "darwin":
227
+ lib_path = (dll_home / "libprism.dylib").resolve()
228
+ else:
229
+ lib_path = (dll_home / "libprism.so").resolve()
230
+ lib = ffi.dlopen(str(lib_path))
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: prismatoid
3
+ Version: 0.4.6
4
+ Summary: The Platform-Agnostic Reader Interface for Speech and Messages
5
+ Author-Email: Ethin Probst <ethindp@pm.me>
6
+ License-Expression: MPL-2.0
7
+ License-File: LICENSE
8
+ License-File: LICENSES/nvdaController/lgpl-2.1.txt
9
+ License-File: LICENSES/nvgt/LICENSE.md
10
+ License-File: LICENSES/prism/mpl-2.0.txt
11
+ License-File: LICENSES/simdutf/apache-2.0.txt
12
+ License-File: NOTICE
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: cffi>=1.0.0
15
+ Requires-Dist: win32more>=0.8.0; sys_platform == "win32"
16
+ Description-Content-Type: text/markdown
17
+
18
+ # PRISM
19
+
20
+ Prism is the Platform-agnostic Reader Interface for Speech and Messages. Since that's a hell of a mouthful, we just call it Prism for short. The name comes from prisms in optics, which are transparent optical components with flat surfaces that refract light into many beams. Thus, the metaphor: refract your TTS strings to send them to many different backends, potentially simultaneously.
21
+
22
+ Prism aims to unify the various screen reader abstraction libraries like SpeechCore, UniversalSpeech, SRAL, Tolk, etc., into a single unified system with a single unified API. Of course, we also support traditional TTS engines. I have tried to develop Prism in such a way that compilation is trivial and requires no external dependencies. To that end, the CMake builder will download all needed dependencies. However, since it uses [cpm.cmake](https://github.com/cpm-cmake/CPM.cmake), vendoring of dependencies is very possible.
23
+
24
+ ## Building
25
+
26
+ To build Prism, all you need do is create a build directory and run cmake as you ordinarily would. The only build option at this time is `PRISM_ENABLE_TESTS`, which will determine whether the test suite is built or not. In the 99 percent of cases, you should disable this option, since running the tests is only for validating that the library functions correctly. However, the tests have currently known problems and are still in a very early stage of development, so validation is platform-dependent and may fail in very strange ways on other platforms than Windows. (Specifically, failure is known to happen on at least MacOS, due to the header's various compiler attributes in use.)
27
+
28
+ ## Documentation
29
+
30
+ Documentation uses [mdbook](https://github.com/rust-lang/mdBook). To view it offline, install mdbook and then run `mdbook serve` from the doc directory.
31
+
32
+ ## API
33
+
34
+ The API is fully documented in the documentation above. If the documentation and header do not align in guarantees or expectations, this is a bug and should be reported.
35
+
36
+ ## Bindings
37
+
38
+ Currently bindings are an in-progress effort. We are working on bindings to at least Python and .NET, although both will take a bit of time to actually do. If you wish to contribute bindings, please don't hesitate to open a pull request. Your bindings must obey the restrictions in the documentation; specifically, where the documentation requires something to not occur, your bindings MUST prohibit that occurence before calling any Prism C API function. For example, calling `prism_backend_speak` with a `NULL` backend or text string MUST be prohibited by your bindings.
39
+
40
+ ## License
41
+
42
+ This project is licensed under the Mozilla Public License version 2.0. Full details are available in the LICENSE file.
43
+
44
+ This project uses code from other projects. Specifically:
45
+
46
+ * The SAPI bridge is credited to the [NVGT](https://github.com/samtupy/nvgt) project, as well as the functions `range_convert` and `range_convert_midpoint` in utils.h and utils.cpp. Similar attribution goes to NVGT for the Android screen reader backend.
47
+ * The `simdutf` library is licensed under the Apache-2.0 license.
48
+ * On Windows, Prism includes NVDA controller client RPC definitions under LGPL-2.1 (and generated RPC stubs from those inputs). Those portions remain under LGPL-2.1. See licenses/ and idl/ for the actual texts. On all non-Windows platforms, this does not apply, however the LGPL licenses are included for completeness in all SDKs.
49
+
50
+ ## Contributing
51
+
52
+ Contributions are welcome. This includes, but is not limited to, documentation enhancements, new backends, bindings, build system improvements, etc. The project uses C++23 so please ensure that your compiler supports that standard.
53
+
@@ -0,0 +1,22 @@
1
+ prism/__init__.py,sha256=hvOVuGBvJt49J93-RVCaqhoVp2a8u-Kuta1V2j3sa1s,127
2
+ prism/core.py,sha256=C8IZwU_Q27ZGY6U3nXrnTRF4p_Jt-yKABGwIGVssrXw,12136
3
+ prism/lib.py,sha256=GkVMQrCrJ_0fXx28aulL5v6wMW1YZ0-itVOsqLumA8U,9162
4
+ prism/_native/libprism.so,sha256=ABoeeKmIJIvxnB1c8oVESAyWEsMF5HpqZ2VlP8CdAac,688801
5
+ prismatoid.libs/libblkid-c32694e6.so.1.1.0,sha256=qUceZmREc6LLqAy3NX4Xr1m2TkPou2LrwO3h80h9afo,332649
6
+ prismatoid.libs/libgio-2-ddbf910d.0.so.0.6800.4,sha256=Adfeh8x1r2h8pdEI2BlEmYcN5AMAvvRRROyxUpZ43tE,2240081
7
+ prismatoid.libs/libgmodule-2-df046848.0.so.0.6800.4,sha256=7jIhAWGKPrTU2fPILs2yat03T6s44E5ztfqVH8VjJ6k,133161
8
+ prismatoid.libs/libltdl-bd763416.so.7,sha256=QovL-rlBiZ4bwSFIXqQpDHXnuld2WWcKheYqGflB3E4,69817
9
+ prismatoid.libs/libmount-59d3f1ae.so.1.1.0,sha256=umNW0PV7kWasfNjuXaM2FNr_rAxzbXEXIsqVGXncYlk,467177
10
+ prismatoid.libs/libpcre2-8-f8b08f65.so.0.11.0,sha256=WQDx7O4g59qf9QHcO0rDD9I-gL0kdHxOE3u5Bb6yHN8,658377
11
+ prismatoid.libs/libselinux-927a9592.so.1,sha256=fZsTd9rhjMSTFMVsSXJVt8X-08qxPPeQJpVWGUCs1vw,334553
12
+ prismatoid.libs/libspeechd-5ca159d0.so.2.6.0,sha256=My9p8dX5RmMiNwc9GeYZUgWpWKVhQ7uAal9dfBeGSQo,199265
13
+ prismatoid-0.4.6.dist-info/METADATA,sha256=08asMsHPHyLEVM5VfoN_HdEBXv8JGcXgimvi--e0qug,4227
14
+ prismatoid-0.4.6.dist-info/WHEEL,sha256=BKaVRDtiU8YK_JC8ujDiSh7QAp8YAN-QxTDTxhgJB8k,119
15
+ prismatoid-0.4.6.dist-info/RECORD,,
16
+ prismatoid-0.4.6.dist-info/licenses/LICENSE,sha256=bJjy9y1AlJwaGk73Q5A_oHcZ2pQdzPH7dHnRwV8Fq3o,15504
17
+ prismatoid-0.4.6.dist-info/licenses/NOTICE,sha256=v4o4GrCMWTkwIOfhO_SKDv44i_qA69vtbRf-z1xfn44,886
18
+ prismatoid-0.4.6.dist-info/licenses/LICENSES/nvdaController/lgpl-2.1.txt,sha256=2DCML-JO2IgZa8Uj30YhR1kAjYd-IlK0jM3ajiuO1Rs,25967
19
+ prismatoid-0.4.6.dist-info/licenses/LICENSES/nvgt/LICENSE.md,sha256=v_AyXYuvXEo6Ra4_2yZ5tOzk8TpmevvhIRozYbtjzQI,903
20
+ prismatoid-0.4.6.dist-info/licenses/LICENSES/prism/mpl-2.0.txt,sha256=ZsEFNaSV9M2BFWB-iQ-BFtZXBkuYVX9mDFHhI7Pz_uY,15190
21
+ prismatoid-0.4.6.dist-info/licenses/LICENSES/simdutf/apache-2.0.txt,sha256=wnT4A3LZDAEpNzcPDh8VCH0i4wjvmLJ86l3A0tCINmw,10279
22
+ prismatoid-0.4.6.dist-info/sboms/auditwheel.cdx.json,sha256=9uqbhJGDrQjAQHAOe5x3vPme7CaCCknnc-YJLUyO1oU,4112