基于 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. 点击"立即分离"等待处理完成

已有 34 条评论

    1. Alex Alex

      This tool has saved me so much time creating instrumental versions for my guitar students. The quality is good enough that they don't notice it's not the real instrumental track. Highly recommended for music teachers!

    2. 小郑 小郑

      如果想把exe发给国外朋友,建议在config.json里设置语言为英文。默认界面是中英文混合的,稍微有点乱。或者修改代码做个语言切换功能,方便国际化。

    3. Pablo Pablo

      The HTML templates are clean and responsive. Works perfectly on my tablet when I access from the same network. The drag-and-drop file upload works great on touch devices too.

    4. 老吴 老吴

      用了两天,处理了上百首歌,稳定性不错。就是内存占用有点高,5stems模型处理长歌时能占到4-5G。建议在UI上提示一下,让用户关掉其他程序再跑大的任务。

    5. Nina Nina

      The 4stems model is magic for acapella extraction. I've tried so many online tools that leave artifacts or kill the reverb. This one preserves much more of the original quality. Perfect for remix projects.