📁 项目方案:UVR-Pro - 专业级便携式人声消除工具
核心原则:一切都在程序根目录下
原则1:零写入系统盘(C盘)
原则2:所有文件自包含
原则3:目录结构清晰分离
原则4:用户数据与程序分离但同目录一、最终运行时的目录结构
UVR-Pro/ ← 用户解压或安装到此目录
├── UVR-Pro.exe ← 主程序(单文件或入口)
├── README.txt ← 使用说明
│
├── core/ ← 核心引擎(不可写)
│ ├── uvrcore.dll/.so ← C++加速库(如有)
│ ├── audio_engine.pyd ← 音频处理引擎
│ ├── model_loader.pyd ← 模型加载器
│ └── config/ ← 引擎配置文件
│ ├── default.yaml
│ └── performance.yaml
│
├── models/ ← 模型库目录(只读)
│ ├── README.md ← 模型说明
│ ├── vr_standard/ ← 预设模型1
│ │ ├── model.pth ← 模型文件
│ │ ├── config.json ← 模型配置
│ │ ├── signature.md5 ← 文件校验
│ │ └── preview.mp3 ← 效果预览
│ ├── dsp_basic/ ← 预设模型2
│ │ ├── algorithm.py ← 算法文件
│ │ └── config.json
│ └── models_index.json ← 模型索引文件
│
├── frontend/ ← 前端资源(只读)
│ ├── index.html ← 如果是Web前端
│ ├── assets/
│ │ ├── css/
│ │ ├── js/
│ │ └── icons/
│ └── electron/ ← 如果是Electron
│ ├── main.js
│ └── preload.js
│
├── runtime/ ← 运行时目录(可写)
│ ├── temp/ ← 临时文件(程序退出可清空)
│ │ ├── audio_cache/
│ │ ├── processing_temp/
│ │ └── model_cache/ ← 模型解压缓存
│ ├── logs/ ← 日志文件
│ │ ├── app.log
│ │ ├── error.log
│ │ └── processing_history.log
│ └── user_data/ ← 用户数据
│ ├── settings.json ← 用户设置
│ ├── recent_files.json ← 最近文件
│ └── custom_presets/ ← 用户自定义预设
│
├── plugins/ ← 插件目录(可扩展)
│ ├── system/ ← 系统插件
│ │ ├── format_converter/
│ │ └── batch_processor/
│ ├── community/ ← 社区插件(预留)
│ └── plugins_config.json ← 插件配置
│
└── docs/ ← 文档
├── user_guide.pdf
├── quick_start.txt
└── license.txt二、前后端完全分离架构
前端(Frontend)
- 技术栈:PyQt5 或 Tkinter(纯Python GUI)
职责:
- 用户交互界面
- 文件选择对话框
- 参数设置面板
- 进度显示和日志
- 特点:不包含任何业务逻辑,仅负责UI展示和事件转发
后端(Backend)
- 技术栈:Python + C扩展(可选)
职责:
- 音频文件读取/写入
- 模型加载和推理
- 音频信号处理
- 任务队列管理
- 特点:无UI依赖,可独立运行
通信方式
前端 (UI Thread) ↔ IPC/Queue ↔ 后端 (Worker Thread)
↓ ↓
用户交互 音频处理
进度显示 模型推理
错误提示 文件I/O三、编译打包方案
编译策略
开发时源代码结构 ≠ 编译后运行结构编译流程
1. 源代码开发阶段
uvrcode/
├── frontend_src/
├── backend_src/
└── resources/
2. 编译构建阶段
build/
├── compile_frontend.py → 生成 frontend/ 目录
├── compile_backend.py → 生成 core/ 目录
└── package_models.py → 打包 models/ 目录
3. 最终分发阶段
dist/
└── UVR-Pro/ ← 完整运行时目录PyInstaller配置关键点
# build.spec 关键配置
import sys
import os
# 禁止写入C盘
if sys.platform == 'win32':
# 设置临时目录到程序所在目录
os.environ['TEMP'] = os.path.join(sys._MEIPASS, 'runtime', 'temp')
os.environ['TMP'] = os.path.join(sys._MEIPASS, 'runtime', 'temp')
a = Analysis(
['main.py'],
pathex=[], # 不依赖系统PATH
binaries=[],
datas=[
# 显式指定每个目录
('src/frontend', 'frontend'),
('src/backend/compiled', 'core'),
('models/packaged', 'models'),
('resources', 'resources'),
],
hiddenimports=[],
hookspath=['hooks'],
runtime_hooks=['runtime_hooks.py'], # 运行时hook禁止写C盘
excludes=['test', 'unittest', 'pydoc'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)四、路径访问规范
绝对禁止的行为
# ❌ 禁止这样写
os.path.expanduser('~') # 可能指向C:\Users
os.environ['APPDATA'] # 系统目录
'C:\\' # 硬编码系统盘
# ✅ 必须这样写
import sys
import os
def get_app_root():
"""获取应用程序根目录"""
if getattr(sys, 'frozen', False):
# 打包后:_MEIPASS目录
return sys._MEIPASS
else:
# 开发时:项目根目录
return os.path.dirname(os.path.abspath(__file__))
def get_runtime_path(subdir=''):
"""获取运行时目录(可写)"""
root = get_app_root()
runtime_dir = os.path.join(root, 'runtime')
# 自动创建子目录
if subdir:
target = os.path.join(runtime_dir, subdir)
os.makedirs(target, exist_ok=True)
return target
return runtime_dir
def get_model_path(model_name):
"""获取模型路径"""
root = get_app_root()
return os.path.join(root, 'models', model_name)五、模型管理策略
模型存放规则
- 原始模型:
models/[model_name]/(只读) - 缓存模型:
runtime/temp/model_cache/(可写,临时) - 用户模型:
runtime/user_data/custom_models/(可写,持久)
模型加载流程
class ModelManager:
def __init__(self):
self.app_root = get_app_root()
self.model_base = os.path.join(self.app_root, 'models')
self.cache_dir = os.path.join(self.app_root, 'runtime', 'temp', 'model_cache')
def load_model(self, model_id):
"""加载模型,优先使用缓存"""
model_dir = os.path.join(self.model_base, model_id)
# 检查缓存
cache_file = os.path.join(self.cache_dir, f"{model_id}.cache")
if os.path.exists(cache_file):
return self._load_from_cache(cache_file)
# 从原始目录加载并创建缓存
model = self._load_original(model_dir)
self._create_cache(model, cache_file)
return model
def _load_original(self, model_dir):
"""从原始目录加载,绝不涉及系统目录"""
# 验证所有文件都在model_dir内
for root, dirs, files in os.walk(model_dir):
for file in files:
full_path = os.path.join(root, file)
# 安全检查:确保路径在允许范围内
if not full_path.startswith(self.model_base):
raise SecurityError(f"模型文件路径越界: {full_path}")
# 加载模型...六、配置文件策略
配置文件位置
CONFIG_PATHS = {
# 系统默认配置(只读)
'defaults': os.path.join(get_app_root(), 'core', 'config', 'default.yaml'),
# 用户配置(可写,在runtime目录)
'user': os.path.join(get_app_root(), 'runtime', 'user_data', 'settings.json'),
# 运行时临时配置(可写)
'session': os.path.join(get_app_root(), 'runtime', 'temp', 'session_config.json')
}七、编译构建脚本示例
build.py
#!/usr/bin/env python3
"""
编译构建脚本 - 确保所有文件都在正确位置
"""
import os
import shutil
import py_compile
from pathlib import Path
class Builder:
def __init__(self):
self.project_root = Path(__file__).parent
self.build_dir = self.project_root / "build"
self.dist_dir = self.project_root / "dist"
def clean(self):
"""清理构建目录"""
if self.build_dir.exists():
shutil.rmtree(self.build_dir)
if self.dist_dir.exists():
shutil.rmtree(self.dist_dir)
def prepare_directory_structure(self):
"""创建标准的目录结构"""
structure = [
"dist/UVR-Pro",
"dist/UVR-Pro/core",
"dist/UVR-Pro/models",
"dist/UVR-Pro/frontend",
"dist/UVR-Pro/runtime/temp",
"dist/UVR-Pro/runtime/logs",
"dist/UVR-Pro/runtime/user_data",
"dist/UVR-Pro/plugins",
"dist/UVR-Pro/docs"
]
for dir_path in structure:
Path(dir_path).mkdir(parents=True, exist_ok=True)
def compile_backend(self):
"""编译后端代码到core目录"""
backend_src = self.project_root / "src" / "backend"
core_target = self.dist_dir / "UVR-Pro" / "core"
# 编译Python文件为.pyc
for py_file in backend_src.rglob("*.py"):
relative = py_file.relative_to(backend_src)
target_file = core_target / relative.with_suffix(".pyc")
target_file.parent.mkdir(parents=True, exist_ok=True)
py_compile.compile(py_file, cfile=str(target_file))
def copy_models(self):
"""复制模型文件到models目录"""
models_src = self.project_root / "models"
models_target = self.dist_dir / "UVR-Pro" / "models"
shutil.copytree(models_src, models_target, dirs_exist_ok=True)
def create_spec_file(self):
"""生成PyInstaller spec文件"""
spec_content = f'''
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main_entry.py'],
pathex=[],
binaries=[],
datas=[
('{self.dist_dir}/UVR-Pro/frontend', 'frontend'),
('{self.dist_dir}/UVR-Pro/core', 'core'),
('{self.dist_dir}/UVR-Pro/models', 'models'),
('{self.dist_dir}/UVR-Pro/docs', 'docs'),
],
hiddenimports=[],
hookspath=[],
runtime_hooks=['runtime_hooks.py'],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
# 禁止写入C盘的runtime hook
runtime_hooks = ['no_cdisk_write.py']
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='UVR-Pro',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='app_icon.ico'
)
'''
spec_file = self.build_dir / "UVR-Pro.spec"
spec_file.write_text(spec_content)
def build(self):
"""执行完整构建流程"""
print("1. 清理旧构建...")
self.clean()
print("2. 创建目录结构...")
self.prepare_directory_structure()
print("3. 编译后端代码...")
self.compile_backend()
print("4. 复制模型文件...")
self.copy_models()
print("5. 生成spec文件...")
self.create_spec_file()
print("6. 运行PyInstaller...")
os.system(f"pyinstaller {self.build_dir/'UVR-Pro.spec'}")
print("构建完成!输出目录: dist/UVR-Pro/")
if __name__ == "__main__":
builder = Builder()
builder.build()八、运行时保护钩子
no_cdisk_write.py (运行时钩子)
"""
运行时钩子 - 禁止程序写入C盘
"""
import os
import sys
def block_cdisk_access():
"""阻止对C盘的写入"""
def safe_join(path, *paths):
"""安全的路径连接,检查是否指向C盘"""
full_path = os.path.join(path, *paths)
full_path = os.path.abspath(full_path)
# 检查是否试图访问C盘系统目录
if sys.platform == 'win32':
if full_path.startswith(('C:\\', 'c:\\')):
# 重定向到程序目录下的runtime目录
if getattr(sys, 'frozen', False):
base = sys._MEIPASS
else:
base = os.path.dirname(os.path.abspath(__file__))
# 提取相对路径部分
rel_path = full_path[3:] # 去掉"C:\"
# 重定向到runtime目录
safe_path = os.path.join(base, 'runtime', 'temp', rel_path.lstrip('\\/'))
os.makedirs(os.path.dirname(safe_path), exist_ok=True)
return safe_path
return full_path
# 替换os.path.join为安全版本
os.path.join = safe_join
# 设置环境变量,强制使用程序目录
if getattr(sys, 'frozen', False):
app_dir = sys._MEIPASS
runtime_dir = os.path.join(app_dir, 'runtime')
os.environ['TEMP'] = os.path.join(runtime_dir, 'temp')
os.environ['TMP'] = os.path.join(runtime_dir, 'temp')
os.environ['APPDATA'] = runtime_dir # 重定向
# 应用保护
block_cdisk_access()九、用户安装和使用流程
安装流程
用户操作:
1. 下载 UVR-Pro.zip
2. 解压到任意位置(如 D:\AudioTools\UVR-Pro\)
3. 直接运行 UVR-Pro.exe
系统变化:
✅ 不会写入注册表
✅ 不会在C盘创建文件
✅ 不会添加系统路径
✅ 所有文件都在解压目录内卸载流程
用户操作:
1. 关闭程序
2. 删除整个 UVR-Pro 文件夹
系统变化:
✅ 无残留注册表项
✅ 无系统目录文件
✅ 完全绿色卸载十、质量保证清单
- [ ] 目录分离:前端/后端/模型/运行时完全分离
- [ ] 路径安全:所有路径都基于程序根目录
- [ ] 无C盘写入:运行时钩子保护
- [ ] 完整包含:所有依赖打包在目录内
- [ ] 相对路径:代码中只使用相对路径
- [ ] 环境隔离:不依赖系统Python环境
- [ ] 用户友好:解压即用,无需配置
这个方案确保:
- 程序绝对绿色,不污染系统
- 所有文件都在一个根目录下
- 前后端物理分离但逻辑统一
- 用户数据与程序数据分离但同目录
- 专业级项目结构,便于维护和扩展