Files
local-transcriber/.scratch/diarization/bench_diar.py
T
Dmitriy DementievandClaude Opus 5 1cb6a36c92 chore(diarization): добавлена обвязка замеров в .scratch
- Зачем:
  - тикеты карты #10-#13 опираются на измерительную обвязку, которая до сих пор
    жила во временном каталоге сессии и исчезла бы вместе с ним.
- Что:
  - перенесены четыре скрипта разведки: ASR, один прогон диаризации, свип порога
    кластеризации и подсчёт чистоты ASR-сегментов.
  - общая часть вынесена в common.py: пути от корня репозитория вместо
    захардкоженных, конфигурация диаризатора, проверка наличия моделей.
  - починен замер пиковой памяти: нужен экспорт K32GetProcessMemoryInfo из
    kernel32 и явные argtypes, иначе дескриптор процесса уезжает 32-битным.
  - модели и выход замеров исключены из истории локальным .gitignore.
- Проверка:
  - export PYTHONIOENCODING=utf-8
  - uv run --with sherpa-onnx python .scratch/diarization/bench_diar.py "<запись>" 8 0.9

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:35:20 +03:00

88 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Один прогон диаризации: скорость, память, распределение по говорящим.
uv run --with sherpa-onnx python .scratch/diarization/bench_diar.py <файл> [потоки]
Сохраняет разметку в ``segments-<порог>.tsv`` рядом со скриптом — она нужна
тикету про проверку границ на слух и скрипту bench_conflict.py.
"""
from __future__ import annotations
import sys
import time
import numpy as np
from common import (
DEFAULT_THREADS,
DISCOVERY_THRESHOLD,
HERE,
SAMPLE_RATE,
load_audio,
make_diarizer,
peak_rss_mb,
)
def main(audio_path: str, threads: int, threshold: float) -> None:
print(f"файл: {audio_path}")
print(f"потоков: {threads}, порог кластеризации: {threshold}")
t0 = time.perf_counter()
samples = load_audio(audio_path)
t_decode = time.perf_counter() - t0
duration = len(samples) / SAMPLE_RATE
print(f"длительность: {duration / 60:.1f} мин ({duration:.0f} с)")
print(f"декодирование: {t_decode:.1f} с ({duration / t_decode:.0f}x RTF)")
t0 = time.perf_counter()
diarizer = make_diarizer(threshold=threshold, threads=threads)
print(f"инициализация моделей: {time.perf_counter() - t0:.1f} с")
progress = {"shown": 0.0}
t_start = time.perf_counter()
def on_progress(processed: int, total: int, _arg=None) -> int:
pct = processed / total * 100
if pct - progress["shown"] >= 20:
progress["shown"] = pct
print(f" ... {pct:.0f}% ({time.perf_counter() - t_start:.0f} с)", flush=True)
return 0
segments = diarizer.process(samples, callback=on_progress).sort_by_start_time()
t_diar = time.perf_counter() - t_start
speakers = sorted({s.speaker for s in segments})
speech = sum(s.end - s.start for s in segments)
rss = peak_rss_mb()
print()
print(f"ДИАРИЗАЦИЯ: {t_diar:.1f} с -> {duration / t_diar:.1f}x RTF")
print(f"пиковая память процесса: {rss:.0f} МБ" if rss else "память: снять не удалось")
print(f"спикеров: {len(speakers)}, интервалов: {len(segments)}")
print(f"речи: {speech / 60:.1f} мин ({speech / duration * 100:.0f}% файла)")
print()
print("распределение по говорящим:")
for spk in speakers:
own = [s for s in segments if s.speaker == spk]
total = sum(s.end - s.start for s in own)
median = np.median([s.end - s.start for s in own])
print(f" spk{spk:<3} {total / 60:6.1f} мин {len(own):4d} интерв. медиана {median:.1f} с")
out = HERE / f"segments-{threshold}.tsv"
out.write_text(
"\n".join(f"{s.start:.3f}\t{s.end:.3f}\t{s.speaker}" for s in segments),
encoding="utf-8",
)
print(f"\nразметка сохранена: {out.name}")
if __name__ == "__main__":
if len(sys.argv) < 2:
raise SystemExit(__doc__)
main(
sys.argv[1],
int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_THREADS,
float(sys.argv[3]) if len(sys.argv) > 3 else DISCOVERY_THRESHOLD,
)