Compare commits

...

No commits in common. "ff7ce42a7f71e96c031b5c74c9f12da5c9fcf8f5" and "56e8216a1d9a921ab196d80236dfb302c2b2fe42" have entirely different histories.

9 changed files with 2 additions and 167 deletions

11
.gitignore vendored
View File

@ -1,11 +0,0 @@
venv/
logs/
__pycache__/
*.pyc
*.pid
*.log
ahserver/
appPublic/
longtasks/
sqlor/
wwwroot/

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# voiceprint

View File

@ -1,12 +0,0 @@
def extract_embedding(audio_path):
signal = MODEL.load_audio(audio_path)
if isinstance(signal, tuple):
signal = signal[0]
signal = signal.unsqueeze(0).to(DEVICE)
with torch.no_grad():
emb = MODEL.encode_batch(signal)
return emb.squeeze().cpu().numpy().tolist()
def verify_speakers(audio_path, ref_path):
score, pred = MODEL.verify_files(audio_path, ref_path)
return float(score), bool(pred)

View File

@ -1,33 +0,0 @@
{
"device": "cuda:1",
"redis_url": "redis://127.0.0.1:6379",
"worker_cnt": 2,
"logger": {
"name": "voiceprint",
"levelname": "info",
"logfile": "$[workdir]$/logs/voiceprint.log"
},
"website": {
"paths": [
["$[workdir]$/wwwroot", ""]
],
"client_max_size": 10000,
"host": "0.0.0.0",
"port": 9087,
"coding": "utf-8",
"indexes": ["index.html"],
"startswiths": [
{"leading": "/api/voiceprint-submit", "registerfunction": "voiceprint_submit"},
{"leading": "/api/voiceprint-status", "registerfunction": "voiceprint_status"},
{"leading": "/api/voiceprint/extract", "registerfunction": "voiceprint_extract"},
{"leading": "/api/voiceprint/verify", "registerfunction": "voiceprint_verify"}
],
"processors": [
[".tmpl", "tmpl"],
[".ui", "bui"],
[".dspy", "dspy"]
],
"session_max_time": 3000,
"session_issue_time": 2500
}
}

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
"""Download ECAPA-TDNN model via ModelScope to /share/models/ecapa-tdnn/"""
import os, sys, time
MODEL_ID = 'iic/speech_ecapa-tdnn_sv_en_voxceleb_16k'
TARGET = '/share/models/ecapa-tdnn'
os.makedirs(TARGET, exist_ok=True)
# Try ModelScope first
for attempt in range(3):
try:
from modelscope import snapshot_download
print(f"Attempt {attempt+1}: ModelScope download...")
snapshot_download(MODEL_ID, local_dir=TARGET)
print("SUCCESS via ModelScope")
sys.exit(0)
except Exception as e:
print(f"ModelScope failed: {e}")
time.sleep(10)
# Fallback: try HF mirror
print("Trying HF mirror...")
os.system(f'export HF_ENDPOINT=https://hf-mirror.com && python3 -c "from huggingface_hub import snapshot_download; snapshot_download(\'speechbrain/spkrec-ecapa-voxceleb\', local_dir=\'{TARGET}\')"')

View File

@ -1,8 +0,0 @@
#!/bin/bash
cd "$(dirname "$0")"
mkdir -p logs
setsid python3 app/voiceprint_app.py -p 9087 >> logs/voiceprint.log 2>&1 &
echo $! > voiceprint.pid
echo "Voiceprint PID=$(cat voiceprint.pid)"
sleep 3
curl -s http://localhost:9087/api/health && echo "" || echo "(checking...)"

View File

@ -1,9 +0,0 @@
#!/bin/bash
cd "$(dirname "$0")"
if [ -f voiceprint.pid ]; then
kill $(cat voiceprint.pid) 2>/dev/null
rm -f voiceprint.pid
echo "Voiceprint stopped"
else
pkill -f voiceprint_app.py 2>/dev/null && echo "Voiceprint stopped (via pkill)"
fi

View File

@ -1,7 +0,0 @@
from ahserver.serverenv import ServerEnv
from vp.engine import VoiceprintEngine
def load_voiceprint():
env = ServerEnv()
env.voiceprint = VoiceprintEngine()

View File

@ -1,63 +0,0 @@
"""ECAPA-TDNN Voiceprint Engine — loads model, processes extract/verify tasks."""
import torch
from longtasks.longtasks import LongTasks
from appPublic.worker import awaitify
from appPublic.jsonConfig import getConfig
from appPublic.log import debug
from speechbrain.inference.speaker import SpeakerRecognition
import numpy as np
class VoiceprintEngine(LongTasks):
def __init__(self):
self.config = getConfig()
super().__init__(self.config.redis_url, 'voiceprint', worker_cnt=self.config.worker_cnt)
self.load_model()
def load_model(self):
device = self.config.device
debug(f'loading ECAPA-TDNN on {device}...')
self.verifier = SpeakerRecognition.from_hparams(
source="speechbrain/spkrec-ecapa-voxceleb",
savedir="/share/models/ecapa-tdnn",
run_opts={"device": device}
)
debug('ECAPA-TDNN loaded')
async def process_task(self, payload, workerid=None):
task_type = payload.get('task_type', 'extract')
audio_file = payload.get('audio_file', '')
if not audio_file:
return {'status': 'FAILED', 'result': 'missing audio_file'}
if task_type == 'extract':
f = awaitify(self._extract)
return await f(audio_file)
elif task_type == 'verify':
ref_file = payload.get('reference_file', '')
if not ref_file:
return {'status': 'FAILED', 'result': 'missing reference_file'}
f = awaitify(self._verify)
return await f(audio_file, ref_file)
return {'status': 'FAILED', 'result': f'unknown task_type: {task_type}'}
def _extract(self, audio_path):
signal = self.verifier.load_audio(audio_path, 16000)
t = torch.tensor(signal).unsqueeze(0).to(self.config.device)
emb = self.verifier.encode_batch(t)
vec = emb.squeeze().cpu().numpy().tolist()
return {
'status': 'SUCCEEDED',
'embedding': vec,
'embedding_dim': len(vec),
'usage': {'audio_duration': round(len(signal) / 16000, 2)},
}
def _verify(self, audio_path, ref_path):
score, pred = self.verifier.verify_files(audio_path, ref_path)
return {
'status': 'SUCCEEDED',
'similarity': round(float(score), 4),
'is_same_speaker': bool(pred),
'usage': {},
}