Resolve ffmpeg / 7-Zip URLs via GitHub API at build time #3
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 Windows release | |
| # Fires on: | |
| # * git tag matching v* (e.g. v0.2.0) — drafts a GitHub Release with the zip attached | |
| # * manual workflow_dispatch — produces the same zip as a build artefact (no Release) | |
| # * pushes to main that touch packaging — smoke test only, no upload | |
| # | |
| # Why all three: the tag path is the one users will actually download. The | |
| # dispatch path lets Jessi cut a one-off without minting a version. The | |
| # main-branch path catches "I broke the spec" before tagging. | |
| on: | |
| push: | |
| tags: ['v*'] | |
| branches: [main] | |
| paths: | |
| - 'packaging/**' | |
| - '.github/workflows/release.yml' | |
| - '**.py' | |
| - 'requirements.txt' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write # needed for softprops/action-gh-release on tag pushes | |
| jobs: | |
| build-windows: | |
| runs-on: windows-latest | |
| env: | |
| PYTHON_VERSION: '3.13' # 3.14 is fine too; 3.13 is the latest with the most-tested PyInstaller bootloader as of mid-2026 | |
| RELEASE_NAME: Xenosaga3-Extractor | |
| # The GitHub-mirrored 7zr.exe URL never changes; we only use it | |
| # to bootstrap unpacking the real 7-Zip installer. | |
| SEVENZIP_BOOTSTRAP_URL: 'https://www.7-zip.org/a/7zr.exe' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python ${{ env.PYTHON_VERSION }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - name: Install build dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install pyinstaller pillow capstone | |
| python -m pip install -r requirements.txt | |
| - name: Render version-info resources | |
| run: python packaging/render_version_info.py | |
| - name: Generate placeholder icon (skipped if packaging/icon.ico is committed) | |
| run: python packaging/build_icon.py | |
| - name: Build with PyInstaller | |
| run: pyinstaller --noconfirm --clean packaging/xenosaga-extractor.spec | |
| # ---- bundle external tools ---------------------------------------- | |
| # We resolve the *latest* upstream release at build time rather than | |
| # hardcoding URLs that go 404 the moment upstream cuts a new version. | |
| # GitHub API is rate-limited (60/hr unauth); we pass GITHUB_TOKEN to | |
| # raise that to 5000/hr. | |
| - name: Download portable ffmpeg (latest BtbN GPL win64) | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $headers = @{ | |
| 'User-Agent' = 'xenosaga-extractor-release' | |
| 'Authorization' = "Bearer $env:GH_TOKEN" | |
| } | |
| $rel = Invoke-RestMethod -Uri 'https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest' -Headers $headers | |
| $asset = $rel.assets | Where-Object { $_.name -like 'ffmpeg-*-win64-gpl.zip' -and $_.name -notlike '*shared*' } | Select-Object -First 1 | |
| if (-not $asset) { throw "No matching ffmpeg asset in $($rel.tag_name)" } | |
| Write-Host "Using ffmpeg asset: $($asset.name) from release $($rel.tag_name)" | |
| $zip = "$env:RUNNER_TEMP/ffmpeg.zip" | |
| Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zip -Headers $headers | |
| Expand-Archive -Path $zip -DestinationPath "$env:RUNNER_TEMP/ffmpeg-unpacked" | |
| $exe = Get-ChildItem -Path "$env:RUNNER_TEMP/ffmpeg-unpacked" -Recurse -Filter ffmpeg.exe | Select-Object -First 1 | |
| if (-not $exe) { throw "ffmpeg.exe not found in unpacked archive" } | |
| New-Item -ItemType Directory -Force -Path "dist/${{ env.RELEASE_NAME }}/tools" | Out-Null | |
| Copy-Item $exe.FullName "dist/${{ env.RELEASE_NAME }}/tools/ffmpeg.exe" | |
| "FFMPEG_SOURCE=$($asset.browser_download_url)" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Download 7-Zip (latest ip7z release, full for 7z.exe + 7z.dll) | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $headers = @{ | |
| 'User-Agent' = 'xenosaga-extractor-release' | |
| 'Authorization' = "Bearer $env:GH_TOKEN" | |
| } | |
| $rel = Invoke-RestMethod -Uri 'https://api.github.com/repos/ip7z/7zip/releases/latest' -Headers $headers | |
| $asset = $rel.assets | Where-Object { $_.name -match '^7z\d+-x64\.exe$' } | Select-Object -First 1 | |
| if (-not $asset) { throw "No matching 7-Zip installer in $($rel.tag_name)" } | |
| Write-Host "Using 7-Zip asset: $($asset.name) from release $($rel.tag_name)" | |
| $tmp = $env:RUNNER_TEMP | |
| Invoke-WebRequest -Uri "${{ env.SEVENZIP_BOOTSTRAP_URL }}" -OutFile "$tmp/7zr.exe" | |
| Invoke-WebRequest -Uri $asset.browser_download_url -OutFile "$tmp/7z-full.exe" -Headers $headers | |
| # The full installer is an SFX archive; 7zr extracts it directly. | |
| & "$tmp/7zr.exe" x "-o$tmp/7z-unpacked" "$tmp/7z-full.exe" -y | Out-Null | |
| Copy-Item "$tmp/7z-unpacked/7z.exe" "dist/${{ env.RELEASE_NAME }}/tools/7z.exe" | |
| Copy-Item "$tmp/7z-unpacked/7z.dll" "dist/${{ env.RELEASE_NAME }}/tools/7z.dll" | |
| "SEVENZIP_SOURCE=$($asset.browser_download_url)" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Drop a TOOLS.txt with licence pointers | |
| shell: pwsh | |
| run: | | |
| @" | |
| Portable binaries bundled with this release: | |
| ffmpeg.exe — from $env:FFMPEG_SOURCE | |
| GPL build by BtbN. Sources: https://github.com/BtbN/FFmpeg-Builds | |
| 7z.exe — from $env:SEVENZIP_SOURCE | |
| 7z.dll — same. | |
| LGPL with unRAR restriction. © Igor Pavlov. | |
| Sources: https://github.com/ip7z/7zip | |
| Both are unmodified upstream binaries. We redistribute them here | |
| purely so the extractor works on a fresh Windows box with zero | |
| additional installs. Replace the contents of this tools/ folder | |
| with your own preferred builds at any time. | |
| "@ | Set-Content -Path "dist/${{ env.RELEASE_NAME }}/tools/TOOLS.txt" | |
| # ---- (optional) code signing ------------------------------------- | |
| # Uncomment + populate the secrets once you have a code-signing cert. | |
| # The cheapest path that actually clears SmartScreen "reputation" | |
| # immediately is Azure Trusted Signing (~$10/month). | |
| # SignPath.io also offers a free OSS tier worth applying for. | |
| # | |
| # - name: Sign binaries | |
| # uses: azure/trusted-signing-action@v0.5.1 | |
| # with: | |
| # azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| # azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| # azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} | |
| # endpoint: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} | |
| # trusted-signing-account-name: ${{ secrets.TRUSTED_SIGNING_ACCOUNT }} | |
| # certificate-profile-name: ${{ secrets.TRUSTED_SIGNING_PROFILE }} | |
| # files-folder: dist/${{ env.RELEASE_NAME }} | |
| # files-folder-filter: exe,dll | |
| # ---- assemble the user-facing zip -------------------------------- | |
| - name: Resolve version | |
| id: ver | |
| shell: pwsh | |
| run: | | |
| $v = python -c "import sys; sys.path.insert(0, '.'); from __init__ import __version__; print(__version__)" | |
| "version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| - name: Create release zip | |
| shell: pwsh | |
| run: | | |
| $zip = "${{ env.RELEASE_NAME }}-${{ steps.ver.outputs.version }}-win64.zip" | |
| Compress-Archive -Path "dist/${{ env.RELEASE_NAME }}/*" -DestinationPath $zip | |
| "ZIP_PATH=$zip" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Upload build artefact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.RELEASE_NAME }}-win64 | |
| path: ${{ env.ZIP_PATH }} | |
| if-no-files-found: error | |
| - name: Attach to GitHub Release (tag pushes only) | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| draft: true | |
| files: ${{ env.ZIP_PATH }} | |
| generate_release_notes: true | |
| body: | | |
| ## Windows pre-built release | |
| Download `${{ env.RELEASE_NAME }}-${{ steps.ver.outputs.version }}-win64.zip`, | |
| unzip anywhere, double-click **gui.exe**. No Python, ffmpeg, or | |
| 7-Zip install required — everything's in the zip. | |
| ### Windows SmartScreen will warn you the first time | |
| Because this build isn't yet code-signed, Windows will pop up | |
| "Windows protected your PC." Click **More info → Run anyway**. | |
| Once Microsoft sees enough downloads of the exact same file, the | |
| warning disappears for everyone. We're tracking a code-signing | |
| cert; see `docs/security.md` in the zip for the long version. |