Skip to content

Build Executables

Build Executables #25

name: Build Executables
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: '要上传资源的 Release 标签(如 v1.2.0),留空则上传到最新 Release'
required: false
default: ''
permissions:
contents: write
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
variant: CPU
platform: Windows
pytorch_url: https://download.pytorch.org/whl/cpu
sep: ";"
shell: pwsh
- os: windows-latest
variant: GPU
platform: Windows
pytorch_url: https://download.pytorch.org/whl/cu121
sep: ";"
shell: pwsh
- os: ubuntu-latest
variant: CPU
platform: Linux
pytorch_url: https://download.pytorch.org/whl/cpu
sep: ":"
shell: bash
- os: ubuntu-latest
variant: GPU
platform: Linux
pytorch_url: https://download.pytorch.org/whl/cu121
sep: ":"
shell: bash
runs-on: ${{ matrix.os }}
name: Build ${{ matrix.platform }}-${{ matrix.variant }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y build-essential libsndfile1 ffmpeg portaudio19-dev
- name: Install FFmpeg (Windows)
if: runner.os == 'Windows'
run: choco install ffmpeg -y
- name: Install Python dependencies
run: |
python -m pip install --upgrade "pip<24.1"
pip install pyinstaller
pip install torch torchaudio --index-url ${{ matrix.pytorch_url }}
pip install omegaconf==2.0.6 --no-deps
pip install PyYAML antlr4-python3-runtime hydra-core
pip install fairseq==0.12.2 --no-deps
pip install "jinja2<3.1.5"
pip install gradio==3.50.2 librosa soundfile scipy numpy praat-parselmouth pyworld faiss-cpu tqdm requests python-dotenv colorama huggingface_hub pedalboard ffmpeg-python av imageio-ffmpeg
pip install demucs torchcrepe --no-deps
pip install julius dora-search lameenc openunmix treetable
python -c "import torch; print(f'PyTorch: {torch.__version__}, CUDA: {torch.cuda.is_available()}')"
- name: Install audio-separator
run: pip install audio-separator onnxruntime
- name: Bundle FFmpeg runtime
shell: bash
run: |
python - <<'PY'
import os
import shutil
import stat
from pathlib import Path
import imageio_ffmpeg
bundle_dir = Path("tools/ffmpeg/bin")
bundle_dir.mkdir(parents=True, exist_ok=True)
ffmpeg_src = Path(imageio_ffmpeg.get_ffmpeg_exe())
ffmpeg_name = "ffmpeg.exe" if os.name == "nt" else "ffmpeg"
ffmpeg_dest = bundle_dir / ffmpeg_name
shutil.copy2(ffmpeg_src, ffmpeg_dest)
ffmpeg_dest.chmod(ffmpeg_dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
print(f"Bundled ffmpeg: {ffmpeg_dest}")
ffprobe_src = shutil.which("ffprobe")
if ffprobe_src:
ffprobe_name = "ffprobe.exe" if os.name == "nt" else "ffprobe"
ffprobe_dest = bundle_dir / ffprobe_name
shutil.copy2(ffprobe_src, ffprobe_dest)
ffprobe_dest.chmod(ffprobe_dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
print(f"Bundled ffprobe: {ffprobe_dest}")
PY
- name: Download all AI models
shell: bash
env:
PYTHONIOENCODING: utf-8
run: |
echo "=== Download all base models (HuBERT, RMVPE, UVR5, DeEcho, pretrained) ==="
python tools/download_models.py --all
echo "=== Download Roformer separator models ==="
python -c "
from audio_separator.separator import Separator
import os
model_dir = os.path.join('assets', 'separator_models')
os.makedirs(model_dir, exist_ok=True)
# Vocal separation model
sep = Separator(output_dir='.', model_file_dir=model_dir)
sep.load_model('vocals_mel_band_roformer.ckpt')
del sep
# Karaoke model
sep2 = Separator(output_dir='.', model_file_dir=model_dir)
sep2.load_model('mel_band_roformer_karaoke_aufr33_viperx_sdr_10.1956.ckpt')
del sep2
print('Roformer models downloaded.')
"
echo "=== Verify downloaded models ==="
find assets/ -name "*.pt" -o -name "*.pth" -o -name "*.ckpt" -o -name "*.onnx" | while read f; do
SIZE=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f")
echo " $f ($(( SIZE / 1048576 )) MB)"
done
- name: Build executable
shell: bash
run: |
SEP="${{ matrix.sep }}"
NAME="AI-RVC-${{ matrix.platform }}-${{ matrix.variant }}"
pyinstaller --name "${NAME}" \
--onedir \
--add-data "ui${SEP}ui" \
--add-data "infer${SEP}infer" \
--add-data "lib${SEP}lib" \
--add-data "models${SEP}models" \
--add-data "tools${SEP}tools" \
--add-data "i18n${SEP}i18n" \
--add-data "configs${SEP}configs" \
--add-data "assets/hubert${SEP}assets/hubert" \
--add-data "assets/rmvpe${SEP}assets/rmvpe" \
--add-data "assets/uvr5_weights${SEP}assets/uvr5_weights" \
--add-data "assets/pretrained_v2${SEP}assets/pretrained_v2" \
--add-data "assets/separator_models${SEP}assets/separator_models" \
--hidden-import=torch \
--hidden-import=torchaudio \
--hidden-import=gradio \
--hidden-import=librosa \
--hidden-import=soundfile \
--hidden-import=fairseq \
--hidden-import=audio_separator \
--hidden-import=demucs \
--hidden-import=pedalboard \
--collect-all torch \
--collect-all torchaudio \
--collect-all gradio \
--collect-all gradio_client \
run.py
- name: Create portable package
shell: bash
run: |
NAME="AI-RVC-${{ matrix.platform }}-${{ matrix.variant }}"
PKG="${NAME}-Portable"
mkdir -p "${PKG}"
# onedir 输出在 dist/NAME/ 目录下,复制全部内容
cp -r dist/${NAME}/* "${PKG}/"
cp README.md "${PKG}/"
[ -f LICENSE ] && cp LICENSE "${PKG}/"
if [ "${{ matrix.variant }}" = "GPU" ]; then
VARIANT_NOTE="GPU 版(CUDA 12.1),支持 NVIDIA 显卡加速
如果没有 NVIDIA 显卡,程序会自动回退到 CPU 推理"
else
VARIANT_NOTE="CPU 版,无需显卡即可运行
如需 GPU 加速,请下载 GPU 版本或使用本地安装方式:python install.py"
fi
if [ "${{ matrix.platform }}" = "Windows" ]; then
EXE_NAME="${NAME}.exe"
cat > "${PKG}/使用说明.txt" << HEREDOC
AI-RVC ${{ matrix.platform }} 便携版(${{ matrix.variant }})
使用方法:
双击 ${EXE_NAME} 启动
浏览器访问 http://127.0.0.1:7860
${VARIANT_NOTE}
AI 模型已内置,无需额外下载
无需安装 Python,解压即用
HEREDOC
else
EXE_NAME="${NAME}"
chmod +x "${PKG}/${EXE_NAME}"
cat > "${PKG}/使用说明.txt" << HEREDOC
AI-RVC ${{ matrix.platform }} 便携版(${{ matrix.variant }})
使用方法:
chmod +x ${EXE_NAME}
./${EXE_NAME}
浏览器访问 http://127.0.0.1:7860
${VARIANT_NOTE}
AI 模型已内置,无需额外下载
无需安装 Python,解压即用
HEREDOC
fi
- name: Compress package
shell: bash
run: |
NAME="AI-RVC-${{ matrix.platform }}-${{ matrix.variant }}"
PKG="${NAME}-Portable"
if [ "${{ matrix.platform }}" = "Windows" ]; then
# 先压缩成 zip
7z a -tzip "${PKG}.zip" "${PKG}"
# 超过 1.9GB 时删除 zip 改用 7z(压缩率更高)
FILE_SIZE=$(stat -c%s "${PKG}.zip" 2>/dev/null || wc -c < "${PKG}.zip" | tr -d ' ')
if [ "$FILE_SIZE" -gt 1900000000 ]; then
rm "${PKG}.zip"
# 先尝试不分卷的 7z
7z a "${PKG}.7z" "${PKG}"
SEVENZ_SIZE=$(stat -c%s "${PKG}.7z" 2>/dev/null || wc -c < "${PKG}.7z" | tr -d ' ')
if [ "$SEVENZ_SIZE" -gt 1900000000 ]; then
# 7z 也超限,改用分卷
rm "${PKG}.7z"
7z a -v1900m "${PKG}.7z" "${PKG}"
fi
fi
else
tar -czf "${PKG}.tar.gz" "${PKG}"
# 超过 1.9GB 时拆分
FILE_SIZE=$(stat -c%s "${PKG}.tar.gz" 2>/dev/null || stat -f%z "${PKG}.tar.gz")
if [ "$FILE_SIZE" -gt 1900000000 ]; then
split -b 1900M "${PKG}.tar.gz" "${PKG}.tar.gz.part"
rm "${PKG}.tar.gz"
fi
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: AI-RVC-${{ matrix.platform }}-${{ matrix.variant }}
path: AI-RVC-${{ matrix.platform }}-${{ matrix.variant }}-Portable*
upload-release:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: List artifacts
run: ls -lh AI-RVC-*
- name: Determine target tag
id: tag
run: |
if [ "${{ github.event_name }}" = "push" ]; then
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
elif [ -n "${{ github.event.inputs.tag }}" ]; then
echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
else
LATEST=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName -q '.[0].tagName')
echo "tag=${LATEST}" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload assets to release
run: |
TAG="${{ steps.tag.outputs.tag }}"
echo "上传资源到 Release: ${TAG}"
# 收集所有构建产物(含分卷文件)
mapfile -t FILES < <(find . -maxdepth 1 -name "AI-RVC-*-Portable*" -type f | sort)
upload_asset_with_retry() {
local tag="$1"
local file="$2"
local attempt
local exit_code
for attempt in 1 2 3; do
echo "上传(${attempt}/3): ${file}"
if timeout 30m gh release upload "${tag}" "${file}" \
--repo ${{ github.repository }} \
--clobber; then
return 0
fi
exit_code=$?
echo "上传失败(退出码=${exit_code}): ${file}"
if [ "${attempt}" -lt 3 ]; then
sleep $(( attempt * 20 ))
fi
done
echo "上传最终失败: ${file}"
return 1
}
echo "找到以下文件:"
for f in "${FILES[@]}"; do
SIZE=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f")
echo " $f ($(( SIZE / 1048576 )) MB)"
done
# 逐个上传
for f in "${FILES[@]}"; do
upload_asset_with_retry "${TAG}" "$f"
done
echo "全部上传完成"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}