基于 Spleeter 的人声分离工具,提供本地网页界面,支持 2stems/4stems/5stems 模型

2026-02-17T13:04:51.png

GitHub 项目地址

vocal-separate 官方仓库

https://github.com/jianchang512/vocal-separate

这是一个基于 Spleeter 的人声分离工具,提供本地网页界面,支持 2stems/4stems/5stems 模型 。

打包成 exe 的完整方案

方案一:使用 PyInstaller 打包(推荐)

1. 环境准备

# 1. 克隆项目
git clone https://github.com/jianchang512/vocal-separate.git
cd vocal-separate

# 2. 创建虚拟环境
python -m venv venv
# Windows 激活虚拟环境
venv\Scripts\activate

# 3. 安装依赖
pip install --upgrade pip
pip install pyinstaller
pip install -r requirements.txt

2. 创建打包脚本 build_exe.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
vocal-separate v0.0.4 EXE 打包脚本
使用 PyInstaller 将项目打包成单个可执行文件
"""

import os
import sys
import shutil
import site
import subprocess
from pathlib import Path

def clean_build_dirs():
    """清理之前的构建目录"""
    dirs_to_clean = ['build', 'dist']
    for dir_name in dirs_to_clean:
        if os.path.exists(dir_name):
            shutil.rmtree(dir_name)
            print(f"已清理 {dir_name} 目录")
    
    # 删除 spec 文件
    spec_files = [f for f in os.listdir('.') if f.endswith('.spec')]
    for spec in spec_files:
        os.remove(spec)
        print(f"已删除 {spec}")

def get_site_packages_path():
    """获取 site-packages 路径"""
    for path in site.getsitepackages():
        if 'site-packages' in path:
            return path
    return None

def find_model_path():
    """查找预训练模型路径"""
    # 可能的模型存放位置
    possible_paths = [
        './models',
        './pretrained_models',
        os.path.join(os.path.dirname(sys.executable), 'models'),
        os.path.join(os.getcwd(), 'models'),
    ]
    
    # 检查 site-packages 中的 spleeter 模型
    site_packages = get_site_packages_path()
    if site_packages:
        spleeter_path = os.path.join(site_packages, 'spleeter')
        if os.path.exists(spleeter_path):
            pretrained_path = os.path.join(spleeter_path, 'pretrained')
            if os.path.exists(pretrained_path):
                possible_paths.append(pretrained_path)
    
    for path in possible_paths:
        if os.path.exists(path):
            print(f"找到模型目录: {path}")
            return path
    
    print("警告:未找到预训练模型目录,打包后可能需要联网下载")
    return None

def create_spec_file(project_root, model_path):
    """创建 PyInstaller spec 文件"""
    spec_content = f"""# -*- mode: python ; coding: utf-8 -*-

import sys
from PyInstaller.utils.hooks import collect_data_files, collect_submodules

a = Analysis(
    ['start.py'],
    pathex=['{project_root}'],
    binaries=[],
    datas=[
        # 包含静态文件
        ('./static', 'static'),
        ('./templates', 'templates'),
        # 包含配置文件
        ('./config.json', '.'),
        ('./config.yaml', '.'),
        # 包含模型文件
        ('{model_path}', 'models'),
    ],
    hiddenimports=[
        'spleeter',
        'spleeter.*',
        'tensorflow',
        'tensorflow.*',
        'librosa',
        'librosa.*',
        'ffmpeg',
        'ffmpeg.*',
        'numpy',
        'scipy',
        'sklearn',
        'sklearn.*',
        'joblib',
        'joblib.*',
        'pydub',
        'pydub.*',
        'flask',
        'flask.*',
        'werkzeug',
        'werkzeug.*',
        'jinja2',
        'jinja2.*',
        'markupsafe',
        'markupsafe.*',
        'itsdangerous',
        'click',
        'threading',
        'multiprocessing',
    ],
    hookspath=[],
    hooksconfig={{}},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
)

pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='vocal-separate',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,  # 显示控制台窗口,便于调试
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='icon.ico' if os.path.exists('icon.ico') else None,
)

# 创建快捷启动脚本
with open('start_exe.py', 'w') as f:
    f.write('''
import os
import sys
import webbrowser
import time
from threading import Timer

def open_browser():
    webbrowser.open('http://127.0.0.1:5000')

if __name__ == '__main__':
    # 切换到程序所在目录
    os.chdir(os.path.dirname(sys.executable))
    
    # 启动主程序
    from start import app
    
    # 延时打开浏览器
    Timer(1.5, open_browser).start()
    
    # 运行 Flask 应用
    app.run(host='127.0.0.1', port=5000, debug=False, threaded=True)
''')
"""
    
    with open('vocal-separate.spec', 'w', encoding='utf-8') as f:
        f.write(spec_content)
    
    print("已生成 spec 文件")

def check_ffmpeg():
    """检查 ffmpeg 是否可用"""
    try:
        subprocess.run(['ffmpeg', '-version'], capture_output=True)
        return True
    except FileNotFoundError:
        print("警告:未找到 ffmpeg,程序可能无法处理视频文件")
        return False

def download_ffmpeg():
    """下载 ffmpeg(Windows 系统)"""
    if sys.platform == 'win32':
        import urllib.request
        import zipfile
        
        ffmpeg_url = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
        print("正在下载 ffmpeg...")
        
        try:
            urllib.request.urlretrieve(ffmpeg_url, "ffmpeg.zip")
            with zipfile.ZipFile("ffmpeg.zip", 'r') as zip_ref:
                zip_ref.extractall("ffmpeg_temp")
            
            # 复制 ffmpeg.exe 到项目目录
            for root, dirs, files in os.walk("ffmpeg_temp"):
                for file in files:
                    if file == "ffmpeg.exe":
                        shutil.copy(os.path.join(root, file), "ffmpeg.exe")
                        print("ffmpeg 下载完成")
                        break
            
            # 清理临时文件
            shutil.rmtree("ffmpeg_temp")
            os.remove("ffmpeg.zip")
            
        except Exception as e:
            print(f"下载 ffmpeg 失败: {e}")

def main():
    """主打包函数"""
    print("=" * 50)
    print("vocal-separate v0.0.4 EXE 打包工具")
    print("=" * 50)
    
    # 检查 ffmpeg
    if not check_ffmpeg() and sys.platform == 'win32':
        choice = input("未找到 ffmpeg,是否自动下载?(y/n): ")
        if choice.lower() == 'y':
            download_ffmpeg()
    
    # 查找模型路径
    model_path = find_model_path()
    if not model_path:
        print("提示:模型将在首次运行时下载")
    
    # 清理旧文件
    clean_build_dirs()
    
    # 获取项目根目录
    project_root = os.getcwd()
    
    # 创建 spec 文件
    create_spec_file(project_root, model_path or './models')
    
    # 执行 PyInstaller
    print("\n开始打包,这可能需要几分钟时间...")
    cmd = [
        'pyinstaller',
        'vocal-separate.spec',
        '--clean',
        '--noconfirm',
    ]
    
    try:
        subprocess.run(cmd, check=True)
        print("\n✅ 打包完成!")
        print(f"可执行文件位于: {os.path.join(project_root, 'dist', 'vocal-separate')}")
        print("\n使用方法:")
        print("1. 进入 dist/vocal-separate 目录")
        print("2. 双击运行 vocal-separate.exe")
        print("3. 浏览器会自动打开 http://127.0.0.1:5000")
        print("4. 拖拽音视频文件进行分离")
    except subprocess.CalledProcessError as e:
        print(f"\n❌ 打包失败: {e}")
        return 1
    
    return 0

if __name__ == '__main__':
    sys.exit(main())

3. 创建启动脚本 start_exe.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
vocal-separate 启动脚本(用于 exe 打包)
"""

import os
import sys
import webbrowser
import time
import logging
from threading import Timer

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('vocal-separate.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('vocal-separate')

def open_browser():
    """延时打开浏览器"""
    time.sleep(1.5)
    url = 'http://127.0.0.1:5000'
    try:
        webbrowser.open(url)
        logger.info(f"已打开浏览器访问: {url}")
    except Exception as e:
        logger.error(f"打开浏览器失败: {e}")

def check_environment():
    """检查运行环境"""
    # 切换到程序所在目录
    os.chdir(os.path.dirname(sys.executable))
    
    # 检查 ffmpeg
    ffmpeg_paths = [
        'ffmpeg.exe',
        os.path.join(os.path.dirname(sys.executable), 'ffmpeg.exe'),
    ]
    
    ffmpeg_found = False
    for path in ffmpeg_paths:
        if os.path.exists(path):
            os.environ['PATH'] = os.path.dirname(path) + os.pathsep + os.environ['PATH']
            ffmpeg_found = True
            logger.info(f"找到 ffmpeg: {path}")
            break
    
    if not ffmpeg_found:
        logger.warning("未找到 ffmpeg,视频处理功能可能受限")
    
    # 检查模型目录
    model_dirs = [
        'models',
        'pretrained_models',
        os.path.join(os.path.dirname(sys.executable), 'models'),
    ]
    
    for model_dir in model_dirs:
        if os.path.exists(model_dir):
            logger.info(f"找到模型目录: {model_dir}")
            break
    else:
        logger.info("未找到本地模型,将在首次使用时下载")

def main():
    """主函数"""
    print("=" * 50)
    print("vocal-separate v0.0.4 人声分离工具")
    print("=" * 50)
    
    # 检查环境
    check_environment()
    
    # 启动浏览器线程
    Timer(1.5, open_browser).start()
    
    # 导入并启动 Flask 应用
    try:
        # 动态导入 start 模块
        sys.path.insert(0, os.path.dirname(sys.executable))
        
        # 这里需要根据实际的项目入口文件调整
        # 假设主文件是 start.py
        import importlib.util
        spec = importlib.util.spec_from_file_location(
            "start", 
            os.path.join(os.path.dirname(sys.executable), "start.py")
        )
        start_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(start_module)
        
        # 获取 Flask app
        if hasattr(start_module, 'app'):
            app = start_module.app
        else:
            # 尝试获取其他可能的变量名
            for attr in ['application', 'server', 'webapp']:
                if hasattr(start_module, attr):
                    app = getattr(start_module, attr)
                    break
            else:
                raise AttributeError("找不到 Flask 应用实例")
        
        logger.info("启动服务器 http://127.0.0.1:5000")
        app.run(
            host='127.0.0.1',
            port=5000,
            debug=False,
            threaded=True
        )
        
    except Exception as e:
        logger.error(f"启动失败: {e}")
        print(f"\n❌ 启动失败: {e}")
        print("请查看 vocal-separate.log 获取详细信息")
        input("按回车键退出...")
        sys.exit(1)

if __name__ == '__main__':
    main()

4. 运行打包

# 执行打包脚本
python build_exe.py

方案二:使用 auto-py-to-exe(可视化界面)

# 安装 auto-py-to-exe
pip install auto-py-to-exe

# 启动可视化界面
auto-py-to-exe

在可视化界面中配置:

  • Script Location: 选择 start.py
  • Onefile: 选择 One Directory(推荐,便于包含模型文件)
  • Console Window: 勾选(显示控制台便于调试)
  • Icon: 可选,设置程序图标
  • Additional Files: 添加以下文件和目录

    • ./static/ 目录
    • ./templates/ 目录
    • 预训练模型目录(如 ./models/
    • ffmpeg.exe(Windows 系统)

方案三:创建安装包(使用 Inno Setup)

创建 setup_script.iss

[Setup]
AppName=vocal-separate
AppVersion=0.0.4
DefaultDirName={pf}\vocal-separate
DefaultGroupName=vocal-separate
UninstallDisplayIcon={app}\vocal-separate.exe
Compression=lzma2
SolidCompression=yes
OutputDir=installer
OutputBaseFilename=vocal-separate-0.0.4-setup

[Files]
Source: "dist\vocal-separate\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "ffmpeg.exe"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Name: "{group}\vocal-separate"; Filename: "{app}\vocal-separate.exe"
Name: "{group}\Uninstall vocal-separate"; Filename: "{uninstallexe}"
Name: "{commondesktop}\vocal-separate"; Filename: "{app}\vocal-separate.exe"

[Run]
Filename: "{app}\vocal-separate.exe"; Description: "启动 vocal-separate"; Flags: postinstall nowait skipifsilent

注意事项

1. 模型文件处理

  • 项目内置了 Spleeter 预训练模型,打包时需要包含这些模型文件
  • 模型文件通常较大(几百MB),建议采用外置模型的方式,首次运行时下载

2. 依赖项说明

  • ffmpeg:必须包含,用于处理视频文件的音轨提取
  • TensorFlow:如果支持 GPU 加速,需要额外打包 CUDA 库

3. 性能优化建议

  • 对于无 NVIDIA GPU 的电脑,建议默认使用 2stems 模型,避免内存溢出
  • 打包时可以包含 CPU 版本的 TensorFlow,减小体积

4. 打包后的文件结构

vocal-separate/
├── vocal-separate.exe      # 主程序
├── ffmpeg.exe              # ffmpeg 工具
├── models/                 # 预训练模型
├── static/                 # 静态资源
├── templates/              # HTML模板
├── config.json             # 配置文件
└── vocal-separate.log      # 运行日志

使用说明

打包完成后,用户只需:

  1. 解压或安装程序
  2. 双击运行 vocal-separate.exe
  3. 等待浏览器自动打开
  4. 拖拽音视频文件到网页界面
  5. 点击"立即分离"等待处理完成