Add offline desktop speech with Silero Xenia for Russian

This commit is contained in:
sergeych committed 2026-09-28 13:45:00 +04:00
1 parent 9cd8ebc422
commit 9dbe30b20c
28 files changed
+1865 -16

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Download the pinned Supertonic 3 CPU model. No packages or credentials required."""
import hashlib
import json
from pathlib import Path
import sys
import urllib.request
ROOT = Path(__file__).resolve().parent.parent
MANIFEST = json.loads((ROOT / "scripts/supertonic-3-model.json").read_text())
def valid(path, entry):
if not path.is_file() or path.stat().st_size != entry["size"]:
return False
digest = hashlib.sha256() if "sha256" in entry else hashlib.sha1()
if "git_blob" in entry:
digest.update(f"blob {entry['size']}\0".encode())
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest() == entry.get("sha256", entry.get("git_blob"))
def download(manifest, destination):
total = sum(entry["size"] for entry in manifest["files"])
print(f"{manifest['repository']}: {total / 1e6:.1f} MB; destination: {destination}", flush=True)
base = f"https://huggingface.co/{manifest['repository']}/resolve/{manifest['revision']}"
for entry in manifest["files"]:
target = destination / entry["path"]
if valid(target, entry):
print(f"Verified {entry['path']}", flush=True)
continue
target.parent.mkdir(parents=True, exist_ok=True)
temporary = target.with_suffix(target.suffix + ".part")
print(f"Downloading {entry['path']} ({entry['size'] / 1e6:.1f} MB)", flush=True)
try:
with urllib.request.urlopen(f"{base}/{entry['path']}", timeout=60) as source, temporary.open("wb") as output:
while chunk := source.read(1024 * 1024):
output.write(chunk)
if not valid(temporary, entry):
raise RuntimeError(f"Checksum mismatch: {entry['path']}")
temporary.replace(target)
finally:
temporary.unlink(missing_ok=True)
def main():
destination = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT / ".local/tts/supertonic-3"
print("Supertonic model license: OpenRAIL-M (saved as LICENSE alongside the model).", flush=True)
download(MANIFEST, destination)
print("Ready. Run ./gradlew :composeApp:run and use Read aloud in a book.", flush=True)
if __name__ == "__main__":
main()