name: Build Windows Release on: workflow_dispatch: inputs: version: description: 'Version name for the release (e.g., 1.0.0)' required: true type: string workflow_call: inputs: version: description: 'Version name for the release (e.g., 1.0.0)' required: true type: string permissions: contents: write env: PYTHON_VERSION: '3.13' jobs: build-windows: runs-on: windows-latest steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 - name: Get version id: get_version shell: powershell run: | $version = "${{ inputs.version || github.event.inputs.version }}" Write-Host "Extracted version: $version" echo "VERSION=$version" >> $env:GITHUB_OUTPUT - name: Setup Python uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: Cache Python dependencies uses: actions/cache@v4 with: path: | venv ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Create virtual environment and install dependencies shell: powershell run: | python -m venv venv .\venv\Scripts\Activate.ps1 python -m pip install --upgrade pip pip install --no-cache-dir -r requirements.txt pip install --no-cache-dir cx_Freeze - name: Prepare build variables shell: powershell run: | $version = "${{ steps.get_version.outputs.VERSION }}" echo "VERSION=$version" >> $env:GITHUB_ENV Write-Host "Prepared build variables for version: $version" - name: Create cx_Freeze setup script shell: powershell run: | # Create setup script for cx_Freeze New-Item -Path "setup_cxfreeze.py" -ItemType File -Force Add-Content -Path "setup_cxfreeze.py" -Value "import os" Add-Content -Path "setup_cxfreeze.py" -Value "from cx_Freeze import setup, Executable" Add-Content -Path "setup_cxfreeze.py" -Value "" Add-Content -Path "setup_cxfreeze.py" -Value 'version = os.environ.get("VERSION", "0.0.0")' Add-Content -Path "setup_cxfreeze.py" -Value "" Add-Content -Path "setup_cxfreeze.py" -Value "build_exe_options = dict(" Add-Content -Path "setup_cxfreeze.py" -Value " optimize=2," Add-Content -Path "setup_cxfreeze.py" -Value " packages=[" Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtCore",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtGui",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtWidgets",' Add-Content -Path "setup_cxfreeze.py" -Value ' "requests",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL",' Add-Content -Path "setup_cxfreeze.py" -Value ' "packaging",' Add-Content -Path "setup_cxfreeze.py" -Value ' "markdown",' Add-Content -Path "setup_cxfreeze.py" -Value ' "pyglet",' Add-Content -Path "setup_cxfreeze.py" -Value ' "loguru",' Add-Content -Path "setup_cxfreeze.py" -Value ' "setuptools",' Add-Content -Path "setup_cxfreeze.py" -Value " ]," Add-Content -Path "setup_cxfreeze.py" -Value " excludes=[" Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtBluetooth",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtNetwork",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtOpenGL",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtPrintSupport",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtSvg",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtTest",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtXml",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtSql",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtHelp",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtMultimedia",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtQml",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtQuick",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtWebEngineCore",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL.ImageDraw",' Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL.ImageFont",' Add-Content -Path "setup_cxfreeze.py" -Value ' "numpy",' Add-Content -Path "setup_cxfreeze.py" -Value ' "scipy",' Add-Content -Path "setup_cxfreeze.py" -Value ' "wx",' Add-Content -Path "setup_cxfreeze.py" -Value ' "pandas",' Add-Content -Path "setup_cxfreeze.py" -Value ' "tkinter",' Add-Content -Path "setup_cxfreeze.py" -Value ' "yt_dlp",' Add-Content -Path "setup_cxfreeze.py" -Value ' "unittest",' Add-Content -Path "setup_cxfreeze.py" -Value ' "test",' Add-Content -Path "setup_cxfreeze.py" -Value ' "tests",' Add-Content -Path "setup_cxfreeze.py" -Value " ]," Add-Content -Path "setup_cxfreeze.py" -Value " include_files=[" Add-Content -Path "setup_cxfreeze.py" -Value ' ("src", "src"),' Add-Content -Path "setup_cxfreeze.py" -Value ' ("assets/branding/icons", "lib/assets/branding/icons"),' Add-Content -Path "setup_cxfreeze.py" -Value ' ("assets/Icon", "lib/assets/Icon"),' Add-Content -Path "setup_cxfreeze.py" -Value ' ("assets/sound", "lib/assets/sound"),' Add-Content -Path "setup_cxfreeze.py" -Value ' ("languages", "lib/languages"),' Add-Content -Path "setup_cxfreeze.py" -Value " ]," Add-Content -Path "setup_cxfreeze.py" -Value ")" Add-Content -Path "setup_cxfreeze.py" -Value "" Add-Content -Path "setup_cxfreeze.py" -Value "executables = [" Add-Content -Path "setup_cxfreeze.py" -Value " Executable(" Add-Content -Path "setup_cxfreeze.py" -Value ' script="main.py",' Add-Content -Path "setup_cxfreeze.py" -Value ' target_name=f"YTSage-v{version}.exe",' Add-Content -Path "setup_cxfreeze.py" -Value ' base="gui",' Add-Content -Path "setup_cxfreeze.py" -Value ' icon="assets/branding/icons/YTSage.ico",' Add-Content -Path "setup_cxfreeze.py" -Value " )" Add-Content -Path "setup_cxfreeze.py" -Value "]" Add-Content -Path "setup_cxfreeze.py" -Value "" Add-Content -Path "setup_cxfreeze.py" -Value "setup(" Add-Content -Path "setup_cxfreeze.py" -Value ' name="YTSage",' Add-Content -Path "setup_cxfreeze.py" -Value " version=version," Add-Content -Path "setup_cxfreeze.py" -Value ' description="YTSage",' Add-Content -Path "setup_cxfreeze.py" -Value ' options={"build_exe": build_exe_options},' Add-Content -Path "setup_cxfreeze.py" -Value " executables=executables," Add-Content -Path "setup_cxfreeze.py" -Value ")" - name: Build Standard Version (ZIP) shell: powershell run: | .\venv\Scripts\Activate.ps1 $version = "$env:VERSION" Write-Host "Building Standard Version v$version ..." # Clean previous build artifacts if (Test-Path "build\exe.*") { Remove-Item "build\exe.*" -Recurse -Force } if (Test-Path "build\bdist.*") { Remove-Item "build\bdist.*" -Recurse -Force } if (Test-Path "dist") { Remove-Item "dist" -Recurse -Force } # Build executable using setup script python setup_cxfreeze.py build_exe --build-exe "dist\YTSage" # Remove screenshots folder to reduce build size if (Test-Path "dist\YTSage\lib\assets\branding\screenshots") { Remove-Item "dist\YTSage\lib\assets\branding\screenshots" -Recurse -Force Write-Host "Removed screenshots folder from standard build" } - name: Setup FFmpeg for bundle shell: powershell run: | Write-Host "Setting up FFmpeg for bundled version..." # Create ffmpeg directory New-Item -ItemType Directory -Path "ffmpeg-temp" -Force # Download FFmpeg $ffmpegUrl = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" Write-Host "Downloading FFmpeg from: $ffmpegUrl" Invoke-WebRequest -Uri $ffmpegUrl -OutFile "ffmpeg-temp\ffmpeg.zip" # Extract FFmpeg Expand-Archive -Path "ffmpeg-temp\ffmpeg.zip" -DestinationPath "ffmpeg-temp" -Force # Find the extracted directory (GyanD ffmpeg has specific naming) $ffmpegDir = Get-ChildItem -Path "ffmpeg-temp" -Directory | Where-Object { $_.Name -like "ffmpeg-*" } | Select-Object -First 1 if ($ffmpegDir) { # Set environment variable for the build $ffmpegBinPath = Join-Path $ffmpegDir.FullName "bin" Write-Host "FFmpeg binaries found at: $ffmpegBinPath" echo "FFMPEG_PATH=$ffmpegBinPath" >> $env:GITHUB_ENV # Verify files exist (only ffmpeg and ffprobe are needed) $ffmpegExe = Join-Path $ffmpegBinPath "ffmpeg.exe" $ffprobeExe = Join-Path $ffmpegBinPath "ffprobe.exe" if (Test-Path $ffmpegExe) { Write-Host "[OK] ffmpeg.exe found" } else { Write-Host "[ERROR] ffmpeg.exe missing" } if (Test-Path $ffprobeExe) { Write-Host "[OK] ffprobe.exe found" } else { Write-Host "[ERROR] ffprobe.exe missing" } } else { Write-Host "Error: Could not find FFmpeg directory after extraction" exit 1 } - name: Build FFmpeg Version (ZIP) shell: powershell run: | .\venv\Scripts\Activate.ps1 $version = "$env:VERSION" Write-Host "Building FFmpeg Version v$version ..." # Clean previous build artifacts if (Test-Path "build\exe.*") { Remove-Item "build\exe.*" -Recurse -Force } if (Test-Path "build\bdist.*") { Remove-Item "build\bdist.*" -Recurse -Force } # Create modified setup script for FFmpeg version (Get-Content "setup_cxfreeze.py") -replace 'YTSage-v\{version\}\.exe', 'YTSage-v{version}-ffmpeg.exe' | Set-Content "setup_cxfreeze_ffmpeg.py" # Build executable with FFmpeg using setup script python setup_cxfreeze_ffmpeg.py build_exe --build-exe "dist\YTSage-FFmpeg" # Remove screenshots folder to reduce build size if (Test-Path "dist\YTSage-FFmpeg\lib\assets\branding\screenshots") { Remove-Item "dist\YTSage-FFmpeg\lib\assets\branding\screenshots" -Recurse -Force Write-Host "Removed screenshots folder from FFmpeg build" } # Copy FFmpeg binaries into dist folder post-build (more reliable than CLI include) # Note: ffplay.exe is excluded as it's not needed by the application if ($env:FFMPEG_PATH) { $destDir = "dist\YTSage-FFmpeg" $ffmpegExe = Join-Path $env:FFMPEG_PATH "ffmpeg.exe" $ffprobeExe = Join-Path $env:FFMPEG_PATH "ffprobe.exe" foreach ($file in @($ffmpegExe, $ffprobeExe)) { if (Test-Path $file) { $name = Split-Path $file -Leaf Copy-Item $file -Destination (Join-Path $destDir $name) -Force Write-Host "Copied $name into $destDir" } else { Write-Host "Warning: FFmpeg binary not found: $file" } } } - name: Package and prepare release artifacts (ZIPs) shell: powershell run: | $version = "${{ steps.get_version.outputs.VERSION }}" Write-Host "Preparing ZIP artifacts for version: $version" New-Item -ItemType Directory -Path "artifacts" -Force # List dist directories Write-Host "Files in dist directory:" if (Test-Path "dist") { Get-ChildItem -Path "dist" -Recurse | ForEach-Object { Write-Host " $($_.FullName)" } } else { Write-Host " No dist directory found!" exit 1 } # Create Standard ZIP $standardDist = Join-Path (Get-Location) "dist\YTSage" if (Test-Path $standardDist) { $stdZip = "artifacts\YTSage-v$version-portable.zip" if (Test-Path $stdZip) { Remove-Item $stdZip -Force } Compress-Archive -Path "$standardDist\*" -DestinationPath $stdZip -CompressionLevel Optimal Write-Host "Created: $(Resolve-Path $stdZip)" } else { Write-Host "Warning: Standard dist folder not found at $standardDist" } # Create FFmpeg ZIP $ffmpegDist = Join-Path (Get-Location) "dist\YTSage-FFmpeg" if (Test-Path $ffmpegDist) { $ffZip = "artifacts\YTSage-v$version-ffmpeg-portable.zip" if (Test-Path $ffZip) { Remove-Item $ffZip -Force } Compress-Archive -Path "$ffmpegDist\*" -DestinationPath $ffZip -CompressionLevel Optimal Write-Host "Created: $(Resolve-Path $ffZip)" } else { Write-Host "Warning: FFmpeg dist folder not found at $ffmpegDist" } # List all artifacts Write-Host "Final artifacts:" if (Test-Path "artifacts") { $artifactFiles = Get-ChildItem artifacts if ($artifactFiles) { $artifactFiles | ForEach-Object { Write-Host " $($_.Name)" } } else { Write-Host " No artifacts created!" } } else { Write-Host " Artifacts directory not found!" } - name: Create draft release uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.get_version.outputs.VERSION }} name: YTSage v${{ steps.get_version.outputs.VERSION }} draft: true prerelease: false body: | # YTSage v${{ steps.get_version.outputs.VERSION }} **Release Date**: ${{ github.event.head_commit.timestamp }} files: | artifacts/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}