fix(ci): windows installer not included to release build #80
Workflow file for this run
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 & Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| actions: read | |
| jobs: | |
| # Download ffmpeg & yt-dlp | |
| prepare-binaries: | |
| name: Download ffmpeg & yt-dlp | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache binaries | |
| id: cache-bins | |
| uses: actions/cache@v4 | |
| with: | |
| path: assets/binaries | |
| key: binaries-${{ hashFiles('scripts/prepare_binaries.py') }}-v1 | |
| - name: Download binaries | |
| if: steps.cache-bins.outputs.cache-hit != 'true' | |
| run: python3 scripts/prepare_binaries.py --all | |
| - name: Upload binary artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binaries | |
| path: assets/binaries | |
| retention-days: 1 | |
| # Windows | |
| build-windows: | |
| name: Windows | |
| needs: prepare-binaries | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: binaries | |
| path: assets/binaries | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: "3.x" | |
| channel: stable | |
| cache: true | |
| - run: flutter pub get | |
| # Installer | |
| - name: Build Windows (installer) | |
| run: flutter build windows --release --dart-define=PORTABLE=false | |
| - name: Inject binaries — installer | |
| shell: pwsh | |
| run: | | |
| $dest = "build\windows\x64\runner\Release\bin" | |
| New-Item -ItemType Directory -Force -Path $dest | Out-Null | |
| Copy-Item "assets\binaries\windows\ffmpeg.exe" $dest | |
| Copy-Item "assets\binaries\windows\yt-dlp.exe" $dest | |
| - name: Create Inno Setup script | |
| shell: pwsh | |
| run: | | |
| $version = (Get-Content pubspec.yaml | Select-String "^version:" | ` | |
| ForEach-Object { $_ -replace "version:\s*", "" } | ` | |
| ForEach-Object { $_ -replace "\+.*", "" }).Trim() | |
| $iss = @" | |
| [Setup] | |
| AppName=Frame Extractor | |
| AppVersion=$version | |
| AppPublisher=nokarin | |
| AppPublisherURL=https://github.com/nokarin-dev/frameextractor | |
| AppSupportURL=https://github.com/nokarin-dev/frameextractor/issues | |
| DefaultDirName={autopf}\FrameExtractor | |
| DefaultGroupName=Frame Extractor | |
| AllowNoIcons=yes | |
| OutputDir=. | |
| OutputBaseFilename=FrameExtractor-windows-installer | |
| Compression=lzma2/ultra64 | |
| SolidCompression=yes | |
| WizardStyle=modern | |
| PrivilegesRequiredOverridesAllowed=dialog | |
| UninstallDisplayIcon={app}\frameextractor.exe | |
| SetupIconFile=windows\runner\resources\app_icon.ico | |
| [Languages] | |
| Name: "english"; MessagesFile: "compiler:Default.isl" | |
| [Tasks] | |
| Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" | |
| [Files] | |
| Source: "build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs | |
| [Icons] | |
| Name: "{group}\Frame Extractor"; Filename: "{app}\frameextractor.exe" | |
| Name: "{group}\Uninstall Frame Extractor"; Filename: "{uninstallexe}" | |
| Name: "{commondesktop}\Frame Extractor"; Filename: "{app}\frameextractor.exe"; Tasks: desktopicon | |
| [Run] | |
| Filename: "{app}\frameextractor.exe"; Description: "{cm:LaunchProgram,Frame Extractor}"; Flags: nowait postinstall skipifsilent | |
| "@ | |
| $iss | Out-File -FilePath "installer.iss" -Encoding UTF8 | |
| Write-Host "Inno Setup script created for version $version" | |
| - name: Compile installer with Inno Setup | |
| shell: pwsh | |
| run: | | |
| # Inno Setup is pre-installed on GitHub Actions Windows runners | |
| $inno = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" | |
| & $inno "installer.iss" | |
| Write-Host "Installer compiled: FrameExtractor-windows-installer.exe" | |
| # Portable | |
| - name: Build Windows (portable) | |
| run: flutter build windows --release --dart-define=PORTABLE=true | |
| - name: Inject binaries — portable | |
| shell: pwsh | |
| run: | | |
| $dest = "build\windows\x64\runner\Release\bin" | |
| New-Item -ItemType Directory -Force -Path $dest | Out-Null | |
| Copy-Item "assets\binaries\windows\ffmpeg.exe" $dest | |
| Copy-Item "assets\binaries\windows\yt-dlp.exe" $dest | |
| - name: Package Windows portable | |
| shell: pwsh | |
| run: | | |
| Compress-Archive -Path "build\windows\x64\runner\Release\*" ` | |
| -DestinationPath "FrameExtractor-windows-portable.zip" | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-builds | |
| path: | | |
| FrameExtractor-windows-installer.exe | |
| FrameExtractor-windows-portable.zip | |
| #.Linux | |
| build-linux: | |
| name: Linux | |
| needs: prepare-binaries | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update -y | |
| sudo apt-get install -y \ | |
| clang cmake ninja-build pkg-config \ | |
| libgtk-3-dev liblzma-dev libstdc++-12-dev \ | |
| dpkg-dev fakeroot | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: binaries | |
| path: assets/binaries | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: "3.x" | |
| channel: stable | |
| cache: true | |
| - run: flutter pub get | |
| # Build | |
| - name: Build Linux release | |
| run: flutter build linux --release --dart-define=PORTABLE=false | |
| - name: Inject binaries | |
| run: | | |
| mkdir -p build/linux/x64/release/bundle/bin | |
| cp assets/binaries/linux/ffmpeg build/linux/x64/release/bundle/bin/ | |
| cp assets/binaries/linux/yt-dlp build/linux/x64/release/bundle/bin/ | |
| chmod +x build/linux/x64/release/bundle/bin/* | |
| # deb package | |
| - name: Get version | |
| run: | | |
| echo "VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //;s/+.*//')" >> $GITHUB_ENV | |
| - name: Build .deb package | |
| run: | | |
| PKG="frameextractor_${{ env.VERSION }}_amd64" | |
| BUNDLE="build/linux/x64/release/bundle" | |
| # Create debian package structure | |
| mkdir -p "$PKG/DEBIAN" | |
| mkdir -p "$PKG/opt/frameextractor" | |
| mkdir -p "$PKG/usr/bin" | |
| mkdir -p "$PKG/usr/share/applications" | |
| mkdir -p "$PKG/usr/share/icons/hicolor/256x256/apps" | |
| # Copy app bundle | |
| cp -r "$BUNDLE/." "$PKG/opt/frameextractor/" | |
| # Symlink to /usr/bin for CLI access | |
| ln -s /opt/frameextractor/frameextractor "$PKG/usr/bin/frameextractor" | |
| # Desktop entry | |
| cat > "$PKG/usr/share/applications/frameextractor.desktop" << 'DESKTOP' | |
| [Desktop Entry] | |
| Version=1.0 | |
| Type=Application | |
| Name=Frame Extractor | |
| GenericName=Video Frame Extractor | |
| Comment=Extract frames from video files using ffmpeg | |
| Exec=/opt/frameextractor/frameextractor | |
| Icon=frameextractor | |
| Categories=Video;AudioVideo; | |
| Terminal=false | |
| StartupWMClass=frameextractor | |
| DESKTOP | |
| # App icon (copy from Flutter assets if present) | |
| if [ -f "assets/icon.png" ]; then | |
| cp "assets/icon.png" "$PKG/usr/share/icons/hicolor/256x256/apps/frameextractor.png" | |
| fi | |
| # DEBIAN/control | |
| cat > "$PKG/DEBIAN/control" << EOF | |
| Package: frameextractor | |
| Version: ${{ env.VERSION }} | |
| Architecture: amd64 | |
| Maintainer: nokarin <noreply@github.com> | |
| Description: Frame Extractor | |
| Extract frames from local video files and YouTube using ffmpeg. | |
| Supports PNG, JPG, WebP output with configurable FPS and quality. | |
| Depends: libgtk-3-0, libblkid1, liblzma5 | |
| Section: video | |
| Priority: optional | |
| Homepage: https://github.com/nokarin-dev/frameextractor | |
| EOF | |
| # post-install script to fix permissions | |
| cat > "$PKG/DEBIAN/postinst" << 'POSTINST' | |
| #!/bin/sh | |
| chmod +x /opt/frameextractor/frameextractor | |
| chmod +x /opt/frameextractor/bin/ffmpeg 2>/dev/null || true | |
| chmod +x /opt/frameextractor/bin/yt-dlp 2>/dev/null || true | |
| update-desktop-database /usr/share/applications 2>/dev/null || true | |
| exit 0 | |
| POSTINST | |
| chmod 755 "$PKG/DEBIAN/postinst" | |
| # post-remove script | |
| cat > "$PKG/DEBIAN/postrm" << 'POSTRM' | |
| #!/bin/sh | |
| update-desktop-database /usr/share/applications 2>/dev/null || true | |
| exit 0 | |
| POSTRM | |
| chmod 755 "$PKG/DEBIAN/postrm" | |
| # Build the .deb | |
| fakeroot dpkg-deb --build "$PKG" | |
| mv "$PKG.deb" "FrameExtractor-linux-installer.deb" | |
| echo "✓ .deb built: FrameExtractor-linux-installer.deb" | |
| dpkg-deb --info "FrameExtractor-linux-installer.deb" | |
| # AppImage package | |
| - name: Install FUSE | |
| run: sudo apt-get install -y libfuse2 | |
| - name: Build .AppImage | |
| run: | | |
| BUNDLE="build/linux/x64/release/bundle" | |
| wget -q "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" \ | |
| -O /tmp/appimagetool.AppImage | |
| chmod +x /tmp/appimagetool.AppImage | |
| cd /tmp && ./appimagetool.AppImage --appimage-extract > /dev/null 2>&1 | |
| APPIMAGETOOL="/tmp/squashfs-root/AppRun" | |
| cd "$GITHUB_WORKSPACE" | |
| # Create AppDir structure | |
| mkdir -p AppDir/usr/bin | |
| mkdir -p AppDir/usr/lib | |
| mkdir -p AppDir/usr/share/applications | |
| mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps | |
| # Detect executable name (may be FrameExtractor or frameextractor) | |
| EXE=$(find "$BUNDLE" -maxdepth 1 -type f ! -name "*.so" ! -name "*.desktop" | head -1) | |
| EXE_NAME=$(basename "$EXE") | |
| echo "Detected executable: $EXE_NAME" | |
| # Copy app files | |
| cp "$EXE" AppDir/usr/bin/frameextractor | |
| chmod +x AppDir/usr/bin/frameextractor | |
| # Copy shared libraries | |
| if [ -d "$BUNDLE/lib" ]; then | |
| cp -r "$BUNDLE/lib/"* AppDir/usr/lib/ 2>/dev/null || true | |
| fi | |
| # Copy data directory | |
| if [ -d "$BUNDLE/data" ]; then | |
| cp -r "$BUNDLE/data" AppDir/usr/ | |
| fi | |
| # Copy bundled binaries | |
| mkdir -p AppDir/usr/bin/bin | |
| cp "$BUNDLE/bin/ffmpeg" AppDir/usr/bin/bin/ | |
| cp "$BUNDLE/bin/yt-dlp" AppDir/usr/bin/bin/ | |
| chmod +x AppDir/usr/bin/bin/* | |
| # App icon | |
| if [ -f "assets/icon.png" ]; then | |
| cp "assets/icon.png" AppDir/usr/share/icons/hicolor/256x256/apps/frameextractor.png | |
| cp "assets/icon.png" AppDir/frameextractor.png | |
| else | |
| # Create a minimal placeholder icon | |
| convert -size 256x256 xc:gray AppDir/frameextractor.png 2>/dev/null || printf 'PNG' > AppDir/frameextractor.png | |
| fi | |
| # .desktop file (required by AppImage) | |
| cat > AppDir/frameextractor.desktop << 'DESKTOP' | |
| [Desktop Entry] | |
| Version=1.0 | |
| Type=Application | |
| Name=Frame Extractor | |
| GenericName=Video Frame Extractor | |
| Comment=Extract frames from video files using ffmpeg | |
| Exec=frameextractor | |
| Icon=frameextractor | |
| Categories=Video;AudioVideo; | |
| Terminal=false | |
| DESKTOP | |
| # AppRun entrypoint | |
| cat > AppDir/AppRun << 'APPRUN' | |
| #!/bin/sh | |
| APPDIR="$(dirname "$(readlink -f "$0")")" | |
| export PATH="$APPDIR/usr/bin:$APPDIR/usr/bin/bin:$PATH" | |
| export LD_LIBRARY_PATH="$APPDIR/usr/lib:$LD_LIBRARY_PATH" | |
| exec "$APPDIR/usr/bin/frameextractor" "$@" | |
| APPRUN | |
| chmod +x AppDir/AppRun | |
| ARCH=x86_64 "$APPIMAGETOOL" --no-appstream AppDir "FrameExtractor-linux.AppImage" | |
| chmod +x "FrameExtractor-linux.AppImage" | |
| echo "✓ AppImage built: FrameExtractor-linux.AppImage" | |
| # ortable | |
| - name: Build Linux (portable) | |
| run: flutter build linux --release --dart-define=PORTABLE=true | |
| - name: Inject binaries (portable) | |
| run: | | |
| mkdir -p build/linux/x64/release/bundle/bin | |
| cp assets/binaries/linux/ffmpeg build/linux/x64/release/bundle/bin/ | |
| cp assets/binaries/linux/yt-dlp build/linux/x64/release/bundle/bin/ | |
| chmod +x build/linux/x64/release/bundle/bin/* | |
| - name: Package portable (.tar.gz) | |
| run: | | |
| tar -czf FrameExtractor-linux-portable.tar.gz \ | |
| -C build/linux/x64/release/bundle . | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-builds | |
| path: | | |
| FrameExtractor-linux-installer.deb | |
| FrameExtractor-linux.AppImage | |
| FrameExtractor-linux-portable.tar.gz | |
| # Android APK | |
| build-android: | |
| name: Android | |
| needs: prepare-binaries | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: binaries | |
| path: assets/binaries | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: "3.x" | |
| channel: stable | |
| cache: true | |
| - run: flutter pub get | |
| # Signing | |
| - name: Setup release signing | |
| env: | |
| KEYSTORE_B64: ${{ secrets.KEYSTORE_BASE64 }} | |
| if: env.KEYSTORE_B64 != '' | |
| run: | | |
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore.jks | |
| cat > android/key.properties << EOF | |
| storePassword=${{ secrets.STORE_PASSWORD }} | |
| keyPassword=${{ secrets.KEY_PASSWORD }} | |
| keyAlias=${{ secrets.KEY_ALIAS }} | |
| storeFile=keystore.jks | |
| EOF | |
| # Build split APKs | |
| - name: Build split APKs | |
| run: | | |
| flutter build apk --release --dart-define=PORTABLE=false --split-per-abi | |
| - name: Rename APKs | |
| run: | | |
| cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk FrameExtractor-android-arm64.apk | |
| cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk FrameExtractor-android-arm32.apk | |
| cp build/app/outputs/flutter-apk/app-x86_64-release.apk FrameExtractor-android-x86_64.apk | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-build | |
| path: | | |
| FrameExtractor-android-arm64.apk | |
| FrameExtractor-android-arm32.apk | |
| FrameExtractor-android-x86_64.apk | |
| # Release | |
| release: | |
| name: Create Release | |
| needs: [build-windows, build-linux, build-android] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| pattern: "*-build*" | |
| merge-multiple: true | |
| - name: Generate changelog | |
| run: | | |
| VERSION="${{ github.ref_name }}" | |
| VERSION_BARE="${VERSION#v}" | |
| SECTION=$(awk \ | |
| "/^## \[$VERSION_BARE\]/{found=1; next} found && /^## \[/{exit} found{print}" \ | |
| changelog.md) | |
| if [ -z "$SECTION" ]; then | |
| PREV_TAG=$(git tag --sort=-version:refname | sed -n '2p') | |
| if [ -z "$PREV_TAG" ]; then | |
| SECTION=$(git log --oneline --no-decorate | head -20) | |
| else | |
| SECTION=$(git log --oneline --no-decorate ${PREV_TAG}..HEAD) | |
| fi | |
| fi | |
| echo "CHANGELOG<<EOF" >> $GITHUB_ENV | |
| echo "$SECTION" >> $GITHUB_ENV | |
| echo "EOF" >> $GITHUB_ENV | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: "Frame Extractor ${{ github.ref_name }}" | |
| body: | | |
| ${{ env.CHANGELOG }} | |
| --- | |
| ## Downloads | |
| | Platform | File | Notes | | |
| |---|---|---| | |
| | 🪟 Windows | `FrameExtractor-windows-installer.zip` | Extract & run `.exe` | | |
| | 🪟 Windows Portable | `FrameExtractor-windows-portable.zip` | No install needed | | |
| | 🐧 Linux (.deb) | `FrameExtractor-linux-installer.deb` | `sudo dpkg -i *.deb` - installs to `/opt/frameextractor` | | |
| | 🐧 Linux (.AppImage) | `FrameExtractor-linux.AppImage` | Single file, runs on any distro | | |
| | 🐧 Linux Portable | `FrameExtractor-linux-portable.tar.gz` | Extract & run anywhere | | |
| | 🤖 Android (arm64) | `FrameExtractor-android-arm64.apk` | Most modern phones (2018+) | | |
| | 🤖 Android (arm32) | `FrameExtractor-android-arm32.apk` | Older 32-bit phones | | |
| | 🤖 Android (x86_64) | `FrameExtractor-android-x86_64.apk` | Emulators / Chromebooks | | |
| > **Windows/Linux/macOS:** ffmpeg & yt-dlp bundled inside the zip/dmg. | |
| > **Android:** yt-dlp bundled in APK, ffmpeg via ffmpeg_kit. | |
| > **iOS:** ffmpeg via ffmpeg_kit, YouTube via API. | |
| draft: false | |
| prerelease: ${{ contains(github.ref_name, '-') }} | |
| files: | | |
| dist/FrameExtractor-windows-installer.exe | |
| dist/FrameExtractor-windows-portable.zip | |
| dist/FrameExtractor-linux-installer.deb | |
| dist/FrameExtractor-linux.AppImage | |
| dist/FrameExtractor-linux-portable.tar.gz | |
| dist/FrameExtractor-android-arm64.apk | |
| dist/FrameExtractor-android-arm32.apk | |
| dist/FrameExtractor-android-x86_64.apk | |
| fail_on_unmatched_files: false | |
| token: ${{ secrets.GITHUB_TOKEN }} |