AI-RVC / install.py
mason369's picture
Release v1.2.1
a9536c4 verified
# -*- coding: utf-8 -*-
"""
RVC 安装脚本
自动创建虚拟环境 → 安装依赖 → 启动应用
用法:
python install.py # 完整安装并启动
python install.py --check # 仅检查依赖
python install.py --no-run # 安装但不启动
python install.py --cpu # 安装 CPU 版本
"""
import subprocess
import sys
import os
from pathlib import Path
ROOT_DIR = Path(__file__).parent
VENV_DIR = ROOT_DIR / "venv310"
PYTHON310_CANDIDATES = [
r"C:\Users\Administrator\AppData\Local\Programs\Python\Python310\python.exe",
r"C:\Python310\python.exe",
r"C:\Program Files\Python310\python.exe",
r"C:\Program Files (x86)\Python310\python.exe",
]
PACKAGES = {
"torch": {"import": "torch", "name": "PyTorch", "pip": "torch"},
"torchaudio": {"import": "torchaudio", "name": "torchaudio", "pip": "torchaudio"},
"gradio": {"import": "gradio", "name": "Gradio", "pip": "gradio==3.50.2"},
"librosa": {"import": "librosa", "name": "librosa", "pip": "librosa"},
"soundfile": {"import": "soundfile", "name": "soundfile", "pip": "soundfile"},
"av": {"import": "av", "name": "PyAV", "pip": "av"},
"scipy": {"import": "scipy", "name": "scipy", "pip": "scipy"},
"numpy": {
"import": "numpy",
"name": "numpy",
"pip": "numpy<2,>=1.23.0",
"dist": "numpy",
"min_version": "1.23.0",
"max_exclusive_version": "2.0.0",
},
"parselmouth": {"import": "parselmouth", "name": "praat-parselmouth", "pip": "praat-parselmouth"},
"pyworld": {"import": "pyworld", "name": "pyworld", "pip": "pyworld"},
"torchcrepe": {"import": "torchcrepe", "name": "torchcrepe", "pip": "torchcrepe"},
"faiss": {"import": "faiss", "name": "faiss-cpu", "pip": "faiss-cpu"},
"tqdm": {"import": "tqdm", "name": "tqdm", "pip": "tqdm"},
"requests": {"import": "requests", "name": "requests", "pip": "requests"},
"dotenv": {"import": "dotenv", "name": "python-dotenv", "pip": "python-dotenv"},
"colorama": {"import": "colorama", "name": "colorama", "pip": "colorama"},
"mcp": {"import": "mcp", "name": "mcp", "pip": "mcp"},
"demucs": {"import": "demucs", "name": "demucs", "pip": "demucs"},
"audio_separator": {
"import": "audio_separator",
"name": "audio-separator",
"pip": "audio-separator",
"dist": "audio-separator",
"min_version": "0.44.1",
},
"huggingface_hub": {"import": "huggingface_hub", "name": "huggingface_hub", "pip": "huggingface_hub"},
"pedalboard": {"import": "pedalboard", "name": "pedalboard", "pip": "pedalboard"},
"ffmpeg": {"import": "ffmpeg", "name": "ffmpeg-python", "pip": "ffmpeg-python"},
"fairseq": {"import": "fairseq", "name": "fairseq", "pip": "fairseq==0.12.2"},
}
# === 虚拟环境 ===
def find_python310():
"""查找系统中的 Python 3.10"""
if sys.version_info[:2] == (3, 10):
return sys.executable
for p in PYTHON310_CANDIDATES:
if os.path.isfile(p):
return p
try:
r = subprocess.run(
["py", "-3.10", "-c", "import sys; print(sys.executable)"],
capture_output=True, text=True, timeout=10,
)
if r.returncode == 0:
return r.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
return None
def get_venv_python():
"""获取虚拟环境的 Python 路径"""
if os.name == "nt":
return str(VENV_DIR / "Scripts" / "python.exe")
return str(VENV_DIR / "bin" / "python")
def create_venv():
"""创建 Python 3.10 虚拟环境"""
venv_py = get_venv_python()
if os.path.isfile(venv_py):
r = subprocess.run([venv_py, "--version"], capture_output=True, text=True)
if r.returncode == 0 and "3.10" in r.stdout:
print(f" [OK] 虚拟环境已存在: {VENV_DIR}")
return True
py310 = find_python310()
if not py310:
print(" [错误] 未找到 Python 3.10")
print(" 下载: https://www.python.org/downloads/release/python-31011/")
return False
print(f" 使用 Python: {py310}")
print(f" 创建虚拟环境: {VENV_DIR}")
r = subprocess.run([py310, "-m", "venv", str(VENV_DIR)], capture_output=True, text=True)
if r.returncode != 0:
print(f" [错误] 创建失败:\n{r.stderr}")
return False
print(" [OK] 虚拟环境创建成功")
print(" 升级 pip ...")
subprocess.run([venv_py, "-m", "pip", "install", "--upgrade", "pip"],
capture_output=True, text=True)
return True
# === 依赖检查与安装 ===
def check_package(venv_py, import_name):
"""用虚拟环境的 Python 检查包是否已安装"""
r = subprocess.run(
[venv_py, "-c", f"import {import_name}"],
capture_output=True, text=True,
)
return r.returncode == 0
def get_installed_version(venv_py, distribution_name):
"""返回虚拟环境中已安装发行包版本;无法读取时返回 None。"""
code = (
"from importlib.metadata import PackageNotFoundError, version\n"
f"dist = {distribution_name!r}\n"
"try:\n"
" print(version(dist))\n"
"except PackageNotFoundError:\n"
" raise SystemExit(1)\n"
)
r = subprocess.run([venv_py, "-c", code], capture_output=True, text=True)
if r.returncode != 0:
return None
return r.stdout.strip() or None
def _version_parts(version_text):
parts = []
for part in str(version_text or "").split("."):
digits = ""
for char in part:
if not char.isdigit():
break
digits += char
parts.append(int(digits or 0))
return tuple(parts)
def _version_at_least(installed, required):
installed_parts = _version_parts(installed)
required_parts = _version_parts(required)
width = max(len(installed_parts), len(required_parts))
installed_parts += (0,) * (width - len(installed_parts))
required_parts += (0,) * (width - len(required_parts))
return installed_parts >= required_parts
def _version_less_than(installed, upper_bound):
installed_parts = _version_parts(installed)
upper_parts = _version_parts(upper_bound)
width = max(len(installed_parts), len(upper_parts))
installed_parts += (0,) * (width - len(installed_parts))
upper_parts += (0,) * (width - len(upper_parts))
return installed_parts < upper_parts
def detect_cuda_version():
"""检测系统 CUDA 版本,返回对应的 PyTorch index-url"""
try:
r = subprocess.run(
["nvidia-smi", "--query-gpu=driver_version", "--format=csv,noheader"],
capture_output=True, text=True, timeout=10,
)
if r.returncode == 0:
# nvidia-smi 存在,尝试获取 CUDA 版本
r2 = subprocess.run(
["nvidia-smi"],
capture_output=True, text=True, timeout=10,
)
output = r2.stdout
# 从 nvidia-smi 输出中提取 CUDA Version
import re
match = re.search(r"CUDA Version:\s*(\d+)\.(\d+)", output)
if match:
major, minor = int(match.group(1)), int(match.group(2))
if (major, minor) >= (12, 6):
return "https://download.pytorch.org/whl/cu126"
elif (major, minor) >= (12, 4):
return "https://download.pytorch.org/whl/cu124"
elif (major, minor) >= (12, 1):
return "https://download.pytorch.org/whl/cu121"
elif (major, minor) >= (11, 8):
return "https://download.pytorch.org/whl/cu118"
else:
return None
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
return None
def pip_install(venv_py, package, extra="", index_url=None, no_deps=False, version_spec=""):
"""用虚拟环境的 pip 安装包"""
target = f"{package}[{extra}]{version_spec}" if extra else f"{package}{version_spec}"
print(f" 安装 {target} ...")
cmd = [venv_py, "-m", "pip", "install", target]
if index_url:
cmd.extend(["--index-url", index_url])
if no_deps:
cmd.append("--no-deps")
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
# fairseq 依赖冲突时尝试 --no-deps 回退
if not no_deps and "ResolutionImpossible" in r.stderr:
print(f" [依赖冲突] 尝试 --no-deps 安装 {target} ...")
return pip_install(venv_py, package, extra=extra, index_url=index_url, no_deps=True)
print(f" [失败] {target}")
lines = r.stderr.strip().splitlines()
if lines:
print(f" {lines[-1]}")
return False
print(f" [完成] {target}")
return True
def check_all(venv_py):
"""检查所有依赖"""
print("=" * 50)
print("RVC 依赖检查")
print("=" * 50)
missing = []
for key, info in PACKAGES.items():
ok = check_package(venv_py, info["import"])
status = "OK" if ok else "未安装"
min_version = info.get("min_version")
max_exclusive_version = info.get("max_exclusive_version")
if ok and (min_version or max_exclusive_version):
installed_version = get_installed_version(
venv_py,
info.get("dist", info["pip"]),
)
if not installed_version:
ok = False
requirements = []
if min_version:
requirements.append(f">= {min_version}")
if max_exclusive_version:
requirements.append(f"< {max_exclusive_version}")
status = f"需更新 (无法读取版本,要求 {' 且 '.join(requirements)})"
elif (
(not min_version or _version_at_least(installed_version, min_version))
and (
not max_exclusive_version
or _version_less_than(installed_version, max_exclusive_version)
)
):
status = f"OK ({installed_version})"
else:
ok = False
requirements = []
if min_version:
requirements.append(f">= {min_version}")
if max_exclusive_version:
requirements.append(f"< {max_exclusive_version}")
status = f"需更新 ({installed_version} 不满足 {' 且 '.join(requirements)})"
mark = "[v]" if ok else "[x]"
print(f" {mark} {info['name']:30s} {status}")
if not ok:
missing.append(info)
print("-" * 50)
if missing:
print(f"缺少 {len(missing)} 个依赖包")
else:
print("所有依赖已安装")
return missing
def install_all(venv_py, gpu=True):
"""安装所有缺失的依赖"""
missing = check_all(venv_py)
if not missing:
print("\n无需安装,所有依赖已就绪。")
return True
# 检测 CUDA 版本
cuda_index_url = None
if gpu:
cuda_index_url = detect_cuda_version()
if cuda_index_url:
print(f"\n 检测到 CUDA,使用 PyTorch 源: {cuda_index_url}")
else:
print("\n [错误] 未检测到支持的 CUDA,GPU 安装停止。")
print(" 如需 CPU 版 PyTorch,请显式使用 --cpu。")
return False
print(f"\n开始安装 {len(missing)} 个缺失的依赖...\n")
failed = []
for info in missing:
pip_name = info["pip"]
if pip_name in ("torch", "torchaudio"):
if gpu and cuda_index_url:
ok = pip_install(venv_py, pip_name, index_url=cuda_index_url)
else:
ok = pip_install(venv_py, pip_name, index_url="https://download.pytorch.org/whl/cpu")
elif pip_name == "audio-separator":
version_spec = f">={info['min_version']}" if info.get("min_version") else ""
ok = pip_install(
venv_py,
pip_name,
extra="gpu" if gpu else "cpu",
version_spec=version_spec,
)
if ok:
# audio-separator 0.31+ declares numpy>=2, while the current
# Gradio 3.x UI stack is pinned to numpy 1.x. The separator
# model table and runtime imports work with numpy 1.26, so
# restore the app-compatible numpy line after separator install.
ok = pip_install(venv_py, "numpy<2,>=1.23.0")
else:
ok = pip_install(venv_py, pip_name)
if not ok:
failed.append(info["name"])
print("\n" + "=" * 50)
if failed:
print(f"安装完成,{len(failed)} 个包失败: {', '.join(failed)}")
return False
print("所有依赖安装成功!")
return True
def launch_app(venv_py):
"""用虚拟环境启动应用"""
run_script = str(ROOT_DIR / "run.py")
print(f"\n启动应用: {run_script}")
print("=" * 50)
try:
subprocess.run([venv_py, run_script], cwd=str(ROOT_DIR))
except KeyboardInterrupt:
print("\n已停止")
# === 主入口 ===
def main():
import argparse
parser = argparse.ArgumentParser(description="RVC 安装脚本")
parser.add_argument("--cpu", action="store_true", help="安装 CPU 版本")
parser.add_argument("--check", action="store_true", help="仅检查依赖")
parser.add_argument("--no-run", action="store_true", help="安装后不启动")
args = parser.parse_args()
print("=" * 50)
print("RVC 安装程序")
print("=" * 50)
# 1. 创建虚拟环境
print("\n[1/3] 检查虚拟环境")
if not create_venv():
sys.exit(1)
venv_py = get_venv_python()
# 2. 安装依赖
print(f"\n[2/3] 检查依赖")
if args.check:
check_all(venv_py)
return
gpu = not args.cpu
if not install_all(venv_py, gpu=gpu):
print("\n部分依赖安装失败,可尝试手动安装。")
sys.exit(1)
# 3. 启动应用
if args.no_run:
print("\n安装完成。运行方式:")
print(f" {venv_py} run.py")
return
print(f"\n[3/3] 启动应用")
launch_app(venv_py)
if __name__ == "__main__":
main()