Build Executables #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 gradio==3.50.2 librosa soundfile scipy numpy praat-parselmouth pyworld torchcrepe faiss-cpu tqdm requests python-dotenv colorama demucs huggingface_hub pedalboard ffmpeg-python av imageio-ffmpeg | |
| - 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 }} |