batchalign 0.10.0__cp310-abi3-win_amd64.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 (146) hide show
  1. batchalign/BUILD.bazel +376 -0
  2. batchalign/README.md +140 -0
  3. batchalign/__init__.py +233 -0
  4. batchalign/__main__.py +6 -0
  5. batchalign/_core/BUILD.bazel +84 -0
  6. batchalign/_core/__init__.py +56 -0
  7. batchalign/_core/_core.pyd +0 -0
  8. batchalign/_core/_proto_generated.py +1099 -0
  9. batchalign/_core/proto.py +186 -0
  10. batchalign/_pytest_runner.py +29 -0
  11. batchalign/api.py +890 -0
  12. batchalign/backends/__init__.py +106 -0
  13. batchalign/backends/_qwen_lang.py +31 -0
  14. batchalign/backends/ai/__init__.py +7 -0
  15. batchalign/backends/ai/dspy.py +450 -0
  16. batchalign/backends/asr/__init__.py +35 -0
  17. batchalign/backends/asr/_torch_audio.py +70 -0
  18. batchalign/backends/asr/aliyun.py +136 -0
  19. batchalign/backends/asr/chatwhisper.py +921 -0
  20. batchalign/backends/asr/funasr.py +78 -0
  21. batchalign/backends/asr/funaudio.py +225 -0
  22. batchalign/backends/asr/google.py +359 -0
  23. batchalign/backends/asr/malayalam_wav2vec2.py +126 -0
  24. batchalign/backends/asr/openai_whisper.py +118 -0
  25. batchalign/backends/asr/qwen.py +163 -0
  26. batchalign/backends/asr/qwen3_asr.py +211 -0
  27. batchalign/backends/asr/rev.py +493 -0
  28. batchalign/backends/asr/tencent.py +217 -0
  29. batchalign/backends/asr/whisper.py +147 -0
  30. batchalign/backends/base.py +237 -0
  31. batchalign/backends/fa/__init__.py +13 -0
  32. batchalign/backends/fa/qwen3_fa.py +221 -0
  33. batchalign/backends/fa/wav2vec2.py +345 -0
  34. batchalign/backends/fa/whisper_fa.py +300 -0
  35. batchalign/backends/morphosyntax/__init__.py +7 -0
  36. batchalign/backends/morphosyntax/stanza.py +745 -0
  37. batchalign/backends/morphosyntax/ud/__init__.py +0 -0
  38. batchalign/backends/morphosyntax/ud/dp.py +277 -0
  39. batchalign/backends/morphosyntax/ud/en/__init__.py +0 -0
  40. batchalign/backends/morphosyntax/ud/en/irr.py +215 -0
  41. batchalign/backends/morphosyntax/ud/fr/__init__.py +0 -0
  42. batchalign/backends/morphosyntax/ud/fr/apm.py +15 -0
  43. batchalign/backends/morphosyntax/ud/fr/apmn.py +81 -0
  44. batchalign/backends/morphosyntax/ud/fr/case.py +33 -0
  45. batchalign/backends/morphosyntax/ud/it/__init__.py +5 -0
  46. batchalign/backends/morphosyntax/ud/it/workarounds.py +427 -0
  47. batchalign/backends/morphosyntax/ud/ja/__init__.py +0 -0
  48. batchalign/backends/morphosyntax/ud/ja/verbforms.py +147 -0
  49. batchalign/backends/morphosyntax/ud/lang.py +37 -0
  50. batchalign/backends/morphosyntax/ud/render.py +1373 -0
  51. batchalign/backends/morphosyntax/ud/tokenize.py +235 -0
  52. batchalign/backends/speaker/__init__.py +8 -0
  53. batchalign/backends/speaker/pyannote.py +182 -0
  54. batchalign/backends/speaker/pyannote_ai.py +376 -0
  55. batchalign/backends/translate/__init__.py +15 -0
  56. batchalign/backends/translate/aliyun.py +170 -0
  57. batchalign/backends/translate/google.py +222 -0
  58. batchalign/backends/translate/nllb.py +147 -0
  59. batchalign/backends/translate/tencent.py +167 -0
  60. batchalign/backends/utseg/__init__.py +9 -0
  61. batchalign/backends/utseg/cantonese.py +69 -0
  62. batchalign/backends/utseg/chatutterance.py +287 -0
  63. batchalign/backends/utseg/cleanup.py +101 -0
  64. batchalign/backends/utseg/malayalam_sat.py +73 -0
  65. batchalign/backends/utseg/support/filled_pauses.eng +5 -0
  66. batchalign/backends/utseg/support/replacements.eng +7 -0
  67. batchalign/base.py +75 -0
  68. batchalign/cli/__init__.py +233 -0
  69. batchalign/cli/_common.py +184 -0
  70. batchalign/cli/_logging.py +147 -0
  71. batchalign/cli/_options.py +61 -0
  72. batchalign/cli/ai.py +96 -0
  73. batchalign/cli/align.py +188 -0
  74. batchalign/cli/cache.py +160 -0
  75. batchalign/cli/compare.py +144 -0
  76. batchalign/cli/convert.py +123 -0
  77. batchalign/cli/daemon.py +225 -0
  78. batchalign/cli/diarize.py +94 -0
  79. batchalign/cli/hidden/coref.py +31 -0
  80. batchalign/cli/morphotag.py +132 -0
  81. batchalign/cli/transcribe.py +358 -0
  82. batchalign/cli/translate.py +86 -0
  83. batchalign/cli/tui/__init__.py +18 -0
  84. batchalign/cli/tui/bridge.py +97 -0
  85. batchalign/cli/tui/dashboard.py +654 -0
  86. batchalign/cli/tui/errors.py +380 -0
  87. batchalign/cli/tui/hints.py +50 -0
  88. batchalign/cli/tui/interface.py +740 -0
  89. batchalign/cli/tui/task.py +124 -0
  90. batchalign/cli/utseg.py +88 -0
  91. batchalign/cli/version.py +112 -0
  92. batchalign/config.py +544 -0
  93. batchalign/inputs.py +225 -0
  94. batchalign/lang.py +76 -0
  95. batchalign/recipes.py +222 -0
  96. batchalign/tests/__init__.py +0 -0
  97. batchalign/tests/conftest.py +91 -0
  98. batchalign/tests/fixture_paths.py +12 -0
  99. batchalign/tests/test_api_introspection.py +157 -0
  100. batchalign/tests/test_api_smoke.py +167 -0
  101. batchalign/tests/test_backends.py +381 -0
  102. batchalign/tests/test_ca_strip.py +34 -0
  103. batchalign/tests/test_cache_cli.py +60 -0
  104. batchalign/tests/test_cantonese_fa.py +53 -0
  105. batchalign/tests/test_chat_discovery.py +39 -0
  106. batchalign/tests/test_chatwhisper_device.py +99 -0
  107. batchalign/tests/test_chatwhisper_segment.py +76 -0
  108. batchalign/tests/test_cli.py +580 -0
  109. batchalign/tests/test_config.py +121 -0
  110. batchalign/tests/test_convert_cli.py +43 -0
  111. batchalign/tests/test_daemon_e2e.py +80 -0
  112. batchalign/tests/test_daemon_port.py +107 -0
  113. batchalign/tests/test_diarize_cli.py +160 -0
  114. batchalign/tests/test_dp_shim.py +61 -0
  115. batchalign/tests/test_golden_fixtures.py +217 -0
  116. batchalign/tests/test_google_asr.py +154 -0
  117. batchalign/tests/test_lang.py +83 -0
  118. batchalign/tests/test_malayalam_sat.py +70 -0
  119. batchalign/tests/test_malayalam_wav2vec2.py +98 -0
  120. batchalign/tests/test_morphotag_clear.py +84 -0
  121. batchalign/tests/test_morphotag_cli_failure.py +25 -0
  122. batchalign/tests/test_morphotag_render.py +819 -0
  123. batchalign/tests/test_morphotag_tokenize.py +86 -0
  124. batchalign/tests/test_proto.py +172 -0
  125. batchalign/tests/test_pyannote_ai.py +228 -0
  126. batchalign/tests/test_qwen3_contract.py +85 -0
  127. batchalign/tests/test_recipes.py +223 -0
  128. batchalign/tests/test_rev_presegmentation.py +76 -0
  129. batchalign/tests/test_revai_preflight.py +118 -0
  130. batchalign/tests/test_safe_resolve.py +60 -0
  131. batchalign/tests/test_sibling_media.py +59 -0
  132. batchalign/tests/test_stanza_manifest_refresh.py +80 -0
  133. batchalign/tests/test_stanza_pipeline_cache.py +335 -0
  134. batchalign/tests/test_tui_bridge.py +84 -0
  135. batchalign/tests/test_tui_dashboard.py +314 -0
  136. batchalign/tests/test_tui_interface.py +368 -0
  137. batchalign/tests/test_tui_parallel.py +552 -0
  138. batchalign/tests/test_tui_task.py +140 -0
  139. batchalign/tests/test_utseg_cleanup.py +81 -0
  140. batchalign/tests/test_utseg_sliding_window.py +60 -0
  141. batchalign/tests/test_utseg_typed_assignments.py +160 -0
  142. batchalign/tests/test_version_cmd.py +60 -0
  143. batchalign-0.10.0.dist-info/METADATA +268 -0
  144. batchalign-0.10.0.dist-info/RECORD +146 -0
  145. batchalign-0.10.0.dist-info/WHEEL +4 -0
  146. batchalign-0.10.0.dist-info/entry_points.txt +2 -0
batchalign/BUILD.bazel ADDED
@@ -0,0 +1,376 @@
1
+ """Bazel-native dev / test path for the batchalign Python package.
2
+
3
+ Runtime targets (no maturin, no uv at `bazel run` time):
4
+
5
+ bazel run //python/batchalign # CLI (py_binary)
6
+ bazel test //python/batchalign:tests # pytest (py_test)
7
+ bazel build //python/batchalign # py_binary + .so + py_library
8
+
9
+ The cdylib is built natively by `rust_shared_library` in
10
+ `//crates/batchalign/batchalign-engine` and lands at `_core.so`
11
+ alongside the Python sources in the runfiles tree.
12
+
13
+ Release-only (still maturin — sole reader of pyproject.toml's full
14
+ multi-platform tagging matrix + optional-deps groups):
15
+
16
+ bazel run //python/batchalign:wheel # build + install under
17
+ # python/target/wheels/<tag>.whl
18
+
19
+ The wheel command is one target end-to-end: it triggers the maturin
20
+ build (cached as `:_wheel_artifact`, also depended on by `:sidecar`)
21
+ and copies the result into `python/target/wheels/<tag>.whl` with the
22
+ PyPI-tagged basename. No multi-step incantations; no separate `bazel
23
+ build` + `cp` recipe for contributors or CI to memorize.
24
+
25
+ PyPI uploads are CI-only via trusted publishing
26
+ (`.github/workflows/publish-pypi.yml`). There is no local-publish
27
+ target — local twine uploads invite the wrong mental model and bypass
28
+ the OIDC + branch-protection guarantees that the CI flow provides.
29
+
30
+ `pyproject.toml` is the single source of truth for the wheel
31
+ (version, runtime deps, all `[project.optional-dependencies]`
32
+ groups, scripts, classifiers).
33
+ """
34
+
35
+ # Zero hand-listed deps. `all_runtime_deps()` returns every package in
36
+ # requirements.lock.txt, with rules_python's marker-conditional dangling
37
+ # names filtered out (see `//bazel/python:deps.bzl` for the why; the
38
+ # filter is a per-name list of *marker-excluded names from
39
+ # requirements.lock.txt*, not a list of deps to include).
40
+ load("//bazel/python:deps.bzl", "all_runtime_deps")
41
+ load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
42
+ load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
43
+
44
+ package(default_visibility = ["//visibility:public"])
45
+
46
+ _RUNTIME_DEPS = all_runtime_deps()
47
+
48
+ py_library(
49
+ name = "batchalign_lib",
50
+ # `_core/` is a Bazel subpackage (it owns the proto-codegen genrule),
51
+ # so glob stops there. We pull the `_core` sources back in by
52
+ # depending on its `:proto` py_library, which exports
53
+ # `batchalign._core.{__init__,proto,_proto_generated}` plus the
54
+ # staged `_core.so`.
55
+ srcs = glob(
56
+ ["**/*.py"],
57
+ exclude = ["tests/**"],
58
+ ),
59
+ # Non-Python data shipped with backends (e.g. BA2's disfluency /
60
+ # replacement word-lists `backends/utseg/support/*.eng` that the
61
+ # CHATUtterance segmenter reads at runtime). Without these in runfiles
62
+ # the lists load empty and `uh` never becomes `&-uh`.
63
+ data = glob(["**/support/*"]),
64
+ # `imports = [".."]` puts python/ on PYTHONPATH so `import batchalign`
65
+ # resolves. The package layout is python/batchalign/{__init__,...}.py.
66
+ imports = [".."],
67
+ deps = _RUNTIME_DEPS + ["//python/batchalign/_core:proto"],
68
+ )
69
+
70
+ # Default runnable target — `bazel run //python/batchalign` invokes
71
+ # `python -m batchalign` against the Bazel-built `.so`.
72
+ py_binary(
73
+ name = "batchalign",
74
+ srcs = ["__main__.py"],
75
+ main = "__main__.py",
76
+ deps = [":batchalign_lib"],
77
+ )
78
+
79
+ py_test(
80
+ name = "pytest",
81
+ srcs = glob(["tests/**/*.py"]) + ["_pytest_runner.py"],
82
+ main = "_pytest_runner.py",
83
+ # The tests form a package (`tests/__init__.py`). Pytest's default
84
+ # `prepend` mode resolves their source symlinks and inserts the checkout's
85
+ # `python/` directory ahead of Bazel's runfiles package. That is invisible
86
+ # on developer machines with a maturin-built `_core.abi3.so`, but clean CI
87
+ # then loses the staged `_core.so`. Importlib mode leaves Bazel's package
88
+ # root authoritative throughout collection.
89
+ args = ["python/batchalign/tests", "-q", "--import-mode=importlib"],
90
+ # Vendored parity fixtures used by `test_golden_fixtures.py`. Must
91
+ # be in runfiles for the test to find them — without this they were
92
+ # silently skipped on every CI run (the earlier copy lived in a
93
+ # developer-local `talkbank-alignment/` directory). Same for the
94
+ # Rust NUM2LANG table + the E316 spec, both probed by the hermetic
95
+ # Landing 3 #16/#17 guards.
96
+ data = [
97
+ "//resources/test_fixtures:parity_fixtures",
98
+ "//crates/batchalign/batchalign-core:asr_data",
99
+ "//resources/spec/errors:E316_angle_bracket_in_mor_stem.md",
100
+ ],
101
+ # all_requirements already contains pytest + pytest-xdist via the
102
+ # dev extra in pyproject; don't re-list them or Bazel errors on
103
+ # duplicate-deps.
104
+ deps = [":batchalign_lib"] + _RUNTIME_DEPS,
105
+ )
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # Maturin-driven wheel (release path).
109
+ # ---------------------------------------------------------------------------
110
+ # Two-layer shape:
111
+ #
112
+ # :_wheel_artifact genrule. Produces `batchalign.whl` (wheel
113
+ # bytes) + `wheel_tag.txt` (maturin's PyPI-
114
+ # tagged basename, e.g.
115
+ # `batchalign-0.7.1-cp312-cp312-macosx_11_0_arm64.whl`).
116
+ # Tagged `no-cache` because maturin reads
117
+ # `.rs` / `.py` / `pyproject.toml` from the
118
+ # source tree directly, not through Bazel
119
+ # inputs — letting Bazel always re-invoke
120
+ # maturin (cargo's incremental cache makes the
121
+ # no-op case sub-second) is cheaper than
122
+ # declaring every Rust crate target in `srcs`
123
+ # for invalidation and paying for a parallel
124
+ # rules_rust build of every crate maturin
125
+ # already compiles. Not user-facing — both
126
+ # `:sidecar` and `:wheel` consume it.
127
+ #
128
+ # :wheel sh_binary. `bazel run //python/batchalign:wheel`
129
+ # builds :_wheel_artifact, reads the tag, and
130
+ # copies the wheel into
131
+ # `python/target/wheels/<tag>.whl`. This is
132
+ # the single user-facing wheel command —
133
+ # contributors, the justfile, and CI all
134
+ # invoke it the same way. No
135
+ # memorized-shell-snippet alternative.
136
+ #
137
+ # Hermetic C toolchain. The toolchains_llvm-managed clang + ar +
138
+ # ranlib are passed as execpaths; the shell scripts resolve them and
139
+ # export CC/CXX/AR/RANLIB before invoking maturin. cc-rs inside
140
+ # cargo then picks up the pinned compiler instead of /usr/bin/cc,
141
+ # which decouples every `*-sys` crate build from the host
142
+ # CommandLineTools state. See MODULE.bazel's `toolchains_llvm` block
143
+ # for the cflag injection that papers over the macOS 26.x SDK
144
+ # `__kernel_ptr_semantics` regression.
145
+ #
146
+ # `$(COMPILATION_MODE)` is a Bazel make-variable that expands to
147
+ # `opt`, `dbg`, or `fastbuild` based on `-c <mode>`. The shell scripts
148
+ # translate that into maturin's `--release` / dev profile, so users
149
+ # never need PYAPP_PROFILE / MATURIN_PROFILE env vars — `bazel run
150
+ # -c opt //...` is the only knob.
151
+ #
152
+ # Historical note: this used to be two parallel targets (a
153
+ # `sh_binary :wheel` that ran maturin and dropped a wheel into the
154
+ # source tree, plus a `:wheel_file` genrule that ran maturin a second
155
+ # time for Bazel-tracked output). Editing a single .rs file forced
156
+ # two maturin runs. Collapsed: one genrule produces the wheel bytes;
157
+ # the sh_binary is now a thin copy-out that reuses that output. A
158
+ # follow-up pass also stripped the rules_rust crate targets from
159
+ # `srcs` — they were never consumed by maturin, only triggering a
160
+ # Bazel-side rust_library build that doubled the rustc cost on every
161
+ # `.rs` edit. The `no-cache` tag below preserves correctness without
162
+ # that cost.
163
+ #
164
+ # Caveat — the rules_rust build isn't fully eliminated. `srcs` pulls
165
+ # in `:_proto_generated_py`, which transitively depends on
166
+ # `//crates/batchalign/batchalign-core:proto_schema_json`. That genrule
167
+ # uses `:emit_proto_schema` (a rust_binary) as its tool, and
168
+ # `:emit_proto_schema` links `:batchalign_core` as a rust_library.
169
+ # Result: every wheel build first compiles `batchalign_core` + a chunk
170
+ # of its transitive crate-universe deps via rules_rust in the exec
171
+ # config (visible as `Compiling Rust rlib batchalign_core ... [for
172
+ # tool]`), then maturin rebuilds the same crate graph from scratch
173
+ # under cargo's target/. Two compiles of overlapping crates per cold
174
+ # wheel build. We accept this for now — cargo's incremental cache
175
+ # absorbs the hit on warm rebuilds, and the schema-emitter-as-tool
176
+ # pattern is what gives us regen-on-build for `_proto_generated.py`
177
+ # without a hand-checked-in artifact. If this cost ever bites, three
178
+ # escape hatches: (a) check in `proto.schema.json` with a CI diff
179
+ # guard, (b) factor proto types into a leaf `batchalign-proto` crate
180
+ # so `:emit_proto_schema` doesn't pull all of `batchalign_core`, or
181
+ # (c) move schema emission into maturin's build itself and ship the
182
+ # generated `.py` inside the wheel.
183
+ config_setting(
184
+ name = "windows",
185
+ constraint_values = ["@platforms//os:windows"],
186
+ )
187
+
188
+ _LLVM_TOOLS = select({
189
+ ":windows": [],
190
+ "//conditions:default": [
191
+ "//bazel/python:llvm_clang",
192
+ "//bazel/python:llvm_ar",
193
+ "//bazel/python:llvm_ranlib",
194
+ ],
195
+ })
196
+
197
+ genrule(
198
+ name = "_wheel_artifact",
199
+ outs = ["batchalign.whl", "wheel_tag.txt"],
200
+ srcs = [
201
+ # `_proto_generated.py` is the only input the genrule action
202
+ # actually *reads via Bazel runfiles* — maturin_build.sh stages
203
+ # it into the source tree before maturin packages. The `.rs`
204
+ # sources under `crates/` and `.py` sources under
205
+ # `python/batchalign/` are read directly via
206
+ # `BUILD_WORKSPACE_DIRECTORY`, NOT through Bazel inputs (see the
207
+ # `tags` comment below for why).
208
+ "//python/batchalign/_core:_proto_generated_py",
209
+ # `pyproject.toml` IS declared as a Bazel input. It owns the
210
+ # wheel version (`[project].version`), the runtime deps, the
211
+ # optional-dependencies extras, and the maturin config — every
212
+ # one of these changes the wheel's identity or contents. Without
213
+ # it in `srcs`, a version bump in pyproject.toml does not
214
+ # invalidate this action and `bazel run //python/batchalign:wheel`
215
+ # silently serves the previous wheel. The `tags = ["no-cache"]`
216
+ # below does NOT compensate for this: that tag disables result
217
+ # caching, not action skipping — Bazel's incremental analysis
218
+ # still skips when declared inputs are unchanged. Declaring a
219
+ # plain TOML file in `srcs` does not trigger any `rust_library`
220
+ # rebuild (that concern only applies to `.rs` files reached via
221
+ # rules_rust).
222
+ "//python:pyproject.toml",
223
+ ] + _LLVM_TOOLS,
224
+ tools = [
225
+ "//bazel/python:maturin_build.sh",
226
+ "//bazel/python:maturin_genrule.sh",
227
+ "//bazel/python:hermeticity_guard.sh",
228
+ "//bazel/python:runfiles_resolve.sh",
229
+ "@multitool//tools/uv",
230
+ "@rules_rust//rust/toolchain:current_cargo_files",
231
+ "@rules_rust//rust/toolchain:current_rustc_files",
232
+ ] + _LLVM_TOOLS,
233
+ cmd = " ".join([
234
+ "PYAPP_RUST_BIN_DIRS='$(execpaths @rules_rust//rust/toolchain:current_cargo_files) $(execpaths @rules_rust//rust/toolchain:current_rustc_files)'",
235
+ "$(execpath //bazel/python:maturin_genrule.sh)",
236
+ "$(location batchalign.whl)",
237
+ "$(location wheel_tag.txt)",
238
+ "$(execpath @multitool//tools/uv)",
239
+ "$(execpath //python/batchalign/_core:_proto_generated_py)",
240
+ "$(COMPILATION_MODE)",
241
+ ]) + select({
242
+ ":windows": " host",
243
+ "//conditions:default": " llvm " + " ".join([
244
+ "$(execpath //bazel/python:llvm_clang)",
245
+ "$(execpath //bazel/python:llvm_ar)",
246
+ "$(execpath //bazel/python:llvm_ranlib)",
247
+ ]),
248
+ }),
249
+ # `local = True`: maturin needs network access (cargo fetches) and a
250
+ # persistent target/ cache that a sandboxed action can't supply.
251
+ # uv, rustc, and cargo remain Bazel-managed. The C toolchain follows the
252
+ # platform contract in maturin_build.sh (LLVM, Xcode, or Visual Studio).
253
+ local = True,
254
+ # `no-cache`: disables caching of the action's outputs (local disk
255
+ # cache + remote cache). The `.rs` under `crates/` and `.py` under
256
+ # `python/batchalign/` are read from the source tree at run time,
257
+ # NOT through Bazel's declared input set — if we declared all of
258
+ # them in `srcs`, every `.rs` edit would force a parallel Bazel-side
259
+ # `rust_library` rebuild of every crate maturin already compiles
260
+ # via cargo (rustc twice per edit). Skipping the declaration keeps
261
+ # the GUI sidecar dev loop fast; cargo's incremental cache and
262
+ # maturin's "already up to date" check keep the no-op case
263
+ # sub-second.
264
+ #
265
+ # Caveat: `no-cache` does NOT force re-execution. Bazel's
266
+ # incremental analysis still skips this action when its declared
267
+ # input hashes are unchanged. That is why `pyproject.toml` is an
268
+ # explicit `srcs` entry — without it, a version bump silently
269
+ # serves the previously built wheel. Source-tree-resident `.rs` /
270
+ # `.py` edits also do not invalidate this action on their own;
271
+ # they're picked up when some declared input (e.g. the proto
272
+ # generator) changes or when the user forces a rebuild
273
+ # (`bazel clean`, deleting `bazel-bin/python/batchalign/*.whl`, or
274
+ # touching pyproject.toml). If pure `.rs`/`.py` invalidation
275
+ # becomes a real pain point, the next step is to declare those
276
+ # globs in `srcs` and accept the double-build cost, or move the
277
+ # whole wheel build out of a genrule into a `repository_rule` /
278
+ # external action with explicit input tracking.
279
+ tags = ["no-cache"],
280
+ )
281
+
282
+ # User-facing wheel command. `bazel run //python/batchalign:wheel`
283
+ # (and `just batchalign wheel`, and CI wheel jobs) all funnel through
284
+ # this one target. It reads the cached `:_wheel_artifact` outputs from
285
+ # runfiles and copies them into `python/target/wheels/<tag>.whl`
286
+ # inside the source tree. No second maturin run; no shell snippet
287
+ # contributors need to memorize.
288
+ sh_binary(
289
+ name = "wheel",
290
+ srcs = ["//bazel/python:install_wheel.sh"],
291
+ args = [
292
+ "$(rootpath :batchalign.whl)",
293
+ "$(rootpath :wheel_tag.txt)",
294
+ ],
295
+ data = [
296
+ ":batchalign.whl",
297
+ ":wheel_tag.txt",
298
+ ],
299
+ )
300
+
301
+ # Standalone production-daemon sidecar binary built via PyApp.
302
+ #
303
+ # `bazel build //python/batchalign:sidecar` produces a self-contained
304
+ # executable at `bazel-bin/python/batchalign/sidecar` that bundles
305
+ # CPython + the batchalign wheel + the `[api]` extra. The binary IS
306
+ # the daemon — running it boots `batchalign3 daemon` directly without
307
+ # the user typing the subcommand. Named "sidecar" because that's the
308
+ # role it plays in a deployment (companion service next to the main
309
+ # CLI / wheel install, not a separate top-level app).
310
+ #
311
+ # Architecture (all Bazel-managed, no host tool requirements beyond
312
+ # bazel + just):
313
+ # - Wheel input from `:batchalign.whl` (one of `:_wheel_artifact`'s
314
+ # outputs). Depending on the specific output rather than the
315
+ # genrule label keeps `wheel_tag.txt` out of the sidecar's input
316
+ # set (clean and explicit; not strictly required since the tag
317
+ # file is small and irrelevant to PyApp).
318
+ # - pyapp source from `@pyapp_src` (Bazel `git_repository`, pinned
319
+ # by SHA in MODULE.bazel; fetched lazily on first build).
320
+ # - cargo + rustc from `@rules_rust//rust/toolchain` (PATH-injected
321
+ # by `pyapp_install.sh` so the inner `cargo install` never sees
322
+ # host cargo).
323
+ #
324
+ # `local = True` is required: pyapp's build.rs writes into its source
325
+ # dir at compile time (an upstream design choice — see
326
+ # bazel/python/pyapp_install.sh for the writable-copy workaround).
327
+ # All other hermeticity holds.
328
+ genrule(
329
+ name = "sidecar",
330
+ outs = ["sidecar"],
331
+ srcs = [
332
+ ":batchalign.whl",
333
+ # pyapp source is fetched lazily via git_repository in
334
+ # MODULE.bazel — Bazel only clones when this target is
335
+ # actually built.
336
+ "@pyapp_src//:srcs",
337
+ "@pyapp_src//:Cargo.toml",
338
+ ],
339
+ tools = [
340
+ "//bazel/python:pyapp_install.sh",
341
+ # Bazel-provided cargo + rustc. PATH-injected by the script
342
+ # via PYAPP_RUST_BIN_DIRS, so the inner `cargo install`
343
+ # never touches host cargo.
344
+ "@rules_rust//rust/toolchain:current_cargo_files",
345
+ "@rules_rust//rust/toolchain:current_rustc_files",
346
+ ],
347
+ # `local = True` is required: pyapp's build.rs writes into its
348
+ # source dir (an upstream design choice); cargo install's tmpdir
349
+ # copy of the source satisfies that, but only if cargo can use a
350
+ # persistent build cache — which sandboxed actions can't supply.
351
+ # All other hermeticity holds: cargo + rustc from rules_rust,
352
+ # pyapp source from @pyapp_src (Bazel-fetched, pinned SHA), wheel
353
+ # from :batchalign.whl. No crates.io network, no host cargo.
354
+ cmd = " ".join([
355
+ "PYAPP_RUST_BIN_DIRS='$(execpaths @rules_rust//rust/toolchain:current_cargo_files) $(execpaths @rules_rust//rust/toolchain:current_rustc_files)'",
356
+ "$(execpath //bazel/python:pyapp_install.sh)",
357
+ "$@",
358
+ "$(execpath //python/batchalign:batchalign.whl)",
359
+ "$(execpath @pyapp_src//:Cargo.toml)",
360
+ "$(COMPILATION_MODE)",
361
+ ]),
362
+ local = True,
363
+ )
364
+
365
+ # Lint kept as a shell-out because mypy/ruff configs sit in the uv-managed
366
+ # venv. Could become a py_test rule later; for now sh_binary is enough.
367
+ sh_binary(
368
+ name = "lint",
369
+ srcs = ["//bazel/python:lint.sh"],
370
+ args = ["$(rootpath @multitool//tools/uv)"],
371
+ data = [
372
+ "@multitool//tools/uv",
373
+ "//bazel/python:hermeticity_guard.sh",
374
+ "//python:pyproject.toml",
375
+ ],
376
+ )
batchalign/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # batchalign
2
+
3
+ TalkBank CHAT processing pipeline — ASR, forced alignment, morphosyntax
4
+ (`%mor` / `%gra`), utterance segmentation, translation, and compare.
5
+ The user-facing Python package; the runtime is a PyO3 extension backed
6
+ by the Rust crates in `crates/batchalign/`.
7
+
8
+ User and developer docs live in `book/src/batchalign/` (the mdBook is
9
+ the source of truth). See `book/src/batchalign/developer/building.md`
10
+ for the canonical build recipe.
11
+
12
+ ## Install
13
+
14
+ From PyPI (stable wheels):
15
+
16
+ ```bash
17
+ pip install batchalign
18
+ ```
19
+
20
+ From source: **use the `just` recipes** — they go through Bazel, so
21
+ every dep (proto codegen, Rust crates, PyO3 extension, Python wheel
22
+ extras) is materialized for you. Don't reach for `maturin develop` or
23
+ `uv sync` directly unless you're debugging the build itself.
24
+
25
+ ```bash
26
+ just batchalign build # build every Bazel target for batchalign
27
+ just batchalign test # run every Bazel test target
28
+ just batchalign cli --help # run `batchalign3` via the development bridge
29
+ just batchalign pytest # pytest (with full Bazel dep graph)
30
+ just batchalign wheel # host-platform wheel at python/target/wheels/
31
+ just batchalign sidecar # standalone daemon binary via PyApp
32
+ just batchalign lint # mypy (+ ruff)
33
+ just batchalign versions # source-of-truth version readout
34
+ ```
35
+
36
+ `just --list batchalign` shows the full recipe list. The `just`
37
+ recipes call into Bazel, so `tools/bazel` (the bundled wrapper) takes
38
+ care of:
39
+
40
+ - regenerating the pydantic-v2 wire types from
41
+ `crates/batchalign/batchalign-core/src/proto/*.rs`
42
+ - rebuilding the `batchalign_core` PyO3 cdylib via maturin under the
43
+ hood
44
+ - staging the binary into the wheel
45
+ - propagating dependency changes to dependent targets
46
+
47
+ If you genuinely need `bazel` directly (because a recipe you want
48
+ isn't wrapped):
49
+
50
+ ```bash
51
+ bazel build //... # everything
52
+ bazel test //... # everything
53
+ bazel run //book:html # static book HTML
54
+ bazel run //apps/batchalign/batchalign-gui:openapi # GUI OpenAPI codegen
55
+ ```
56
+
57
+ The base wheel ships only the lightweight runtime. Heavy ML backends
58
+ are gated behind extras — install only what you use:
59
+
60
+ ```bash
61
+ pip install 'batchalign[whisper]' # Whisper ASR
62
+ pip install 'batchalign[malayalam]' # Malayalam Wav2Vec2 XLSR ASR
63
+ pip install 'batchalign[stanza]' # morphosyntax (%mor / %gra)
64
+ pip install 'batchalign[pyannote]' # optional local Pyannote backend API
65
+ pip install 'batchalign[revai]' # Rev.AI cloud ASR
66
+ pip install 'batchalign[google]' # Gemini cloud ASR + diarization
67
+ pip install 'batchalign[cantonese]' # Cantonese pipeline (FunASR, Tencent)
68
+ pip install 'batchalign[qwen3]' # Qwen3 ASR + forced aligner
69
+ pip install 'batchalign[nllb]' # NLLB translation
70
+ pip install 'batchalign[api]' # FastAPI daemon (`batchalign3 daemon`)
71
+ pip install 'batchalign[all]' # everything
72
+ ```
73
+
74
+ ## CLI
75
+
76
+ ```bash
77
+ batchalign3 --help
78
+
79
+ batchalign3 transcribe input_dir -o output_dir --lang eng
80
+ batchalign3 transcribe input_dir -o output_dir --lang eng --engine whisper --diarize --diarize-engine pyannote-ai
81
+ batchalign3 diarize input_dir -o output_dir --engine pyannote-ai
82
+ batchalign3 align input_dir -o output_dir --engine wav2vec
83
+ batchalign3 morphotag input_dir -o output_dir --language en
84
+ batchalign3 utseg input_dir -o output_dir
85
+ batchalign3 translate input_dir -o output_dir --target eng
86
+ batchalign3 compare input_dir gold_dir -o output_dir
87
+
88
+ batchalign3 version # banner, version, git SHA
89
+ batchalign3 cache {path,stats,clear} # local result cache
90
+ batchalign3 daemon # FastAPI server (needs [api])
91
+ ```
92
+
93
+ When `-o` is omitted, results are written back in place. The CLI accepts
94
+ either a single CHAT/media file or a folder (walked recursively).
95
+
96
+ The diarization CLI defaults to pyannoteAI cloud. Select the optional local
97
+ backend with `--engine pyannote` (or `--diarize-engine pyannote` on
98
+ `transcribe`). Configure the cloud API key in the shared Batchalign config file
99
+ (or set `BATCHALIGN_PYANNOTE_KEY`):
100
+
101
+ ```ini
102
+ [diarize]
103
+ engine.pyannote.key = YOUR_API_KEY
104
+ ```
105
+
106
+ ## Programmatic API
107
+
108
+ ```python
109
+ import batchalign as ba
110
+
111
+ # Build a backend chain.
112
+ pipeline = ba.recipes.morphotag(
113
+ stanza_backend=ba.StanzaBackend(lang="en"),
114
+ )
115
+
116
+ # Or compose your own.
117
+ asr = ba.WhisperBackend(language=ba.LanguageCode.from_iso("eng"))
118
+ utseg = ba.CHATUtteranceBackend(model="talkbank/CHATUtterance-en")
119
+ pipeline = ba.recipes.transcribe(asr_backend=asr, utseg_backend=utseg)
120
+
121
+ # Run.
122
+ inputs = [ba.media_from_path("session.wav")]
123
+ outcomes = list(pipeline.run(inputs))
124
+ for outcome in outcomes:
125
+ outcome.write("session.cha")
126
+ ```
127
+
128
+ ## Repository layout
129
+
130
+ This package is one slice of the `talkbank-tools` monorepo:
131
+
132
+ - `python/batchalign/` — Python package (this file).
133
+ - `crates/batchalign/` — Rust crates (`batchalign-core`, `batchalign-engine`).
134
+ - The pinned TalkBank Chatter dependency supplies CHAT parsing, models, and transforms.
135
+ - `apps/batchalign/batchalign-gui/` — Tauri desktop GUI.
136
+ - `book/` — user + developer documentation (mdBook; source of truth).
137
+
138
+ For repo conventions, build commands, and the BA3 cutover plan, see
139
+ `CLAUDE.md` at the repo root and
140
+ `book/src/batchalign/developer/landing-status.md`.