tapscribe 1.1.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 (282) hide show
  1. tapscribe-1.1.0/LICENSE +21 -0
  2. tapscribe-1.1.0/PKG-INFO +399 -0
  3. tapscribe-1.1.0/README.md +297 -0
  4. tapscribe-1.1.0/pyproject.toml +295 -0
  5. tapscribe-1.1.0/setup.cfg +4 -0
  6. tapscribe-1.1.0/tapscribe/__init__.py +28 -0
  7. tapscribe-1.1.0/tapscribe/__main__.py +319 -0
  8. tapscribe-1.1.0/tapscribe/app.py +2197 -0
  9. tapscribe-1.1.0/tapscribe/audio.py +228 -0
  10. tapscribe-1.1.0/tapscribe/auth.py +148 -0
  11. tapscribe-1.1.0/tapscribe/batch_pipeline.py +199 -0
  12. tapscribe-1.1.0/tapscribe/batch_strip.py +246 -0
  13. tapscribe-1.1.0/tapscribe/batch_summarize.py +164 -0
  14. tapscribe-1.1.0/tapscribe/batch_transcribe.py +515 -0
  15. tapscribe-1.1.0/tapscribe/bridges_catalog.py +37 -0
  16. tapscribe-1.1.0/tapscribe/chunking.py +76 -0
  17. tapscribe-1.1.0/tapscribe/config.py +256 -0
  18. tapscribe-1.1.0/tapscribe/config_store.py +451 -0
  19. tapscribe-1.1.0/tapscribe/cuda_torch.py +156 -0
  20. tapscribe-1.1.0/tapscribe/hallucinations.py +207 -0
  21. tapscribe-1.1.0/tapscribe/install_picker.py +1307 -0
  22. tapscribe-1.1.0/tapscribe/install_target.py +114 -0
  23. tapscribe-1.1.0/tapscribe/language_select.py +155 -0
  24. tapscribe-1.1.0/tapscribe/live.py +1043 -0
  25. tapscribe-1.1.0/tapscribe/live_control.py +209 -0
  26. tapscribe-1.1.0/tapscribe/live_relay.py +392 -0
  27. tapscribe-1.1.0/tapscribe/logging_setup.py +96 -0
  28. tapscribe-1.1.0/tapscribe/moonshine_live.py +603 -0
  29. tapscribe-1.1.0/tapscribe/name_resolution.py +238 -0
  30. tapscribe-1.1.0/tapscribe/nb_whisper.py +193 -0
  31. tapscribe-1.1.0/tapscribe/people.py +199 -0
  32. tapscribe-1.1.0/tapscribe/preflight.py +225 -0
  33. tapscribe-1.1.0/tapscribe/recorder.py +762 -0
  34. tapscribe-1.1.0/tapscribe/roster.py +120 -0
  35. tapscribe-1.1.0/tapscribe/runtime_probe.py +194 -0
  36. tapscribe-1.1.0/tapscribe/session_maintenance.py +528 -0
  37. tapscribe-1.1.0/tapscribe/session_merge.py +432 -0
  38. tapscribe-1.1.0/tapscribe/session_paths.py +199 -0
  39. tapscribe-1.1.0/tapscribe/sessions.py +908 -0
  40. tapscribe-1.1.0/tapscribe/setup_install.py +226 -0
  41. tapscribe-1.1.0/tapscribe/setup_state.py +144 -0
  42. tapscribe-1.1.0/tapscribe/speech_gate.py +336 -0
  43. tapscribe-1.1.0/tapscribe/strip_silence.py +255 -0
  44. tapscribe-1.1.0/tapscribe/summarizers/__init__.py +103 -0
  45. tapscribe-1.1.0/tapscribe/summarizers/api.py +147 -0
  46. tapscribe-1.1.0/tapscribe/summarizers/base.py +177 -0
  47. tapscribe-1.1.0/tapscribe/summarizers/catalog.py +413 -0
  48. tapscribe-1.1.0/tapscribe/summarizers/command.py +181 -0
  49. tapscribe-1.1.0/tapscribe/summarizers/local.py +234 -0
  50. tapscribe-1.1.0/tapscribe/tap_fan_out.py +498 -0
  51. tapscribe-1.1.0/tapscribe/tap_relay.py +338 -0
  52. tapscribe-1.1.0/tapscribe/text.py +199 -0
  53. tapscribe-1.1.0/tapscribe/tls.py +123 -0
  54. tapscribe-1.1.0/tapscribe/transcribers/__init__.py +448 -0
  55. tapscribe-1.1.0/tapscribe/transcribers/_chunked.py +164 -0
  56. tapscribe-1.1.0/tapscribe/transcribers/_moonshine_window.py +327 -0
  57. tapscribe-1.1.0/tapscribe/transcribers/_parakeet_tdt.py +103 -0
  58. tapscribe-1.1.0/tapscribe/transcribers/_voxtral_common.py +208 -0
  59. tapscribe-1.1.0/tapscribe/transcribers/base.py +292 -0
  60. tapscribe-1.1.0/tapscribe/transcribers/catalog.py +862 -0
  61. tapscribe-1.1.0/tapscribe/transcribers/faster_whisper.py +177 -0
  62. tapscribe-1.1.0/tapscribe/transcribers/mlx_parakeet.py +217 -0
  63. tapscribe-1.1.0/tapscribe/transcribers/mlx_voxtral.py +114 -0
  64. tapscribe-1.1.0/tapscribe/transcribers/mlx_whisper.py +173 -0
  65. tapscribe-1.1.0/tapscribe/transcribers/moonshine_mlx.py +68 -0
  66. tapscribe-1.1.0/tapscribe/transcribers/moonshine_onnx.py +80 -0
  67. tapscribe-1.1.0/tapscribe/transcribers/parakeet.py +172 -0
  68. tapscribe-1.1.0/tapscribe/transcribers/voxtral.py +137 -0
  69. tapscribe-1.1.0/tapscribe/wav_append.py +114 -0
  70. tapscribe-1.1.0/tapscribe/wav_cache.py +599 -0
  71. tapscribe-1.1.0/tapscribe/wav_predecode.py +62 -0
  72. tapscribe-1.1.0/tapscribe/web/components/active-taps.html +64 -0
  73. tapscribe-1.1.0/tapscribe/web/components/config-card.html +35 -0
  74. tapscribe-1.1.0/tapscribe/web/components/live-channel.html +108 -0
  75. tapscribe-1.1.0/tapscribe/web/components/live-feed.html +36 -0
  76. tapscribe-1.1.0/tapscribe/web/components/merged-transcript.html +73 -0
  77. tapscribe-1.1.0/tapscribe/web/components/next/people.html +49 -0
  78. tapscribe-1.1.0/tapscribe/web/components/next/recordings.html +185 -0
  79. tapscribe-1.1.0/tapscribe/web/components/next/sessions.html +138 -0
  80. tapscribe-1.1.0/tapscribe/web/components/next/spine.html +60 -0
  81. tapscribe-1.1.0/tapscribe/web/components/next/summary.html +139 -0
  82. tapscribe-1.1.0/tapscribe/web/components/next/taps.html +103 -0
  83. tapscribe-1.1.0/tapscribe/web/components/next/views.html +475 -0
  84. tapscribe-1.1.0/tapscribe/web/dashboard.css +705 -0
  85. tapscribe-1.1.0/tapscribe/web/js/api.js +480 -0
  86. tapscribe-1.1.0/tapscribe/web/js/api.test.js +261 -0
  87. tapscribe-1.1.0/tapscribe/web/js/components/active-taps.js +232 -0
  88. tapscribe-1.1.0/tapscribe/web/js/components/active-taps.test.js +55 -0
  89. tapscribe-1.1.0/tapscribe/web/js/components/config-card.js +178 -0
  90. tapscribe-1.1.0/tapscribe/web/js/components/live-channel.js +322 -0
  91. tapscribe-1.1.0/tapscribe/web/js/components/live-feed.js +274 -0
  92. tapscribe-1.1.0/tapscribe/web/js/components/live-feed.test.js +167 -0
  93. tapscribe-1.1.0/tapscribe/web/js/components/merged-transcript.js +229 -0
  94. tapscribe-1.1.0/tapscribe/web/js/formatters.js +82 -0
  95. tapscribe-1.1.0/tapscribe/web/js/lib/chrome.js +75 -0
  96. tapscribe-1.1.0/tapscribe/web/js/lib/format.js +141 -0
  97. tapscribe-1.1.0/tapscribe/web/js/lib/render.js +268 -0
  98. tapscribe-1.1.0/tapscribe/web/js/lib/templates.js +198 -0
  99. tapscribe-1.1.0/tapscribe/web/js/model-select.js +121 -0
  100. tapscribe-1.1.0/tapscribe/web/js/model-select.test.js +77 -0
  101. tapscribe-1.1.0/tapscribe/web/js/next/components/engine.js +102 -0
  102. tapscribe-1.1.0/tapscribe/web/js/next/components/language-picker.js +38 -0
  103. tapscribe-1.1.0/tapscribe/web/js/next/components/spine.js +381 -0
  104. tapscribe-1.1.0/tapscribe/web/js/next/components/spine.test.js +70 -0
  105. tapscribe-1.1.0/tapscribe/web/js/next/components/summarizer-controls.js +259 -0
  106. tapscribe-1.1.0/tapscribe/web/js/next/components/waveform.js +251 -0
  107. tapscribe-1.1.0/tapscribe/web/js/next/components/waveform.test.js +28 -0
  108. tapscribe-1.1.0/tapscribe/web/js/next/main.js +713 -0
  109. tapscribe-1.1.0/tapscribe/web/js/next/poll-pacer.js +46 -0
  110. tapscribe-1.1.0/tapscribe/web/js/next/poll-pacer.test.js +78 -0
  111. tapscribe-1.1.0/tapscribe/web/js/next/shell.js +326 -0
  112. tapscribe-1.1.0/tapscribe/web/js/next/shell.test.js +100 -0
  113. tapscribe-1.1.0/tapscribe/web/js/next/subtitles.js +50 -0
  114. tapscribe-1.1.0/tapscribe/web/js/next/subtitles.test.js +54 -0
  115. tapscribe-1.1.0/tapscribe/web/js/next/ui.js +77 -0
  116. tapscribe-1.1.0/tapscribe/web/js/next/views/capture.js +216 -0
  117. tapscribe-1.1.0/tapscribe/web/js/next/views/people.js +245 -0
  118. tapscribe-1.1.0/tapscribe/web/js/next/views/recordings.js +892 -0
  119. tapscribe-1.1.0/tapscribe/web/js/next/views/sessions.js +727 -0
  120. tapscribe-1.1.0/tapscribe/web/js/next/views/settings.js +402 -0
  121. tapscribe-1.1.0/tapscribe/web/js/next/views/summary.js +490 -0
  122. tapscribe-1.1.0/tapscribe/web/js/next/views/taps.js +96 -0
  123. tapscribe-1.1.0/tapscribe/web/js/next/views/transcript.js +798 -0
  124. tapscribe-1.1.0/tapscribe/web/js/next/views/transcript.test.js +76 -0
  125. tapscribe-1.1.0/tapscribe/web/js/setup/setup.css +164 -0
  126. tapscribe-1.1.0/tapscribe/web/js/setup/setup.js +253 -0
  127. tapscribe-1.1.0/tapscribe/web/js/speakers.js +26 -0
  128. tapscribe-1.1.0/tapscribe/web/js/templates.js +288 -0
  129. tapscribe-1.1.0/tapscribe/web/js/types.d.ts +637 -0
  130. tapscribe-1.1.0/tapscribe/web/js/vc/components/alert/alert.css +32 -0
  131. tapscribe-1.1.0/tapscribe/web/js/vc/components/alert/alert.element.js +14 -0
  132. tapscribe-1.1.0/tapscribe/web/js/vc/components/alert/alert.html +10 -0
  133. tapscribe-1.1.0/tapscribe/web/js/vc/components/alert/alert.js +61 -0
  134. tapscribe-1.1.0/tapscribe/web/js/vc/components/button/button.css +57 -0
  135. tapscribe-1.1.0/tapscribe/web/js/vc/components/button/button.element.js +17 -0
  136. tapscribe-1.1.0/tapscribe/web/js/vc/components/button/button.html +7 -0
  137. tapscribe-1.1.0/tapscribe/web/js/vc/components/button/button.js +81 -0
  138. tapscribe-1.1.0/tapscribe/web/js/vc/components/chip/chip.css +44 -0
  139. tapscribe-1.1.0/tapscribe/web/js/vc/components/chip/chip.element.js +15 -0
  140. tapscribe-1.1.0/tapscribe/web/js/vc/components/chip/chip.html +7 -0
  141. tapscribe-1.1.0/tapscribe/web/js/vc/components/chip/chip.js +27 -0
  142. tapscribe-1.1.0/tapscribe/web/js/vc/components/empty-state/empty-state.css +19 -0
  143. tapscribe-1.1.0/tapscribe/web/js/vc/components/empty-state/empty-state.element.js +10 -0
  144. tapscribe-1.1.0/tapscribe/web/js/vc/components/empty-state/empty-state.html +8 -0
  145. tapscribe-1.1.0/tapscribe/web/js/vc/components/empty-state/empty-state.js +28 -0
  146. tapscribe-1.1.0/tapscribe/web/js/vc/components/field/field.css +41 -0
  147. tapscribe-1.1.0/tapscribe/web/js/vc/components/field/field.html +8 -0
  148. tapscribe-1.1.0/tapscribe/web/js/vc/components/field/field.js +68 -0
  149. tapscribe-1.1.0/tapscribe/web/js/vc/components/panel/panel.css +47 -0
  150. tapscribe-1.1.0/tapscribe/web/js/vc/components/panel/panel.html +7 -0
  151. tapscribe-1.1.0/tapscribe/web/js/vc/components/panel/panel.js +58 -0
  152. tapscribe-1.1.0/tapscribe/web/js/vc/components/progress/progress.css +26 -0
  153. tapscribe-1.1.0/tapscribe/web/js/vc/components/progress/progress.element.js +12 -0
  154. tapscribe-1.1.0/tapscribe/web/js/vc/components/progress/progress.html +7 -0
  155. tapscribe-1.1.0/tapscribe/web/js/vc/components/progress/progress.js +32 -0
  156. tapscribe-1.1.0/tapscribe/web/js/vc/components/spinner/spinner.css +28 -0
  157. tapscribe-1.1.0/tapscribe/web/js/vc/components/spinner/spinner.element.js +11 -0
  158. tapscribe-1.1.0/tapscribe/web/js/vc/components/spinner/spinner.html +7 -0
  159. tapscribe-1.1.0/tapscribe/web/js/vc/components/spinner/spinner.js +27 -0
  160. tapscribe-1.1.0/tapscribe/web/js/vc/lib/component.js +42 -0
  161. tapscribe-1.1.0/tapscribe/web/js/vc/lib/element.js +113 -0
  162. tapscribe-1.1.0/tapscribe/web/js/vc/lib/templates.js +198 -0
  163. tapscribe-1.1.0/tapscribe/web/js/vc/lib/tone.js +48 -0
  164. tapscribe-1.1.0/tapscribe/web/next.css +1322 -0
  165. tapscribe-1.1.0/tapscribe/web/next.html +52 -0
  166. tapscribe-1.1.0/tapscribe/web/setup.html +29 -0
  167. tapscribe-1.1.0/tapscribe/web/tokens.css +55 -0
  168. tapscribe-1.1.0/tapscribe/web/tones.css +21 -0
  169. tapscribe-1.1.0/tapscribe.egg-info/PKG-INFO +399 -0
  170. tapscribe-1.1.0/tapscribe.egg-info/SOURCES.txt +280 -0
  171. tapscribe-1.1.0/tapscribe.egg-info/dependency_links.txt +1 -0
  172. tapscribe-1.1.0/tapscribe.egg-info/entry_points.txt +2 -0
  173. tapscribe-1.1.0/tapscribe.egg-info/requires.txt +96 -0
  174. tapscribe-1.1.0/tapscribe.egg-info/top_level.txt +1 -0
  175. tapscribe-1.1.0/tests/test_api_state_etag_quiet_tap.py +204 -0
  176. tapscribe-1.1.0/tests/test_app_bridges.py +92 -0
  177. tapscribe-1.1.0/tests/test_audio.py +291 -0
  178. tapscribe-1.1.0/tests/test_auth.py +93 -0
  179. tapscribe-1.1.0/tests/test_auth_non_ascii_credentials.py +110 -0
  180. tapscribe-1.1.0/tests/test_batch_pipeline.py +438 -0
  181. tapscribe-1.1.0/tests/test_batch_strip.py +117 -0
  182. tapscribe-1.1.0/tests/test_batch_summarize.py +373 -0
  183. tapscribe-1.1.0/tests/test_batch_transcribe.py +1211 -0
  184. tapscribe-1.1.0/tests/test_batch_transcribe_atomic_writes.py +137 -0
  185. tapscribe-1.1.0/tests/test_bulk_reclaim_audio.py +121 -0
  186. tapscribe-1.1.0/tests/test_bulk_reclaim_mid_walk_race.py +155 -0
  187. tapscribe-1.1.0/tests/test_chunking.py +80 -0
  188. tapscribe-1.1.0/tests/test_cli_flags.py +36 -0
  189. tapscribe-1.1.0/tests/test_concurrency_races.py +368 -0
  190. tapscribe-1.1.0/tests/test_config_env.py +99 -0
  191. tapscribe-1.1.0/tests/test_dashboard_pipeline_trigger.py +206 -0
  192. tapscribe-1.1.0/tests/test_domain_errors_fastapi_free.py +192 -0
  193. tapscribe-1.1.0/tests/test_ensure_cuda_torch.py +139 -0
  194. tapscribe-1.1.0/tests/test_hallucinations.py +249 -0
  195. tapscribe-1.1.0/tests/test_hallucinations_config_write.py +208 -0
  196. tapscribe-1.1.0/tests/test_idle_ttl_config.py +194 -0
  197. tapscribe-1.1.0/tests/test_imports.py +21 -0
  198. tapscribe-1.1.0/tests/test_install_matrix.py +42 -0
  199. tapscribe-1.1.0/tests/test_install_picker.py +1442 -0
  200. tapscribe-1.1.0/tests/test_install_picker_not_imported_by_app.py +111 -0
  201. tapscribe-1.1.0/tests/test_install_target.py +141 -0
  202. tapscribe-1.1.0/tests/test_language_select.py +181 -0
  203. tapscribe-1.1.0/tests/test_live_autostart_default_off.py +113 -0
  204. tapscribe-1.1.0/tests/test_live_cmd.py +525 -0
  205. tapscribe-1.1.0/tests/test_live_control.py +213 -0
  206. tapscribe-1.1.0/tests/test_live_gate_knob_apply.py +174 -0
  207. tapscribe-1.1.0/tests/test_live_gate_knob_no_restart.py +164 -0
  208. tapscribe-1.1.0/tests/test_live_matches_noop.py +178 -0
  209. tapscribe-1.1.0/tests/test_live_relay.py +736 -0
  210. tapscribe-1.1.0/tests/test_live_supervision.py +626 -0
  211. tapscribe-1.1.0/tests/test_local_test_bridge.py +104 -0
  212. tapscribe-1.1.0/tests/test_logging_setup.py +174 -0
  213. tapscribe-1.1.0/tests/test_main_cli.py +550 -0
  214. tapscribe-1.1.0/tests/test_moonshine_live.py +792 -0
  215. tapscribe-1.1.0/tests/test_moonshine_window.py +356 -0
  216. tapscribe-1.1.0/tests/test_name_resolution.py +232 -0
  217. tapscribe-1.1.0/tests/test_nb_whisper.py +65 -0
  218. tapscribe-1.1.0/tests/test_package_bridge.py +61 -0
  219. tapscribe-1.1.0/tests/test_parakeet_tdt.py +78 -0
  220. tapscribe-1.1.0/tests/test_people.py +145 -0
  221. tapscribe-1.1.0/tests/test_people_registry_memoized.py +134 -0
  222. tapscribe-1.1.0/tests/test_poll_caching.py +277 -0
  223. tapscribe-1.1.0/tests/test_poll_caching_session_sidecars.py +248 -0
  224. tapscribe-1.1.0/tests/test_preflight.py +159 -0
  225. tapscribe-1.1.0/tests/test_recorder.py +99 -0
  226. tapscribe-1.1.0/tests/test_recorder_subcomponents.py +484 -0
  227. tapscribe-1.1.0/tests/test_registry_single_source.py +185 -0
  228. tapscribe-1.1.0/tests/test_resolve_install_state.py +216 -0
  229. tapscribe-1.1.0/tests/test_roster.py +93 -0
  230. tapscribe-1.1.0/tests/test_routes.py +3441 -0
  231. tapscribe-1.1.0/tests/test_runtime_probe.py +105 -0
  232. tapscribe-1.1.0/tests/test_search_endpoint.py +235 -0
  233. tapscribe-1.1.0/tests/test_session_active_tap_guard.py +160 -0
  234. tapscribe-1.1.0/tests/test_session_layout.py +90 -0
  235. tapscribe-1.1.0/tests/test_session_maintenance_bulk_reclaim.py +231 -0
  236. tapscribe-1.1.0/tests/test_session_maintenance_fault_injection.py +175 -0
  237. tapscribe-1.1.0/tests/test_session_merge.py +280 -0
  238. tapscribe-1.1.0/tests/test_session_merge_selection.py +128 -0
  239. tapscribe-1.1.0/tests/test_session_merge_stripped_silence_gate.py +127 -0
  240. tapscribe-1.1.0/tests/test_session_paths_symlink_containment.py +189 -0
  241. tapscribe-1.1.0/tests/test_sessions_path_safety.py +92 -0
  242. tapscribe-1.1.0/tests/test_setup_install.py +281 -0
  243. tapscribe-1.1.0/tests/test_setup_state.py +191 -0
  244. tapscribe-1.1.0/tests/test_speech_gate.py +405 -0
  245. tapscribe-1.1.0/tests/test_strip_silence.py +178 -0
  246. tapscribe-1.1.0/tests/test_strip_silence_cli.py +88 -0
  247. tapscribe-1.1.0/tests/test_strip_silence_split.py +502 -0
  248. tapscribe-1.1.0/tests/test_summarizers.py +306 -0
  249. tapscribe-1.1.0/tests/test_summarizers_api.py +229 -0
  250. tapscribe-1.1.0/tests/test_summarizers_local.py +486 -0
  251. tapscribe-1.1.0/tests/test_tap_endpoint.py +1176 -0
  252. tapscribe-1.1.0/tests/test_tap_fan_out.py +1257 -0
  253. tapscribe-1.1.0/tests/test_tap_fan_out_chaos.py +265 -0
  254. tapscribe-1.1.0/tests/test_tap_fan_out_fault_injection.py +223 -0
  255. tapscribe-1.1.0/tests/test_tap_fan_out_lifecycle_safety.py +348 -0
  256. tapscribe-1.1.0/tests/test_tap_fan_out_probe_identity.py +195 -0
  257. tapscribe-1.1.0/tests/test_tap_fan_out_resume_append.py +155 -0
  258. tapscribe-1.1.0/tests/test_tap_relay.py +666 -0
  259. tapscribe-1.1.0/tests/test_text.py +468 -0
  260. tapscribe-1.1.0/tests/test_text_module_is_pure.py +184 -0
  261. tapscribe-1.1.0/tests/test_tls.py +71 -0
  262. tapscribe-1.1.0/tests/test_transcriber_lease.py +200 -0
  263. tapscribe-1.1.0/tests/test_transcribers_base.py +281 -0
  264. tapscribe-1.1.0/tests/test_transcribers_cache_eviction.py +209 -0
  265. tapscribe-1.1.0/tests/test_transcribers_catalog.py +724 -0
  266. tapscribe-1.1.0/tests/test_transcribers_factory.py +254 -0
  267. tapscribe-1.1.0/tests/test_transcribers_faster_whisper.py +223 -0
  268. tapscribe-1.1.0/tests/test_transcribers_mlx_moonshine.py +117 -0
  269. tapscribe-1.1.0/tests/test_transcribers_mlx_parakeet.py +448 -0
  270. tapscribe-1.1.0/tests/test_transcribers_mlx_voxtral.py +210 -0
  271. tapscribe-1.1.0/tests/test_transcribers_mlx_whisper.py +174 -0
  272. tapscribe-1.1.0/tests/test_transcribers_onnx_moonshine.py +121 -0
  273. tapscribe-1.1.0/tests/test_transcribers_parakeet.py +240 -0
  274. tapscribe-1.1.0/tests/test_transcribers_voxtral.py +278 -0
  275. tapscribe-1.1.0/tests/test_transcribers_voxtral_common.py +209 -0
  276. tapscribe-1.1.0/tests/test_version_consistency.py +32 -0
  277. tapscribe-1.1.0/tests/test_voxtral_text.py +93 -0
  278. tapscribe-1.1.0/tests/test_wav_append.py +132 -0
  279. tapscribe-1.1.0/tests/test_wav_cache.py +582 -0
  280. tapscribe-1.1.0/tests/test_wav_cache_atomic_writes.py +251 -0
  281. tapscribe-1.1.0/tests/test_wav_cache_inflight_append.py +114 -0
  282. tapscribe-1.1.0/tests/test_wav_predecode.py +52 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TapScribe contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,399 @@
1
+ Metadata-Version: 2.4
2
+ Name: tapscribe
3
+ Version: 1.1.0
4
+ Summary: Local-first transcription recorder + operator dashboard built on Whisper.
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 TapScribe contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Project-URL: Homepage, https://github.com/Vortiago/TapScribe
28
+ Project-URL: Repository, https://github.com/Vortiago/TapScribe
29
+ Project-URL: Issues, https://github.com/Vortiago/TapScribe/issues
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3 :: Only
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Programming Language :: Python :: 3.14
37
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
38
+ Requires-Python: >=3.12
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: fastapi>=0.110
42
+ Requires-Dist: uvicorn>=0.27
43
+ Requires-Dist: python-multipart>=0.0.9
44
+ Requires-Dist: numpy>=1.24
45
+ Requires-Dist: websockets>=12
46
+ Requires-Dist: cryptography>=42
47
+ Requires-Dist: silero-vad[onnx-cpu]>=5.0
48
+ Requires-Dist: torch>=2.1
49
+ Provides-Extra: whisper-live
50
+ Requires-Dist: whisperlivekit>=0.1; extra == "whisper-live"
51
+ Provides-Extra: whisper-cpu
52
+ Requires-Dist: faster-whisper>=1.0; extra == "whisper-cpu"
53
+ Provides-Extra: whisper-mlx
54
+ Requires-Dist: mlx-whisper>=0.4; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "whisper-mlx"
55
+ Provides-Extra: voxtral-cpu
56
+ Requires-Dist: transformers>=4.46; extra == "voxtral-cpu"
57
+ Requires-Dist: torch>=2.1; extra == "voxtral-cpu"
58
+ Requires-Dist: mistral-common>=1.5; extra == "voxtral-cpu"
59
+ Provides-Extra: parakeet-cpu
60
+ Requires-Dist: transformers<6,>=5.12; extra == "parakeet-cpu"
61
+ Requires-Dist: librosa>=0.10; extra == "parakeet-cpu"
62
+ Requires-Dist: torch>=2.1; extra == "parakeet-cpu"
63
+ Provides-Extra: parakeet-mlx
64
+ Requires-Dist: parakeet-mlx<0.6,>=0.5; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "parakeet-mlx"
65
+ Provides-Extra: moonshine-mlx
66
+ Requires-Dist: mlx-audio<0.5,>=0.4.1; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "moonshine-mlx"
67
+ Provides-Extra: moonshine-cpu
68
+ Requires-Dist: useful-moonshine-onnx<20270101,>=20251120; extra == "moonshine-cpu"
69
+ Provides-Extra: cuda-libs
70
+ Requires-Dist: nvidia-cublas-cu12; sys_platform != "darwin" and extra == "cuda-libs"
71
+ Requires-Dist: nvidia-cudnn-cu12; sys_platform != "darwin" and extra == "cuda-libs"
72
+ Provides-Extra: summarize
73
+ Requires-Dist: mlx-lm<0.33,>=0.31; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "summarize"
74
+ Requires-Dist: llama-cpp-python<0.4,>=0.3.26; (sys_platform != "darwin" or platform_machine != "arm64") and extra == "summarize"
75
+ Requires-Dist: huggingface-hub>=0.24; extra == "summarize"
76
+ Provides-Extra: whisper
77
+ Requires-Dist: tapscribe[whisper-cpu,whisper-live,whisper-mlx]; extra == "whisper"
78
+ Provides-Extra: mlx
79
+ Requires-Dist: tapscribe[whisper-mlx]; extra == "mlx"
80
+ Provides-Extra: voxtral
81
+ Requires-Dist: tapscribe[voxtral-cpu]; extra == "voxtral"
82
+ Provides-Extra: parakeet
83
+ Requires-Dist: tapscribe[parakeet-mlx]; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "parakeet"
84
+ Requires-Dist: tapscribe[parakeet-cpu]; (sys_platform != "darwin" or platform_machine != "arm64") and extra == "parakeet"
85
+ Provides-Extra: moonshine
86
+ Requires-Dist: tapscribe[moonshine-mlx]; (sys_platform == "darwin" and platform_machine == "arm64") and extra == "moonshine"
87
+ Requires-Dist: tapscribe[moonshine-cpu]; (sys_platform != "darwin" or platform_machine != "arm64") and extra == "moonshine"
88
+ Provides-Extra: dev
89
+ Requires-Dist: pytest>=8; extra == "dev"
90
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
91
+ Requires-Dist: pytest-cov>=5; extra == "dev"
92
+ Requires-Dist: httpx>=0.27; extra == "dev"
93
+ Requires-Dist: hypothesis>=6.100; extra == "dev"
94
+ Requires-Dist: ruff>=0.5; extra == "dev"
95
+ Requires-Dist: bandit>=1.7; extra == "dev"
96
+ Requires-Dist: pip-audit>=2.7; extra == "dev"
97
+ Requires-Dist: sounddevice>=0.4; extra == "dev"
98
+ Requires-Dist: soundfile>=0.12; extra == "dev"
99
+ Requires-Dist: scipy>=1.10; extra == "dev"
100
+ Requires-Dist: playwright>=1.40; extra == "dev"
101
+ Dynamic: license-file
102
+
103
+ # TapScribe
104
+
105
+ Local transcription recorder and operator dashboard.
106
+
107
+ TapScribe records one WAV per utterance per speaker over a WebSocket, runs
108
+ Whisper, Voxtral, or Parakeet batch transcription on demand, and supervises a
109
+ [WhisperLiveKit](https://github.com/QuentinFuxa/WhisperLiveKit) child process
110
+ for live captions. Nothing leaves the machine.
111
+
112
+ A FastAPI app serves a REST API and a dashboard at `/`:
113
+
114
+ - Start, stop, and restart `whisperlivekit-server` from the dashboard.
115
+ - One WAV per utterance per speaker, written to `recordings/<session>/`.
116
+ - Re-transcribe single WAVs or merge a whole session into one transcript.
117
+ - Hallucination filter with substring, `exact:`, and `re:` rules. Suppressed
118
+ segments are kept in an audit array.
119
+ - Optional silero-VAD pass writes trimmed copies to `<session>/stripped/`.
120
+ Originals are not touched.
121
+ - Summarize a session's merged transcript — bundled local model, an
122
+ operator CLI, or an OpenAI-compatible API — and persist the result.
123
+ - End-of-meeting pipeline: strip → transcribe → summarize as one job, so a
124
+ Bridge can take a session from raw WAVs to a summary with no operator
125
+ in the loop.
126
+ - People registry: a cross-session identity → display-name map, with
127
+ merge/detach, so the same speaker keeps one name across meetings.
128
+
129
+ Audio reaches TapScribe via a *bridge*: usually a browser extension that taps
130
+ the meeting platform's audio tracks and forwards raw PCM over WebSocket. The
131
+ included bridge, `spacialchat-bridge/`, targets spatial.chat. See
132
+ [`bridges/README.md`](bridges/README.md) for the wire protocol if you want to
133
+ add another.
134
+
135
+ ## Dashboard
136
+
137
+ One operator console at `/` — the "Stages" UI. A slim left spine
138
+ navigates the global views (Taps · Sessions · People · Settings) and the
139
+ per-session journey (Capture → Recordings → Transcript → Summary); the
140
+ active-taps rail on the right follows you across views. Live captions
141
+ stream in Capture; silence-stripping and the per-WAV files live in
142
+ Recordings; the **▶ transcribe range** button, the meeting-languages
143
+ declaration (you declare the meeting's languages, not a model — ADR-0011),
144
+ and the merged transcript live in Transcript; the global live and batch
145
+ engine pickers live in Settings.
146
+
147
+ ![Merged session transcript](docs/dashboard-shots/06-real-audio-transcript.png)
148
+
149
+ The screenshot is captured live by the browser E2E test described
150
+ under [Tests](#tests), running the real Apollo 11 audio fixture
151
+ through the bridge and a real `faster-whisper` `tiny.en`.
152
+ Whisper self-flagged the imperfect output as low-confidence; bigger
153
+ models clean that up considerably.
154
+
155
+ ## Quick start (macOS / Linux)
156
+
157
+ ```bash
158
+ bash start.sh # localhost only
159
+ bash start.sh --lan # bind 0.0.0.0
160
+ ```
161
+
162
+ The script finds Python 3.12+, creates `.venv`, installs dependencies
163
+ (`whisperlivekit`, `python-multipart`, `transformers`, plus `mlx-whisper` on
164
+ Apple Silicon), and launches the TapScribe recorder on port 8001. Live
165
+ captions are **off by default**: start `whisperlivekit-server` from the
166
+ dashboard (or boot with it running via `--auto-live`). When live is running,
167
+ the recorder supervises it as a child on an ephemeral internal port (pin one
168
+ with `SX_PORT_WLK`); child logs are prefixed `[wlk]`. Ctrl+C stops the
169
+ recorder and any child.
170
+
171
+ On first run, pick transcription models in the **browser setup screen**: open
172
+ `http://localhost:8001/setup` (`/` redirects there until a model is installed)
173
+ and choose Whisper / Voxtral / Parakeet — the page installs them. `/setup`
174
+ doubles as a "manage models" surface. Headless hosts can pass
175
+ `--non-interactive` to install the saved/default selection without the browser.
176
+
177
+ Open `http://localhost:8001/`. On first run two secrets are generated and
178
+ printed:
179
+
180
+ - A dashboard password for HTTP Basic auth, persisted to `.auth-password`.
181
+ - A `/tap` bearer token for the bridge, persisted to `.tap-token`. Paste it
182
+ into the bridge popup along with the host and port.
183
+
184
+ Rotate with `.venv/bin/tapscribe --rotate-password` or `--rotate-tap-token`
185
+ (these are `tapscribe` flags, not `start.sh` flags). Pass `--tls` to `start.sh`
186
+ to serve `https://` and `wss://`; a self-signed cert is generated on first boot
187
+ (`.tapscribe-cert.pem`, `.tapscribe-key.pem`) and reused after. Supply your
188
+ own with `--cert <path> --key <path>`.
189
+
190
+ ## Windows
191
+
192
+ ### Windows Bundle (no Python needed)
193
+
194
+ Download **`TapScribe-Setup-win-x64.exe`** from the
195
+ [latest release](https://github.com/Vortiago/TapScribe/releases/latest) and run
196
+ it. It installs per-user (no admin prompt) and needs no Python, no checkout, and
197
+ no PowerShell — it carries its own interpreter. A tray icon starts the
198
+ dashboard, and its **Copy password** item gives you the generated dashboard
199
+ login on first run.
200
+
201
+ The Bundle is **unsigned**, so SmartScreen shows "Windows protected your PC".
202
+ Click **More info → Run anyway**.
203
+
204
+ Program files land in `%LOCALAPPDATA%\Programs\TapScribe`; your recordings,
205
+ transcripts and settings live separately in `%USERPROFILE%\TapScribe`, so
206
+ uninstalling never deletes them. Model weights are cached in
207
+ `%USERPROFILE%\.cache\huggingface`.
208
+
209
+ See [packaging/README.md](packaging/README.md) for what's in the Bundle and
210
+ [ADR-0015](docs/adr/0015-windows-bundle-embedded-interpreter.md) for why it
211
+ embeds an interpreter instead of freezing to a single binary.
212
+
213
+ ### From a checkout (developers)
214
+
215
+ ```powershell
216
+ .\start.ps1
217
+ .\start.ps1 -Lan
218
+ ```
219
+
220
+ ## Configuration
221
+
222
+ Eight files under `config/` shape transcription, live captions, and
223
+ summarization; all are re-read on every job. Most are editable from the
224
+ dashboard's Settings view instead of by hand.
225
+
226
+ | File | Shapes | Format |
227
+ |---|---|---|
228
+ | `config/prompt.txt` | Batch `initial_prompt` | Prose under ~150 words. Biases style and vocabulary. |
229
+ | `config/hotwords.txt` | Batch `hotwords` | Comma- or space-separated proper nouns. faster-whisper only. |
230
+ | `config/hallucinations.txt` | Post-decode suppression | substring, `exact:`, or `re:`. Matches are kept in an audit array. |
231
+ | `config/live-prompt.txt` | Live `--init-prompt` | Same prose format; independent of `prompt.txt`. |
232
+ | `config/live-model.txt` | Default live-channel model | Model id; the live channel adopts it on (re)start. |
233
+ | `config/batch-model.txt` | Default batch model (generalist) | Model id; what the end-of-meeting pipeline transcribes with. |
234
+ | `config/languages.txt` | Candidate languages (ADR-0010) | Comma-separated ISO codes; default `da,no,en`. |
235
+ | `config/summarizer.json` | Default summarizer | JSON: source, command/model/base_url, prompt. `api_key` is write-only. |
236
+
237
+ Templates: `config/prompt.example.txt`, `config/hotwords.example.txt`.
238
+ `config/hallucinations.txt` ships with rules for common YouTube-trained
239
+ Whisper hallucinations.
240
+
241
+ ## Architecture
242
+
243
+ One backend, one supervised child, N bridges. Audio flows in over WebSocket;
244
+ captions and recordings come out.
245
+
246
+ ```mermaid
247
+ flowchart LR
248
+ subgraph Meeting["Meeting platform (e.g. spatial.chat)"]
249
+ Bridge["Bridge<br/>(browser extension<br/>or native helper)"]
250
+ end
251
+
252
+ subgraph Host["TapScribe host"]
253
+ Backend["TapScribe backend<br/>FastAPI :8001<br/>/tap · /api · dashboard"]
254
+ WLK["whisperlivekit-server<br/>(supervised child, internal port)"]
255
+ WAVs[("recordings/<br/>&lt;session&gt;/*.wav")]
256
+ end
257
+
258
+ Operator["Operator browser<br/>(dashboard)"]
259
+
260
+ Bridge -- "PCM 16k mono<br/>over WS /tap" --> Backend
261
+ Backend -- "forwards PCM" --> WLK
262
+ WLK -- "settled live captions" --> Backend
263
+ Backend -- "one WAV per utterance" --> WAVs
264
+ Operator <-- "HTTPS (500 ms /api/state poll)" --> Backend
265
+ ```
266
+
267
+ - **Bridges** tap a meeting platform's audio and stream raw PCM to `/tap`.
268
+ One WebSocket per speaker per utterance. See [`bridges/README.md`](bridges/README.md).
269
+ - **Backend** (`tapscribe/`) fans each PCM frame out to two sinks: a
270
+ per-utterance WAV on disk, and an internal relay to the supervised
271
+ WhisperLiveKit child for live captions. It also serves the operator
272
+ dashboard.
273
+ - **WhisperLiveKit** runs as a child process the backend starts, stops, and
274
+ restarts from the dashboard. Bridges never talk to it directly.
275
+
276
+ ### Audio pipeline (per utterance)
277
+
278
+ One `/tap` WebSocket = one utterance. Each PCM frame is tee'd: appended to
279
+ the per-utterance WAV on disk **and** forwarded to the WhisperLiveKit child
280
+ for live captions. Settled caption lines flow back to the operator
281
+ dashboard. WAV writing is independent of the live relay — if WhisperLiveKit
282
+ is down, recording still works.
283
+
284
+ ```mermaid
285
+ sequenceDiagram
286
+ autonumber
287
+ participant B as Bridge
288
+ participant T as /tap handler
289
+ participant W as WAV file
290
+ participant R as WlKRelay
291
+ participant L as WhisperLiveKit
292
+ participant D as Dashboard
293
+
294
+ B->>T: open /tap?identity&name<br/>(one WS per utterance)
295
+ T->>W: open recordings/<session>/<utt>.wav
296
+ T->>R: connect to WhisperLiveKit
297
+
298
+ loop each PCM frame (16 kHz mono s16le)
299
+ B->>T: PCM bytes
300
+ T->>W: append frame
301
+ T->>R: forward frame
302
+ R->>L: PCM
303
+ L-->>R: settled caption line
304
+ R-->>T: on_settled_line(text)
305
+ T-->>D: push to live feed
306
+ end
307
+
308
+ B->>T: close (mute / leave)
309
+ T->>W: finalise WAV<br/>(or delete if empty)
310
+ T->>R: close
311
+ ```
312
+
313
+ ## After the meeting
314
+
315
+ - **Summarize** (`POST /api/sessions/{session}/summarize`) turns a session's
316
+ merged transcript into a summary via a bundled local model, an operator
317
+ CLI (`command`), or an OpenAI-compatible API (`api`, e.g. Ollama). The
318
+ summary and the operator's default source/model/prompt both persist
319
+ (`session-summary.json`, `config/summarizer.json`).
320
+ - **End-of-meeting pipeline** (`POST`/`GET /api/tap/sessions/{session}/pipeline`)
321
+ chains strip → transcribe → summarize as one job. A Bridge triggers it
322
+ and polls it with the tap bearer token — no dashboard needed.
323
+ - **People** (`/api/people*`) is the cross-session identity → display-name
324
+ registry: every new speaker auto-gets a Person, merge/detach combine or
325
+ split them, and a per-session alias can override the name for one meeting.
326
+
327
+ See [CONTEXT.md](CONTEXT.md) for the full model (Bracketed meeting, Detached
328
+ session, Person/Identity/Roster).
329
+
330
+ ## Backends
331
+
332
+ | Family | Models | Backend | Languages | Notes |
333
+ |---|---|---|---|---|
334
+ | Whisper | `tiny(.en)`/`base(.en)`/`small(.en)`/`medium(.en)`, `large-v3`, `large-v3-turbo` | mlx-whisper (AS) / faster-whisper | English-only suffixes; else multilingual | `large-v3-turbo` is the default generalist (ADR-0010). |
335
+ | NB-Whisper | `nb-whisper-tiny`/`base`/`small`/`medium`/`large` | faster-whisper on CT2 weights | Norwegian | Pulled from `NbAiLab/nb-whisper-*/ct2/`. No MLX. |
336
+ | Voxtral | `voxtral-mini` | mlx-voxtral (AS) / HF transformers | EN/ES/FR/PT/HI/DE/NL/IT | Batch-only (no live channel yet). First load downloads ~6 GB. |
337
+ | Parakeet | `parakeet-tdt-0.6b-v3` | parakeet-mlx (AS) / HF transformers | 25 EU languages, no Norwegian | Batch-only (no live channel yet); replaced Canary (ADR-0006). |
338
+ | Moonshine | `moonshine-tiny`/`base` | mlx-audio (AS) / ONNX-CPU (`useful-moonshine-onnx`) | English | Live-only, lightweight low-latency captions — `MoonshineLiveChannel`. Install via `[moonshine-mlx]` / `[moonshine-cpu]` (picker: "Moonshine"). |
339
+
340
+ On Apple Silicon, live and batch both route through mlx-whisper by default.
341
+ Pass `--no-mlx` to opt out.
342
+
343
+ ## Tests
344
+
345
+ ```bash
346
+ pip install -e ".[dev]"
347
+ python -m pytest -q
348
+ ```
349
+
350
+ Four layers:
351
+
352
+ - **Unit + route tests** (`tests/test_*.py`) cover pure helpers
353
+ (hallucination filter, prompt/hotwords reading, slug parsing, WAV I/O,
354
+ model routing) and FastAPI routes via `TestClient`. Whisper / Voxtral
355
+ backends are stubbed; the suite stays under 20 s.
356
+ - **HTTP pipeline E2E** (`tests/e2e/test_pipeline_e2e.py`) boots a real
357
+ uvicorn server, streams two synthetic WAVs concurrently through real
358
+ `/tap` WebSockets, then walks every dashboard HTTP route to verify
359
+ the recorder finalised the WAVs, fanned settled lines into the live
360
+ feed, and produced a merged session transcript. Uses a
361
+ `FakeTranscriber` so the test runs without faster-whisper installed.
362
+ - **Real-Whisper E2E** (same file, `test_pipeline_with_real_whisper`)
363
+ streams committed CC-licensed audio fixtures (Apollo 11 English, Marlene
364
+ Dietrich Norwegian) through the bridge and runs real `faster-whisper`
365
+ on what the recorder wrote. Skipped automatically when
366
+ `faster-whisper` isn't installed. See
367
+ [`tests/fixtures/audio/README.md`](tests/fixtures/audio/README.md) for
368
+ licence details and how to add more clips.
369
+ - **Dashboard UI E2E** (`tests/e2e/test_dashboard_ui.py`) launches
370
+ headless Chromium via Playwright against the running server and
371
+ asserts on actual DOM. Two variants:
372
+ - The fast plumbing check (synthetic WAVs + `FakeTranscriber`)
373
+ verifies taps-rail rows appear while bridges stream, settled
374
+ lines land in Capture's captions feed with correct per-speaker
375
+ attribution, and Transcript's **▶ transcribe range** button
376
+ renders the merged transcript with both speakers' text — plus the
377
+ alias-applied copy-to-clipboard contract.
378
+ - The real-audio check (`@pytest.mark.real_audio`) streams the
379
+ committed Apollo 11 fixture through the bridge, clicks the same
380
+ button, and waits for real `faster-whisper` to produce a merged
381
+ transcript in the UI. This is what produces the screenshot in
382
+ the [Dashboard](#dashboard) section above.
383
+
384
+ ```bash
385
+ pip install -e ".[dev]" && python -m playwright install chromium
386
+ python -m pytest tests/e2e/test_dashboard_ui.py
387
+ ```
388
+
389
+ - **Bridge browser E2E** (`tests/e2e/test_bridge_extension_e2e.py`,
390
+ `test_bridge_meeting_e2e.py`) loads the real MV3 extension in a headed
391
+ Chromium to test the tap/mute/pipeline flow end to end. Needs a display
392
+ (`xvfb-run -a python -m pytest ... -m browser_e2e`); self-skips without one.
393
+
394
+ GitHub Actions runs the suite and `ruff check` on every push and PR across
395
+ Python 3.12-3.14 on Ubuntu, macOS, and Windows.
396
+
397
+ ## License
398
+
399
+ MIT. See [LICENSE](LICENSE).