Локальный FastAPI-сервис поверх GigaAM v3 и sherpa-onnx: приём аудио, очередь задач, разделение по говорящим, постобработка терминов. Доставка на Windows - ZIP со встроенным Python, без установки чего-либо. Обновление кода при запуске тянется из релизов Gitea: меняется только папка app, десятки килобайт вместо всего пакета. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
4.7 KiB
Python
113 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Скачивает модели распознавания и диаризации в папку models.
|
|
|
|
Запускается один раз после распаковки. Около 900 МБ.
|
|
"""
|
|
import os
|
|
import sys
|
|
import tarfile
|
|
from pathlib import Path
|
|
|
|
# Встраиваемая сборка Python идёт без хранилища сертификатов, поэтому urllib
|
|
# на HTTPS падает. Берём requests: он ходит через certifi, который уже в пакете.
|
|
import requests
|
|
|
|
BASE = Path(os.environ.get("TALKSCORE_ASR_HOME") or Path(__file__).resolve().parent)
|
|
MODELS = BASE / "models"
|
|
|
|
SEG_URL = ("https://github.com/k2-fsa/sherpa-onnx/releases/download/"
|
|
"speaker-segmentation-models/sherpa-onnx-pyannote-segmentation-3-0.tar.bz2")
|
|
EMB_URL = ("https://github.com/k2-fsa/sherpa-onnx/releases/download/"
|
|
"speaker-recongition-models/nemo_en_titanet_small.onnx")
|
|
|
|
|
|
def setup_console() -> None:
|
|
"""Windows-консоль по умолчанию не в UTF-8, иначе русский текст рассыпается."""
|
|
for stream in (sys.stdout, sys.stderr):
|
|
try:
|
|
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
except (AttributeError, ValueError):
|
|
pass
|
|
|
|
|
|
def download(url: str, dst: Path, label: str) -> None:
|
|
if dst.exists() and dst.stat().st_size > 0:
|
|
print(f" {label}: уже на месте")
|
|
return
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = dst.with_suffix(dst.suffix + ".part")
|
|
with requests.get(url, stream=True, timeout=60) as r:
|
|
r.raise_for_status()
|
|
total = int(r.headers.get("Content-Length", 0))
|
|
done = 0
|
|
with tmp.open("wb") as out:
|
|
for chunk in r.iter_content(chunk_size=1 << 20):
|
|
out.write(chunk)
|
|
done += len(chunk)
|
|
if total:
|
|
sys.stdout.write(
|
|
f"\r {label}: {done / total * 100:5.1f}% "
|
|
f"({done / 1e6:.0f}/{total / 1e6:.0f} МБ)")
|
|
sys.stdout.flush()
|
|
# Переименование в самом конце: прерванная закачка не оставит битый файл,
|
|
# который на следующем запуске сочли бы готовым.
|
|
tmp.replace(dst)
|
|
print()
|
|
|
|
|
|
def fetch_gigaam(target: Path) -> None:
|
|
"""Качает модель распознавания с Hugging Face.
|
|
|
|
Ускоренный транспорт xet на части сетей не проходит, поэтому при неудаче
|
|
повторяем обычным способом - он медленнее, но работает везде.
|
|
"""
|
|
os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1")
|
|
from huggingface_hub import snapshot_download
|
|
|
|
# Только вариант e2e_rnnt: он единственный ставит пунктуацию и заглавные.
|
|
kwargs = dict(repo_id="istupakov/gigaam-v3-onnx", local_dir=str(target),
|
|
allow_patterns=["*e2e_rnnt*", "config.json"])
|
|
try:
|
|
snapshot_download(**kwargs)
|
|
except Exception as exc: # noqa: BLE001 - причина может быть любой, важен откат
|
|
print(f"\n Быстрая загрузка не удалась ({type(exc).__name__}), пробую обычную")
|
|
os.environ["HF_HUB_DISABLE_XET"] = "1"
|
|
snapshot_download(**kwargs)
|
|
|
|
|
|
def main() -> int:
|
|
setup_console()
|
|
MODELS.mkdir(parents=True, exist_ok=True)
|
|
|
|
print("Диаризация:")
|
|
download(EMB_URL, MODELS / "nemo_en_titanet_small.onnx", "голосовые отпечатки")
|
|
|
|
seg_dir = MODELS / "sherpa-onnx-pyannote-segmentation-3-0"
|
|
if (seg_dir / "model.onnx").is_file():
|
|
print(" сегментация: уже на месте")
|
|
else:
|
|
archive = MODELS / "seg.tar.bz2"
|
|
download(SEG_URL, archive, "сегментация")
|
|
with tarfile.open(archive, "r:bz2") as tar:
|
|
tar.extractall(MODELS)
|
|
archive.unlink()
|
|
|
|
print("\nРаспознавание (GigaAM v3, около 850 МБ):")
|
|
target = MODELS / "gigaam"
|
|
if (target / "config.json").is_file():
|
|
print(" уже на месте")
|
|
else:
|
|
fetch_gigaam(target)
|
|
|
|
print("\nГотово. Теперь запустите start.bat")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except requests.RequestException as exc:
|
|
print(f"\nОшибка сети: {exc}")
|
|
print("Проверьте подключение и доступность github.com и huggingface.co")
|
|
sys.exit(1)
|