Skip to content

Resolve ffmpeg / 7-Zip URLs via GitHub API at build time #4

Resolve ffmpeg / 7-Zip URLs via GitHub API at build time

Resolve ffmpeg / 7-Zip URLs via GitHub API at build time #4

Workflow file for this run

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.