whospeaks 0.0.1__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 (141) hide show
  1. whospeaks-0.0.1/LICENSE +21 -0
  2. whospeaks-0.0.1/PKG-INFO +192 -0
  3. whospeaks-0.0.1/README.md +77 -0
  4. whospeaks-0.0.1/pyproject.toml +146 -0
  5. whospeaks-0.0.1/setup.cfg +4 -0
  6. whospeaks-0.0.1/src/common/__init__.py +1 -0
  7. whospeaks-0.0.1/src/common/audio_utils.py +218 -0
  8. whospeaks-0.0.1/src/common/pythonpath.py +36 -0
  9. whospeaks-0.0.1/src/embeddings/__init__.py +1 -0
  10. whospeaks-0.0.1/src/embeddings/benchmark_voice_embeddings.py +870 -0
  11. whospeaks-0.0.1/src/embeddings/build_live_sentence_embedding_corpus.py +415 -0
  12. whospeaks-0.0.1/src/embeddings/build_sentence_embedding_corpus.py +447 -0
  13. whospeaks-0.0.1/src/embeddings/embedding_providers.py +768 -0
  14. whospeaks-0.0.1/src/paths/__init__.py +57 -0
  15. whospeaks-0.0.1/src/realtime/__init__.py +1 -0
  16. whospeaks-0.0.1/src/realtime/realtime_capture.py +815 -0
  17. whospeaks-0.0.1/src/realtime/realtime_gui_html.py +792 -0
  18. whospeaks-0.0.1/src/realtime/realtime_server.py +127 -0
  19. whospeaks-0.0.1/src/realtime/realtime_speaker_engine.py +594 -0
  20. whospeaks-0.0.1/src/realtime/realtime_speakerdiarize.py +1461 -0
  21. whospeaks-0.0.1/src/realtime/realtime_transcript.py +326 -0
  22. whospeaks-0.0.1/src/replay/__init__.py +1 -0
  23. whospeaks-0.0.1/src/replay/youtube_local_filefeed_replay.py +344 -0
  24. whospeaks-0.0.1/src/speakers/__init__.py +1 -0
  25. whospeaks-0.0.1/src/speakers/realtime_speaker_memory.py +319 -0
  26. whospeaks-0.0.1/src/speakers/speaker_embedding_cluster.py +696 -0
  27. whospeaks-0.0.1/src/whospeaks_cli/__init__.py +7 -0
  28. whospeaks-0.0.1/src/whospeaks_cli/__main__.py +7 -0
  29. whospeaks-0.0.1/src/whospeaks_cli/main.py +2049 -0
  30. whospeaks-0.0.1/src/window/__init__.py +1 -0
  31. whospeaks-0.0.1/src/window/browser_live_speaker_eval.py +123 -0
  32. whospeaks-0.0.1/src/window/browser_live_speaker_scoring.py +471 -0
  33. whospeaks-0.0.1/src/window/event_client.py +110 -0
  34. whospeaks-0.0.1/src/window/fact_lens_sidecar.py +1240 -0
  35. whospeaks-0.0.1/src/window/language_config.py +236 -0
  36. whospeaks-0.0.1/src/window/live_speaker_probe_scoring.py +825 -0
  37. whospeaks-0.0.1/src/window/meeting_intelligence.py +625 -0
  38. whospeaks-0.0.1/src/window/meeting_intelligence_pipeline.py +948 -0
  39. whospeaks-0.0.1/src/window/meeting_intelligence_server.py +2920 -0
  40. whospeaks-0.0.1/src/window/modal_asr_server.py +159 -0
  41. whospeaks-0.0.1/src/window/modal_youtube_window_diarize_gui.py +341 -0
  42. whospeaks-0.0.1/src/window/public_events.py +281 -0
  43. whospeaks-0.0.1/src/window/review_flags.py +146 -0
  44. whospeaks-0.0.1/src/window/session_store.py +735 -0
  45. whospeaks-0.0.1/src/window/speaker_color_allocation.py +569 -0
  46. whospeaks-0.0.1/src/window/window_config.py +493 -0
  47. whospeaks-0.0.1/src/window/window_diarizer.py +5931 -0
  48. whospeaks-0.0.1/src/window/window_domain.py +107 -0
  49. whospeaks-0.0.1/src/window/window_events.py +69 -0
  50. whospeaks-0.0.1/src/window/window_gui_html.py +6031 -0
  51. whospeaks-0.0.1/src/window/window_media.py +75 -0
  52. whospeaks-0.0.1/src/window/window_preview.py +341 -0
  53. whospeaks-0.0.1/src/window/window_remote_asr.py +191 -0
  54. whospeaks-0.0.1/src/window/window_speaker_refinement.py +263 -0
  55. whospeaks-0.0.1/src/window/window_text.py +319 -0
  56. whospeaks-0.0.1/src/window/window_validation_replay.py +283 -0
  57. whospeaks-0.0.1/src/window/youtube_window_diarize_gui.py +2990 -0
  58. whospeaks-0.0.1/src/workers/__init__.py +1 -0
  59. whospeaks-0.0.1/src/workers/kroko_realtime_preview_worker.py +129 -0
  60. whospeaks-0.0.1/tests/test_audio_utils.py +37 -0
  61. whospeaks-0.0.1/tests/test_browser_audio_modes.py +36 -0
  62. whospeaks-0.0.1/tests/test_core_regressions.py +3594 -0
  63. whospeaks-0.0.1/tests/test_fact_lens_sidecar.py +168 -0
  64. whospeaks-0.0.1/tests/test_helper_pythonpath.py +35 -0
  65. whospeaks-0.0.1/tests/test_kroko_installer.py +88 -0
  66. whospeaks-0.0.1/tests/test_meeting_intelligence.py +204 -0
  67. whospeaks-0.0.1/tests/test_meeting_intelligence_pipeline.py +195 -0
  68. whospeaks-0.0.1/tests/test_meeting_intelligence_server.py +230 -0
  69. whospeaks-0.0.1/tests/test_review_corrections.py +576 -0
  70. whospeaks-0.0.1/tests/test_session_store.py +183 -0
  71. whospeaks-0.0.1/tests/test_whospeaks_cli.py +398 -0
  72. whospeaks-0.0.1/vendor/RealtimeSTT/__init__.py +44 -0
  73. whospeaks-0.0.1/vendor/RealtimeSTT/assets/__init__.py +1 -0
  74. whospeaks-0.0.1/vendor/RealtimeSTT/assets/warmup_audio.wav +0 -0
  75. whospeaks-0.0.1/vendor/RealtimeSTT/audio_input.py +242 -0
  76. whospeaks-0.0.1/vendor/RealtimeSTT/audio_recorder.py +825 -0
  77. whospeaks-0.0.1/vendor/RealtimeSTT/audio_recorder_client.py +987 -0
  78. whospeaks-0.0.1/vendor/RealtimeSTT/core/__init__.py +3 -0
  79. whospeaks-0.0.1/vendor/RealtimeSTT/core/audio_input_worker.py +359 -0
  80. whospeaks-0.0.1/vendor/RealtimeSTT/core/initialization.py +664 -0
  81. whospeaks-0.0.1/vendor/RealtimeSTT/core/lifecycle.py +273 -0
  82. whospeaks-0.0.1/vendor/RealtimeSTT/core/manual_audio_input.py +159 -0
  83. whospeaks-0.0.1/vendor/RealtimeSTT/core/preroll.py +434 -0
  84. whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime.py +1460 -0
  85. whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime_boundary_detector.py +559 -0
  86. whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime_callbacks.py +23 -0
  87. whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime_text_stabilizer.py +1136 -0
  88. whospeaks-0.0.1/vendor/RealtimeSTT/core/recorder_config.py +201 -0
  89. whospeaks-0.0.1/vendor/RealtimeSTT/core/recording.py +479 -0
  90. whospeaks-0.0.1/vendor/RealtimeSTT/core/recording_buffers.py +165 -0
  91. whospeaks-0.0.1/vendor/RealtimeSTT/core/runtime.py +56 -0
  92. whospeaks-0.0.1/vendor/RealtimeSTT/core/safepipe.py +240 -0
  93. whospeaks-0.0.1/vendor/RealtimeSTT/core/shutdown.py +69 -0
  94. whospeaks-0.0.1/vendor/RealtimeSTT/core/silero_vad.py +591 -0
  95. whospeaks-0.0.1/vendor/RealtimeSTT/core/state.py +86 -0
  96. whospeaks-0.0.1/vendor/RealtimeSTT/core/text_formatting.py +72 -0
  97. whospeaks-0.0.1/vendor/RealtimeSTT/core/transcription.py +251 -0
  98. whospeaks-0.0.1/vendor/RealtimeSTT/core/transcription_api.py +186 -0
  99. whospeaks-0.0.1/vendor/RealtimeSTT/core/voice_activity.py +409 -0
  100. whospeaks-0.0.1/vendor/RealtimeSTT/core/wakeword.py +223 -0
  101. whospeaks-0.0.1/vendor/RealtimeSTT/install_kroko.py +801 -0
  102. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/__init__.py +26 -0
  103. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/_model_utils.py +110 -0
  104. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/base.py +167 -0
  105. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/cohere_transcribe_engine.py +39 -0
  106. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/factory.py +70 -0
  107. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/faster_whisper_engine.py +114 -0
  108. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/funasr_engine.py +347 -0
  109. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/granite_speech_engine.py +39 -0
  110. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/hf_transformers_engines.py +465 -0
  111. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/kroko_onnx_engine.py +813 -0
  112. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/moonshine_engine.py +45 -0
  113. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/omnilingual_asr_engine.py +481 -0
  114. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/openai_api_engine.py +29 -0
  115. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/openai_whisper_engine.py +120 -0
  116. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/parakeet_engine.py +153 -0
  117. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/qwen3_asr_engine.py +164 -0
  118. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/sherpa_onnx_engine.py +392 -0
  119. whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/whisper_cpp_engine.py +113 -0
  120. whospeaks-0.0.1/vendor/RealtimeSTT_server/__init__.py +0 -0
  121. whospeaks-0.0.1/vendor/RealtimeSTT_server/install_packages.py +55 -0
  122. whospeaks-0.0.1/vendor/RealtimeSTT_server/stt_cli_client.py +285 -0
  123. whospeaks-0.0.1/vendor/RealtimeSTT_server/stt_server.py +921 -0
  124. whospeaks-0.0.1/vendor/remote_servers/faster-whisper-asr/asr_server.py +502 -0
  125. whospeaks-0.0.1/vendor/remote_servers/faster-whisper-asr/mlx_asr_server.py +219 -0
  126. whospeaks-0.0.1/vendor/remote_servers/faster-whisper-asr/test_parent_watchdog.py +49 -0
  127. whospeaks-0.0.1/vendor/remote_servers/voice-embeddings-server/embeddings_server.py +666 -0
  128. whospeaks-0.0.1/vendor/remote_servers/voice-embeddings-server/tools/benchmark_voice_embeddings.py +861 -0
  129. whospeaks-0.0.1/vendor/stream2sentence/__init__.py +14 -0
  130. whospeaks-0.0.1/vendor/stream2sentence/avoid_pause_words.py +65 -0
  131. whospeaks-0.0.1/vendor/stream2sentence/data/language_contexts.json +3092 -0
  132. whospeaks-0.0.1/vendor/stream2sentence/delimiter_ignore_prefixes.py +34 -0
  133. whospeaks-0.0.1/vendor/stream2sentence/quick_yield_boundary.py +2528 -0
  134. whospeaks-0.0.1/vendor/stream2sentence/stream2sentence.py +1038 -0
  135. whospeaks-0.0.1/vendor/stream2sentence/stream2sentence_time_based.py +241 -0
  136. whospeaks-0.0.1/whospeaks.egg-info/PKG-INFO +192 -0
  137. whospeaks-0.0.1/whospeaks.egg-info/SOURCES.txt +139 -0
  138. whospeaks-0.0.1/whospeaks.egg-info/dependency_links.txt +1 -0
  139. whospeaks-0.0.1/whospeaks.egg-info/entry_points.txt +8 -0
  140. whospeaks-0.0.1/whospeaks.egg-info/requires.txt +108 -0
  141. whospeaks-0.0.1/whospeaks.egg-info/top_level.txt +13 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WhoSpeaksLive 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,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: whospeaks
3
+ Version: 0.0.1
4
+ Summary: Local speaker diarization and realtime voice-labeling tools.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/KoljaB/WhoSpeaksLive
7
+ Project-URL: Repository, https://github.com/KoljaB/WhoSpeaksLive
8
+ Project-URL: Documentation, https://github.com/KoljaB/WhoSpeaksLive/tree/master/docs
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Provides-Extra: controller
13
+ Requires-Dist: av>=11.0.0; extra == "controller"
14
+ Requires-Dist: emoji>=2.15.0; extra == "controller"
15
+ Requires-Dist: librosa>=0.10.1; extra == "controller"
16
+ Requires-Dist: nltk>=3.9.4; extra == "controller"
17
+ Requires-Dist: numpy<3,>=2; extra == "controller"
18
+ Requires-Dist: onnxruntime>=1.17; extra == "controller"
19
+ Requires-Dist: soundfile>=0.12.1; extra == "controller"
20
+ Requires-Dist: yt-dlp>=2024.3.10; extra == "controller"
21
+ Provides-Extra: local
22
+ Requires-Dist: av>=11.0.0; extra == "local"
23
+ Requires-Dist: emoji>=2.15.0; extra == "local"
24
+ Requires-Dist: espnet==202511; extra == "local"
25
+ Requires-Dist: espnet-model-zoo==0.1.7; extra == "local"
26
+ Requires-Dist: faster-whisper>=1.2.1; extra == "local"
27
+ Requires-Dist: huggingface-hub<1,>=0.34; extra == "local"
28
+ Requires-Dist: librosa>=0.10.1; extra == "local"
29
+ Requires-Dist: matplotlib>=3.8; extra == "local"
30
+ Requires-Dist: modelscope>=1.14; extra == "local"
31
+ Requires-Dist: nltk>=3.9.4; extra == "local"
32
+ Requires-Dist: numpy<3,>=2; extra == "local"
33
+ Requires-Dist: onnxruntime>=1.17; extra == "local"
34
+ Requires-Dist: pyannote.audio<4,>=3.1; extra == "local"
35
+ Requires-Dist: resemblyzer>=0.1.4; extra == "local"
36
+ Requires-Dist: soundfile>=0.12.1; extra == "local"
37
+ Requires-Dist: speechbrain>=1.0.0; extra == "local"
38
+ Requires-Dist: stanza>=1.13.0; extra == "local"
39
+ Requires-Dist: torch>=2.2; extra == "local"
40
+ Requires-Dist: torchaudio>=2.2; extra == "local"
41
+ Requires-Dist: transformers<5,>=4.39; extra == "local"
42
+ Requires-Dist: yt-dlp>=2024.3.10; extra == "local"
43
+ Provides-Extra: preview
44
+ Requires-Dist: webrtcvad==2.0.10; extra == "preview"
45
+ Provides-Extra: server
46
+ Requires-Dist: av>=11.0.0; extra == "server"
47
+ Requires-Dist: espnet==202511; extra == "server"
48
+ Requires-Dist: espnet-model-zoo==0.1.7; extra == "server"
49
+ Requires-Dist: fastapi>=0.110; extra == "server"
50
+ Requires-Dist: faster-whisper>=1.2.1; extra == "server"
51
+ Requires-Dist: huggingface-hub<1,>=0.34; extra == "server"
52
+ Requires-Dist: librosa>=0.10.1; extra == "server"
53
+ Requires-Dist: matplotlib>=3.8; extra == "server"
54
+ Requires-Dist: modelscope>=1.14; extra == "server"
55
+ Requires-Dist: numpy<3,>=2; extra == "server"
56
+ Requires-Dist: onnxruntime>=1.17; extra == "server"
57
+ Requires-Dist: pyannote.audio<4,>=3.1; extra == "server"
58
+ Requires-Dist: python-multipart>=0.0.9; extra == "server"
59
+ Requires-Dist: resemblyzer>=0.1.4; extra == "server"
60
+ Requires-Dist: soundfile>=0.12.1; extra == "server"
61
+ Requires-Dist: speechbrain>=1.0.0; extra == "server"
62
+ Requires-Dist: torch>=2.2; extra == "server"
63
+ Requires-Dist: torchaudio>=2.2; extra == "server"
64
+ Requires-Dist: transformers<5,>=4.39; extra == "server"
65
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "server"
66
+ Provides-Extra: complete
67
+ Requires-Dist: av>=11.0.0; extra == "complete"
68
+ Requires-Dist: emoji>=2.15.0; extra == "complete"
69
+ Requires-Dist: espnet==202511; extra == "complete"
70
+ Requires-Dist: espnet-model-zoo==0.1.7; extra == "complete"
71
+ Requires-Dist: faster-whisper>=1.2.1; extra == "complete"
72
+ Requires-Dist: huggingface-hub<1,>=0.34; extra == "complete"
73
+ Requires-Dist: librosa>=0.10.1; extra == "complete"
74
+ Requires-Dist: matplotlib>=3.8; extra == "complete"
75
+ Requires-Dist: modelscope>=1.14; extra == "complete"
76
+ Requires-Dist: nltk>=3.9.4; extra == "complete"
77
+ Requires-Dist: numpy<3,>=2; extra == "complete"
78
+ Requires-Dist: onnxruntime>=1.17; extra == "complete"
79
+ Requires-Dist: pyannote.audio<4,>=3.1; extra == "complete"
80
+ Requires-Dist: resemblyzer>=0.1.4; extra == "complete"
81
+ Requires-Dist: soundfile>=0.12.1; extra == "complete"
82
+ Requires-Dist: speechbrain>=1.0.0; extra == "complete"
83
+ Requires-Dist: stanza>=1.13.0; extra == "complete"
84
+ Requires-Dist: torch>=2.2; extra == "complete"
85
+ Requires-Dist: torchaudio>=2.2; extra == "complete"
86
+ Requires-Dist: transformers<5,>=4.39; extra == "complete"
87
+ Requires-Dist: yt-dlp>=2024.3.10; extra == "complete"
88
+ Provides-Extra: all
89
+ Requires-Dist: av>=11.0.0; extra == "all"
90
+ Requires-Dist: emoji>=2.15.0; extra == "all"
91
+ Requires-Dist: espnet==202511; extra == "all"
92
+ Requires-Dist: espnet-model-zoo==0.1.7; extra == "all"
93
+ Requires-Dist: faster-whisper>=1.2.1; extra == "all"
94
+ Requires-Dist: huggingface-hub<1,>=0.34; extra == "all"
95
+ Requires-Dist: librosa>=0.10.1; extra == "all"
96
+ Requires-Dist: matplotlib>=3.8; extra == "all"
97
+ Requires-Dist: modelscope>=1.14; extra == "all"
98
+ Requires-Dist: nltk>=3.9.4; extra == "all"
99
+ Requires-Dist: numpy<3,>=2; extra == "all"
100
+ Requires-Dist: onnxruntime>=1.17; extra == "all"
101
+ Requires-Dist: pyannote.audio<4,>=3.1; extra == "all"
102
+ Requires-Dist: resemblyzer>=0.1.4; extra == "all"
103
+ Requires-Dist: soundfile>=0.12.1; extra == "all"
104
+ Requires-Dist: speechbrain>=1.0.0; extra == "all"
105
+ Requires-Dist: stanza>=1.13.0; extra == "all"
106
+ Requires-Dist: torch>=2.2; extra == "all"
107
+ Requires-Dist: torchaudio>=2.2; extra == "all"
108
+ Requires-Dist: transformers<5,>=4.39; extra == "all"
109
+ Requires-Dist: fastapi>=0.110; extra == "all"
110
+ Requires-Dist: python-multipart>=0.0.9; extra == "all"
111
+ Requires-Dist: uvicorn[standard]>=0.29; extra == "all"
112
+ Requires-Dist: yt-dlp>=2024.3.10; extra == "all"
113
+ Requires-Dist: webrtcvad==2.0.10; extra == "all"
114
+ Dynamic: license-file
115
+
116
+ # WhoSpeaksLive
117
+
118
+ WhoSpeaksLive is a local-first speaker diarization app for turning live or replayed media into speaker-labeled transcripts, with a browser UI for fast live speaker feedback, final sentence assignment, speaker library management, and validation.
119
+
120
+ ## Demo
121
+
122
+ https://github.com/user-attachments/assets/2de749e0-6c02-47de-b949-bd90b4f4efbb
123
+
124
+ For faster realtime ASR preview text like shown in the demo, use [Kroko Pro/commercial streaming models](https://docs.kroko.ai/on-premise/#2-commercial-oem-models); the public Community models work, but Pro/private models must be installed and licensed separately.
125
+
126
+ ## Current Scope
127
+
128
+ WhoSpeaksLive performs best on clean recordings where one person speaks at a time into good microphones. Diarization accuracy can degrade with background noise, background music, echo, crosstalk, overlapping speech, or low-quality microphones, and it may become less reliable as the active speaker count grows. The system assumes complete utterances can be assigned to a single speaker, so cases where one speaker starts a sentence and another finishes it are not expected to score well.
129
+
130
+ All Kroko languages supported by this integration work with realtime preview text: German, English, Spanish, French, Italian, Hebrew, Dutch, Portuguese, Swedish, and Turkish. Set `--language` or `WHOSPEAKS_LANGUAGE` to keep final ASR, Kroko/Banafo preview model selection, and stream2sentence sentence splitting on the same language. See the [configuration guide](docs/configuration.md#language) for language codes and model details.
131
+
132
+ Without realtime preview text, WhoSpeaksLive can also work with additional languages. The key requirement is that the language is supported by Whisper and by at least one configured sentence segmenter, meaning NLTK or Stanza.
133
+
134
+ That currently makes these additional languages principally supported without realtime preview text: Afrikaans, Arabic, Belarusian, Bulgarian, Catalan, Czech, Welsh, Danish, Greek, Estonian, Basque, Persian, Finnish, Faroese, Galician, Hindi, Croatian, Hungarian, Armenian, Indonesian, Icelandic, Japanese, Georgian, Kazakh, Korean, Latin, Lithuanian, Latvian, Malayalam, Marathi, Maltese, Myanmar/Burmese, Norwegian, Norwegian Nynorsk, Polish, Romanian, Russian, Sanskrit, Sindhi, Slovak, Slovenian, Albanian, Serbian, Tamil, Telugu, Thai, Ukrainian, Urdu, Vietnamese, and Chinese.
135
+
136
+ CPU-only operation is not the recommended path for the current stack. The system is GPU-heavy today; a CPU-only setup may be possible, but should be treated as a separate optimization target and will likely require engineering work, slower processing, and some accuracy or throughput tradeoffs.
137
+
138
+ ## License
139
+
140
+ WhoSpeaksLive's own code is licensed under the [MIT License](LICENSE).
141
+
142
+ Optional Kroko/Banafo preview support uses separately licensed upstream components and model files. Missing public Community preview models are downloaded automatically from Hugging Face when realtime preview starts. This repository's MIT license does not relicense Kroko/Banafo assets; before downloading, bundling, or deploying them, review and respect the current terms from [Kroko by Banafo](https://kroko.ai/), the [Banafo/Kroko-ASR model card](https://huggingface.co/Banafo/Kroko-ASR), and the [kroko-ai/kroko-onnx repository](https://github.com/kroko-ai/kroko-onnx).
143
+
144
+ ## Start Here
145
+
146
+ For a guided setup, install the lightweight CLI and let it inspect the machine:
147
+
148
+ ```powershell
149
+ pip install whospeaks
150
+ whospeaks
151
+ ```
152
+
153
+ The `whospeaks` starter shows the current component state and gives direct controls for language, realtime text, provider quality, backend URLs, ASR runtime, browser port, diagnostics, installation, exact launch-command printing, and launching the browser UI.
154
+
155
+ The short `whospeaks` command is a setup and launcher wrapper. It saves a small profile, runs doctor checks, and expands that profile into the longer `whospeaks-window ...` browser-server command when you launch.
156
+
157
+ For a Linux container server:
158
+
159
+ ```bash
160
+ docker build -t whospeaks:local .
161
+ docker run --rm --name whospeaks -p 8796:8796 -v whospeaks-data:/data -v whospeaks-models:/models whospeaks:local
162
+ ```
163
+
164
+ See [Docker](docs/docker.md) for the full build/run path and volume notes.
165
+
166
+ For a manual full working setup, follow these in order:
167
+
168
+ 1. [Installation](docs/installation.md): install the Windows controller.
169
+ 2. [External ASR and embeddings servers](docs/external-servers.md): set up the Linux GPU services.
170
+ 3. [Quickstart](docs/quickstart.md): verify a local or remote smoke run, then run the tuned provider stack.
171
+
172
+ ## Documentation
173
+
174
+ | Topic | Document |
175
+ | --- | --- |
176
+ | Documentation map | [docs/index.md](docs/index.md) |
177
+ | Product overview and use cases | [docs/overview.md](docs/overview.md) |
178
+ | Installation | [docs/installation.md](docs/installation.md) |
179
+ | macOS setup | [docs/macos-setup.md](docs/macos-setup.md) |
180
+ | Quickstart | [docs/quickstart.md](docs/quickstart.md) |
181
+ | Live window workflow | [docs/live-window-workflow.md](docs/live-window-workflow.md) |
182
+ | Meeting intelligence server | [docs/meeting-intelligence-server.md](docs/meeting-intelligence-server.md) |
183
+ | Speaker libraries | [docs/speaker-libraries.md](docs/speaker-libraries.md) |
184
+ | External ASR and embeddings servers | [docs/external-servers.md](docs/external-servers.md) |
185
+ | Docker server image | [docs/docker.md](docs/docker.md) |
186
+ | Configuration guide | [docs/configuration.md](docs/configuration.md) |
187
+ | Technical description | [docs/technical-description.md](docs/technical-description.md) |
188
+ | Technical architecture | [docs/architecture.md](docs/architecture.md) |
189
+ | Validation and scoring | [docs/validation-and-scoring.md](docs/validation-and-scoring.md) |
190
+ | Modal deployment | [docs/modal-deployment.md](docs/modal-deployment.md) |
191
+ | Troubleshooting | [docs/troubleshooting.md](docs/troubleshooting.md) |
192
+ | Development workflow | [docs/development.md](docs/development.md) |
@@ -0,0 +1,77 @@
1
+ # WhoSpeaksLive
2
+
3
+ WhoSpeaksLive is a local-first speaker diarization app for turning live or replayed media into speaker-labeled transcripts, with a browser UI for fast live speaker feedback, final sentence assignment, speaker library management, and validation.
4
+
5
+ ## Demo
6
+
7
+ https://github.com/user-attachments/assets/2de749e0-6c02-47de-b949-bd90b4f4efbb
8
+
9
+ For faster realtime ASR preview text like shown in the demo, use [Kroko Pro/commercial streaming models](https://docs.kroko.ai/on-premise/#2-commercial-oem-models); the public Community models work, but Pro/private models must be installed and licensed separately.
10
+
11
+ ## Current Scope
12
+
13
+ WhoSpeaksLive performs best on clean recordings where one person speaks at a time into good microphones. Diarization accuracy can degrade with background noise, background music, echo, crosstalk, overlapping speech, or low-quality microphones, and it may become less reliable as the active speaker count grows. The system assumes complete utterances can be assigned to a single speaker, so cases where one speaker starts a sentence and another finishes it are not expected to score well.
14
+
15
+ All Kroko languages supported by this integration work with realtime preview text: German, English, Spanish, French, Italian, Hebrew, Dutch, Portuguese, Swedish, and Turkish. Set `--language` or `WHOSPEAKS_LANGUAGE` to keep final ASR, Kroko/Banafo preview model selection, and stream2sentence sentence splitting on the same language. See the [configuration guide](docs/configuration.md#language) for language codes and model details.
16
+
17
+ Without realtime preview text, WhoSpeaksLive can also work with additional languages. The key requirement is that the language is supported by Whisper and by at least one configured sentence segmenter, meaning NLTK or Stanza.
18
+
19
+ That currently makes these additional languages principally supported without realtime preview text: Afrikaans, Arabic, Belarusian, Bulgarian, Catalan, Czech, Welsh, Danish, Greek, Estonian, Basque, Persian, Finnish, Faroese, Galician, Hindi, Croatian, Hungarian, Armenian, Indonesian, Icelandic, Japanese, Georgian, Kazakh, Korean, Latin, Lithuanian, Latvian, Malayalam, Marathi, Maltese, Myanmar/Burmese, Norwegian, Norwegian Nynorsk, Polish, Romanian, Russian, Sanskrit, Sindhi, Slovak, Slovenian, Albanian, Serbian, Tamil, Telugu, Thai, Ukrainian, Urdu, Vietnamese, and Chinese.
20
+
21
+ CPU-only operation is not the recommended path for the current stack. The system is GPU-heavy today; a CPU-only setup may be possible, but should be treated as a separate optimization target and will likely require engineering work, slower processing, and some accuracy or throughput tradeoffs.
22
+
23
+ ## License
24
+
25
+ WhoSpeaksLive's own code is licensed under the [MIT License](LICENSE).
26
+
27
+ Optional Kroko/Banafo preview support uses separately licensed upstream components and model files. Missing public Community preview models are downloaded automatically from Hugging Face when realtime preview starts. This repository's MIT license does not relicense Kroko/Banafo assets; before downloading, bundling, or deploying them, review and respect the current terms from [Kroko by Banafo](https://kroko.ai/), the [Banafo/Kroko-ASR model card](https://huggingface.co/Banafo/Kroko-ASR), and the [kroko-ai/kroko-onnx repository](https://github.com/kroko-ai/kroko-onnx).
28
+
29
+ ## Start Here
30
+
31
+ For a guided setup, install the lightweight CLI and let it inspect the machine:
32
+
33
+ ```powershell
34
+ pip install whospeaks
35
+ whospeaks
36
+ ```
37
+
38
+ The `whospeaks` starter shows the current component state and gives direct controls for language, realtime text, provider quality, backend URLs, ASR runtime, browser port, diagnostics, installation, exact launch-command printing, and launching the browser UI.
39
+
40
+ The short `whospeaks` command is a setup and launcher wrapper. It saves a small profile, runs doctor checks, and expands that profile into the longer `whospeaks-window ...` browser-server command when you launch.
41
+
42
+ For a Linux container server:
43
+
44
+ ```bash
45
+ docker build -t whospeaks:local .
46
+ docker run --rm --name whospeaks -p 8796:8796 -v whospeaks-data:/data -v whospeaks-models:/models whospeaks:local
47
+ ```
48
+
49
+ See [Docker](docs/docker.md) for the full build/run path and volume notes.
50
+
51
+ For a manual full working setup, follow these in order:
52
+
53
+ 1. [Installation](docs/installation.md): install the Windows controller.
54
+ 2. [External ASR and embeddings servers](docs/external-servers.md): set up the Linux GPU services.
55
+ 3. [Quickstart](docs/quickstart.md): verify a local or remote smoke run, then run the tuned provider stack.
56
+
57
+ ## Documentation
58
+
59
+ | Topic | Document |
60
+ | --- | --- |
61
+ | Documentation map | [docs/index.md](docs/index.md) |
62
+ | Product overview and use cases | [docs/overview.md](docs/overview.md) |
63
+ | Installation | [docs/installation.md](docs/installation.md) |
64
+ | macOS setup | [docs/macos-setup.md](docs/macos-setup.md) |
65
+ | Quickstart | [docs/quickstart.md](docs/quickstart.md) |
66
+ | Live window workflow | [docs/live-window-workflow.md](docs/live-window-workflow.md) |
67
+ | Meeting intelligence server | [docs/meeting-intelligence-server.md](docs/meeting-intelligence-server.md) |
68
+ | Speaker libraries | [docs/speaker-libraries.md](docs/speaker-libraries.md) |
69
+ | External ASR and embeddings servers | [docs/external-servers.md](docs/external-servers.md) |
70
+ | Docker server image | [docs/docker.md](docs/docker.md) |
71
+ | Configuration guide | [docs/configuration.md](docs/configuration.md) |
72
+ | Technical description | [docs/technical-description.md](docs/technical-description.md) |
73
+ | Technical architecture | [docs/architecture.md](docs/architecture.md) |
74
+ | Validation and scoring | [docs/validation-and-scoring.md](docs/validation-and-scoring.md) |
75
+ | Modal deployment | [docs/modal-deployment.md](docs/modal-deployment.md) |
76
+ | Troubleshooting | [docs/troubleshooting.md](docs/troubleshooting.md) |
77
+ | Development workflow | [docs/development.md](docs/development.md) |
@@ -0,0 +1,146 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "whospeaks"
7
+ version = "0.0.1"
8
+ description = "Local speaker diarization and realtime voice-labeling tools."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ dependencies = []
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/KoljaB/WhoSpeaksLive"
16
+ Repository = "https://github.com/KoljaB/WhoSpeaksLive"
17
+ Documentation = "https://github.com/KoljaB/WhoSpeaksLive/tree/master/docs"
18
+
19
+ [project.optional-dependencies]
20
+ controller = [
21
+ "av>=11.0.0",
22
+ "emoji>=2.15.0",
23
+ "librosa>=0.10.1",
24
+ "nltk>=3.9.4",
25
+ "numpy>=2,<3",
26
+ "onnxruntime>=1.17",
27
+ "soundfile>=0.12.1",
28
+ "yt-dlp>=2024.3.10",
29
+ ]
30
+ local = [
31
+ "av>=11.0.0",
32
+ "emoji>=2.15.0",
33
+ "espnet==202511",
34
+ "espnet-model-zoo==0.1.7",
35
+ "faster-whisper>=1.2.1",
36
+ "huggingface-hub>=0.34,<1",
37
+ "librosa>=0.10.1",
38
+ "matplotlib>=3.8",
39
+ "modelscope>=1.14",
40
+ "nltk>=3.9.4",
41
+ "numpy>=2,<3",
42
+ "onnxruntime>=1.17",
43
+ "pyannote.audio>=3.1,<4",
44
+ "resemblyzer>=0.1.4",
45
+ "soundfile>=0.12.1",
46
+ "speechbrain>=1.0.0",
47
+ "stanza>=1.13.0",
48
+ "torch>=2.2",
49
+ "torchaudio>=2.2",
50
+ "transformers>=4.39,<5",
51
+ "yt-dlp>=2024.3.10",
52
+ ]
53
+ preview = [
54
+ "webrtcvad==2.0.10",
55
+ ]
56
+ server = [
57
+ "av>=11.0.0",
58
+ "espnet==202511",
59
+ "espnet-model-zoo==0.1.7",
60
+ "fastapi>=0.110",
61
+ "faster-whisper>=1.2.1",
62
+ "huggingface-hub>=0.34,<1",
63
+ "librosa>=0.10.1",
64
+ "matplotlib>=3.8",
65
+ "modelscope>=1.14",
66
+ "numpy>=2,<3",
67
+ "onnxruntime>=1.17",
68
+ "pyannote.audio>=3.1,<4",
69
+ "python-multipart>=0.0.9",
70
+ "resemblyzer>=0.1.4",
71
+ "soundfile>=0.12.1",
72
+ "speechbrain>=1.0.0",
73
+ "torch>=2.2",
74
+ "torchaudio>=2.2",
75
+ "transformers>=4.39,<5",
76
+ "uvicorn[standard]>=0.29",
77
+ ]
78
+ complete = [
79
+ "av>=11.0.0",
80
+ "emoji>=2.15.0",
81
+ "espnet==202511",
82
+ "espnet-model-zoo==0.1.7",
83
+ "faster-whisper>=1.2.1",
84
+ "huggingface-hub>=0.34,<1",
85
+ "librosa>=0.10.1",
86
+ "matplotlib>=3.8",
87
+ "modelscope>=1.14",
88
+ "nltk>=3.9.4",
89
+ "numpy>=2,<3",
90
+ "onnxruntime>=1.17",
91
+ "pyannote.audio>=3.1,<4",
92
+ "resemblyzer>=0.1.4",
93
+ "soundfile>=0.12.1",
94
+ "speechbrain>=1.0.0",
95
+ "stanza>=1.13.0",
96
+ "torch>=2.2",
97
+ "torchaudio>=2.2",
98
+ "transformers>=4.39,<5",
99
+ "yt-dlp>=2024.3.10",
100
+ ]
101
+ all = [
102
+ "av>=11.0.0",
103
+ "emoji>=2.15.0",
104
+ "espnet==202511",
105
+ "espnet-model-zoo==0.1.7",
106
+ "faster-whisper>=1.2.1",
107
+ "huggingface-hub>=0.34,<1",
108
+ "librosa>=0.10.1",
109
+ "matplotlib>=3.8",
110
+ "modelscope>=1.14",
111
+ "nltk>=3.9.4",
112
+ "numpy>=2,<3",
113
+ "onnxruntime>=1.17",
114
+ "pyannote.audio>=3.1,<4",
115
+ "resemblyzer>=0.1.4",
116
+ "soundfile>=0.12.1",
117
+ "speechbrain>=1.0.0",
118
+ "stanza>=1.13.0",
119
+ "torch>=2.2",
120
+ "torchaudio>=2.2",
121
+ "transformers>=4.39,<5",
122
+ "fastapi>=0.110",
123
+ "python-multipart>=0.0.9",
124
+ "uvicorn[standard]>=0.29",
125
+ "yt-dlp>=2024.3.10",
126
+ "webrtcvad==2.0.10",
127
+ ]
128
+
129
+ [project.scripts]
130
+ whospeaks = "whospeaks_cli.main:main"
131
+ whospeaks-window = "window.youtube_window_diarize_gui:main"
132
+ whospeaks-realtime = "realtime.realtime_speakerdiarize:main"
133
+ whospeaks-filefeed-replay = "replay.youtube_local_filefeed_replay:main"
134
+ whospeaks-embedding-benchmark = "embeddings.benchmark_voice_embeddings:main"
135
+ whospeaks-browser-live-eval = "window.browser_live_speaker_eval:main"
136
+ whospeaks-meeting-intelligence = "window.meeting_intelligence_server:main"
137
+
138
+ [tool.setuptools]
139
+ include-package-data = true
140
+
141
+ [tool.setuptools.packages.find]
142
+ where = ["src", "vendor"]
143
+
144
+ [tool.setuptools.package-data]
145
+ stream2sentence = ["data/*.json"]
146
+ RealtimeSTT = ["assets/**/*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Shared audio and serialization helpers."""
@@ -0,0 +1,218 @@
1
+ """Shared audio, numeric, and JSON helpers for diarization scripts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ import wave
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import numpy as np
12
+
13
+ SAMPLE_RATE = 16000
14
+ INT16_MAX_ABS_VALUE = 32767.0
15
+
16
+
17
+ def json_dumps(data: dict[str, Any]) -> str:
18
+ return json.dumps(data, ensure_ascii=False, separators=(",", ":"), default=str)
19
+
20
+
21
+ def sigmoid(value: float) -> float:
22
+ return 1.0 / (1.0 + math.exp(-max(-60.0, min(60.0, value))))
23
+
24
+
25
+ def clamp01(value: float) -> float:
26
+ return max(0.0, min(1.0, value))
27
+
28
+
29
+ def audio_to_float_mono(value: Any) -> np.ndarray:
30
+ if value is None:
31
+ return np.empty(0, dtype=np.float32)
32
+
33
+ if isinstance(value, np.ndarray):
34
+ audio = value
35
+ elif isinstance(value, (bytes, bytearray, memoryview)):
36
+ raw = bytes(value)
37
+ if len(raw) % 4 == 0:
38
+ audio = np.frombuffer(raw, dtype=np.float32)
39
+ elif len(raw) % 2 == 0:
40
+ audio = np.frombuffer(raw, dtype=np.int16).astype(np.float32) / INT16_MAX_ABS_VALUE
41
+ else:
42
+ audio = np.empty(0, dtype=np.float32)
43
+ else:
44
+ audio = np.asarray(value)
45
+
46
+ audio = np.asarray(audio)
47
+ if audio.ndim > 1:
48
+ audio = audio.mean(axis=1)
49
+ if audio.dtype.kind in {"i", "u"}:
50
+ audio = audio.astype(np.float32) / INT16_MAX_ABS_VALUE
51
+ else:
52
+ audio = audio.astype(np.float32, copy=False)
53
+ audio = np.nan_to_num(audio, nan=0.0, posinf=0.0, neginf=0.0)
54
+ return np.clip(audio.reshape(-1), -1.0, 1.0).astype(np.float32)
55
+
56
+
57
+ def trim_silence(audio: np.ndarray, sample_rate: int = SAMPLE_RATE) -> np.ndarray:
58
+ audio = np.asarray(audio, dtype=np.float32).reshape(-1)
59
+ if len(audio) < int(sample_rate * 0.25):
60
+ return audio
61
+
62
+ frame = max(1, int(sample_rate * 0.03))
63
+ hop = max(1, int(sample_rate * 0.01))
64
+ if len(audio) < frame:
65
+ return audio
66
+
67
+ rms = []
68
+ for start in range(0, max(1, len(audio) - frame + 1), hop):
69
+ chunk = audio[start:start + frame]
70
+ rms.append(float(np.sqrt(np.mean(chunk * chunk) + 1e-12)))
71
+ if not rms:
72
+ return audio
73
+
74
+ peak = max(rms)
75
+ threshold = max(peak * 0.08, 0.003)
76
+ active = [index for index, value in enumerate(rms) if value >= threshold]
77
+ if not active:
78
+ return audio
79
+
80
+ pad = int(sample_rate * 0.10)
81
+ start = max(0, active[0] * hop - pad)
82
+ end = min(len(audio), active[-1] * hop + frame + pad)
83
+ return audio[start:end]
84
+
85
+
86
+ def pad_audio(audio: np.ndarray, minimum_seconds: float, sample_rate: int = SAMPLE_RATE) -> np.ndarray:
87
+ minimum_samples = int(max(0.0, minimum_seconds) * sample_rate)
88
+ if len(audio) >= minimum_samples:
89
+ return audio
90
+ return np.pad(audio, (0, minimum_samples - len(audio))).astype(np.float32)
91
+
92
+
93
+ def write_wav(path: Path, audio: np.ndarray, sample_rate: int = SAMPLE_RATE) -> None:
94
+ path.parent.mkdir(parents=True, exist_ok=True)
95
+ audio = np.asarray(audio, dtype=np.float32).reshape(-1)
96
+ audio_int16 = (np.clip(audio, -1.0, 1.0) * INT16_MAX_ABS_VALUE).astype(np.int16)
97
+ with wave.open(str(path), "wb") as wav_file:
98
+ wav_file.setnchannels(1)
99
+ wav_file.setsampwidth(2)
100
+ wav_file.setframerate(sample_rate)
101
+ wav_file.writeframes(audio_int16.tobytes())
102
+
103
+
104
+ def _audio_frame_to_mono_float(frame: Any) -> np.ndarray:
105
+ array = np.asarray(frame.to_ndarray())
106
+ if array.ndim > 1:
107
+ if array.shape[0] <= array.shape[-1]:
108
+ array = array.mean(axis=0)
109
+ else:
110
+ array = array.mean(axis=1)
111
+ return audio_to_float_mono(array)
112
+
113
+
114
+ def _load_audio_file_with_av(path: Path, sample_rate: int) -> tuple[np.ndarray, int]:
115
+ import av
116
+ from av.audio.resampler import AudioResampler
117
+
118
+ chunks: list[np.ndarray] = []
119
+ with av.open(str(path)) as container:
120
+ stream = next((candidate for candidate in container.streams if candidate.type == "audio"), None)
121
+ if stream is None:
122
+ raise ValueError(f"No audio stream found in {path}")
123
+ resampler = AudioResampler(format="flt", layout="mono", rate=sample_rate)
124
+ for frame in container.decode(stream):
125
+ resampled = resampler.resample(frame)
126
+ if resampled is None:
127
+ continue
128
+ if not isinstance(resampled, list):
129
+ resampled = [resampled]
130
+ chunks.extend(_audio_frame_to_mono_float(item) for item in resampled)
131
+ try:
132
+ flushed = resampler.resample(None)
133
+ except Exception:
134
+ flushed = []
135
+ if flushed is not None:
136
+ if not isinstance(flushed, list):
137
+ flushed = [flushed]
138
+ chunks.extend(_audio_frame_to_mono_float(item) for item in flushed)
139
+ if not chunks:
140
+ raise ValueError(f"No audio samples decoded from {path}")
141
+ return np.concatenate(chunks).astype(np.float32), sample_rate
142
+
143
+
144
+ def _load_audio_file_with_librosa(path: Path, sample_rate: int) -> tuple[np.ndarray, int]:
145
+ import librosa
146
+
147
+ audio, source_rate = librosa.load(str(path), sr=sample_rate, mono=True, dtype=np.float32)
148
+ audio = np.nan_to_num(audio, nan=0.0, posinf=0.0, neginf=0.0)
149
+ return np.asarray(audio, dtype=np.float32), int(source_rate or sample_rate)
150
+
151
+
152
+ def load_audio_file(path: Path, sample_rate: int = SAMPLE_RATE) -> tuple[np.ndarray, int]:
153
+ import soundfile as sf
154
+
155
+ try:
156
+ audio, source_rate = sf.read(str(path), dtype="float32", always_2d=False)
157
+ except Exception as soundfile_error:
158
+ for loader in (_load_audio_file_with_av, _load_audio_file_with_librosa):
159
+ try:
160
+ return loader(path, sample_rate)
161
+ except ModuleNotFoundError:
162
+ continue
163
+ except Exception:
164
+ continue
165
+ raise soundfile_error
166
+ if getattr(audio, "ndim", 1) > 1:
167
+ audio = audio.mean(axis=1)
168
+ if source_rate != sample_rate:
169
+ try:
170
+ import librosa
171
+
172
+ audio = librosa.resample(audio, orig_sr=source_rate, target_sr=sample_rate)
173
+ except ModuleNotFoundError:
174
+ import torch
175
+ import torchaudio.functional as audio_functional
176
+
177
+ tensor = torch.from_numpy(np.asarray(audio, dtype=np.float32))
178
+ audio = audio_functional.resample(
179
+ tensor,
180
+ orig_freq=int(source_rate),
181
+ new_freq=int(sample_rate),
182
+ ).cpu().numpy()
183
+ source_rate = sample_rate
184
+ audio = np.nan_to_num(audio, nan=0.0, posinf=0.0, neginf=0.0)
185
+ return np.asarray(audio, dtype=np.float32), source_rate
186
+
187
+
188
+ def normalize_vector(value: Any) -> np.ndarray:
189
+ if hasattr(value, "detach"):
190
+ value = value.detach().cpu().numpy()
191
+ elif hasattr(value, "cpu") and hasattr(value, "numpy"):
192
+ value = value.cpu().numpy()
193
+ vector = np.asarray(value, dtype=np.float32).reshape(-1)
194
+ vector = np.nan_to_num(vector, nan=0.0, posinf=0.0, neginf=0.0)
195
+ norm = float(np.linalg.norm(vector))
196
+ if norm <= 0.0:
197
+ raise ValueError("Embedding provider returned an empty vector.")
198
+ return (vector / norm).astype(np.float32)
199
+
200
+
201
+ def cosine_similarity(left: np.ndarray, right: np.ndarray) -> float:
202
+ if left.shape != right.shape:
203
+ raise ValueError(f"Embedding shape mismatch: {left.shape} vs {right.shape}")
204
+ denom = float(np.linalg.norm(left) * np.linalg.norm(right))
205
+ if denom <= 0.0:
206
+ return 0.0
207
+ return float(np.dot(left, right) / denom)
208
+
209
+
210
+ def softmax(values: list[float], temperature: float) -> list[float]:
211
+ if not values:
212
+ return []
213
+ if temperature <= 0:
214
+ raise ValueError("Softmax temperature must be greater than zero.")
215
+ max_value = max(values)
216
+ exps = [math.exp((value - max_value) / temperature) for value in values]
217
+ total = sum(exps)
218
+ return [value / total for value in exps]