speaker-detector 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- speaker_detector/constants.py +10 -0
- speaker_detector/core.py +2 -0
- speaker_detector/server.py +23 -10
- speaker_detector/speaker_state.py +103 -0
- {speaker_detector-0.1.6.dist-info → speaker_detector-0.1.7.dist-info}/METADATA +2 -2
- {speaker_detector-0.1.6.dist-info → speaker_detector-0.1.7.dist-info}/RECORD +9 -8
- speaker_detector/state.py +0 -69
- {speaker_detector-0.1.6.dist-info → speaker_detector-0.1.7.dist-info}/WHEEL +0 -0
- {speaker_detector-0.1.6.dist-info → speaker_detector-0.1.7.dist-info}/entry_points.txt +0 -0
- {speaker_detector-0.1.6.dist-info → speaker_detector-0.1.7.dist-info}/top_level.txt +0 -0
speaker_detector/core.py
CHANGED
speaker_detector/server.py
CHANGED
@@ -1,26 +1,36 @@
|
|
1
1
|
# ── Core Imports ─────────────────────────────────────────────
|
2
2
|
import os, signal, time
|
3
|
-
from flask import Flask, request, send_from_directory, send_file
|
3
|
+
from flask import Flask, request, send_from_directory, send_file, jsonify
|
4
4
|
from flask_cors import CORS
|
5
5
|
from pathlib import Path
|
6
6
|
|
7
7
|
# ── Internal Modules ─────────────────────────────────────────
|
8
|
-
from speaker_detector.state import stop_event
|
9
8
|
from speaker_detector.utils.paths import STATIC_DIR, INDEX_HTML, COMPONENTS_DIR
|
9
|
+
from speaker_detector.speaker_state import LISTENING_MODE, start_detection_loop, stop_detection_loop, stop_event
|
10
|
+
from speaker_detector.constants import BACKEND_VERSION
|
11
|
+
|
12
|
+
|
10
13
|
|
11
14
|
# ── App Setup ────────────────────────────────────────────────
|
12
15
|
app = Flask(__name__, static_folder=str(STATIC_DIR))
|
13
|
-
CORS(app)
|
16
|
+
CORS(app, resources={r"/api/*": {"origins": "*"}}, supports_credentials=True)
|
14
17
|
|
15
18
|
|
16
19
|
# ── Routes ──────────────────────────────────────────────────
|
17
|
-
@app.
|
18
|
-
def
|
19
|
-
response
|
20
|
-
|
21
|
-
|
20
|
+
@app.route("/api/<path:dummy>", methods=["OPTIONS"])
|
21
|
+
def cors_preflight(dummy):
|
22
|
+
response = jsonify({"ok": True})
|
23
|
+
response.headers["Access-Control-Allow-Origin"] = "*"
|
24
|
+
response.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization"
|
25
|
+
response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
|
22
26
|
return response
|
23
27
|
|
28
|
+
|
29
|
+
|
30
|
+
@app.route("/api/version")
|
31
|
+
def get_version():
|
32
|
+
return jsonify({"version": BACKEND_VERSION})
|
33
|
+
|
24
34
|
@app.route("/")
|
25
35
|
def serve_index():
|
26
36
|
return send_file(INDEX_HTML)
|
@@ -47,22 +57,25 @@ def not_found(e):
|
|
47
57
|
|
48
58
|
# ── Route Registrations ─────────────────────────────────────
|
49
59
|
from speaker_detector.routes.index_routes import index_bp
|
50
|
-
from speaker_detector.routes.
|
60
|
+
from speaker_detector.routes.listening_mode_routes import listening_bp
|
51
61
|
from speaker_detector.routes.speaker_routes import speakers_bp
|
52
62
|
from speaker_detector.routes.background_routes import background_bp
|
53
63
|
from speaker_detector.routes.rebuild_routes import rebuild_bp
|
54
64
|
from speaker_detector.routes.identify_routes import identify_bp
|
55
65
|
from speaker_detector.routes.recordings_routes import recordings_bp
|
56
66
|
from speaker_detector.routes.meetings_routes import meetings_bp
|
67
|
+
from speaker_detector.routes.correction_routes import correction_bp
|
68
|
+
|
57
69
|
|
58
70
|
app.register_blueprint(index_bp)
|
59
|
-
app.register_blueprint(
|
71
|
+
app.register_blueprint(listening_bp)
|
60
72
|
app.register_blueprint(speakers_bp)
|
61
73
|
app.register_blueprint(background_bp)
|
62
74
|
app.register_blueprint(rebuild_bp)
|
63
75
|
app.register_blueprint(identify_bp)
|
64
76
|
app.register_blueprint(recordings_bp)
|
65
77
|
app.register_blueprint(meetings_bp)
|
78
|
+
app.register_blueprint(correction_bp)
|
66
79
|
|
67
80
|
# ── Interrupt Handler ───────────────────────────────────────
|
68
81
|
def handle_interrupt(sig, frame):
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# speaker_detector/speaker_state.py
|
2
|
+
|
3
|
+
import threading
|
4
|
+
import tempfile
|
5
|
+
import time
|
6
|
+
import sounddevice as sd
|
7
|
+
import soundfile as sf
|
8
|
+
from datetime import datetime
|
9
|
+
|
10
|
+
from speaker_detector.constants import DEFAULT_CONFIDENCE_THRESHOLD, DEFAULT_INTERVAL_MS
|
11
|
+
from speaker_detector.core import identify_speaker
|
12
|
+
|
13
|
+
# ── Shared Speaker Detection State ─────────────────────────────
|
14
|
+
|
15
|
+
current_speaker_state = {
|
16
|
+
"speaker": None,
|
17
|
+
"confidence": None,
|
18
|
+
"is_speaking": False,
|
19
|
+
}
|
20
|
+
|
21
|
+
def get_current_speaker():
|
22
|
+
return current_speaker_state
|
23
|
+
|
24
|
+
LISTENING_MODE = {"mode": "off"} # Options: "off", "single", "multi"
|
25
|
+
DETECTION_INTERVAL_MS = DEFAULT_INTERVAL_MS
|
26
|
+
DETECTION_THRESHOLD = DEFAULT_CONFIDENCE_THRESHOLD
|
27
|
+
|
28
|
+
MIC_AVAILABLE = True
|
29
|
+
stop_event = threading.Event()
|
30
|
+
detection_thread = None
|
31
|
+
|
32
|
+
# ── Background Detection Loop ─────────────────────────────
|
33
|
+
|
34
|
+
def detection_loop():
|
35
|
+
global MIC_AVAILABLE
|
36
|
+
|
37
|
+
samplerate = 16000
|
38
|
+
duration = 2 # seconds
|
39
|
+
|
40
|
+
while not stop_event.is_set():
|
41
|
+
try:
|
42
|
+
audio = sd.rec(int(duration * samplerate), samplerate=samplerate, channels=1, dtype="int16")
|
43
|
+
sd.wait()
|
44
|
+
|
45
|
+
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
46
|
+
sf.write(tmp.name, audio, samplerate)
|
47
|
+
MIC_AVAILABLE = True
|
48
|
+
|
49
|
+
speaker, conf = identify_speaker(tmp.name, threshold=DETECTION_THRESHOLD)
|
50
|
+
current_speaker_state["speaker"] = speaker
|
51
|
+
current_speaker_state["confidence"] = conf
|
52
|
+
current_speaker_state["is_speaking"] = speaker != "unknown" and conf >= DETECTION_THRESHOLD
|
53
|
+
|
54
|
+
print(f"{datetime.now().strftime('%H:%M:%S')} 🧠 Detected: {speaker} ({conf:.2f})")
|
55
|
+
|
56
|
+
except Exception as e:
|
57
|
+
print(f"❌ Detection loop error: {e}")
|
58
|
+
current_speaker_state["speaker"] = None
|
59
|
+
current_speaker_state["confidence"] = None
|
60
|
+
current_speaker_state["is_speaking"] = False
|
61
|
+
if isinstance(e, sd.PortAudioError):
|
62
|
+
MIC_AVAILABLE = False
|
63
|
+
|
64
|
+
time.sleep(DETECTION_INTERVAL_MS / 1000.0)
|
65
|
+
|
66
|
+
# ── Lifecycle Control ─────────────────────────────────────
|
67
|
+
|
68
|
+
def start_detection_loop():
|
69
|
+
global detection_thread
|
70
|
+
if detection_thread and detection_thread.is_alive():
|
71
|
+
return
|
72
|
+
print("🔁 Starting detection loop...")
|
73
|
+
stop_event.clear()
|
74
|
+
detection_thread = threading.Thread(target=detection_loop, daemon=True)
|
75
|
+
detection_thread.start()
|
76
|
+
|
77
|
+
def stop_detection_loop():
|
78
|
+
if detection_thread and detection_thread.is_alive():
|
79
|
+
print("⏹️ Stopping detection loop...")
|
80
|
+
stop_event.set()
|
81
|
+
|
82
|
+
def get_active_speaker():
|
83
|
+
if LISTENING_MODE["mode"] == "off":
|
84
|
+
return {
|
85
|
+
"speaker": None,
|
86
|
+
"confidence": None,
|
87
|
+
"is_speaking": False,
|
88
|
+
"status": "disabled"
|
89
|
+
}
|
90
|
+
if not MIC_AVAILABLE:
|
91
|
+
return {
|
92
|
+
"speaker": None,
|
93
|
+
"confidence": None,
|
94
|
+
"is_speaking": False,
|
95
|
+
"status": "mic unavailable"
|
96
|
+
}
|
97
|
+
|
98
|
+
return {
|
99
|
+
"speaker": current_speaker_state.get("speaker"),
|
100
|
+
"confidence": current_speaker_state.get("confidence"),
|
101
|
+
"is_speaking": current_speaker_state.get("is_speaking", False),
|
102
|
+
"status": "listening"
|
103
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: speaker-detector
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.7
|
4
4
|
Summary: A CLI + Web tool for speaker enrollment and identification using SpeechBrain.
|
5
5
|
Author-email: Lara Whybrow <lara.whybrow@gmail.com>
|
6
6
|
License: MIT
|
@@ -29,7 +29,7 @@ Requires-Dist: sounddevice
|
|
29
29
|
Requires-Dist: soundfile
|
30
30
|
Requires-Dist: pydub
|
31
31
|
|
32
|
-
|
32
|
+
23/07/2025 - Lara Whybrow, Creator - Works as far as I can see, but now we have a robust set of tools for training and managing the data and can focus now on refinement and bug fixing.
|
33
33
|
|
34
34
|
# speaker-detector 🎙️
|
35
35
|
|
@@ -1,9 +1,10 @@
|
|
1
1
|
speaker_detector/__main__.py,sha256=EClCwCzb6h6YBpt0hrnG4h0mlNhNePyg_xBNNSVm1os,65
|
2
2
|
speaker_detector/cli.py,sha256=TxJhu3Pjhg41tkcu--aLtn0vZwBYyoVEef10zqSBzig,2619
|
3
|
-
speaker_detector/
|
3
|
+
speaker_detector/constants.py,sha256=DcD5Ys45loHOPmC1ORqXy2de4dYu9WsMn87FqUx1PDc,233
|
4
|
+
speaker_detector/core.py,sha256=HiMaCiIhlm6gteLxPjciyRtbKWpuB-R6F3LTCtsV-rw,5269
|
4
5
|
speaker_detector/server copy.py,sha256=A1WplNK8yGe9AnEjrSRqHO-uJJsMwIlvEzDhgu72XyY,10723
|
5
|
-
speaker_detector/server.py,sha256=
|
6
|
-
speaker_detector/
|
6
|
+
speaker_detector/server.py,sha256=pxEx0yty0C5tlEMDr_G_zAkM4af1WYuTJzBg0bhElRc,4075
|
7
|
+
speaker_detector/speaker_state.py,sha256=v_CfuRFjfSYCOb8VKsnmkJPbAX5AuH8xG2O7XTAywu8,3470
|
7
8
|
speaker_detector/model/ECAPA_TDNN.py,sha256=KB5T-ye4c9ZWgTgn_SMH-T_-qYSEHQJJtf3xHjsfNPk,19024
|
8
9
|
speaker_detector/model/classifier.ckpt,sha256=_Z42NP5ovQpCfJXjVMDGdzdPYrP0NORbeFmZUNhg1TU,5534328
|
9
10
|
speaker_detector/model/embedding_model.ckpt,sha256=BXXLZIRea5oQ25vLdNWsMrMmuNyQNSZx00Xi7j0BJqI,83316686
|
@@ -18,8 +19,8 @@ speaker_detector/web/static/scripts/loader copy.js,sha256=BwhTS_ulxb62cwF6qAk1ng
|
|
18
19
|
speaker_detector/web/static/scripts/loader.js,sha256=OWgmKfZ0E7bKaVPR47Q5aA-JKFeD3741K_wS_lqphz4,503
|
19
20
|
speaker_detector/web/static/scripts/script copy.js,sha256=LLcKKjTjXEy9yj5e4gQhgIsItRLYAJe_9V7mppyM8Bc,31494
|
20
21
|
speaker_detector/web/static/scripts/script.js,sha256=UyHWk1HrkWW6ZyxQYRLOPbtCMLHcBbNudMfTJ9k3neA,1023
|
21
|
-
speaker_detector-0.1.
|
22
|
-
speaker_detector-0.1.
|
23
|
-
speaker_detector-0.1.
|
24
|
-
speaker_detector-0.1.
|
25
|
-
speaker_detector-0.1.
|
22
|
+
speaker_detector-0.1.7.dist-info/METADATA,sha256=A7ixcI1t6vzezax94t5KWpwLEed8GB6Jl_hDz8RVwl8,5464
|
23
|
+
speaker_detector-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
24
|
+
speaker_detector-0.1.7.dist-info/entry_points.txt,sha256=2B30ee2cTyeeA49x_TBURl53bDRiLWGK3NWhb9rlK3s,63
|
25
|
+
speaker_detector-0.1.7.dist-info/top_level.txt,sha256=PJ5rfvd3GAbzMbc7-Fwhtufjf6HxzzTiiHociOy7RiM,17
|
26
|
+
speaker_detector-0.1.7.dist-info/RECORD,,
|
speaker_detector/state.py
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# speaker_detector/state.py
|
2
|
-
|
3
|
-
import threading
|
4
|
-
import tempfile
|
5
|
-
import time
|
6
|
-
import sounddevice as sd
|
7
|
-
import soundfile as sf
|
8
|
-
from datetime import datetime
|
9
|
-
from pathlib import Path
|
10
|
-
|
11
|
-
from speaker_detector.core import identify_speaker # ✅ safe import — no circular loop
|
12
|
-
|
13
|
-
# ── Global State ─────────────────────────────────────────────
|
14
|
-
current_speaker = {"speaker": None, "confidence": None}
|
15
|
-
LISTENING_MODE = {"mode": "off"} # Values: "off", "single", "multi"
|
16
|
-
DETECTION_INTERVAL_MS = 3000
|
17
|
-
DETECTION_THRESHOLD = 0.75
|
18
|
-
|
19
|
-
MIC_AVAILABLE = True
|
20
|
-
stop_event = threading.Event() # ✅ defined here, no self-import
|
21
|
-
detection_thread = None
|
22
|
-
|
23
|
-
# ── Background Detection Loop ────────────────────────────────
|
24
|
-
def detection_loop():
|
25
|
-
global MIC_AVAILABLE
|
26
|
-
|
27
|
-
samplerate = 16000
|
28
|
-
duration = 2
|
29
|
-
|
30
|
-
while not stop_event.is_set():
|
31
|
-
try:
|
32
|
-
audio = sd.rec(int(duration * samplerate), samplerate=samplerate, channels=1, dtype="int16")
|
33
|
-
sd.wait()
|
34
|
-
|
35
|
-
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
|
36
|
-
sf.write(tmp.name, audio, samplerate)
|
37
|
-
MIC_AVAILABLE = True
|
38
|
-
speaker, conf = identify_speaker(tmp.name, threshold=DETECTION_THRESHOLD)
|
39
|
-
current_speaker.update(speaker=speaker, confidence=conf)
|
40
|
-
print(f"{datetime.now().strftime('%H:%M:%S')} 🧠 Detected: {speaker} ({conf:.2f})")
|
41
|
-
except Exception as e:
|
42
|
-
print(f"❌ Detection loop error: {e}")
|
43
|
-
current_speaker.update(speaker=None, confidence=None)
|
44
|
-
if isinstance(e, sd.PortAudioError):
|
45
|
-
MIC_AVAILABLE = False
|
46
|
-
|
47
|
-
time.sleep(DETECTION_INTERVAL_MS / 1000.0)
|
48
|
-
|
49
|
-
# ── Control Functions ────────────────────────────────────────
|
50
|
-
def start_detection_loop():
|
51
|
-
global detection_thread
|
52
|
-
if detection_thread and detection_thread.is_alive():
|
53
|
-
return
|
54
|
-
print("🔁 Starting detection loop...")
|
55
|
-
stop_event.clear()
|
56
|
-
detection_thread = threading.Thread(target=detection_loop, daemon=True)
|
57
|
-
detection_thread.start()
|
58
|
-
|
59
|
-
def stop_detection_loop():
|
60
|
-
if detection_thread and detection_thread.is_alive():
|
61
|
-
print("⏹️ Stopping detection loop...")
|
62
|
-
stop_event.set()
|
63
|
-
|
64
|
-
def get_active_speaker():
|
65
|
-
if LISTENING_MODE["mode"] == "off":
|
66
|
-
return {"speaker": None, "confidence": None, "status": "disabled"}
|
67
|
-
if not MIC_AVAILABLE:
|
68
|
-
return {"speaker": None, "confidence": None, "status": "mic unavailable"}
|
69
|
-
return {**current_speaker, "status": "listening"}
|
File without changes
|
File without changes
|
File without changes
|