Skip to content

fix(ci): windows installer not included to release build #80

fix(ci): windows installer not included to release build

fix(ci): windows installer not included to release build #80

Workflow file for this run

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 }}