lct-hack/backend/scripts/models.py
2026-09-24 01:10:49 +03:00

91 lines
4 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.

"""make models: вспомогательные веса голоса в backend/models/.
Эмбеддинги и VAD качаются с Hugging Face. Whisper small, Qwen3 и Vikhr
скачиваются отдельным `make local-models` с проверкой SHA-256. Старый GigaAM
доступен через `--legacy-gigaam`, но в выбранный стек больше не входит.
Синтез (Silero TTS v5) лежит на models.silero.ai — российском хосте, который
не отвечает из-под VPN: его скрипт не качает, а проверяет и говорит, что делать.
Докачка продолжается с места обрыва: сеть на стенде бывает медленной.
"""
import argparse
import ssl
import sys
import urllib.request
from pathlib import Path
import certifi
ROOT = Path(__file__).resolve().parents[1]
MODELS = ROOT / "models"
E5 = "https://huggingface.co/Xenova/multilingual-e5-small/resolve/main/"
GIGAAM = "https://huggingface.co/istupakov/gigaam-v3-onnx/resolve/main/"
VAD = "https://huggingface.co/istupakov/silero-vad-onnx/resolve/main/"
FILES = {
"e5-small/config.json": E5 + "config.json",
"e5-small/tokenizer.json": E5 + "tokenizer.json",
"e5-small/model_quantized.onnx": E5 + "onnx/model_quantized.onnx",
"silero-vad/config.json": VAD + "config.json",
"silero-vad/silero_vad.onnx": VAD + "silero_vad.onnx",
}
LEGACY_GIGAAM_FILES = {
"gigaam-v3-onnx/config.json": GIGAAM + "config.json",
"gigaam-v3-onnx/v3_vocab.txt": GIGAAM + "v3_vocab.txt",
"gigaam-v3-onnx/v3_rnnt_encoder.int8.onnx": GIGAAM + "v3_rnnt_encoder.int8.onnx",
"gigaam-v3-onnx/v3_rnnt_decoder.int8.onnx": GIGAAM + "v3_rnnt_decoder.int8.onnx",
"gigaam-v3-onnx/v3_rnnt_joint.int8.onnx": GIGAAM + "v3_rnnt_joint.int8.onnx",
"gigaam-v3-onnx/v3_ctc.int8.onnx": GIGAAM + "v3_ctc.int8.onnx",
}
SILERO_TTS = "silero-tts/v5_ru.pt"
SILERO_TTS_URL = "https://models.silero.ai/models/tts/ru/v5_ru.pt"
def fetch(url: str, target: Path) -> None:
partial = target.with_suffix(target.suffix + ".part")
offset = partial.stat().st_size if partial.exists() else 0
request = urllib.request.Request(url, headers={"Range": f"bytes={offset}-"} if offset else {})
with urllib.request.urlopen(
request, timeout=60, context=ssl.create_default_context(cafile=certifi.where())
) as response:
resumed = offset > 0 and response.status == 206
with partial.open("ab" if resumed else "wb") as out:
total = (offset if resumed else 0) + int(response.headers.get("Content-Length", 0))
while chunk := response.read(1 << 20):
out.write(chunk)
done = out.tell()
if total:
print(f"\r {target.name}: {done // (1 << 20)} из {total // (1 << 20)} МБ", end="", flush=True)
partial.rename(target)
print()
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--legacy-gigaam", action="store_true")
args = parser.parse_args()
files = {**FILES, **(LEGACY_GIGAAM_FILES if args.legacy_gigaam else {})}
for relative, url in files.items():
target = MODELS / relative
if target.exists():
print(f" {relative}: уже есть")
continue
target.parent.mkdir(parents=True, exist_ok=True)
fetch(url, target)
tts = MODELS / SILERO_TTS
if tts.exists():
print(f" {SILERO_TTS}: уже есть")
print("все модели на месте")
return 0
# Не качаем с зеркал: .pt грузится через pickle, файл из непроверенного
# источника — это чужой код на стенде.
print(f"""
{SILERO_TTS}: НЕТ. Хост models.silero.ai не отвечает из-под VPN — скачайте вручную:
{SILERO_TTS_URL}
и положите в backend/models/{SILERO_TTS}""")
return 1
if __name__ == "__main__":
sys.exit(main())