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.
- whospeaks-0.0.1/LICENSE +21 -0
- whospeaks-0.0.1/PKG-INFO +192 -0
- whospeaks-0.0.1/README.md +77 -0
- whospeaks-0.0.1/pyproject.toml +146 -0
- whospeaks-0.0.1/setup.cfg +4 -0
- whospeaks-0.0.1/src/common/__init__.py +1 -0
- whospeaks-0.0.1/src/common/audio_utils.py +218 -0
- whospeaks-0.0.1/src/common/pythonpath.py +36 -0
- whospeaks-0.0.1/src/embeddings/__init__.py +1 -0
- whospeaks-0.0.1/src/embeddings/benchmark_voice_embeddings.py +870 -0
- whospeaks-0.0.1/src/embeddings/build_live_sentence_embedding_corpus.py +415 -0
- whospeaks-0.0.1/src/embeddings/build_sentence_embedding_corpus.py +447 -0
- whospeaks-0.0.1/src/embeddings/embedding_providers.py +768 -0
- whospeaks-0.0.1/src/paths/__init__.py +57 -0
- whospeaks-0.0.1/src/realtime/__init__.py +1 -0
- whospeaks-0.0.1/src/realtime/realtime_capture.py +815 -0
- whospeaks-0.0.1/src/realtime/realtime_gui_html.py +792 -0
- whospeaks-0.0.1/src/realtime/realtime_server.py +127 -0
- whospeaks-0.0.1/src/realtime/realtime_speaker_engine.py +594 -0
- whospeaks-0.0.1/src/realtime/realtime_speakerdiarize.py +1461 -0
- whospeaks-0.0.1/src/realtime/realtime_transcript.py +326 -0
- whospeaks-0.0.1/src/replay/__init__.py +1 -0
- whospeaks-0.0.1/src/replay/youtube_local_filefeed_replay.py +344 -0
- whospeaks-0.0.1/src/speakers/__init__.py +1 -0
- whospeaks-0.0.1/src/speakers/realtime_speaker_memory.py +319 -0
- whospeaks-0.0.1/src/speakers/speaker_embedding_cluster.py +696 -0
- whospeaks-0.0.1/src/whospeaks_cli/__init__.py +7 -0
- whospeaks-0.0.1/src/whospeaks_cli/__main__.py +7 -0
- whospeaks-0.0.1/src/whospeaks_cli/main.py +2049 -0
- whospeaks-0.0.1/src/window/__init__.py +1 -0
- whospeaks-0.0.1/src/window/browser_live_speaker_eval.py +123 -0
- whospeaks-0.0.1/src/window/browser_live_speaker_scoring.py +471 -0
- whospeaks-0.0.1/src/window/event_client.py +110 -0
- whospeaks-0.0.1/src/window/fact_lens_sidecar.py +1240 -0
- whospeaks-0.0.1/src/window/language_config.py +236 -0
- whospeaks-0.0.1/src/window/live_speaker_probe_scoring.py +825 -0
- whospeaks-0.0.1/src/window/meeting_intelligence.py +625 -0
- whospeaks-0.0.1/src/window/meeting_intelligence_pipeline.py +948 -0
- whospeaks-0.0.1/src/window/meeting_intelligence_server.py +2920 -0
- whospeaks-0.0.1/src/window/modal_asr_server.py +159 -0
- whospeaks-0.0.1/src/window/modal_youtube_window_diarize_gui.py +341 -0
- whospeaks-0.0.1/src/window/public_events.py +281 -0
- whospeaks-0.0.1/src/window/review_flags.py +146 -0
- whospeaks-0.0.1/src/window/session_store.py +735 -0
- whospeaks-0.0.1/src/window/speaker_color_allocation.py +569 -0
- whospeaks-0.0.1/src/window/window_config.py +493 -0
- whospeaks-0.0.1/src/window/window_diarizer.py +5931 -0
- whospeaks-0.0.1/src/window/window_domain.py +107 -0
- whospeaks-0.0.1/src/window/window_events.py +69 -0
- whospeaks-0.0.1/src/window/window_gui_html.py +6031 -0
- whospeaks-0.0.1/src/window/window_media.py +75 -0
- whospeaks-0.0.1/src/window/window_preview.py +341 -0
- whospeaks-0.0.1/src/window/window_remote_asr.py +191 -0
- whospeaks-0.0.1/src/window/window_speaker_refinement.py +263 -0
- whospeaks-0.0.1/src/window/window_text.py +319 -0
- whospeaks-0.0.1/src/window/window_validation_replay.py +283 -0
- whospeaks-0.0.1/src/window/youtube_window_diarize_gui.py +2990 -0
- whospeaks-0.0.1/src/workers/__init__.py +1 -0
- whospeaks-0.0.1/src/workers/kroko_realtime_preview_worker.py +129 -0
- whospeaks-0.0.1/tests/test_audio_utils.py +37 -0
- whospeaks-0.0.1/tests/test_browser_audio_modes.py +36 -0
- whospeaks-0.0.1/tests/test_core_regressions.py +3594 -0
- whospeaks-0.0.1/tests/test_fact_lens_sidecar.py +168 -0
- whospeaks-0.0.1/tests/test_helper_pythonpath.py +35 -0
- whospeaks-0.0.1/tests/test_kroko_installer.py +88 -0
- whospeaks-0.0.1/tests/test_meeting_intelligence.py +204 -0
- whospeaks-0.0.1/tests/test_meeting_intelligence_pipeline.py +195 -0
- whospeaks-0.0.1/tests/test_meeting_intelligence_server.py +230 -0
- whospeaks-0.0.1/tests/test_review_corrections.py +576 -0
- whospeaks-0.0.1/tests/test_session_store.py +183 -0
- whospeaks-0.0.1/tests/test_whospeaks_cli.py +398 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/__init__.py +44 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/assets/__init__.py +1 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/assets/warmup_audio.wav +0 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/audio_input.py +242 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/audio_recorder.py +825 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/audio_recorder_client.py +987 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/__init__.py +3 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/audio_input_worker.py +359 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/initialization.py +664 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/lifecycle.py +273 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/manual_audio_input.py +159 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/preroll.py +434 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime.py +1460 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime_boundary_detector.py +559 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime_callbacks.py +23 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/realtime_text_stabilizer.py +1136 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/recorder_config.py +201 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/recording.py +479 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/recording_buffers.py +165 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/runtime.py +56 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/safepipe.py +240 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/shutdown.py +69 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/silero_vad.py +591 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/state.py +86 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/text_formatting.py +72 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/transcription.py +251 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/transcription_api.py +186 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/voice_activity.py +409 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/core/wakeword.py +223 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/install_kroko.py +801 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/__init__.py +26 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/_model_utils.py +110 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/base.py +167 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/cohere_transcribe_engine.py +39 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/factory.py +70 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/faster_whisper_engine.py +114 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/funasr_engine.py +347 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/granite_speech_engine.py +39 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/hf_transformers_engines.py +465 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/kroko_onnx_engine.py +813 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/moonshine_engine.py +45 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/omnilingual_asr_engine.py +481 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/openai_api_engine.py +29 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/openai_whisper_engine.py +120 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/parakeet_engine.py +153 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/qwen3_asr_engine.py +164 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/sherpa_onnx_engine.py +392 -0
- whospeaks-0.0.1/vendor/RealtimeSTT/transcription_engines/whisper_cpp_engine.py +113 -0
- whospeaks-0.0.1/vendor/RealtimeSTT_server/__init__.py +0 -0
- whospeaks-0.0.1/vendor/RealtimeSTT_server/install_packages.py +55 -0
- whospeaks-0.0.1/vendor/RealtimeSTT_server/stt_cli_client.py +285 -0
- whospeaks-0.0.1/vendor/RealtimeSTT_server/stt_server.py +921 -0
- whospeaks-0.0.1/vendor/remote_servers/faster-whisper-asr/asr_server.py +502 -0
- whospeaks-0.0.1/vendor/remote_servers/faster-whisper-asr/mlx_asr_server.py +219 -0
- whospeaks-0.0.1/vendor/remote_servers/faster-whisper-asr/test_parent_watchdog.py +49 -0
- whospeaks-0.0.1/vendor/remote_servers/voice-embeddings-server/embeddings_server.py +666 -0
- whospeaks-0.0.1/vendor/remote_servers/voice-embeddings-server/tools/benchmark_voice_embeddings.py +861 -0
- whospeaks-0.0.1/vendor/stream2sentence/__init__.py +14 -0
- whospeaks-0.0.1/vendor/stream2sentence/avoid_pause_words.py +65 -0
- whospeaks-0.0.1/vendor/stream2sentence/data/language_contexts.json +3092 -0
- whospeaks-0.0.1/vendor/stream2sentence/delimiter_ignore_prefixes.py +34 -0
- whospeaks-0.0.1/vendor/stream2sentence/quick_yield_boundary.py +2528 -0
- whospeaks-0.0.1/vendor/stream2sentence/stream2sentence.py +1038 -0
- whospeaks-0.0.1/vendor/stream2sentence/stream2sentence_time_based.py +241 -0
- whospeaks-0.0.1/whospeaks.egg-info/PKG-INFO +192 -0
- whospeaks-0.0.1/whospeaks.egg-info/SOURCES.txt +139 -0
- whospeaks-0.0.1/whospeaks.egg-info/dependency_links.txt +1 -0
- whospeaks-0.0.1/whospeaks.egg-info/entry_points.txt +8 -0
- whospeaks-0.0.1/whospeaks.egg-info/requires.txt +108 -0
- whospeaks-0.0.1/whospeaks.egg-info/top_level.txt +13 -0
whospeaks-0.0.1/LICENSE
ADDED
|
@@ -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.
|
whospeaks-0.0.1/PKG-INFO
ADDED
|
@@ -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 @@
|
|
|
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]
|