transcribe-cpp 0.3.1__tar.gz → 0.4.0__tar.gz

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 (36) hide show
  1. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/.gitignore +2 -0
  2. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/PKG-INFO +73 -6
  3. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/README.md +70 -3
  4. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/pyproject.toml +3 -3
  5. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/src/transcribe_cpp/__init__.py +909 -188
  6. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/src/transcribe_cpp/_generated.py +182 -7
  7. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/src/transcribe_cpp/errors.py +22 -0
  8. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/conftest.py +50 -0
  9. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_abi.py +2 -2
  10. transcribe_cpp-0.4.0/tests/test_compute_lock.py +671 -0
  11. transcribe_cpp-0.4.0/tests/test_diarize.py +83 -0
  12. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_errors.py +8 -0
  13. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_family_ext.py +10 -10
  14. transcribe_cpp-0.4.0/tests/test_langid.py +143 -0
  15. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_lifetime.py +15 -23
  16. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_prompting.py +1 -1
  17. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_streaming.py +76 -6
  18. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_transcribe.py +12 -0
  19. transcribe_cpp-0.4.0/tests/test_vad.py +42 -0
  20. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/uv.lock +3 -3
  21. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/LICENSE +0 -0
  22. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/_generate/README.md +0 -0
  23. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/_generate/check_version_sync.py +0 -0
  24. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/_generate/generate.py +0 -0
  25. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/examples/stream_wav.py +0 -0
  26. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/examples/transcribe_wav.py +0 -0
  27. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/src/transcribe_cpp/_abi.py +0 -0
  28. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/src/transcribe_cpp/_library.py +0 -0
  29. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/src/transcribe_cpp/py.typed +0 -0
  30. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_backends.py +0 -0
  31. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_device_select.py +0 -0
  32. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_example.py +0 -0
  33. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_pcm.py +0 -0
  34. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_provider_discovery.py +0 -0
  35. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_text_controls.py +0 -0
  36. {transcribe_cpp-0.3.1 → transcribe_cpp-0.4.0}/tests/test_utf8_paths.py +0 -0
@@ -59,6 +59,8 @@ __pycache__/
59
59
 
60
60
  # WER evaluation data + generated working reports.
61
61
  /samples/wer/
62
+ # FLEURS language ID corpus (scripts/langid/ingest.py).
63
+ /samples/langid/
62
64
  # Diarization eval corpora (AMI audio + fetched RTTMs); regenerable via
63
65
  # scripts/diar/ingest_ami.py + fetch_ami_forced_alignment.py. The committed
64
66
  # oracle case lives at samples/sortformer-2spk-mix.wav (outside samples/diar/).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: transcribe-cpp
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Python bindings for transcribe.cpp
5
5
  Project-URL: Homepage, https://github.com/handy-computer/transcribe.cpp
6
6
  Project-URL: Repository, https://github.com/handy-computer/transcribe.cpp
@@ -21,9 +21,9 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
22
22
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
23
  Requires-Python: >=3.9
24
- Requires-Dist: transcribe-cpp-native==0.3.1.*
24
+ Requires-Dist: transcribe-cpp-native==0.4.0.*
25
25
  Provides-Extra: cu12
26
- Requires-Dist: transcribe-cpp-native-cu12==0.3.1.*; extra == 'cu12'
26
+ Requires-Dist: transcribe-cpp-native-cu12==0.4.0.*; extra == 'cu12'
27
27
  Provides-Extra: test
28
28
  Requires-Dist: numpy; extra == 'test'
29
29
  Requires-Dist: pytest>=7; extra == 'test'
@@ -101,6 +101,72 @@ Long transcriptions can be cancelled from another thread with
101
101
  `session.cancel()` — the run raises `Aborted` with the partial transcript on
102
102
  `exc.partial_result` (same for `OutputTruncated`).
103
103
 
104
+ ### Diarization
105
+
106
+ Models whose `model.roles` include `Role.DIARIZE` (Sortformer) answer "who
107
+ spoke when" through a diarize session. `run()` returns `SpeakerSegment`
108
+ rows (`speaker_id` in `1..model.diarize_info.max_speakers`), grouped by
109
+ speaker. Locking, `Busy`, `cancel()` and `close()` work as on `Session`. A
110
+ model without the role raises `UnsupportedRole`, as `model.session()` and
111
+ `model.capabilities` do on a model without `Role.ASR` (Sortformer serves only
112
+ `Role.DIARIZE`).
113
+
114
+ ```python
115
+ with model.diarize_session() as diarizer:
116
+ turns = diarizer.run(pcm, family=transcribe_cpp.SortformerDiarizeOptions(
117
+ preset="very_high_latency"))
118
+ for turn in turns:
119
+ print(turn.speaker_id, turn.t0_ms, turn.t1_ms)
120
+ ```
121
+
122
+ ### Language ID
123
+
124
+ Models whose `model.roles` include `Role.LANGID` (VoxLingua107 ECAPA-TDNN)
125
+ identify the spoken language through a langid session. `run()` returns a
126
+ `LangIdResult` with candidates ranked by `p`. Codes are the model's own labels
127
+ (`"iw"`, `"jw"`; `model.langid_label_index("he")` resolves aliases), so match
128
+ `result.code` against the ASR model's `capabilities.languages` yourself.
129
+ `allowed=None` scores every label; `allowed_mass` is the unrestricted
130
+ probability the allowed set captured. An empty `allowed` list raises
131
+ `InvalidArgument`; clips under `model.langid_info.min_audio_ms` raise
132
+ `InputTooShort`, and longer ones than `max_audio_ms` are scored on their first
133
+ `max_audio_ms`. Every allowed label comes back as a candidate. Locking,
134
+ `Busy`, `cancel()` and `close()` work as on `Session`.
135
+
136
+ ```python
137
+ with model.langid_session() as lid:
138
+ result = lid.run(pcm, allowed=["en", "de", "fr"])
139
+ print(result.code, result.candidates[0].p, result.allowed_mass)
140
+ ```
141
+
142
+ ### Voice activity detection
143
+
144
+ Models whose `model.roles` include `Role.VAD` (Silero VAD, GGUF or the
145
+ whisper.cpp `.bin`) score speech probability per frame of
146
+ `model.vad_info.frame_samples` samples through a VAD session. `run()` scores one
147
+ clip and returns a `VadResult` with `probs` and speech `segments`
148
+ (`start_sample`/`end_sample`; options default to Silero's
149
+ `get_speech_timestamps`). `stream_feed()` streams audio of any chunk size and
150
+ returns the probabilities of the frames each call completed (`first_frame` is
151
+ the position of the first); `stream_flush()` scores the zero-padded remainder
152
+ and ends the stream, `stream_reset()` drops it. The VAD stream does not take
153
+ the model's ASR stream lease. Locking, `Busy`, `cancel()` and `close()` work as
154
+ on `Session`.
155
+
156
+ ```python
157
+ with model.vad_session() as vad:
158
+ for seg in vad.run(pcm, min_silence_ms=300).segments:
159
+ print(seg.start_sample, seg.end_sample)
160
+
161
+ frame = model.vad_info.frame_samples
162
+ probs = []
163
+ for chunk in chunks:
164
+ r = vad.stream_feed(chunk)
165
+ print(f"{len(r.probs)} frames from sample {r.first_frame * frame}")
166
+ probs.extend(r.probs)
167
+ probs.extend(vad.stream_flush().probs)
168
+ ```
169
+
104
170
  ## Backends
105
171
 
106
172
  `Model(backend=...)` applies a backend policy (`"auto"` uses the best
@@ -151,9 +217,10 @@ TRANSCRIBE_LIBRARY=../../build-shared/src/libtranscribe.dylib \
151
217
 
152
218
  ## Notes
153
219
 
154
- - One run/stream at a time per `Model` in 0.x: sessions share the model's
155
- compute backend, so serialize runs across sessions (or load one model per
156
- worker). See the `Model` docstring.
220
+ - One compute call at a time per `Model`: the binding serializes calls across
221
+ all sessions of a model with a model-wide lock (load one model per worker
222
+ for parallelism). While a stream is active, other runs and stream begins on
223
+ that model raise `transcribe_cpp.Busy`. See the `Model` docstring.
157
224
  - Import package: `transcribe_cpp`
158
225
  - Distribution: `transcribe-cpp`
159
226
  - License: MIT
@@ -70,6 +70,72 @@ Long transcriptions can be cancelled from another thread with
70
70
  `session.cancel()` — the run raises `Aborted` with the partial transcript on
71
71
  `exc.partial_result` (same for `OutputTruncated`).
72
72
 
73
+ ### Diarization
74
+
75
+ Models whose `model.roles` include `Role.DIARIZE` (Sortformer) answer "who
76
+ spoke when" through a diarize session. `run()` returns `SpeakerSegment`
77
+ rows (`speaker_id` in `1..model.diarize_info.max_speakers`), grouped by
78
+ speaker. Locking, `Busy`, `cancel()` and `close()` work as on `Session`. A
79
+ model without the role raises `UnsupportedRole`, as `model.session()` and
80
+ `model.capabilities` do on a model without `Role.ASR` (Sortformer serves only
81
+ `Role.DIARIZE`).
82
+
83
+ ```python
84
+ with model.diarize_session() as diarizer:
85
+ turns = diarizer.run(pcm, family=transcribe_cpp.SortformerDiarizeOptions(
86
+ preset="very_high_latency"))
87
+ for turn in turns:
88
+ print(turn.speaker_id, turn.t0_ms, turn.t1_ms)
89
+ ```
90
+
91
+ ### Language ID
92
+
93
+ Models whose `model.roles` include `Role.LANGID` (VoxLingua107 ECAPA-TDNN)
94
+ identify the spoken language through a langid session. `run()` returns a
95
+ `LangIdResult` with candidates ranked by `p`. Codes are the model's own labels
96
+ (`"iw"`, `"jw"`; `model.langid_label_index("he")` resolves aliases), so match
97
+ `result.code` against the ASR model's `capabilities.languages` yourself.
98
+ `allowed=None` scores every label; `allowed_mass` is the unrestricted
99
+ probability the allowed set captured. An empty `allowed` list raises
100
+ `InvalidArgument`; clips under `model.langid_info.min_audio_ms` raise
101
+ `InputTooShort`, and longer ones than `max_audio_ms` are scored on their first
102
+ `max_audio_ms`. Every allowed label comes back as a candidate. Locking,
103
+ `Busy`, `cancel()` and `close()` work as on `Session`.
104
+
105
+ ```python
106
+ with model.langid_session() as lid:
107
+ result = lid.run(pcm, allowed=["en", "de", "fr"])
108
+ print(result.code, result.candidates[0].p, result.allowed_mass)
109
+ ```
110
+
111
+ ### Voice activity detection
112
+
113
+ Models whose `model.roles` include `Role.VAD` (Silero VAD, GGUF or the
114
+ whisper.cpp `.bin`) score speech probability per frame of
115
+ `model.vad_info.frame_samples` samples through a VAD session. `run()` scores one
116
+ clip and returns a `VadResult` with `probs` and speech `segments`
117
+ (`start_sample`/`end_sample`; options default to Silero's
118
+ `get_speech_timestamps`). `stream_feed()` streams audio of any chunk size and
119
+ returns the probabilities of the frames each call completed (`first_frame` is
120
+ the position of the first); `stream_flush()` scores the zero-padded remainder
121
+ and ends the stream, `stream_reset()` drops it. The VAD stream does not take
122
+ the model's ASR stream lease. Locking, `Busy`, `cancel()` and `close()` work as
123
+ on `Session`.
124
+
125
+ ```python
126
+ with model.vad_session() as vad:
127
+ for seg in vad.run(pcm, min_silence_ms=300).segments:
128
+ print(seg.start_sample, seg.end_sample)
129
+
130
+ frame = model.vad_info.frame_samples
131
+ probs = []
132
+ for chunk in chunks:
133
+ r = vad.stream_feed(chunk)
134
+ print(f"{len(r.probs)} frames from sample {r.first_frame * frame}")
135
+ probs.extend(r.probs)
136
+ probs.extend(vad.stream_flush().probs)
137
+ ```
138
+
73
139
  ## Backends
74
140
 
75
141
  `Model(backend=...)` applies a backend policy (`"auto"` uses the best
@@ -120,9 +186,10 @@ TRANSCRIBE_LIBRARY=../../build-shared/src/libtranscribe.dylib \
120
186
 
121
187
  ## Notes
122
188
 
123
- - One run/stream at a time per `Model` in 0.x: sessions share the model's
124
- compute backend, so serialize runs across sessions (or load one model per
125
- worker). See the `Model` docstring.
189
+ - One compute call at a time per `Model`: the binding serializes calls across
190
+ all sessions of a model with a model-wide lock (load one model per worker
191
+ for parallelism). While a stream is active, other runs and stream begins on
192
+ that model raise `transcribe_cpp.Busy`. See the `Model` docstring.
126
193
  - Import package: `transcribe_cpp`
127
194
  - Distribution: `transcribe-cpp`
128
195
  - License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "transcribe-cpp"
7
- version = "0.3.1"
7
+ version = "0.4.0"
8
8
  description = "Python bindings for transcribe.cpp"
9
9
  readme = "README.md"
10
10
  # 3.8 is EOL (2024-10); 3.9 is the floor. The binding is ctypes-only, so there
@@ -34,13 +34,13 @@ classifiers = [
34
34
  # packaging fix still resolves); the import-time version/header-hash check in
35
35
  # _library.py is the runtime backstop. check_version_sync.py gates this pin
36
36
  # against include/transcribe.h.
37
- dependencies = ["transcribe-cpp-native==0.3.1.*"]
37
+ dependencies = ["transcribe-cpp-native==0.4.0.*"]
38
38
 
39
39
  [project.optional-dependencies]
40
40
  # Opt-in accelerator providers — ADDITIVE: they install alongside the default
41
41
  # provider and the best one wins at runtime. Same base-version pin contract
42
42
  # as the hard dependency (gated by check_version_sync.py).
43
- cu12 = ["transcribe-cpp-native-cu12==0.3.1.*"]
43
+ cu12 = ["transcribe-cpp-native-cu12==0.4.0.*"]
44
44
  # Test-only deps. Run with: uv run --extra test pytest (from bindings/python).
45
45
  # numpy is here so the numpy PCM-input tests run in every lane instead of
46
46
  # silently skipping wherever numpy happens to be absent.