voice_audio_multi/tools/model_manager_deprecated.py
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

2768 lines
110 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import dataclasses
import fractions
import io
import json
import os
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
import types
import warnings
from pathlib import Path
from typing import Any, Iterable
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen
# Heavy install/convert deps are imported lazily so ``list --json`` / ``info``
# work without Torch in the environment.
torch: Any = None
safe_open: Any = None
load_file: Any = None
save_file: Any = None
yaml: Any = None
def _ensure_install_deps() -> None:
"""Import Torch / safetensors / yaml on first install/convert use."""
global torch, safe_open, load_file, save_file, yaml
if torch is not None:
return
import torch as _torch
from safetensors import safe_open as _safe_open
from safetensors.torch import load_file as _load_file
from safetensors.torch import save_file as _save_file
import yaml as _yaml
torch = _torch
safe_open = _safe_open
load_file = _load_file
save_file = _save_file
yaml = _yaml
REPO_ROOT = Path(__file__).resolve().parents[1]
MODEL_MANAGER_ASSETS = REPO_ROOT / "assets" / "model_manager"
DEMUCS_ROOT_URL = "https://dl.fbaipublicfiles.com/demucs/"
WHISPER_MEDIUM_PT_URL = (
"https://openaipublic.azureedge.net/main/whisper/models/"
"345ae4da62f9b3d59415adc60127b97c714f32e89e936602e85993674d08dcb1/medium.pt"
)
DEMUCS_REMOTE_MODELS = {
"955717e8": DEMUCS_ROOT_URL + "hybrid_transformer/955717e8-8726e21a.th",
"f7e0c4bc": DEMUCS_ROOT_URL + "hybrid_transformer/f7e0c4bc-ba3fe64a.th",
"d12395a8": DEMUCS_ROOT_URL + "hybrid_transformer/d12395a8-e57c48e6.th",
"92cfc3b6": DEMUCS_ROOT_URL + "hybrid_transformer/92cfc3b6-ef3bcb9c.th",
"04573f0d": DEMUCS_ROOT_URL + "hybrid_transformer/04573f0d-f3cf25b2.th",
"75fc33f5": DEMUCS_ROOT_URL + "hybrid_transformer/75fc33f5-1941ce65.th",
"5c90dfd2": DEMUCS_ROOT_URL + "5c90dfd2-34c22ccb.th",
}
DEMUCS_BAGS: dict[str, dict[str, Any]] = {
"htdemucs": {"models": ["955717e8"]},
"htdemucs_ft": {
"models": ["f7e0c4bc", "d12395a8", "92cfc3b6", "04573f0d"],
"weights": [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
},
"htdemucs_6s": {"models": ["5c90dfd2"]},
"hdemucs_mmi": {"models": ["75fc33f5"], "segment": 44},
}
def huggingface_token() -> str | None:
token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
if token:
return token.strip()
token_path = Path.home() / ".cache" / "huggingface" / "token"
if token_path.is_file():
cached = token_path.read_text(encoding="utf-8").strip()
if cached:
return cached
return None
def hf_endpoint() -> str:
"""Base URL for Hugging Face Hub requests.
Honors the standard HF_ENDPOINT environment variable (e.g.
HF_ENDPOINT=https://hf-mirror.com for the hf-mirror mirror). Falls back to
https://huggingface.co. Empty values and trailing slashes are tolerated.
"""
endpoint = os.environ.get("HF_ENDPOINT", "").strip().rstrip("/")
return endpoint or "https://huggingface.co"
def http_headers() -> dict[str, str]:
headers = {"User-Agent": "audio.cpp model_manager.py"}
token = huggingface_token()
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
@dataclasses.dataclass(frozen=True)
class SnapshotSource:
repo_id: str
revision: str = "main"
include_prefixes: tuple[str, ...] = ()
include_suffixes: tuple[str, ...] = ()
exclude_prefixes: tuple[str, ...] = ()
strip_prefix: str = ""
@dataclasses.dataclass(frozen=True)
class SnapshotPlacement:
source: SnapshotSource
target_subdir: str = ""
required_files: tuple[str, ...] = ()
@dataclasses.dataclass(frozen=True)
class CompositeSnapshotSource:
placements: tuple[SnapshotPlacement, ...]
@dataclasses.dataclass(frozen=True)
class ConverterSource:
kind: str
description: str
url: str = ""
output_weights_file: str = ""
config_kind: str = ""
@dataclasses.dataclass(frozen=True)
class UnsupportedSource:
reason: str
@dataclasses.dataclass(frozen=True)
class ModelPackage:
id: str
display_name: str
target_directory: str
required_files: tuple[str, ...]
source: SnapshotSource | CompositeSnapshotSource | ConverterSource | UnsupportedSource
description: str = ""
# Optional identity fields for ``list --json`` consumers. When unset,
# family/tasks/standalone/gated are derived from local package metadata only.
family: str | None = None
standalone: bool | None = None
parent_package_id: str | None = None
tasks: tuple[str, ...] = ()
modes: tuple[str, ...] = ()
gated: bool | None = None
UTILITY_CONVERTER_KINDS = {"pytorch_to_safetensors"}
POSTPROCESS_SNAPSHOT_PACKAGE_IDS = {
"voxcpm2",
}
# HF repos that require accepting a license / access grant before download.
_GATED_REPO_MARKERS = (
"kyutai/pocket-tts",
"stabilityai/",
)
def package_install_kind(package: ModelPackage) -> str:
source = package.source
if isinstance(source, SnapshotSource):
return "composite" if package.id in POSTPROCESS_SNAPSHOT_PACKAGE_IDS else "snapshot"
if isinstance(source, CompositeSnapshotSource):
return "composite"
if isinstance(source, ConverterSource):
return "utility" if source.kind in UTILITY_CONVERTER_KINDS else "composite"
return "unsupported"
def package_needs_install_deps(package: ModelPackage) -> bool:
if package.id in POSTPROCESS_SNAPSHOT_PACKAGE_IDS:
return True
source = package.source
if isinstance(source, ConverterSource):
return True
return False
def package_usage_examples(package: ModelPackage) -> list[str]:
if package.id == "voxcpm2_audiovae":
return [
"python tools/model_manager.py install voxcpm2_audiovae --source-file models/VoxCPM2/audiovae.pth --models-root models --overwrite",
"python tools/model_manager.py install voxcpm2_audiovae --source-file /path/to/audiovae.pth --output-file /path/to/audiovae.safetensors --overwrite",
]
return []
CATALOG: tuple[ModelPackage, ...] = (
ModelPackage(
id="ace_step",
display_name="ACE-Step 1.5",
target_directory="Ace-Step1.5",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="ACE-Step/Ace-Step1.5"),
required_files=(
"config.json",
"Qwen3-Embedding-0.6B/model.safetensors",
"acestep-5Hz-lm-1.7B/model.safetensors",
"acestep-v15-turbo/model.safetensors",
"acestep-v15-turbo/silence_latent.pt",
"vae/diffusion_pytorch_model.safetensors",
),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="ACE-Step/acestep-v15-base"),
target_subdir="acestep-v15-base",
required_files=("config.json", "model.safetensors", "silence_latent.pt"),
),
),
),
required_files=(
"Qwen3-Embedding-0.6B/model.safetensors",
"acestep-5Hz-lm-1.7B/model.safetensors",
"acestep-v15-base/model.safetensors",
"acestep-v15-base/silence_latent.safetensors",
"acestep-v15-turbo/model.safetensors",
"acestep-v15-turbo/silence_latent.safetensors",
"vae/diffusion_pytorch_model.safetensors",
),
),
ModelPackage(
id="kokoro_82m_bf16",
display_name="Kokoro 82M GGML",
target_directory="kokoro-82m-v1_0-ggml",
source=SnapshotSource(repo_id="mlx-community/kokoro_mlx"),
required_files=(
"config.json",
"kokoro-v1_0.safetensors",
"voices.json",
"voices/af_heart.f32",
),
),
ModelPackage(
id="moss_tts_nano_100m",
display_name="MOSS-TTS-Nano 100M",
target_directory="MOSS-TTS-Nano-100M",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-TTS-Nano-100M"),
required_files=("config.json", "pytorch_model.bin", "tokenizer.model", "tokenizer_config.json"),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-Audio-Tokenizer-Nano"),
target_subdir="audio_tokenizer",
required_files=("config.json", "model-00001-of-00001.safetensors", "model.safetensors.index.json"),
),
),
),
required_files=(
"config.json",
"model.safetensors",
"tokenizer.model",
"tokenizer_config.json",
"audio_tokenizer/config.json",
"audio_tokenizer/model-00001-of-00001.safetensors",
"audio_tokenizer/model.safetensors.index.json",
),
description="Recommended MOSS-TTS-Nano package; assembles the TTS model and MOSS-Audio-Tokenizer-Nano dependency.",
),
ModelPackage(
id="moss_tts_nano_100m_model",
display_name="MOSS-TTS-Nano 100M model subcomponent",
target_directory="MOSS-TTS-Nano-100M",
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-TTS-Nano-100M"),
required_files=("config.json", "pytorch_model.bin", "tokenizer.model", "tokenizer_config.json"),
description="Subcomponent only. Use moss_tts_nano_100m for the full framework-ready MOSS runtime layout.",
),
ModelPackage(
id="moss_audio_tokenizer_nano",
display_name="MOSS Audio Tokenizer Nano subcomponent",
target_directory="MOSS-Audio-Tokenizer-Nano",
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-Audio-Tokenizer-Nano"),
required_files=("config.json", "model-00001-of-00001.safetensors", "model.safetensors.index.json"),
description="Subcomponent only. Use moss_tts_nano_100m for the full framework-ready MOSS Nano runtime layout.",
),
ModelPackage(
id="moss_audio_tokenizer_v2",
display_name="MOSS Audio Tokenizer v2 subcomponent",
target_directory="MOSS-Audio-Tokenizer-v2",
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-Audio-Tokenizer-v2"),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00003.safetensors",
"model-00002-of-00003.safetensors",
"model-00003-of-00003.safetensors",
),
description="Subcomponent only. Use moss_tts_local_v1_5 for the full framework-ready MOSS-TTS-Local runtime layout.",
),
ModelPackage(
id="moss_tts_local_v1_5",
display_name="MOSS-TTS-Local Transformer v1.5",
target_directory="MOSS-TTS-Local-Transformer-v1.5",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5"),
required_files=(
"config.json",
"model.safetensors",
"tokenizer.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
"special_tokens_map.json",
"added_tokens.json",
"chat_template.jinja",
),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="OpenMOSS-Team/MOSS-Audio-Tokenizer-v2"),
target_subdir="audio_tokenizer",
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00003.safetensors",
"model-00002-of-00003.safetensors",
"model-00003-of-00003.safetensors",
),
),
),
),
required_files=(
"config.json",
"model.safetensors",
"tokenizer.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
"special_tokens_map.json",
"added_tokens.json",
"chat_template.jinja",
"audio_tokenizer/config.json",
"audio_tokenizer/model.safetensors.index.json",
"audio_tokenizer/model-00001-of-00003.safetensors",
"audio_tokenizer/model-00002-of-00003.safetensors",
"audio_tokenizer/model-00003-of-00003.safetensors",
),
description="MOSS-TTS-Local Transformer v1.5 with the MOSS-Audio-Tokenizer-v2 codec dependency.",
),
ModelPackage(
id="omnivoice",
display_name="OmniVoice",
target_directory="OmniVoice",
source=SnapshotSource(repo_id="k2-fsa/OmniVoice"),
required_files=(
"config.json",
"model.safetensors",
"tokenizer.json",
"audio_tokenizer/config.json",
"audio_tokenizer/model.safetensors",
),
),
ModelPackage(
id="fun_asr_nano_2512_q8_0",
display_name="Fun-ASR-Nano-2512 Q8_0 GGUF",
target_directory="Fun-ASR-Nano-2512-GGUF",
source=SnapshotSource(
repo_id="FunAudioLLM/Fun-ASR-Nano-2512-GGUF",
revision="ce72677f84900f0dc57f498ace253bfb3c9155b6",
include_prefixes=("fun-asr-nano-2512-q8_0.gguf",),
),
required_files=("fun-asr-nano-2512-q8_0.gguf",),
family="fun_asr_nano",
tasks=("asr",),
modes=("offline",),
description=(
"Standalone Fun-ASR-Nano-2512 Q8_0 GGUF for offline multilingual ASR; "
"governed by the FunASR Model Open Source License Agreement v1.1."
),
),
ModelPackage(
id="fun_asr_nano_2512_f16",
display_name="Fun-ASR-Nano-2512 F16 GGUF",
target_directory="Fun-ASR-Nano-2512-GGUF",
source=SnapshotSource(
repo_id="FunAudioLLM/Fun-ASR-Nano-2512-GGUF",
revision="ce72677f84900f0dc57f498ace253bfb3c9155b6",
include_prefixes=("fun-asr-nano-2512-f16.gguf",),
),
required_files=("fun-asr-nano-2512-f16.gguf",),
family="fun_asr_nano",
tasks=("asr",),
modes=("offline",),
description=(
"Standalone Fun-ASR-Nano-2512 F16 GGUF for offline multilingual ASR; "
"governed by the FunASR Model Open Source License Agreement v1.1."
),
),
ModelPackage(
id="qwen3_asr_0_6b",
display_name="Qwen3 ASR 0.6B",
target_directory="Qwen3-ASR-0.6B",
source=SnapshotSource(repo_id="Qwen/Qwen3-ASR-0.6B"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"preprocessor_config.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="qwen3_asr_1_7b_hf",
display_name="Qwen3 ASR 1.7B HF",
target_directory="Qwen3-ASR-1.7B-hf",
source=SnapshotSource(
repo_id="Qwen/Qwen3-ASR-1.7B-hf",
include_prefixes=(
"config.json",
"generation_config.json",
"model.safetensors",
"processor_config.json",
"tokenizer_config.json",
"tokenizer.json",
),
),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"processor_config.json",
"tokenizer_config.json",
"tokenizer.json",
),
description="Native Hugging Face Transformers checkpoint; no conversion is required.",
),
ModelPackage(
id="voxtral_realtime",
display_name="Voxtral Mini 4B Realtime GGUF",
target_directory="Voxtral-Mini-4B-Realtime-2602-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf",),
strip_prefix="Voxtral-Mini-4B-Realtime-2602-GGUF/",
),
required_files=("voxtral-mini-4b-realtime-2602-q8_0.gguf",),
family="voxtral_realtime",
tasks=("asr",),
modes=("offline", "streaming"),
description="Standalone audio.cpp Q8_0 GGUF package for Voxtral realtime ASR.",
),
ModelPackage(
id="fish_audio_s2_pro",
display_name="Fish Audio S2 Pro GGUF",
target_directory="Fish-Audio-S2-Pro-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Fish-Audio-S2-Pro-GGUF/fish-audio-s2-pro-q8_0.gguf",),
strip_prefix="Fish-Audio-S2-Pro-GGUF/",
),
required_files=("fish-audio-s2-pro-q8_0.gguf",),
family="fish_audio",
tasks=("tts",),
description="Standalone audio.cpp Q8_0 GGUF package for Fish Audio S2 Pro.",
),
ModelPackage(
id="higgs_audio_stt",
display_name="Higgs Audio STT",
target_directory="higgs-audio-v3-stt",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="bosonai/higgs-audio-v3-stt"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors.index.json",
"model-00001-of-00002.safetensors",
"model-00002-of-00002.safetensors",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
SnapshotPlacement(
source=SnapshotSource(
repo_id="openai/whisper-large-v3",
include_prefixes=("preprocessor_config.json",),
),
target_subdir="../whisper-large-v3",
required_files=("preprocessor_config.json",),
),
),
),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors.index.json",
"model-00001-of-00002.safetensors",
"model-00002-of-00002.safetensors",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
"../whisper-large-v3/preprocessor_config.json",
),
description="Installs Higgs Audio STT plus the sibling Whisper Large v3 preprocessor config required by the framework runtime.",
),
ModelPackage(
id="hviske_asr",
display_name="Hviske ASR",
target_directory="hviske-v5.3",
source=SnapshotSource(repo_id="syvai/hviske-v5.3"),
required_files=("config.json", "generation_config.json", "model.safetensors", "tokenizer.model"),
),
ModelPackage(
id="nemotron_asr",
display_name="Nemotron ASR",
target_directory="nemotron-3.5-asr-streaming-0.6b",
source=SnapshotSource(repo_id="nvidia/nemotron-3.5-asr-streaming-0.6b"),
required_files=("config.json", "model.safetensors", "processor_config.json", "tokenizer.json"),
),
ModelPackage(
id="parakeet_tdt_q8_0",
display_name="Parakeet-TDT 0.6B v3 Q8_0 GGUF",
target_directory="Parakeet-TDT-0.6B-v3-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Parakeet-TDT-0.6B-v3-GGUF/parakeet-tdt-0.6b-v3-q8_0.gguf",),
strip_prefix="Parakeet-TDT-0.6B-v3-GGUF/",
),
required_files=("parakeet-tdt-0.6b-v3-q8_0.gguf",),
family="parakeet_tdt",
tasks=("asr",),
modes=("offline", "streaming"),
standalone=True,
description="Standalone Parakeet-TDT 0.6B v3 Q8_0 GGUF for offline and buffered streaming ASR.",
),
ModelPackage(
id="parakeet_tdt_f16",
display_name="Parakeet-TDT 0.6B v3 F16 GGUF",
target_directory="Parakeet-TDT-0.6B-v3-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Parakeet-TDT-0.6B-v3-GGUF/parakeet-tdt-0.6b-v3-f16.gguf",),
strip_prefix="Parakeet-TDT-0.6B-v3-GGUF/",
),
required_files=("parakeet-tdt-0.6b-v3-f16.gguf",),
family="parakeet_tdt",
tasks=("asr",),
modes=("offline", "streaming"),
standalone=True,
description="Standalone Parakeet-TDT 0.6B v3 F16 GGUF for offline and buffered streaming ASR.",
),
ModelPackage(
id="qwen3_forced_aligner_0_6b",
display_name="Qwen3 Forced Aligner 0.6B",
target_directory="Qwen3-ForcedAligner-0.6B",
source=SnapshotSource(repo_id="Qwen/Qwen3-ForcedAligner-0.6B"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"preprocessor_config.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="qwen3_tts_0_6b_base",
display_name="Qwen3 TTS 12Hz 0.6B Base",
target_directory="Qwen3-TTS-12Hz-0.6B-Base",
source=SnapshotSource(repo_id="Qwen/Qwen3-TTS-12Hz-0.6B-Base"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"speech_tokenizer/config.json",
"speech_tokenizer/model.safetensors",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="vieneu_v3_turbo_v3_turbo",
display_name="VieNeu-TTS v3 Turbo Base",
target_directory="VieNeu-TTS-v3-Turbo",
source=SnapshotSource(repo_id="phuocnguyen90/VieNeu-TTS-v3-Turbo-GGUF"),
required_files=(
"config.json",
"model.gguf",
"speech_tokenizer/config.json",
"tokenizer_config.json",
"tokenizer.json",
"special_tokens_map.json",
),
description="Installs VieNeu-TTS v3 Turbo GGUF model and configuration sidecars for C++ inference.",
family="vieneu_v3_turbo",
tasks=("tts",),
),
ModelPackage(
id="qwen3_tts_1_7b_base",
display_name="Qwen3 TTS 12Hz 1.7B Base",
target_directory="Qwen3-TTS-12Hz-1.7B-Base",
source=SnapshotSource(repo_id="Qwen/Qwen3-TTS-12Hz-1.7B-Base"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"speech_tokenizer/config.json",
"speech_tokenizer/model.safetensors",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="qwen3_tts_1_7b_custom_voice",
display_name="Qwen3 TTS 12Hz 1.7B Custom Voice",
target_directory="Qwen3-TTS-12Hz-1.7B-CustomVoice",
source=SnapshotSource(repo_id="Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"speech_tokenizer/config.json",
"speech_tokenizer/model.safetensors",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="qwen3_tts_1_7b_voice_design",
display_name="Qwen3 TTS 12Hz 1.7B Voice Design",
target_directory="Qwen3-TTS-12Hz-1.7B-VoiceDesign",
source=SnapshotSource(repo_id="Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"speech_tokenizer/config.json",
"speech_tokenizer/model.safetensors",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="qwen3_tts_tokenizer_12hz",
display_name="Qwen3 TTS Tokenizer 12Hz subcomponent",
target_directory="Qwen3-TTS-Tokenizer-12Hz",
source=SnapshotSource(repo_id="Qwen/Qwen3-TTS-Tokenizer-12Hz"),
required_files=("config.json", "model.safetensors"),
description="Subcomponent only. Normal Qwen3 TTS packages already include the speech tokenizer needed by the framework.",
),
ModelPackage(
id="chatterbox",
display_name="Chatterbox",
target_directory="chatterbox",
source=SnapshotSource(repo_id="ResembleAI/chatterbox"),
required_files=(
"ve.safetensors",
"t3_cfg.safetensors",
"t3_mtl23ls_v2.safetensors",
"t3_mtl23ls_v3.safetensors",
"s3gen.safetensors",
"tokenizer.json",
"grapheme_mtl_merged_expanded_v1.json",
"Cangjie5_TC.json",
"conds.pt",
),
),
ModelPackage(
id="sortformer_diar_4spk_v1",
display_name="Sortformer diarization 4 speaker v1",
target_directory="diar_sortformer_4spk-v1",
source=SnapshotSource(repo_id="nvidia/diar_sortformer_4spk-v1"),
required_files=("config.json", "model.safetensors", "processor_config.json"),
),
ModelPackage(
id="pocket_tts",
display_name="PocketTTS",
target_directory="pocket-tts",
source=SnapshotSource(
repo_id="kyutai/pocket-tts",
include_prefixes=("languages/english/",),
),
required_files=(
"languages/english/model.safetensors",
"languages/english/tokenizer.model",
"languages/english/embeddings/alba.safetensors",
),
),
ModelPackage(
id="miocodec_25hz_44k_v2",
display_name="MioCodec 25Hz 44.1kHz v2",
target_directory="MioCodec-25Hz-44.1kHz-v2",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(
repo_id="Aratako/MioCodec-25Hz-44.1kHz-v2",
include_prefixes=("config.yaml", "model.safetensors"),
),
required_files=("config.yaml", "model.safetensors"),
),
SnapshotPlacement(
source=SnapshotSource(
repo_id="mlx-community/wavlm-base-plus-mlx",
include_prefixes=("config.json", "weights.safetensors"),
),
target_subdir="wavlm-base-plus-mlx",
required_files=("config.json", "weights.safetensors"),
),
),
),
required_files=(
"config.yaml",
"model.safetensors",
"wavlm-base-plus-mlx/config.json",
"wavlm-base-plus-mlx/weights.safetensors",
),
),
ModelPackage(
id="miotts_1_7b",
display_name="MioTTS 1.7B",
target_directory="MioTTS-1.7B",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(
repo_id="Aratako/MioTTS-1.7B",
include_prefixes=(
"config.json",
"generation_config.json",
"tokenizer_config.json",
"tokenizer.json",
"vocab.json",
"merges.txt",
"model.safetensors",
),
),
required_files=(
"config.json",
"generation_config.json",
"tokenizer_config.json",
"tokenizer.json",
"vocab.json",
"merges.txt",
"model.safetensors",
),
),
SnapshotPlacement(
source=SnapshotSource(
repo_id="Aratako/MioCodec-25Hz-44.1kHz-v2",
include_prefixes=("config.yaml", "model.safetensors"),
),
target_subdir="../MioCodec-25Hz-44.1kHz-v2",
required_files=("config.yaml", "model.safetensors"),
),
SnapshotPlacement(
source=SnapshotSource(
repo_id="mlx-community/wavlm-base-plus-mlx",
include_prefixes=("config.json", "weights.safetensors"),
),
target_subdir="../MioCodec-25Hz-44.1kHz-v2/wavlm-base-plus-mlx",
required_files=("config.json", "weights.safetensors"),
),
),
),
required_files=(
"config.json",
"generation_config.json",
"tokenizer_config.json",
"tokenizer.json",
"vocab.json",
"merges.txt",
"model.safetensors",
),
description="Installs MioTTS plus the sibling MioCodec dependency required by the framework runtime.",
),
ModelPackage(
id="vibevoice_asr",
display_name="VibeVoice ASR",
target_directory="VibeVoice-ASR",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="microsoft/VibeVoice-ASR"),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00008.safetensors",
"model-00002-of-00008.safetensors",
"model-00003-of-00008.safetensors",
"model-00004-of-00008.safetensors",
"model-00005-of-00008.safetensors",
"model-00006-of-00008.safetensors",
"model-00007-of-00008.safetensors",
"model-00008-of-00008.safetensors",
),
),
),
),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00008.safetensors",
"model-00002-of-00008.safetensors",
"model-00003-of-00008.safetensors",
"model-00004-of-00008.safetensors",
"model-00005-of-00008.safetensors",
"model-00006-of-00008.safetensors",
"model-00007-of-00008.safetensors",
"model-00008-of-00008.safetensors",
"tokenizer.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
description="Installs VibeVoice ASR plus the Qwen2.5 tokenizer files required by the framework runtime.",
),
ModelPackage(
id="vibevoice_1_5b",
display_name="VibeVoice 1.5B",
target_directory="VibeVoice-1.5B",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="microsoft/VibeVoice-1.5B"),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00003.safetensors",
"model-00002-of-00003.safetensors",
"model-00003-of-00003.safetensors",
"preprocessor_config.json",
),
),
),
),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00003.safetensors",
"model-00002-of-00003.safetensors",
"model-00003-of-00003.safetensors",
"preprocessor_config.json",
"tokenizer.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="vibevoice_7b",
display_name="VibeVoice 7B",
target_directory="VibeVoice-7B",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="vibevoice/VibeVoice-7B"),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00010.safetensors",
"model-00002-of-00010.safetensors",
"model-00003-of-00010.safetensors",
"model-00004-of-00010.safetensors",
"model-00005-of-00010.safetensors",
"model-00006-of-00010.safetensors",
"model-00007-of-00010.safetensors",
"model-00008-of-00010.safetensors",
"model-00009-of-00010.safetensors",
"model-00010-of-00010.safetensors",
"preprocessor_config.json",
),
),
),
),
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00010.safetensors",
"model-00002-of-00010.safetensors",
"model-00003-of-00010.safetensors",
"model-00004-of-00010.safetensors",
"model-00005-of-00010.safetensors",
"model-00006-of-00010.safetensors",
"model-00007-of-00010.safetensors",
"model-00008-of-00010.safetensors",
"model-00009-of-00010.safetensors",
"model-00010-of-00010.safetensors",
"preprocessor_config.json",
"tokenizer.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
ModelPackage(
id="higgs_audio_v3_tts_4b",
display_name="Higgs Audio v3 TTS 4B GGUF",
target_directory="Higgs-Audio-v3-TTS-4B-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Higgs-Audio-v3-TTS-4B-GGUF/higgs-audio-v3-tts-4b-q8_0.gguf",),
strip_prefix="Higgs-Audio-v3-TTS-4B-GGUF/",
),
required_files=("higgs-audio-v3-tts-4b-q8_0.gguf",),
family="higgs_audio_tts",
tasks=("tts",),
description="Standalone audio.cpp Q8_0 GGUF package for Higgs Audio v3 TTS 4B.",
),
ModelPackage(
id="heartmula",
display_name="HeartMuLa",
target_directory="HeartMuLa",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="HeartMuLa/HeartMuLaGen"),
required_files=("gen_config.json", "tokenizer.json"),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="HeartMuLa/HeartMuLa-oss-3B"),
target_subdir="HeartMuLa-oss-3B",
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00004.safetensors",
"model-00002-of-00004.safetensors",
"model-00003-of-00004.safetensors",
"model-00004-of-00004.safetensors",
),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="HeartMuLa/HeartCodec-oss-20260123"),
target_subdir="HeartCodec-oss",
required_files=(
"config.json",
"model.safetensors.index.json",
"model-00001-of-00002.safetensors",
"model-00002-of-00002.safetensors",
),
),
),
),
required_files=(
"tokenizer.json",
"gen_config.json",
"HeartMuLa-oss-3B/config.json",
"HeartMuLa-oss-3B/model.safetensors.index.json",
"HeartMuLa-oss-3B/model-00001-of-00004.safetensors",
"HeartMuLa-oss-3B/model-00002-of-00004.safetensors",
"HeartMuLa-oss-3B/model-00003-of-00004.safetensors",
"HeartMuLa-oss-3B/model-00004-of-00004.safetensors",
"HeartCodec-oss/config.json",
"HeartCodec-oss/model.safetensors.index.json",
"HeartCodec-oss/model-00001-of-00002.safetensors",
"HeartCodec-oss/model-00002-of-00002.safetensors",
),
),
ModelPackage(
id="irodori_tts_500m_v3",
display_name="Irodori-TTS 500M v3",
target_directory="Irodori-TTS-500M-v3",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="Aratako/Irodori-TTS-500M-v3"),
required_files=("model.safetensors",),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="llm-jp/llm-jp-3-150m"),
target_subdir="../llm-jp-3-150m",
required_files=("tokenizer.json", "model.safetensors", "config.json"),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="Aratako/Semantic-DACVAE-Japanese-32dim"),
target_subdir="../Semantic-DACVAE-Japanese-32dim",
required_files=("weights.pth",),
),
),
),
required_files=(
"model.safetensors",
"model_config.json",
"../llm-jp-3-150m/tokenizer.json",
"../Semantic-DACVAE-Japanese-32dim/weights.safetensors",
),
description="Installs Irodori-TTS plus the sibling llm-jp tokenizer and DACVAE codec dependencies required by the framework runtime.",
),
ModelPackage(
id="irodori_tts_600m_v3_voice_design",
display_name="Irodori-TTS 600M v3 VoiceDesign",
target_directory="Irodori-TTS-600M-v3-VoiceDesign",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="Aratako/Irodori-TTS-600M-v3-VoiceDesign"),
required_files=("model.safetensors",),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="llm-jp/llm-jp-3-150m"),
target_subdir="../llm-jp-3-150m",
required_files=("tokenizer.json", "model.safetensors", "config.json"),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="Aratako/Semantic-DACVAE-Japanese-32dim"),
target_subdir="../Semantic-DACVAE-Japanese-32dim",
required_files=("weights.pth",),
),
),
),
required_files=(
"model.safetensors",
"model_config.json",
"../llm-jp-3-150m/tokenizer.json",
"../Semantic-DACVAE-Japanese-32dim/weights.safetensors",
),
description="Installs Irodori-TTS VoiceDesign plus the sibling llm-jp tokenizer and DACVAE codec dependencies required by the framework runtime.",
),
ModelPackage(
id="glm_tts",
display_name="GLM-TTS",
target_directory="GLM-TTS",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="zai-org/GLM-TTS"),
required_files=(
"flow/config.yaml",
"flow/flow.pt",
"hift/hift.pt",
"llm/config.json",
"llm/generation_config.json",
"llm/model-00001-of-00002.safetensors",
"llm/model-00002-of-00002.safetensors",
"llm/model.safetensors.index.json",
"speech_tokenizer/config.json",
"speech_tokenizer/model.safetensors",
"speech_tokenizer/preprocessor_config.json",
"vq32k-phoneme-tokenizer/tokenizer.model",
"vq32k-phoneme-tokenizer/tokenizer_config.json",
),
),
SnapshotPlacement(
source=SnapshotSource(
repo_id="mlx-community/index-tts2-mlx",
include_prefixes=("campplus.safetensors",),
),
target_subdir="frontend",
required_files=("campplus.safetensors",),
),
),
),
required_files=(
"audio_cpp_config.json",
"flow/config.yaml",
"flow/model.safetensors",
"frontend/campplus.safetensors",
"hift/model.safetensors",
"llm/config.json",
"llm/generation_config.json",
"llm/model-00001-of-00002.safetensors",
"llm/model-00002-of-00002.safetensors",
"llm/model.safetensors.index.json",
"speech_tokenizer/config.json",
"speech_tokenizer/model.safetensors",
"speech_tokenizer/preprocessor_config.json",
"vq32k-phoneme-tokenizer/tokenizer_config.json",
"vq32k-phoneme-tokenizer/tokenizer_merges.txt",
"vq32k-phoneme-tokenizer/tokenizer_vocab.json",
),
family="glm_tts",
tasks=("tts", "clon"),
modes=("offline",),
standalone=True,
description=(
"Installs GLM-TTS and prepares its Flow, HiFT, ChatGLM tokenizer, "
"and CAMPPlus assets for zero-shot Chinese and English synthesis."
),
),
ModelPackage(
id="inflect_micro_v2_orig",
display_name="Inflect Micro v2 Original-Dtype GGUF",
target_directory="Inflect-Micro-v2-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Inflect-Micro-v2-GGUF/inflect-micro-v2-orig.gguf",),
strip_prefix="Inflect-Micro-v2-GGUF/",
),
required_files=("inflect-micro-v2-orig.gguf",),
family="inflect_v2",
tasks=("tts",),
modes=("offline",),
standalone=True,
description=(
"Standalone Inflect Micro v2 original-dtype GGUF. Inflect v2 is FP32-only "
"today, so quantized GGUF packages are not advertised."
),
),
ModelPackage(
id="outetts_1_0_1b",
display_name="OuteTTS 1.0 1B",
target_directory="Llama-OuteTTS-1.0-1B",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(repo_id="OuteAI/Llama-OuteTTS-1.0-1B"),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"special_tokens_map.json",
"tokenizer.json",
"tokenizer_config.json",
),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="ibm-research/DAC.speech.v1.0"),
target_subdir="../DAC.speech.v1.0",
required_files=("config.json", "weights_24khz_1.5kbps_v1.0.pth"),
),
SnapshotPlacement(
source=SnapshotSource(repo_id="Qwen/Qwen3-ForcedAligner-0.6B"),
target_subdir="../Qwen3-ForcedAligner-0.6B",
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"preprocessor_config.json",
"tokenizer_config.json",
"vocab.json",
"merges.txt",
),
),
),
),
required_files=(
"config.json",
"generation_config.json",
"model.safetensors",
"special_tokens_map.json",
"tokenizer.json",
"tokenizer_config.json",
"../DAC.speech.v1.0/config.json",
"../DAC.speech.v1.0/model.safetensors",
"../Qwen3-ForcedAligner-0.6B/config.json",
"../Qwen3-ForcedAligner-0.6B/generation_config.json",
"../Qwen3-ForcedAligner-0.6B/model.safetensors",
"../Qwen3-ForcedAligner-0.6B/preprocessor_config.json",
"../Qwen3-ForcedAligner-0.6B/tokenizer_config.json",
"../Qwen3-ForcedAligner-0.6B/vocab.json",
"../Qwen3-ForcedAligner-0.6B/merges.txt",
),
description="Installs OuteTTS, its IBM DAC 1.5 kbps codec, and Qwen3 Forced Aligner for reliable voice cloning.",
),
ModelPackage(
id="stable_audio_3_small_music",
display_name="Stable Audio 3 Small Music",
target_directory="stable-audio-3-small-music",
source=SnapshotSource(repo_id="stabilityai/stable-audio-3-small-music"),
required_files=(
"model_config.json",
"model.safetensors",
"t5gemma-b-b-ul2/config.json",
"t5gemma-b-b-ul2/model.safetensors",
"t5gemma-b-b-ul2/tokenizer.json",
"t5gemma-b-b-ul2/tokenizer.model",
),
),
ModelPackage(
id="stable_audio_3_small_sfx",
display_name="Stable Audio 3 Small SFX",
target_directory="stable-audio-3-small-sfx",
source=SnapshotSource(repo_id="stabilityai/stable-audio-3-small-sfx"),
required_files=(
"model_config.json",
"model.safetensors",
"t5gemma-b-b-ul2/config.json",
"t5gemma-b-b-ul2/model.safetensors",
"t5gemma-b-b-ul2/tokenizer.json",
"t5gemma-b-b-ul2/tokenizer.model",
),
),
ModelPackage(
id="stable_audio_3_medium",
display_name="Stable Audio 3 Medium",
target_directory="stable-audio-3-medium",
source=SnapshotSource(repo_id="stabilityai/stable-audio-3-medium"),
required_files=(
"model_config.json",
"model.safetensors",
"t5gemma-b-b-ul2/config.json",
"t5gemma-b-b-ul2/model.safetensors",
"t5gemma-b-b-ul2/tokenizer.json",
"t5gemma-b-b-ul2/tokenizer.model",
),
),
ModelPackage(
id="supertonic_3",
display_name="Supertonic 3",
target_directory="supertonic-3",
source=SnapshotSource(repo_id="mlx-community/supertonic-3-mlx"),
required_files=(
"config/tts.json",
"config/unicode_indexer.json",
"ggml/supertonic.safetensors",
"voice_styles/M1.json",
),
),
ModelPackage(
id="index_tts2",
display_name="IndexTTS2",
target_directory="IndexTTS-2",
source=SnapshotSource(repo_id="mlx-community/index-tts2-mlx"),
required_files=(
"config.yaml",
"bpe.model",
"gpt.safetensors",
"s2mel.safetensors",
"feat1.safetensors",
"feat2.safetensors",
"wav2vec2bert_stats.safetensors",
"semantic_codec_model.safetensors",
"campplus.safetensors",
"w2v-bert-2.0/config.json",
"w2v-bert-2.0/preprocessor_config.json",
"w2v-bert-2.0/model.safetensors",
"bigvgan/config.json",
"bigvgan/model.safetensors",
"qwen0.6bemo4-merge/config.json",
"qwen0.6bemo4-merge/generation_config.json",
"qwen0.6bemo4-merge/tokenizer.json",
"qwen0.6bemo4-merge/tokenizer_config.json",
"qwen0.6bemo4-merge/vocab.json",
"qwen0.6bemo4-merge/merges.txt",
"qwen0.6bemo4-merge/model.safetensors",
),
description="Framework-ready IndexTTS2 safetensors layout with shared components at the model root.",
),
ModelPackage(
id="mel_band_roformer",
display_name="Mel RoFormer MLX",
target_directory="mel-roformer-mlx",
source=SnapshotSource(repo_id="mlx-community/mel-roformer-mlx"),
required_files=("config.json", "model.safetensors"),
),
ModelPackage(
id="vevo2",
display_name="Vevo2",
target_directory="Vevo2",
source=CompositeSnapshotSource(
placements=(
SnapshotPlacement(
source=SnapshotSource(
repo_id="RMSnow/Vevo2",
include_prefixes=(
"acoustic_modeling/fm_emilia101k_singnet7k_repa/",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/",
"contentstyle_modeling/posttrained/",
"contentstyle_modeling/pretrained/",
"tokenizer/contentstyle_fvq16384_12.5hz/",
"tokenizer/prosody_fvq512_6.25hz/",
"vocoder/",
),
exclude_prefixes=(
"contentstyle_modeling/posttrained/optimizer.pt",
"contentstyle_modeling/posttrained/rng_state_",
"contentstyle_modeling/posttrained/scheduler.pt",
"contentstyle_modeling/posttrained/trainer_state.json",
"contentstyle_modeling/posttrained/training_args.bin",
"contentstyle_modeling/pretrained/optimizer.bin",
"contentstyle_modeling/pretrained/random_states_",
"contentstyle_modeling/pretrained/scheduler.bin",
),
),
required_files=(
"acoustic_modeling/fm_emilia101k_singnet7k_repa/config.json",
"acoustic_modeling/fm_emilia101k_singnet7k_repa/model.safetensors",
"acoustic_modeling/fm_emilia101k_singnet7k_repa/whisper_stats.pt",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/config.json",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/model.safetensors",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/whisper_stats.pt",
"contentstyle_modeling/posttrained/amphion_config.json",
"contentstyle_modeling/posttrained/config.json",
"contentstyle_modeling/posttrained/generation_config.json",
"contentstyle_modeling/posttrained/merges.txt",
"contentstyle_modeling/posttrained/model.safetensors",
"contentstyle_modeling/posttrained/tokenizer.json",
"contentstyle_modeling/posttrained/tokenizer_config.json",
"contentstyle_modeling/posttrained/vocab.json",
"contentstyle_modeling/pretrained/config.json",
"contentstyle_modeling/pretrained/generation_config.json",
"contentstyle_modeling/pretrained/merges.txt",
"contentstyle_modeling/pretrained/model.safetensors",
"contentstyle_modeling/pretrained/tokenizer.json",
"contentstyle_modeling/pretrained/tokenizer_config.json",
"contentstyle_modeling/pretrained/vocab.json",
"tokenizer/contentstyle_fvq16384_12.5hz/model.safetensors",
"tokenizer/prosody_fvq512_6.25hz/model.safetensors",
"vocoder/config.json",
"vocoder/model.safetensors",
"vocoder/model_1.safetensors",
"vocoder/model_2.safetensors",
),
),
),
),
required_files=(
"acoustic_modeling/fm_emilia101k_singnet7k_repa/config.json",
"acoustic_modeling/fm_emilia101k_singnet7k_repa/model.safetensors",
"acoustic_modeling/fm_emilia101k_singnet7k_repa/whisper_stats.safetensors",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/config.json",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/model.safetensors",
"acoustic_modeling/fm_emilia101k_singnet7k_repa_text/whisper_stats.safetensors",
"contentstyle_modeling/posttrained/amphion_config.json",
"contentstyle_modeling/posttrained/config.json",
"contentstyle_modeling/posttrained/generation_config.json",
"contentstyle_modeling/posttrained/merges.txt",
"contentstyle_modeling/posttrained/model.safetensors",
"contentstyle_modeling/posttrained/tokenizer.json",
"contentstyle_modeling/posttrained/tokenizer_config.json",
"contentstyle_modeling/posttrained/vocab.json",
"contentstyle_modeling/pretrained/config.json",
"contentstyle_modeling/pretrained/generation_config.json",
"contentstyle_modeling/pretrained/merges.txt",
"contentstyle_modeling/pretrained/model.safetensors",
"contentstyle_modeling/pretrained/tokenizer.json",
"contentstyle_modeling/pretrained/tokenizer_config.json",
"contentstyle_modeling/pretrained/vocab.json",
"tokenizer/contentstyle_fvq16384_12.5hz/model.safetensors",
"tokenizer/prosody_fvq512_6.25hz/model.safetensors",
"vocoder/config.json",
"vocoder/model.safetensors",
"vocoder/model_1.safetensors",
"vocoder/model_2.safetensors",
),
description="Installs Vevo2 plus the sibling whisper-medium dependency required by the framework runtime.",
),
ModelPackage(
id="vevo2_gguf",
display_name="Vevo2 Q8_0 GGUF",
target_directory="Vevo2-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Vevo2-GGUF/vevo2-q8_0.gguf",),
strip_prefix="Vevo2-GGUF/",
),
required_files=("vevo2-q8_0.gguf",),
family="vevo2",
tasks=("tts", "music", "vc", "edit", "svc", "s2s"),
description=(
"Standalone audio.cpp Q8_0 GGUF package for Vevo2. It embeds every config the runtime "
"reads, whisper-medium included, so unlike the safetensors package it needs no sibling "
"whisper-medium download."
),
),
ModelPackage(
id="seed_vc",
display_name="SeedVC-MLX",
target_directory="SeedVC-MLX",
source=SnapshotSource(repo_id="mlx-community/SeedVC-MLX"),
required_files=(
"seed_vc_manifest.json",
"v2/vc_wrapper.json",
"v2/ar.safetensors",
"v2/cfm.safetensors",
"v1/svc.json",
"v1/svc.safetensors",
"v1/whisper_bigvgan.json",
"v1/whisper_bigvgan.safetensors",
"v1/xlsr_hift.json",
"v1/xlsr_hift.safetensors",
"astral/bsq32.json",
"astral/bsq32.safetensors",
"astral/bsq2048.json",
"astral/bsq2048.safetensors",
"campplus/model.safetensors",
"rmvpe/model.safetensors",
"hift/config.json",
"hift/model.safetensors",
"bigvgan/v2_22khz_80band_256x/config.json",
"bigvgan/v2_22khz_80band_256x/model.safetensors",
"bigvgan/v2_44khz_128band_512x/config.json",
"bigvgan/v2_44khz_128band_512x/model.safetensors",
"whisper-small/config.json",
"whisper-small/model.safetensors",
"hubert-large-ll60k/config.json",
"hubert-large-ll60k/model.safetensors",
"wav2vec2-xls-r-300m/config.json",
"wav2vec2-xls-r-300m/model.safetensors",
),
description="Framework-ready SeedVC-MLX bundle. The Hugging Face snapshot already contains the converted safetensors layout.",
),
ModelPackage(
id="citrinet_asr",
display_name="Citrinet ASR converted layout",
target_directory="citrinet",
source=ConverterSource(
kind="nemo_archive",
description="Download and convert the official NeMo archive into framework-friendly safetensors and sidecars.",
url="https://api.ngc.nvidia.com/v2/models/nvidia/nemo/stt_en_citrinet_256/versions/1.0.0rc1/files/stt_en_citrinet_256.nemo",
output_weights_file="citrinet_256.safetensors",
config_kind="citrinet",
),
required_files=(
"citrinet_256.safetensors",
"citrinet_256_config.json",
"citrinet_256_tokenizer.model",
"citrinet_256_vocab.txt",
),
),
ModelPackage(
id="kroko_asr_community_q8_0",
display_name="Kroko Community ASR Q8_0 GGUF",
target_directory="Kroko-ASR-GGUF",
source=SnapshotSource(
repo_id="audio-cpp/audio.cpp-gguf",
include_prefixes=("Kroko-ASR-GGUF/kroko-en-community-64-l-q8_0.gguf",),
strip_prefix="Kroko-ASR-GGUF/",
),
required_files=("kroko-en-community-64-l-q8_0.gguf",),
family="kroko_asr",
standalone=True,
tasks=("asr",),
modes=("offline", "streaming"),
description="Standalone Kroko Community English Q8_0 GGUF for offline and streaming ASR.",
),
ModelPackage(
id="voxcpm2",
display_name="VoxCPM2",
target_directory="VoxCPM2",
source=SnapshotSource(repo_id="OpenBMB/VoxCPM2"),
required_files=(
"config.json",
"model.safetensors",
"tokenizer.json",
"tokenizer_config.json",
"audiovae.pth",
"audiovae.safetensors",
),
),
ModelPackage(
id="voxcpm2_audiovae",
display_name="VoxCPM2 AudioVAE local conversion utility",
target_directory="VoxCPM2",
source=ConverterSource(
kind="pytorch_to_safetensors",
description="Convert a local PyTorch checkpoint into safetensors.",
),
required_files=("audiovae.safetensors",),
description="Utility only. Use voxcpm2 for the full framework-ready VoxCPM2 package.",
),
ModelPackage(
id="htdemucs",
display_name="HTDemucs",
target_directory="htdemucs",
source=ConverterSource(
kind="demucs_reference",
description="Download official Demucs .th checkpoints, or use --source-dir, and assemble framework safetensors plus a manifest.",
),
required_files=(
"manifest.json",
"955717e8/config.json",
"955717e8/model.safetensors",
),
),
)
PACKAGE_BY_ID = {package.id: package for package in CATALOG}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Download or convert official model packages into a local models layout."
)
subparsers = parser.add_subparsers(dest="command", required=True)
list_parser = subparsers.add_parser("list", help="List available model packages.")
list_parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
info_parser = subparsers.add_parser("info", help="Show one package.")
info_parser.add_argument("package_id")
info_parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
install_parser = subparsers.add_parser("install", help="Install one package into a models root.")
install_parser.add_argument("package_id")
install_parser.add_argument("--models-root", default="models", help="Target models root.")
install_parser.add_argument("--overwrite", action="store_true", help="Replace an existing installed directory.")
install_parser.add_argument("--source-file", help="Local source checkpoint for utility packages.")
install_parser.add_argument("--output-file", help="Optional output file for single-file utility packages.")
install_parser.add_argument("--source-dir", help="Optional local source directory for packages that can use checkpoint folders.")
install_parser.add_argument("--variant", help="Variant or model name for packages that support variants.")
return parser.parse_args()
def resolve_path(path: str) -> Path:
candidate = Path(path).expanduser()
if candidate.is_absolute():
return candidate
return REPO_ROOT / candidate
_FAMILY_ID_STRIP_PATTERNS = (
re.compile(r"_\d+_\d+b(?:_base|_custom_voice|_voice_design)?$"),
re.compile(r"_\d+b(?:_base|_custom_voice|_voice_design)?$"),
re.compile(r"_\d+m(?:_bf16)?$"),
re.compile(r"_bf16$"),
re.compile(r"_\d+spk_v\d+$"),
re.compile(r"_\d+hz_\d+k_v\d+$"),
re.compile(r"_3_small_(?:music|sfx)$"),
re.compile(r"_3_medium$"),
re.compile(r"_v\d+(?:_\d+)?$"),
re.compile(r"_\d+$"),
re.compile(r"_model$"),
re.compile(r"_hf$"),
re.compile(r"_12hz$"),
re.compile(r"_(?:base|custom_voice|voice_design)$"),
)
def _default_family_from_package_id(package_id: str) -> str:
"""Strip size/version suffixes from a package id. Prefer ``ModelPackage.family`` for exceptions."""
key = str(package_id or "").strip().lower()
if not key:
return ""
changed = True
while changed and key:
changed = False
for pattern in _FAMILY_ID_STRIP_PATTERNS:
updated = pattern.sub("", key)
if updated and updated != key:
key = updated
changed = True
break
return key
def _default_tasks_from_family(family: str) -> list[str]:
key = str(family or "").strip().lower()
if not key:
return []
if "forced_aligner" in key or key.endswith("_aligner") or key.endswith("_align"):
return ["align"]
if key.endswith("_asr") or key.endswith("_stt") or key in {"whisper", "voxtral_realtime"}:
return ["asr"]
if "vad" in key:
return ["vad"]
if "diar" in key:
return ["diar"]
if any(token in key for token in ("demucs", "roformer", "separator")):
return ["sep"]
if key in {"stable_audio", "ace_step", "heartmula"} or key.endswith("_gen"):
return ["gen"]
if key.endswith("_vc") or key in {"seed_vc", "vevo2"}:
return ["vc"]
if key.endswith("_codec") or key == "miocodec":
return ["codec"]
if key.endswith("_asr") or key.endswith("_stt"):
return ["asr"]
if "tts" in key or key in {
"chatterbox",
"voxcpm2",
"omnivoice",
"supertonic",
"miotts",
"pocket_tts",
"irodori_tts",
"index_tts2",
"vibevoice",
"moss_tts_nano",
"moss_tts_local",
}:
return ["tts"]
return []
def _collect_package_repo_ids(package: ModelPackage) -> list[str]:
source = package.source
repos: list[str] = []
if isinstance(source, SnapshotSource):
repos.append(source.repo_id)
elif isinstance(source, CompositeSnapshotSource):
for placement in source.placements:
repos.append(placement.source.repo_id)
return [repo for repo in repos if isinstance(repo, str) and repo]
def _package_is_gated(package: ModelPackage) -> bool:
if isinstance(package.gated, bool):
return package.gated
repos = [repo.lower() for repo in _collect_package_repo_ids(package)]
return any(any(marker in repo for marker in _GATED_REPO_MARKERS) for repo in repos)
def _package_standalone_fields(package: ModelPackage) -> tuple[bool, str | None]:
if isinstance(package.standalone, bool):
return package.standalone, package.parent_package_id
desc = str(package.description or "").strip().lower()
kind = package_install_kind(package)
if desc.startswith("subcomponent only.") or desc.startswith("utility only.") or kind == "utility":
parent = package.parent_package_id
if not parent:
for token in desc.replace(".", " ").replace(",", " ").split():
if token in PACKAGE_BY_ID and token != package.id:
parent = token
break
return False, parent
if "tokenizer" in package.id.lower() or "audiovae" in package.id.lower():
return False, package.parent_package_id
return True, None
def package_payload(package: ModelPackage) -> dict[str, object]:
source = package.source
if isinstance(source, SnapshotSource):
source_payload: dict[str, object] = {
"kind": "huggingface_snapshot",
"repo_id": source.repo_id,
"revision": source.revision,
"include_prefixes": list(source.include_prefixes),
"include_suffixes": list(source.include_suffixes),
"exclude_prefixes": list(source.exclude_prefixes),
"strip_prefix": source.strip_prefix,
}
installable = True
elif isinstance(source, CompositeSnapshotSource):
source_payload = {
"kind": "composite_snapshot",
"placements": [
{
"repo_id": placement.source.repo_id,
"revision": placement.source.revision,
"target_subdir": placement.target_subdir,
"required_files": list(placement.required_files),
"include_prefixes": list(placement.source.include_prefixes),
"include_suffixes": list(placement.source.include_suffixes),
"exclude_prefixes": list(placement.source.exclude_prefixes),
"strip_prefix": placement.source.strip_prefix,
}
for placement in source.placements
],
"repo_ids": _collect_package_repo_ids(package),
}
installable = True
elif isinstance(source, ConverterSource):
utility = source.kind in UTILITY_CONVERTER_KINDS
source_payload = {
"kind": "utility" if utility else "composite",
"operation_kind": source.kind,
"description": source.description,
}
if source.url:
source_payload["url"] = source.url
if source.output_weights_file:
source_payload["output_weights_file"] = source.output_weights_file
if source.config_kind:
source_payload["config_kind"] = source.config_kind
installable = True
else:
source_payload = {
"kind": "unsupported",
"reason": source.reason,
}
installable = False
family = package.family or _default_family_from_package_id(package.id)
tasks = list(package.tasks) if package.tasks else _default_tasks_from_family(family)
standalone, parent_package_id = _package_standalone_fields(package)
return {
"id": package.id,
"display_name": package.display_name,
"target_directory": package.target_directory,
"description": package.description,
"installable": installable,
"install_kind": package_install_kind(package),
"usage_examples": package_usage_examples(package),
"required_files": list(package.required_files),
"source": source_payload,
"family": family,
"tasks": tasks,
"modes": list(package.modes) if package.modes else (["offline"] if tasks else []),
"standalone": standalone,
"parent_package_id": parent_package_id,
"gated": _package_is_gated(package),
}
def print_package_table(packages: Iterable[ModelPackage]) -> None:
rows = []
for package in packages:
status = package_install_kind(package)
rows.append((package.id, status, package.target_directory, package.display_name))
width_id = max(len("id"), *(len(row[0]) for row in rows))
width_status = max(len("type"), *(len(row[1]) for row in rows))
width_target = max(len("target"), *(len(row[2]) for row in rows))
print(f"{'id':<{width_id}} {'type':<{width_status}} {'target':<{width_target}} name")
for row in rows:
print(f"{row[0]:<{width_id}} {row[1]:<{width_status}} {row[2]:<{width_target}} {row[3]}")
def hf_tree_url(source: SnapshotSource) -> str:
revision = quote(source.revision, safe="")
repo_id = source.repo_id
return f"{hf_endpoint()}/api/models/{repo_id}/tree/{revision}?recursive=true"
def hf_resolve_url(source: SnapshotSource, relative_path: str) -> str:
revision = quote(source.revision, safe="")
path = quote(relative_path, safe="/")
return f"{hf_endpoint()}/{source.repo_id}/resolve/{revision}/{path}"
def http_json(url: str, timeout: float = 30.0) -> object:
# Bounded by default: the WebUI probes package sizes from a request thread before
# starting a download, and an unbounded urlopen there would hang the page.
request = Request(url, headers=http_headers())
with urlopen(request, timeout=timeout) as response:
return json.load(response)
def local_snapshot_path(source: SnapshotSource, remote_path: str) -> str:
if not source.strip_prefix:
return remote_path
if not remote_path.startswith(source.strip_prefix):
raise RuntimeError(f"snapshot path does not start with strip_prefix: {remote_path}")
local_path = remote_path[len(source.strip_prefix):]
if not local_path:
raise RuntimeError(f"snapshot strip_prefix removed full path: {remote_path}")
return local_path
def list_hf_files(source: SnapshotSource) -> list[tuple[str, str, int | None]]:
payload = http_json(hf_tree_url(source))
if not isinstance(payload, list):
raise RuntimeError(f"unexpected HuggingFace tree payload for {source.repo_id}")
files: list[tuple[str, str, int | None]] = []
for entry in payload:
if not isinstance(entry, dict):
continue
path = entry.get("path")
entry_type = entry.get("type")
if not isinstance(path, str) or entry_type != "file":
continue
if source.include_prefixes and not any(path.startswith(prefix) for prefix in source.include_prefixes):
continue
if source.include_suffixes and not any(path.endswith(suffix) for suffix in source.include_suffixes):
continue
if any(path.startswith(prefix) for prefix in source.exclude_prefixes):
continue
size = entry.get("size")
files.append((path, local_snapshot_path(source, path), size if isinstance(size, int) else None))
if not files:
raise RuntimeError(f"no installable files found for {source.repo_id}")
return files
def download_file(url: str, target: Path, expected_size: int | None) -> int:
request = Request(url, headers=http_headers())
with urlopen(request) as response, target.open("wb") as handle:
written = 0
while True:
chunk = response.read(1 << 20)
if not chunk:
break
handle.write(chunk)
written += len(chunk)
if expected_size is not None and written != expected_size:
raise RuntimeError(f"downloaded size mismatch for {target}: {written} != {expected_size}")
return written
def validate_required_files(package: ModelPackage, root: Path) -> None:
missing = [relative for relative in package.required_files if not (root / relative).exists()]
if missing:
raise RuntimeError(f"installed package is missing required files: {missing}")
def validate_composite_required_files(package: ModelPackage, staged_root: Path, final_root: Path) -> None:
missing = [
relative
for relative in package.required_files
if not normalized_join(staged_root, relative).exists() and not normalized_join(final_root, relative).exists()
]
if missing:
raise RuntimeError(f"installed package is missing required files: {missing}")
def validate_required_files_list(required_files: Iterable[str], root: Path, label: str) -> None:
missing = [relative for relative in required_files if not (root / relative).exists()]
if missing:
raise RuntimeError(f"{label} is missing required files: {missing}")
def install_snapshot_into_dir(
source: SnapshotSource,
destination_root: Path,
required_files: Iterable[str],
*,
validate: bool = True,
) -> None:
files = list_hf_files(source)
for remote, relative, expected_size in files:
destination = destination_root / relative
destination.parent.mkdir(parents=True, exist_ok=True)
print(f"download {remote}")
download_file(hf_resolve_url(source, remote), destination, expected_size)
if validate:
validate_required_files_list(required_files, destination_root, source.repo_id)
def write_vevo2_whisper_stats(target_path: Path, source_file: Path) -> None:
payload = torch.load(source_file, map_location="cpu", weights_only=False)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
if set(tensors.keys()) != {"mean", "std"}:
raise RuntimeError(f"Vevo2 whisper stats must contain mean/std only: {sorted(tensors.keys())}")
save_file(tensors, str(target_path))
def whisper_dims(payload: dict[str, Any]) -> dict[str, int]:
raw_dims = payload.get("dims")
if not isinstance(raw_dims, dict):
raise RuntimeError("Whisper checkpoint is missing dims")
dims: dict[str, int] = {}
for key, value in raw_dims.items():
if not isinstance(key, str) or not isinstance(value, int):
raise RuntimeError(f"invalid Whisper dim entry: {key!r}={value!r}")
dims[key] = value
return dims
def whisper_state(payload: dict[str, Any]) -> dict[str, torch.Tensor]:
raw_state = payload.get("model_state_dict")
if not isinstance(raw_state, dict):
raise RuntimeError("Whisper checkpoint is missing model_state_dict")
state: dict[str, torch.Tensor] = {}
for key, value in raw_state.items():
if not isinstance(key, str):
raise RuntimeError(f"invalid non-string Whisper tensor key: {key!r}")
if not isinstance(value, torch.Tensor):
raise RuntimeError(f"invalid non-tensor Whisper state entry: {key}")
state[key] = value.detach().cpu().contiguous()
return state
def whisper_medium_config(dims: dict[str, int], tensor_count: int) -> dict[str, Any]:
return {
"model_type": "whisper",
"variant": "medium",
"source_format": "openai_whisper_pt",
"weight_file": "model.safetensors",
"tensor_count": tensor_count,
"dims": dims,
"audio_encoder": {
"n_mels": dims["n_mels"],
"n_audio_ctx": dims["n_audio_ctx"],
"n_audio_state": dims["n_audio_state"],
"n_audio_head": dims["n_audio_head"],
"n_audio_layer": dims["n_audio_layer"],
},
"text_decoder": {
"n_vocab": dims["n_vocab"],
"n_text_ctx": dims["n_text_ctx"],
"n_text_state": dims["n_text_state"],
"n_text_head": dims["n_text_head"],
"n_text_layer": dims["n_text_layer"],
},
}
def install_whisper_medium_dependency(root: Path) -> None:
root.mkdir(parents=True, exist_ok=True)
ckpt_path = root / "medium.pt"
model_path = root / "model.safetensors"
config_path = root / "config.json"
download_file(WHISPER_MEDIUM_PT_URL, ckpt_path, None)
try:
payload = torch.load(ckpt_path, map_location="cpu", weights_only=False)
if not isinstance(payload, dict):
raise RuntimeError(f"unexpected Whisper checkpoint payload type: {type(payload)}")
dims = whisper_dims(payload)
state = whisper_state(payload)
save_file(state, str(model_path))
reloaded = load_file(str(model_path))
if set(reloaded.keys()) != set(state.keys()):
raise RuntimeError("saved Whisper safetensors key set does not match source checkpoint")
for key, value in state.items():
if not torch.equal(value, reloaded[key]):
raise RuntimeError(f"saved Whisper safetensors changed tensor: {key}")
config_path.write_text(
json.dumps(whisper_medium_config(dims, len(state)), indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
finally:
ckpt_path.unlink(missing_ok=True)
def prepare_vevo2_snapshot_layout(root: Path) -> None:
stats_dirs = (
root / "acoustic_modeling" / "fm_emilia101k_singnet7k_repa",
root / "acoustic_modeling" / "fm_emilia101k_singnet7k_repa_text",
)
if all((stats_dir / "whisper_stats.safetensors").is_file() for stats_dir in stats_dirs):
return
for stats_dir in stats_dirs:
stats_pt = stats_dir / "whisper_stats.pt"
if not stats_pt.is_file():
raise RuntimeError(f"Vevo2 snapshot is missing {stats_pt}")
write_vevo2_whisper_stats(stats_dir / "whisper_stats.safetensors", stats_pt)
def convert_voxcpm2_audiovae(root: Path) -> None:
input_path = root / "audiovae.pth"
output_path = root / "audiovae.safetensors"
payload = torch.load(input_path, map_location="cpu", weights_only=True)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
write_checked_safetensors(tensors, output_path, input_path, overwrite=True)
def convert_moss_tts_weights(root: Path) -> None:
input_path = root / "pytorch_model.bin"
output_path = root / "model.safetensors"
payload = torch.load(input_path, map_location="cpu", weights_only=True)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
write_checked_safetensors(tensors, output_path, input_path, overwrite=True)
def convert_ace_step_silence_latent(root: Path) -> None:
input_path = root / "silence_latent.pt"
output_path = root / "silence_latent.safetensors"
tensor = torch.load(input_path, map_location="cpu", weights_only=True)
if not isinstance(tensor, torch.Tensor):
raise RuntimeError(f"ACE-Step silence latent must be a tensor: {input_path}")
write_checked_safetensors({"silence_latent": tensor.detach().cpu().contiguous()}, output_path, input_path, overwrite=True)
def convert_irodori_dacvae_weights(root: Path) -> None:
input_path = root / "weights.pth"
output_path = root / "weights.safetensors"
payload = torch.load(input_path, map_location="cpu", weights_only=True)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
write_checked_safetensors(tensors, output_path, input_path, overwrite=True)
def convert_outetts_dac_weights(root: Path) -> None:
input_path = root / "weights_24khz_1.5kbps_v1.0.pth"
output_path = root / "model.safetensors"
payload = torch.load(input_path, map_location="cpu", weights_only=True)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
expected = {
"quantizer.quantizers.0.codebook.weight": (1024, 8),
"quantizer.quantizers.1.codebook.weight": (1024, 8),
"decoder.model.0.weight_v": (1536, 1024, 7),
"decoder.model.6.weight_v": (1, 96, 7),
}
for name, shape in expected.items():
if name not in tensors or tuple(tensors[name].shape) != shape:
raise RuntimeError(f"unexpected OuteTTS DAC tensor {name}: {getattr(tensors.get(name), 'shape', None)}")
write_checked_safetensors(tensors, output_path, input_path, overwrite=True)
def write_irodori_model_config(root: Path) -> None:
input_path = root / "model.safetensors"
output_path = root / "model_config.json"
with safe_open(input_path, framework="pt", device="cpu") as handle:
metadata = handle.metadata()
config_json = metadata.get("config_json")
if config_json is None:
raise RuntimeError(f"Irodori-TTS model.safetensors is missing config_json metadata: {input_path}")
output_path.write_text(config_json, encoding="utf-8")
def copy_bundled_model_manager_assets(asset_subdir: str, destination_root: Path, required_files: Iterable[str]) -> None:
source_root = MODEL_MANAGER_ASSETS / asset_subdir
validate_required_files_list(required_files, source_root, f"bundled model-manager assets {asset_subdir}")
for relative in required_files:
source_path = source_root / relative
destination_path = destination_root / relative
destination_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source_path, destination_path)
def install_snapshot(package: ModelPackage, source: SnapshotSource, models_root: Path, overwrite: bool) -> Path:
target_dir = models_root / package.target_directory
staging_root = models_root / ".engine_model_staging"
staging_root.mkdir(parents=True, exist_ok=True)
staging_dir = Path(tempfile.mkdtemp(prefix=f"{package.target_directory}.", dir=staging_root))
try:
generated_files = {"audiovae.safetensors"}
pre_validate_files = tuple(
relative
for relative in package.required_files
if relative not in generated_files
)
install_snapshot_into_dir(
source,
staging_dir,
pre_validate_files,
validate=package.id not in POSTPROCESS_SNAPSHOT_PACKAGE_IDS,
)
if package.id == "voxcpm2":
convert_voxcpm2_audiovae(staging_dir)
validate_required_files(package, staging_dir)
elif package.id == "moss_tts_nano_100m_model":
convert_moss_tts_weights(staging_dir)
if target_dir.exists():
if not overwrite:
raise RuntimeError(f"model directory already exists: {target_dir}")
shutil.rmtree(target_dir)
target_dir.parent.mkdir(parents=True, exist_ok=True)
staging_dir.rename(target_dir)
return target_dir
except Exception:
shutil.rmtree(staging_dir, ignore_errors=True)
raise
finally:
try:
if staging_root.exists() and not any(staging_root.iterdir()):
staging_root.rmdir()
except OSError:
pass
def normalized_join(base: Path, relative: str) -> Path:
if not relative:
return base
return Path(os.path.normpath(str(base / relative)))
def install_composite_snapshot(
package: ModelPackage,
source: CompositeSnapshotSource,
models_root: Path,
overwrite: bool,
) -> Path:
package_root = models_root / package.target_directory
staging_root = models_root / ".engine_model_staging"
staging_root.mkdir(parents=True, exist_ok=True)
staging_bundle = Path(tempfile.mkdtemp(prefix=f"{package.target_directory}.", dir=staging_root))
staged_roots: dict[Path, Path] = {}
try:
staged_package_root = staging_bundle / package.target_directory
for placement in source.placements:
destination_root = normalized_join(staged_package_root, placement.target_subdir)
final_root = normalized_join(package_root, placement.target_subdir)
if final_root.exists() and not overwrite:
validate_required_files_list(placement.required_files, final_root, str(final_root))
continue
destination_root.mkdir(parents=True, exist_ok=True)
install_snapshot_into_dir(placement.source, destination_root, placement.required_files)
staged_roots[final_root] = destination_root
if package.id == "vevo2":
whisper_root = staging_bundle / "whisper-medium"
install_whisper_medium_dependency(whisper_root)
staged_roots[package_root.parent / "whisper-medium"] = whisper_root
prepare_vevo2_snapshot_layout(staged_package_root)
elif package.id == "ace_step":
convert_ace_step_silence_latent(staged_package_root / "acestep-v15-turbo")
convert_ace_step_silence_latent(staged_package_root / "acestep-v15-base")
elif package.id == "moss_tts_nano_100m":
convert_moss_tts_weights(staged_package_root)
elif package.id in {"irodori_tts_500m_v3", "irodori_tts_600m_v3_voice_design"}:
write_irodori_model_config(staged_package_root)
dacvae_root = staged_package_root.parent / "Semantic-DACVAE-Japanese-32dim"
if dacvae_root.exists():
convert_irodori_dacvae_weights(dacvae_root)
elif package.id == "glm_tts":
subprocess.run(
[
sys.executable,
str(REPO_ROOT / "tools" / "community_models" / "convert_glm_tts.py"),
"--model-dir",
str(staged_package_root),
"--overwrite",
],
check=True,
)
elif package.id == "outetts_1_0_1b":
dac_root = staged_package_root.parent / "DAC.speech.v1.0"
if dac_root.exists():
convert_outetts_dac_weights(dac_root)
elif package.id == "vibevoice_asr":
copy_bundled_model_manager_assets(
"vibevoice_1_5b",
staged_package_root,
("tokenizer.json", "tokenizer_config.json", "vocab.json", "merges.txt"),
)
elif package.id in {"vibevoice_1_5b", "vibevoice_7b"}:
# VibeVoice 1.5B and 7B share the same Qwen2.5 tokenizer, and neither
# upstream repo ships the tokenizer files, so both reuse one bundle.
copy_bundled_model_manager_assets(
"vibevoice_1_5b",
staged_package_root,
("tokenizer.json", "tokenizer_config.json", "vocab.json", "merges.txt"),
)
validate_composite_required_files(package, staged_package_root, package_root)
top_level_roots: list[Path] = []
for final_root in sorted(staged_roots.keys(), key=lambda path: len(path.parts)):
if any(final_root.is_relative_to(existing) for existing in top_level_roots):
continue
top_level_roots.append(final_root)
for final_root in sorted(top_level_roots, key=lambda path: len(path.parts), reverse=True):
if final_root.exists():
if not overwrite:
raise RuntimeError(f"model directory already exists: {final_root}")
shutil.rmtree(final_root)
for final_root in sorted(top_level_roots, key=lambda path: len(path.parts)):
destination_root = staged_roots[final_root]
final_root.parent.mkdir(parents=True, exist_ok=True)
destination_root.rename(final_root)
return package_root
except Exception:
shutil.rmtree(staging_bundle, ignore_errors=True)
raise
finally:
try:
if staging_root.exists() and not any(staging_root.iterdir()):
staging_root.rmdir()
except OSError:
pass
def checkpoint_state_dict(payload: Any) -> dict[str, Any]:
if not isinstance(payload, dict):
raise RuntimeError(f"checkpoint payload must be a dict, got {type(payload).__name__}")
state = payload.get("state_dict", payload)
if not isinstance(state, dict):
raise RuntimeError(f"checkpoint state_dict must be a dict, got {type(state).__name__}")
return state
def install_demucs_import_stubs() -> None:
if "demucs.htdemucs" not in sys.modules:
demucs_pkg = sys.modules.get("demucs")
if demucs_pkg is None:
demucs_pkg = types.ModuleType("demucs")
demucs_pkg.__path__ = [] # type: ignore[attr-defined]
sys.modules["demucs"] = demucs_pkg
htdemucs = types.ModuleType("demucs.htdemucs")
class HTDemucs:
pass
htdemucs.HTDemucs = HTDemucs
sys.modules["demucs.htdemucs"] = htdemucs
setattr(demucs_pkg, "htdemucs", htdemucs)
if "openunmix.filtering" not in sys.modules:
filtering = types.ModuleType("openunmix.filtering")
def _wiener_stub(*_args: Any, **_kwargs: Any) -> Any:
raise RuntimeError("openunmix.filtering.wiener stub should not be called during Demucs conversion")
filtering.wiener = _wiener_stub
openunmix = types.ModuleType("openunmix")
openunmix.filtering = filtering
sys.modules["openunmix"] = openunmix
sys.modules["openunmix.filtering"] = filtering
if "julius" not in sys.modules:
julius = types.ModuleType("julius")
def _resample_frac_stub(*_args: Any, **_kwargs: Any) -> Any:
raise RuntimeError("julius.resample_frac stub should not be called during Demucs conversion")
julius.resample_frac = _resample_frac_stub
sys.modules["julius"] = julius
if "dora.log" not in sys.modules:
log = types.ModuleType("dora.log")
def _fatal_stub(message: str) -> None:
raise RuntimeError(message)
log.fatal = _fatal_stub
dora = types.ModuleType("dora")
dora.log = log
sys.modules["dora"] = dora
sys.modules["dora.log"] = log
def load_demucs_package(path: Path) -> dict[str, Any]:
install_demucs_import_stubs()
with warnings.catch_warnings():
warnings.simplefilter("ignore")
package = torch.load(path, map_location="cpu", weights_only=False)
if not isinstance(package, dict):
raise RuntimeError(f"unexpected Demucs package type in {path}: {type(package)}")
required = {"klass", "args", "kwargs", "state"}
missing = required.difference(package)
if missing:
raise RuntimeError(f"Demucs package {path} is missing keys: {sorted(missing)}")
return package
def find_demucs_checkpoint(repo_dir: Path, signature: str) -> Path:
matches = sorted(repo_dir.glob(f"{signature}*.th"))
if not matches:
raise RuntimeError(f"could not find checkpoint for signature '{signature}' in {repo_dir}")
if len(matches) > 1:
exact = [path for path in matches if path.stem == signature or path.stem.startswith(signature + "-")]
if len(exact) == 1:
return exact[0]
raise RuntimeError(f"ambiguous checkpoints for signature '{signature}' in {repo_dir}: {matches}")
return matches[0]
def normalize_manifest_value(value: Any) -> Any:
if isinstance(value, (str, int, float, bool)) or value is None:
return value
if isinstance(value, fractions.Fraction):
return float(value)
if isinstance(value, Path):
return str(value)
if isinstance(value, (list, tuple)):
return [normalize_manifest_value(item) for item in value]
if isinstance(value, dict):
return {str(key): normalize_manifest_value(item) for key, item in value.items()}
return repr(value)
def export_demucs_model_config(package: dict[str, Any], checkpoint_path: Path, signature: str) -> dict[str, Any]:
klass = package["klass"]
kwargs = {str(key): normalize_manifest_value(value) for key, value in package["kwargs"].items()}
return {
"model_type": "demucs",
"class_name": getattr(klass, "__name__", str(klass)),
"signature": signature,
"checkpoint_file": checkpoint_path.name,
"args": [normalize_manifest_value(value) for value in package["args"]],
"kwargs": kwargs,
"sources": normalize_manifest_value(kwargs.get("sources")),
"samplerate": normalize_manifest_value(kwargs.get("samplerate")),
"audio_channels": normalize_manifest_value(kwargs.get("audio_channels")),
"segment": normalize_manifest_value(kwargs.get("segment")),
}
def export_demucs_state_tensors(package: dict[str, Any]) -> dict[str, torch.Tensor]:
state = package["state"]
if not isinstance(state, dict):
raise RuntimeError(f"unexpected Demucs state payload type: {type(state)}")
converted: dict[str, torch.Tensor] = {}
for key, value in state.items():
if key == "__quantized":
raise RuntimeError("Demucs quantized checkpoints are not supported by this converter yet")
if not isinstance(value, torch.Tensor):
raise RuntimeError(f"unexpected non-tensor entry in Demucs state: {key} -> {type(value)}")
converted[str(key)] = value.detach().cpu().contiguous()
return converted
def load_demucs_bag_manifest(name: str) -> dict[str, Any] | None:
payload = DEMUCS_BAGS.get(name)
if payload is None:
return None
return dict(payload)
def demucs_variant_signatures(name: str) -> list[str]:
bag = load_demucs_bag_manifest(name)
if bag is None:
return [name]
signatures = bag.get("models")
if not isinstance(signatures, list) or not signatures:
raise RuntimeError(f"Demucs bag manifest has invalid model list: {name}")
return [str(signature) for signature in signatures]
def ensure_demucs_checkpoints(repo_dir: Path, name: str) -> None:
repo_dir.mkdir(parents=True, exist_ok=True)
for signature in demucs_variant_signatures(name):
if any(repo_dir.glob(f"{signature}*.th")):
continue
url = DEMUCS_REMOTE_MODELS.get(signature)
if url is None:
raise RuntimeError(f"no remote checkpoint URL found for Demucs signature: {signature}")
filename = Path(url).name
print(f"download {filename}")
download_file(url, repo_dir / filename, None)
def convert_demucs_single(signature: str, repo_dir: Path, output_root: Path) -> dict[str, Any]:
checkpoint_path = find_demucs_checkpoint(repo_dir, signature)
package = load_demucs_package(checkpoint_path)
state = export_demucs_state_tensors(package)
model_dir = output_root / signature
model_dir.mkdir(parents=True, exist_ok=True)
save_file(state, str(model_dir / "model.safetensors"))
write_json(model_dir / "config.json", export_demucs_model_config(package, checkpoint_path, signature))
return {
"signature": signature,
"checkpoint_file": checkpoint_path.name,
"output_dir": str(model_dir.relative_to(output_root)),
"tensor_count": len(state),
}
def install_demucs_reference(
package: ModelPackage,
models_root: Path,
overwrite: bool,
source_dir_arg: str | None,
variant_arg: str | None,
) -> Path:
name = variant_arg or "htdemucs"
target_dir = models_root / package.target_directory
staging_root = models_root / ".engine_model_staging"
staging_root.mkdir(parents=True, exist_ok=True)
staging_dir = Path(tempfile.mkdtemp(prefix="htdemucs.", dir=staging_root))
try:
if source_dir_arg:
repo_dir = resolve_path(source_dir_arg)
if not repo_dir.is_dir():
raise RuntimeError(f"checkpoint repo directory does not exist: {repo_dir}")
else:
repo_dir = staging_dir / "downloaded_checkpoints"
ensure_demucs_checkpoints(repo_dir, name)
bag = load_demucs_bag_manifest(name)
if bag is None:
converted = [convert_demucs_single(name, repo_dir, staging_dir)]
manifest = {
"model_type": "demucs_single",
"name": name,
"model": converted[0],
}
else:
signatures = bag["models"]
if not isinstance(signatures, list) or not signatures:
raise RuntimeError(f"Demucs bag manifest has invalid model list: {name}")
converted = [convert_demucs_single(str(signature), repo_dir, staging_dir) for signature in signatures]
weights = normalize_manifest_value(bag.get("weights"))
segment = normalize_manifest_value(bag.get("segment"))
if len(converted) == 1 and weights is None and segment is None:
manifest = {
"model_type": "demucs_single_alias",
"name": name,
"alias_of": converted[0]["signature"],
"model": converted[0],
}
else:
manifest = {
"model_type": "demucs_bag",
"name": name,
"models": converted,
"weights": weights,
"segment": segment,
}
write_json(staging_dir / "manifest.json", manifest)
validate_required_files(package, staging_dir)
if target_dir.exists():
if not overwrite:
raise RuntimeError(f"model directory already exists: {target_dir}")
shutil.rmtree(target_dir)
target_dir.parent.mkdir(parents=True, exist_ok=True)
staging_dir.rename(target_dir)
return target_dir
except Exception:
shutil.rmtree(staging_dir, ignore_errors=True)
raise
finally:
try:
if staging_root.exists() and not any(staging_root.iterdir()):
staging_root.rmdir()
except OSError:
pass
def archive_member(archive: tarfile.TarFile, name: str) -> bytes:
for member in archive.getmembers():
if not member.isfile():
continue
normalized = member.name.removeprefix("./")
if normalized != name:
continue
extracted = archive.extractfile(member)
if extracted is None:
break
return extracted.read()
raise RuntimeError(f"missing archive member: {name}")
def tensor_state_dict(state: dict[str, Any]) -> dict[str, torch.Tensor]:
tensors: dict[str, torch.Tensor] = {}
seen_storages: set[int] = set()
for key, value in state.items():
if not isinstance(key, str):
raise RuntimeError(f"checkpoint contains a non-string tensor key: {key!r}")
if not torch.is_tensor(value):
raise RuntimeError(f"checkpoint state contains a non-tensor value at key: {key}")
tensor = value.detach().cpu().contiguous()
storage_ptr = tensor.untyped_storage().data_ptr()
if storage_ptr in seen_storages:
tensor = tensor.clone()
storage_ptr = tensor.untyped_storage().data_ptr()
seen_storages.add(storage_ptr)
tensors[key] = tensor
if not tensors:
raise RuntimeError("refusing to write an empty safetensors file")
return tensors
def write_checked_safetensors(tensors: dict[str, torch.Tensor], output_path: Path, source_file: Path, overwrite: bool) -> None:
if output_path.exists() and not overwrite:
raise RuntimeError(f"output file already exists: {output_path}")
output_path.parent.mkdir(parents=True, exist_ok=True)
metadata = {
"source_format": "pytorch",
"source_file": str(source_file),
"tensor_count": str(len(tensors)),
}
save_file(tensors, str(output_path), metadata=metadata)
reloaded = load_file(str(output_path), device="cpu")
if set(reloaded.keys()) != set(tensors.keys()):
raise RuntimeError(f"saved safetensors key set does not match source: {output_path}")
for key, value in tensors.items():
actual = reloaded[key]
if actual.shape != value.shape:
raise RuntimeError(f"saved tensor shape changed for {key}: {tuple(value.shape)} -> {tuple(actual.shape)}")
if actual.dtype != value.dtype:
raise RuntimeError(f"saved tensor dtype changed for {key}: {value.dtype} -> {actual.dtype}")
if not torch.equal(actual, value):
raise RuntimeError(f"saved tensor value changed for {key}")
def yaml_to_json(value: Any) -> Any:
if isinstance(value, dict):
return {str(key): yaml_to_json(item) for key, item in value.items()}
if isinstance(value, list):
return [yaml_to_json(item) for item in value]
return value
def expect_map(value: Any, label: str) -> dict[str, Any]:
if not isinstance(value, dict):
raise RuntimeError(f"{label} must be a map")
return {str(key): item for key, item in value.items()}
def map_at(root: dict[str, Any], key: str) -> dict[str, Any]:
return expect_map(root.get(key), key)
def list_at(root: dict[str, Any], key: str) -> list[Any]:
value = root.get(key)
if not isinstance(value, list):
raise RuntimeError(f"{key} must be a list")
return value
def int_at(root: dict[str, Any], key: str) -> int:
value = root.get(key)
if isinstance(value, int):
return value
if isinstance(value, float) and value.is_integer():
return int(value)
raise RuntimeError(f"{key} must be an integer")
def number_at(root: dict[str, Any], key: str, default: float | None = None) -> float:
value = root.get(key)
if value is None and default is not None:
return default
if isinstance(value, (int, float)):
return float(value)
raise RuntimeError(f"{key} must be a number")
def string_at(root: dict[str, Any], key: str) -> str:
value = root.get(key)
if isinstance(value, str):
return value
raise RuntimeError(f"{key} must be a string")
def string_list(value: Any, label: str) -> list[str]:
if not isinstance(value, list):
raise RuntimeError(f"{label} must be a list")
return [str(item) for item in value]
def jasper_block(
block: dict[str, Any],
*,
flatten_triples: bool,
include_dropout: bool,
include_residual_mode: bool,
se_metadata: str,
) -> dict[str, Any]:
def maybe_flatten(key: str) -> Any:
raw = block.get(key)
if not flatten_triples:
return raw
if isinstance(raw, list) and len(raw) == 1:
return raw[0]
return raw
out: dict[str, Any] = {
"dilation": maybe_flatten("dilation") if block.get("dilation") is not None else 1,
"filters": block.get("filters"),
"kernel": maybe_flatten("kernel"),
"repeat": block.get("repeat"),
"residual": block.get("residual", False),
"separable": block.get("separable", False),
"stride": maybe_flatten("stride") if block.get("stride") is not None else 1,
}
if include_dropout and "dropout" in block:
out["dropout"] = block["dropout"]
if include_residual_mode:
out["residual_mode"] = block.get("residual_mode")
if "se" in block:
out["se"] = block["se"]
if se_metadata == "reduction_ratio" and "se" in block:
out["se_reduction_ratio"] = block.get("se_reduction_ratio", 8)
if se_metadata == "context_size" and "se_context_size" in block:
out["se_context_size"] = block["se_context_size"]
return out
def jasper_blocks(
blocks: list[Any],
*,
flatten_triples: bool,
include_dropout: bool,
include_residual_mode: bool,
se_metadata: str,
) -> list[dict[str, Any]]:
return [
jasper_block(
expect_map(block, "jasper block"),
flatten_triples=flatten_triples,
include_dropout=include_dropout,
include_residual_mode=include_residual_mode,
se_metadata=se_metadata,
)
for block in blocks
]
def write_json(path: Path, payload: Any) -> None:
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
def write_citrinet_sidecars(root: dict[str, Any], archive: tarfile.TarFile, output_dir: Path) -> None:
tokenizer_name = "citrinet_256_tokenizer.model"
vocab_name = "citrinet_256_vocab.txt"
(output_dir / tokenizer_name).write_bytes(archive_member(archive, "tokenizer.model"))
(output_dir / vocab_name).write_bytes(archive_member(archive, "vocab.txt"))
preprocessor = map_at(root, "preprocessor")
encoder = map_at(root, "encoder")
decoder = map_at(root, "decoder")
vocab_size = int_at(decoder, "num_classes")
config = {
"blank_id": vocab_size,
"jasper": jasper_blocks(
list_at(encoder, "jasper"),
flatten_triples=True,
include_dropout=True,
include_residual_mode=True,
se_metadata="reduction_ratio",
),
"n_fft": int_at(preprocessor, "n_fft"),
"n_mels": int_at(preprocessor, "features"),
"normalize": string_at(preprocessor, "normalize"),
"num_classes": vocab_size + 1,
"pad_to": int_at(preprocessor, "pad_to"),
"sample_rate": int_at(preprocessor, "sample_rate"),
"target": str(root.get("target", "")),
"tokenizer_model_file": tokenizer_name,
"vocab_count": vocab_size,
"vocab_file": vocab_name,
"vocab_size": vocab_size,
"window": string_at(preprocessor, "window"),
"window_size": number_at(preprocessor, "window_size"),
"window_stride": number_at(preprocessor, "window_stride"),
}
write_json(output_dir / "citrinet_256_config.json", config)
def write_nemo_sidecars(config_kind: str, yaml_text: str, archive: tarfile.TarFile, output_dir: Path) -> None:
root = yaml_to_json(yaml.safe_load(yaml_text))
if not isinstance(root, dict):
raise RuntimeError("NeMo model_config.yaml root must be a map")
output_dir.mkdir(parents=True, exist_ok=True)
if config_kind == "citrinet":
write_citrinet_sidecars(root, archive, output_dir)
return
raise RuntimeError(f"unsupported NeMo config kind: {config_kind}")
def install_nemo_archive(package: ModelPackage, source: ConverterSource, models_root: Path, overwrite: bool) -> Path:
target_dir = models_root / package.target_directory
staging_root = models_root / ".engine_model_staging"
staging_root.mkdir(parents=True, exist_ok=True)
staging_dir = Path(tempfile.mkdtemp(prefix=f"{package.target_directory}.", dir=staging_root))
try:
archive_path = staging_dir / Path(source.url).name
print(f"download {archive_path.name}")
download_file(source.url, archive_path, None)
raw_bytes = archive_path.read_bytes()
with tarfile.open(fileobj=io.BytesIO(raw_bytes), mode="r:*") as archive:
weight_bytes = archive_member(archive, "model_weights.ckpt")
config_bytes = archive_member(archive, "model_config.yaml")
payload = torch.load(io.BytesIO(weight_bytes), map_location="cpu", weights_only=False)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
write_checked_safetensors(
tensors,
staging_dir / source.output_weights_file,
archive_path,
overwrite=True,
)
write_nemo_sidecars(source.config_kind, config_bytes.decode("utf-8"), archive, staging_dir)
archive_path.unlink()
validate_required_files(package, staging_dir)
if target_dir.exists():
if not overwrite:
raise RuntimeError(f"model directory already exists: {target_dir}")
shutil.rmtree(target_dir)
target_dir.parent.mkdir(parents=True, exist_ok=True)
staging_dir.rename(target_dir)
return target_dir
except Exception:
shutil.rmtree(staging_dir, ignore_errors=True)
raise
finally:
try:
if staging_root.exists() and not any(staging_root.iterdir()):
staging_root.rmdir()
except OSError:
pass
def install_converter(
package: ModelPackage,
source: ConverterSource,
models_root: Path,
overwrite: bool,
source_file_arg: str | None,
output_file_arg: str | None,
source_dir_arg: str | None,
variant_arg: str | None,
) -> Path:
target_dir = models_root / package.target_directory
if source.kind == "nemo_archive":
return install_nemo_archive(package, source, models_root, overwrite)
if source.kind == "demucs_reference":
return install_demucs_reference(package, models_root, overwrite, source_dir_arg, variant_arg)
if source.kind != "pytorch_to_safetensors":
raise RuntimeError(f"unsupported converter kind: {source.kind}")
if not source_file_arg:
raise RuntimeError(f"{package.id} requires --source-file")
source_file = resolve_path(source_file_arg)
if not source_file.is_file():
raise RuntimeError(f"source checkpoint does not exist: {source_file}")
if output_file_arg:
output_path = resolve_path(output_file_arg)
else:
output_path = target_dir / package.required_files[0]
payload = torch.load(source_file, map_location="cpu", weights_only=True)
state = checkpoint_state_dict(payload)
tensors = tensor_state_dict(state)
write_checked_safetensors(tensors, output_path, source_file, overwrite)
if output_path.parent == target_dir:
validate_required_files(package, target_dir)
return target_dir
def command_list(args: argparse.Namespace) -> int:
if args.json:
print(json.dumps([package_payload(package) for package in CATALOG], indent=2))
else:
print_package_table(CATALOG)
return 0
def command_info(args: argparse.Namespace) -> int:
package = PACKAGE_BY_ID.get(args.package_id)
if package is None:
raise RuntimeError(f"unknown package id: {args.package_id}")
payload = package_payload(package)
if args.json:
print(json.dumps(payload, indent=2))
else:
print(json.dumps(payload, indent=2))
return 0
def command_install(args: argparse.Namespace) -> int:
package = PACKAGE_BY_ID.get(args.package_id)
if package is None:
raise RuntimeError(f"unknown package id: {args.package_id}")
if package_needs_install_deps(package):
_ensure_install_deps()
models_root = resolve_path(args.models_root)
models_root.mkdir(parents=True, exist_ok=True)
source = package.source
if isinstance(source, UnsupportedSource):
raise RuntimeError(f"{package.id} is not installable: {source.reason}")
if isinstance(source, SnapshotSource):
install_path = install_snapshot(package, source, models_root, args.overwrite)
elif isinstance(source, CompositeSnapshotSource):
install_path = install_composite_snapshot(package, source, models_root, args.overwrite)
else:
install_path = install_converter(
package,
source,
models_root,
args.overwrite,
args.source_file,
args.output_file,
args.source_dir,
args.variant,
)
print(f"installed {package.id} -> {install_path}")
return 0
def main() -> int:
args = parse_args()
try:
if args.command == "list":
return command_list(args)
if args.command == "info":
return command_info(args)
if args.command == "install":
return command_install(args)
raise RuntimeError(f"unsupported command: {args.command}")
except HTTPError as ex:
print(f"http error: {ex.code} {ex.reason}", file=sys.stderr)
return 1
except Exception as ex:
print(str(ex), file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())