| |
|
| | """
|
| | 模型下载工具 - 自动从 Hugging Face 下载所需模型
|
| | """
|
| | import os
|
| | import hashlib
|
| | import requests
|
| | from pathlib import Path
|
| | from tqdm import tqdm
|
| | from typing import Optional, Dict, List
|
| |
|
| |
|
| | MODELS = {
|
| | "HP2_all_vocals.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP2_all_vocals.pth",
|
| | "path": "assets/uvr5_weights/HP2_all_vocals.pth",
|
| | "size_mb": 140,
|
| | "description": "UVR5 HP2 vocal model"
|
| | },
|
| | "HP3_all_vocals.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP3_all_vocals.pth",
|
| | "path": "assets/uvr5_weights/HP3_all_vocals.pth",
|
| | "size_mb": 140,
|
| | "description": "UVR5 HP3 vocal model"
|
| | },
|
| | "HP5_only_main_vocal.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/HP5_only_main_vocal.pth",
|
| | "path": "assets/uvr5_weights/HP5_only_main_vocal.pth",
|
| | "size_mb": 140,
|
| | "description": "UVR5 HP5 main vocal model"
|
| | },
|
| | "VR-DeEchoAggressive.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/VR-DeEchoAggressive.pth",
|
| | "path": "assets/uvr5_weights/VR-DeEchoAggressive.pth",
|
| | "size_mb": 130,
|
| | "description": "UVR5 de-echo aggressive"
|
| | },
|
| | "VR-DeEchoDeReverb.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/VR-DeEchoDeReverb.pth",
|
| | "path": "assets/uvr5_weights/VR-DeEchoDeReverb.pth",
|
| | "size_mb": 130,
|
| | "description": "UVR5 de-echo + de-reverb"
|
| | },
|
| | "VR-DeEchoNormal.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/VR-DeEchoNormal.pth",
|
| | "path": "assets/uvr5_weights/VR-DeEchoNormal.pth",
|
| | "size_mb": 130,
|
| | "description": "UVR5 de-echo normal"
|
| | },
|
| | "onnx_dereverb_By_FoxJoy/vocals.onnx": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/uvr5_weights/onnx_dereverb_By_FoxJoy/vocals.onnx",
|
| | "path": "assets/uvr5_weights/onnx_dereverb_By_FoxJoy/vocals.onnx",
|
| | "size_mb": 50,
|
| | "description": "UVR5 ONNX dereverb"
|
| | },
|
| | "hubert_base.pt": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/hubert_base.pt",
|
| | "path": "assets/hubert/hubert_base.pt",
|
| | "size_mb": 189,
|
| | "description": "HuBERT 特征提取模型"
|
| | },
|
| | "rmvpe.pt": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/rmvpe.pt",
|
| | "path": "assets/rmvpe/rmvpe.pt",
|
| | "size_mb": 181,
|
| | "description": "RMVPE 音高提取模型"
|
| | },
|
| | "f0G48k.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0G48k.pth",
|
| | "path": "assets/pretrained_v2/f0G48k.pth",
|
| | "size_mb": 55,
|
| | "description": "48kHz 生成器预训练权重"
|
| | },
|
| | "f0D48k.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0D48k.pth",
|
| | "path": "assets/pretrained_v2/f0D48k.pth",
|
| | "size_mb": 55,
|
| | "description": "48kHz 判别器预训练权重"
|
| | },
|
| | "f0G40k.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0G40k.pth",
|
| | "path": "assets/pretrained_v2/f0G40k.pth",
|
| | "size_mb": 55,
|
| | "description": "40kHz 生成器预训练权重"
|
| | },
|
| | "f0D40k.pth": {
|
| | "url": "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/pretrained_v2/f0D40k.pth",
|
| | "path": "assets/pretrained_v2/f0D40k.pth",
|
| | "size_mb": 55,
|
| | "description": "40kHz 判别器预训练权重"
|
| | }
|
| | }
|
| |
|
| |
|
| | REQUIRED_MODELS = ["hubert_base.pt", "rmvpe.pt", "HP2_all_vocals.pth"]
|
| |
|
| |
|
| | MATURE_DEECHO_MODELS = [
|
| | "VR-DeEchoDeReverb.pth",
|
| | "onnx_dereverb_By_FoxJoy/vocals.onnx",
|
| | "VR-DeEchoNormal.pth",
|
| | "VR-DeEchoAggressive.pth",
|
| | ]
|
| |
|
| |
|
| | def get_project_root() -> Path:
|
| | """获取项目根目录"""
|
| | return Path(__file__).parent.parent
|
| |
|
| |
|
| | def download_file(url: str, dest_path: Path, desc: str = None) -> bool:
|
| | """
|
| | 下载文件,支持断点续传和进度显示
|
| |
|
| | Args:
|
| | url: 下载链接
|
| | dest_path: 目标路径
|
| | desc: 进度条描述
|
| |
|
| | Returns:
|
| | bool: 下载是否成功
|
| | """
|
| | dest_path.parent.mkdir(parents=True, exist_ok=True)
|
| |
|
| |
|
| | resume_pos = 0
|
| | if dest_path.exists():
|
| | resume_pos = dest_path.stat().st_size
|
| |
|
| | headers = {}
|
| | if resume_pos > 0:
|
| | headers["Range"] = f"bytes={resume_pos}-"
|
| | if "huggingface.co" in url:
|
| | hf_token = (
|
| | os.environ.get("HF_TOKEN")
|
| | or os.environ.get("HUGGINGFACE_HUB_TOKEN")
|
| | or os.environ.get("HUGGINGFACE_TOKEN")
|
| | )
|
| | if hf_token:
|
| | headers["Authorization"] = f"Bearer {hf_token}"
|
| |
|
| | try:
|
| | response = requests.get(url, headers=headers, stream=True, timeout=30)
|
| |
|
| |
|
| | if response.status_code == 416:
|
| | print(f" 文件已完整下载: {dest_path.name}")
|
| | return True
|
| |
|
| | if response.status_code not in [200, 206]:
|
| | print(f" 下载失败: HTTP {response.status_code}")
|
| | return False
|
| |
|
| |
|
| | total_size = int(response.headers.get("content-length", 0))
|
| | if response.status_code == 206:
|
| | total_size += resume_pos
|
| |
|
| |
|
| | mode = "ab" if resume_pos > 0 else "wb"
|
| |
|
| | with open(dest_path, mode) as f:
|
| | with tqdm(
|
| | total=total_size,
|
| | initial=resume_pos,
|
| | unit="B",
|
| | unit_scale=True,
|
| | desc=desc or dest_path.name
|
| | ) as pbar:
|
| | for chunk in response.iter_content(chunk_size=8192):
|
| | if chunk:
|
| | f.write(chunk)
|
| | pbar.update(len(chunk))
|
| |
|
| | return True
|
| |
|
| | except requests.exceptions.RequestException as e:
|
| | print(f" 下载错误: {e}")
|
| | return False
|
| |
|
| |
|
| | def check_model(name: str) -> bool:
|
| | """
|
| | 检查模型是否已下载
|
| |
|
| | Args:
|
| | name: 模型名称
|
| |
|
| | Returns:
|
| | bool: 模型是否存在
|
| | """
|
| | if name not in MODELS:
|
| | return False
|
| |
|
| | model_path = get_project_root() / MODELS[name]["path"]
|
| | return model_path.exists()
|
| |
|
| |
|
| | def download_model(name: str) -> bool:
|
| | """
|
| | 下载指定模型
|
| |
|
| | Args:
|
| | name: 模型名称
|
| |
|
| | Returns:
|
| | bool: 下载是否成功
|
| | """
|
| | if name not in MODELS:
|
| | print(f"未知模型: {name}")
|
| | return False
|
| |
|
| | model_info = MODELS[name]
|
| | model_path = get_project_root() / model_info["path"]
|
| |
|
| | if model_path.exists():
|
| | print(f"模型已存在: {name}")
|
| | return True
|
| |
|
| | print(f"正在下载: {model_info['description']} ({model_info['size_mb']}MB)")
|
| | return download_file(model_info["url"], model_path, name)
|
| |
|
| |
|
| | def download_required_models() -> bool:
|
| | """
|
| | 下载所有必需模型
|
| |
|
| | Returns:
|
| | bool: 是否全部下载成功
|
| | """
|
| | print("=" * 50)
|
| | print("检查必需模型...")
|
| | print("=" * 50)
|
| |
|
| | success = True
|
| | for name in REQUIRED_MODELS:
|
| | if not check_model(name):
|
| | if not download_model(name):
|
| | success = False
|
| | else:
|
| | print(f"[OK] {name} 已存在")
|
| |
|
| | return success
|
| |
|
| |
|
| | def download_all_models() -> bool:
|
| | """
|
| | 下载所有模型
|
| |
|
| | Returns:
|
| | bool: 是否全部下载成功
|
| | """
|
| | print("=" * 50)
|
| | print("下载所有模型...")
|
| | print("=" * 50)
|
| |
|
| | success = True
|
| | for name in MODELS:
|
| | if not check_model(name):
|
| | if not download_model(name):
|
| | success = False
|
| | else:
|
| | print(f"[OK] {name} 已存在")
|
| |
|
| | return success
|
| |
|
| |
|
| | def check_all_models() -> Dict[str, bool]:
|
| | """
|
| | 检查所有模型状态
|
| |
|
| | Returns:
|
| | dict: 模型名称 -> 是否存在
|
| | """
|
| | return {name: check_model(name) for name in MODELS}
|
| |
|
| |
|
| | def get_available_mature_deecho_models() -> List[str]:
|
| | """Return locally available mature DeEcho / DeReverb models."""
|
| | return [name for name in MATURE_DEECHO_MODELS if check_model(name)]
|
| |
|
| |
|
| | def get_preferred_mature_deecho_model() -> Optional[str]:
|
| | """Return the preferred learned DeEcho model by priority."""
|
| | available = set(get_available_mature_deecho_models())
|
| | for name in MATURE_DEECHO_MODELS:
|
| | if name in available:
|
| | return name
|
| | return None
|
| |
|
| |
|
| | def download_mature_deecho_models() -> bool:
|
| | """Download mature DeEcho / DeReverb recommended models."""
|
| | print("=" * 50)
|
| | print("Downloading mature DeEcho / DeReverb models...")
|
| | print("=" * 50)
|
| |
|
| | success = True
|
| | for name in MATURE_DEECHO_MODELS:
|
| | if not check_model(name):
|
| | if not download_model(name):
|
| | success = False
|
| | else:
|
| | print(f"[OK] {name} already exists")
|
| |
|
| | return success
|
| |
|
| |
|
| | def print_model_status():
|
| | """打印模型状态"""
|
| | print("=" * 50)
|
| | print("模型状态")
|
| | print("=" * 50)
|
| |
|
| | status = check_all_models()
|
| | for name, exists in status.items():
|
| | info = MODELS[name]
|
| | mark = "OK" if exists else "MISSING"
|
| | print(f" {mark} {name}")
|
| | print(f" {info['description']}")
|
| | print(f" 大小: {info['size_mb']}MB")
|
| | if name in REQUIRED_MODELS:
|
| | print(f" [必需]")
|
| | print()
|
| |
|
| |
|
| | if __name__ == "__main__":
|
| | import argparse
|
| |
|
| | parser = argparse.ArgumentParser(description="RVC 模型下载工具")
|
| | parser.add_argument("--check", action="store_true", help="检查模型状态")
|
| | parser.add_argument("--all", action="store_true", help="下载所有模型")
|
| | parser.add_argument("--model", type=str, help="下载指定模型")
|
| |
|
| | args = parser.parse_args()
|
| |
|
| | if args.check:
|
| | print_model_status()
|
| | elif args.model:
|
| | download_model(args.model)
|
| | elif args.all:
|
| | download_all_models()
|
| | else:
|
| | download_required_models()
|
| |
|