Spaces:
Sleeping
Sleeping
File size: 3,319 Bytes
2e818da | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """Pinned, resumable asset setup for ResearchMate's pocket-tts default voice and Piper fallback voice."""
from __future__ import annotations
import argparse
import hashlib
import json
from dataclasses import asdict, dataclass
from pathlib import Path
@dataclass(frozen=True)
class DownloadSpec:
name: str
repo_id: str
revision: str
destination: str
allow_patterns: tuple[str, ...] = ()
def download_specs() -> list[DownloadSpec]:
return [
DownloadSpec(
"pocket-tts",
"kyutai/pocket-tts",
"4c8ad48f8a003909bc4f1122cbe88a4252124621",
"pocket",
),
DownloadSpec(
"piper-en-us",
"rhasspy/piper-voices",
"e21c7de8d4eab79b902f0d61e662b3f21664b8d2",
"piper",
(
"en/en_US/lessac/medium/en_US-lessac-medium.onnx",
"en/en_US/lessac/medium/en_US-lessac-medium.onnx.json",
"en/en_US/lessac/medium/MODEL_CARD",
),
),
]
def setup_assets(root: Path) -> dict:
from huggingface_hub import snapshot_download
root.mkdir(parents=True, exist_ok=True)
rows: list[dict] = []
for spec in download_specs():
destination = root / spec.destination
try:
snapshot_download(
repo_id=spec.repo_id,
revision=spec.revision,
allow_patterns=list(spec.allow_patterns) or None,
local_dir=destination,
)
rows.append({
**asdict(spec),
"status": "installed",
"bytes": _directory_size(destination),
"checksums": _checksums(destination),
})
except Exception as exc:
rows.append({**asdict(spec), "status": "error", "error": str(exc)})
manifest = {"schema_version": 1, "root": str(root), "assets": rows, "total_bytes": _directory_size(root)}
(root / "installation-manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
return manifest
def _directory_size(path: Path) -> int:
return sum(item.stat().st_size for item in path.rglob("*") if item.is_file()) if path.exists() else 0
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _checksums(path: Path) -> dict[str, str]:
return {
item.relative_to(path).as_posix(): _sha256(item)
for item in path.rglob("*") if item.is_file() and not item.name.startswith(".cache")
}
def main() -> None:
parser = argparse.ArgumentParser(description="Download pinned Pocket TTS and Piper assets.")
parser.add_argument("--root", type=Path, default=Path.home() / ".studybuddy" / "models")
parser.add_argument("--yes", action="store_true", help="Accept the model download without prompting")
args = parser.parse_args()
if not args.yes:
answer = input("Download Pocket TTS and Piper assets? [y/N] ")
if answer.strip().lower() not in {"y", "yes"}:
return
manifest = setup_assets(args.root)
print(json.dumps(manifest, indent=2))
if __name__ == "__main__":
main()
|