Files
toread/scripts/setup-ruaccent.py

59 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Workspace-local RuAccent CPU setup. No sudo or changes to system Python."""
import hashlib
import io
import json
import os
from pathlib import Path
import platform
import runpy
import shutil
import subprocess
import urllib.request
import zipfile
ROOT = Path(__file__).resolve().parent.parent
def main():
uv = shutil.which("uv") or str(ROOT / ".local/tools/uv")
if not Path(uv).is_file():
if platform.system() != "Linux" or platform.machine() != "x86_64":
raise SystemExit("Install uv first on this platform, then rerun this setup script.")
with urllib.request.urlopen("https://pypi.org/pypi/uv/0.12.19/json", timeout=60) as source:
metadata = json.load(source)
wheel = next(x for x in metadata["urls"] if x["filename"].endswith("manylinux_2_17_x86_64.manylinux2014_x86_64.whl"))
with urllib.request.urlopen(wheel["url"], timeout=60) as source:
content = source.read()
expected = "a63d18a0aa38ee9f21a5406afbbaeb41303bcd954be9d6b7c1b95ac275e53958"
if hashlib.sha256(content).hexdigest() != expected:
raise RuntimeError("uv checksum mismatch")
with zipfile.ZipFile(io.BytesIO(content)) as archive:
name = next(x for x in archive.namelist() if x.endswith("/uv"))
Path(uv).parent.mkdir(parents=True, exist_ok=True)
Path(uv).write_bytes(archive.read(name))
Path(uv).chmod(0o755)
environment = dict(os.environ, UV_CACHE_DIR=str(ROOT / ".local/uv-cache"),
UV_PYTHON_INSTALL_DIR=str(ROOT / ".local/python"), UV_SYSTEM_CERTS="true")
venv = ROOT / ".local/ruaccent-runtime"
python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
if not python.is_file():
subprocess.run([uv, "venv", "--python", "3.12.14", str(venv)], env=environment, check=True)
subprocess.run([uv, "pip", "sync", "--python", str(python), str(ROOT / "scripts/ruaccent-requirements.txt")], env=environment, check=True)
model = ROOT / ".local/tts/ruaccent"
manifest = json.loads((ROOT / "scripts/ruaccent-model.json").read_text())
runpy.run_path(str(ROOT / "scripts/download-tts-model.py"))["download"](manifest, model)
# RUAccent 1.5.8.3 expects its rule-engine files beside the installed package.
module_path = subprocess.check_output([str(python), "-c", "import importlib.util; print(importlib.util.find_spec('ruaccent').submodule_search_locations[0])"], text=True).strip()
shutil.copytree(model / "koziev", Path(module_path) / "koziev", dirs_exist_ok=True)
worker = ROOT / "composeApp/src/jvmMain/resources/tts/ruaccent-worker.py"
shutil.copyfile(worker, model / "worker.py")
# Verify a wholly offline initialization before declaring the installation ready.
subprocess.run([str(python), str(model / "worker.py"), str(model), "--check"], check=True, env=environment)
(model / "runtime.json").write_text(json.dumps({"python": str(python), "model": "turbo3.1", "revision": manifest["revision"]}, indent=2) + "\n")
print("RuAccent ready: CPU, turbo3.1, full dictionary; no network during inference.", flush=True)
if __name__ == "__main__":
main()