| |
| """ |
| Prepare Supertone/supertonic assets for TranslateBlue on-device TTS. |
| |
| Native Core ML ``.mlpackage`` export from these ONNX graphs is not reliably supported |
| by current ``coremltools`` (ONNX path removed; legacy converters fail on modern ops). |
| |
| This script downloads ``Supertone/supertonic`` ONNX + configs + voice styles from the Hub. |
| The app loads them with ONNX Runtime and the Core ML execution provider when available. |
| |
| Usage:: |
| |
| python3 -m venv .venv && . .venv/bin/activate |
| pip install -r requirements-export.txt |
| python convert_supertonic_coreml.py --output ./export |
| |
| See README.md for layout and Hub upload paths. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import shutil |
| import sys |
| from pathlib import Path |
| from typing import Iterable, List |
|
|
| from huggingface_hub import HfApi, hf_hub_download |
|
|
| REPO = "Supertone/supertonic" |
| ONNX_FILES = [ |
| "duration_predictor.onnx", |
| "text_encoder.onnx", |
| "vector_estimator.onnx", |
| "vocoder.onnx", |
| ] |
| JSON_FILES = ["tts.json", "unicode_indexer.json"] |
|
|
|
|
| def _download(repo_id: str, rel: str, dest: Path, revision: str) -> None: |
| dest.parent.mkdir(parents=True, exist_ok=True) |
| p = hf_hub_download(repo_id=repo_id, filename=rel, revision=revision) |
| shutil.copy2(p, dest) |
|
|
|
|
| def _iter_voice_files(repo_id: str, revision: str) -> List[str]: |
| api = HfApi() |
| out: List[str] = [] |
| for ent in api.list_repo_tree(repo_id, revision=revision, recursive=True): |
| p = getattr(ent, "path", None) |
| if p and p.startswith("voice_styles/") and p.endswith(".json"): |
| out.append(p) |
| return sorted(out) |
|
|
|
|
| def copy_json_and_voices(repo_id: str, out_onnx: Path, out_voice: Path, revision: str) -> None: |
| out_onnx.mkdir(parents=True, exist_ok=True) |
| for j in JSON_FILES: |
| _download(repo_id, f"onnx/{j}", out_onnx / j, revision) |
| out_voice.mkdir(parents=True, exist_ok=True) |
| for vf in _iter_voice_files(repo_id, revision): |
| _download(repo_id, vf, out_voice / Path(vf).name, revision) |
|
|
|
|
| def write_manifest(path: Path, repo_id: str, revision: str, max_text_len: int) -> None: |
| meta = { |
| "repo_id": repo_id, |
| "revision": revision, |
| "max_text_len": max_text_len, |
| "onnx_models": ONNX_FILES, |
| "note": "fp32 ONNX from Hub under onnx/; runtime uses ORT CoreML EP on device when enabled.", |
| } |
| path.write_text(json.dumps(meta, indent=2), encoding="utf-8") |
|
|
|
|
| def validate_ort(onnx_dir: Path) -> None: |
| import onnxruntime as ort |
|
|
| dp = str(onnx_dir / "duration_predictor.onnx") |
| sess = ort.InferenceSession(dp, providers=["CPUExecutionProvider"]) |
| names = [i.name for i in sess.get_inputs()] |
| assert "text_ids" in names and "text_mask" in names, names |
|
|
|
|
| def main(argv: Iterable[str] | None = None) -> int: |
| ap = argparse.ArgumentParser(description="Download Supertone/supertonic ONNX for TranslateBlue") |
| ap.add_argument("--output", type=Path, default=Path("./export")) |
| ap.add_argument("--repo-id", default=REPO) |
| ap.add_argument("--revision", default="main") |
| ap.add_argument("--max-text-len", type=int, default=300) |
| ap.add_argument("--skip-validate", action="store_true") |
| args = ap.parse_args(list(argv) if argv is not None else None) |
|
|
| root: Path = args.output |
| onnx_fp = root / "onnx" |
| voices = root / "voice_styles" |
|
|
| for name in ONNX_FILES: |
| _download(args.repo_id, f"onnx/{name}", onnx_fp / name, args.revision) |
| copy_json_and_voices(args.repo_id, onnx_fp, voices, args.revision) |
|
|
| write_manifest(root / "manifest.json", args.repo_id, args.revision, args.max_text_len) |
|
|
| if not args.skip_validate: |
| validate_ort(onnx_fp) |
|
|
| print(f"OK: wrote {root.resolve()} (fp32 onnx + voice_styles)") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|