Files
LLM-infa/api_legacy/lib/diarize_embeddings.py
jze9 2697e01714 Рабочий пайплайн: YouTube -> транскрипция -> выжимка -> Postgres
- новый api/: /pipeline/process (yt-dlp, субтитры или Vosk, LLM/экстрактивная
  суммаризация), /videos, /llm; старый код перенесён в api_legacy/
- web/: Flet UI (flet 0.28.3 + flet-web, порт 8550)
- Dockerfile.api: ffmpeg слоем из mwader/static-ffmpeg, pip с кэш-маунтом
- requirements/pyproject: убраны moviepy, imageio-ffmpeg, битый asyncio
- README с инструкцией запуска и загрузкой Vosk-модели

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:12:46 +05:00

70 lines
2.4 KiB
Python

from pathlib import Path
from typing import List, Dict
def diarize_with_resemblyzer(wav_path: Path, window_s: float = 1.5, hop_s: float = 0.75, distance_threshold: float = 0.6) -> List[Dict]:
"""Lightweight embedding-based diarization using resemblyzer + sklearn.
Returns list of segments: {'start': float, 'end': float, 'speaker': 'spk_N'}
"""
try:
import numpy as np
import librosa
from resemblyzer import VoiceEncoder
from sklearn.cluster import AgglomerativeClustering
except Exception as e:
raise RuntimeError(f"Missing dependency for embedding diarization: {e}")
sr = 16000
wav, sr_loaded = librosa.load(str(wav_path), sr=sr)
n = wav.shape[0]
win = int(window_s * sr)
hop = int(hop_s * sr)
if n <= win:
# short file: embed whole
encoder = VoiceEncoder()
emb = encoder.embed_utterance(wav)
labels = [0]
centers = [ (0.0 + n / sr) / 2.0 ]
windows = [(0.0, n / sr)]
else:
encoder = VoiceEncoder()
embeddings = []
centers = []
windows = []
for start in range(0, n - win + 1, hop):
chunk = wav[start:start+win]
try:
e = encoder.embed_utterance(chunk)
except Exception:
# fallback: mean pooling
e = np.mean(chunk)
embeddings.append(e)
t0 = start / sr
t1 = (start + win) / sr
centers.append((t0 + t1) / 2.0)
windows.append((t0, t1))
X = np.vstack(embeddings)
# Agglomerative clustering with distance threshold
model = AgglomerativeClustering(n_clusters=None, distance_threshold=distance_threshold, affinity='euclidean', linkage='average')
labels = model.fit_predict(X)
# merge consecutive windows with same label into segments
segs = []
if len(labels) == 0:
return segs
cur_label = labels[0]
cur_start = windows[0][0]
cur_end = windows[0][1]
for i, lab in enumerate(labels[1:], start=1):
if lab == cur_label:
cur_end = windows[i][1]
else:
segs.append({"start": cur_start, "end": cur_end, "speaker": f"spk_{int(cur_label)+1}"})
cur_label = lab
cur_start = windows[i][0]
cur_end = windows[i][1]
segs.append({"start": cur_start, "end": cur_end, "speaker": f"spk_{int(cur_label)+1}"})
return segs