kokoro-kmp-models / scripts /build_catalog.py
Shusek00's picture
Publish complete Kokoro multilingual catalog v2.0.0
0dbae7f verified
Raw
History Blame Contribute Delete
2.92 kB
#!/usr/bin/env python3
"""Build the public catalog and hydrate artifact sizes and checksums."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def hydrate(value: Any) -> Any:
if isinstance(value, list):
return [hydrate(item) for item in value]
if not isinstance(value, dict):
return value
if "$artifact" in value:
if len(value) != 1 or not isinstance(value["$artifact"], dict):
raise ValueError("$artifact must be the only key and contain an object")
artifact = dict(value["$artifact"])
relative_path = Path(artifact["path"])
if relative_path.is_absolute() or ".." in relative_path.parts:
raise ValueError(f"Artifact path must stay inside the repository: {relative_path}")
path = ROOT / relative_path
if not path.is_file():
raise FileNotFoundError(path)
artifact["sizeBytes"] = path.stat().st_size
artifact["sha256"] = sha256(path)
return artifact
return {key: hydrate(item) for key, item in value.items()}
template = json.loads((ROOT / "catalog.template.json").read_text(encoding="utf-8"))
upstream_inventory = json.loads(
(ROOT / "sources/upstream-assets.json").read_text(encoding="utf-8")
)
voices_by_language: dict[str, list[dict[str, Any]]] = {}
for source_voice in upstream_inventory["voices"]:
voices_by_language.setdefault(source_voice["languageId"], []).append(
{
"id": source_voice["id"],
"displayName": source_voice["displayName"],
"gender": source_voice["gender"],
"modelId": source_voice["modelId"],
"artifact": {
"$artifact": {
"path": source_voice["artifactPath"],
"mediaType": "application/octet-stream",
}
},
}
)
for language in template["languages"]:
if language.pop("$includeUpstreamVoices", False):
upstream_voices = voices_by_language.pop(language["id"], [])
language["voices"].extend(upstream_voices)
language["voices"].sort(key=lambda voice: voice["id"])
if voices_by_language:
raise RuntimeError(
f"Upstream voices refer to unknown languages: {sorted(voices_by_language)}"
)
catalog = hydrate(template)
(ROOT / "catalog.json").write_text(
json.dumps(catalog, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
voice_count = sum(len(language["voices"]) for language in catalog["languages"])
print(f"{ROOT / 'catalog.json'} ({len(catalog['languages'])} profiles, {voice_count} voices)")