Skip to content

Build and Release

Build and Release #17

Workflow file for this run

name: Build Executables
on:
push:
tags:
- 'v*' # Trigger on version tags
workflow_dispatch: # Allow manual trigger
jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
include:
- os: windows-latest
output_name: clipmorph.exe
asset_name: clipmorph-windows.exe
ffmpeg_dir: windows
- os: macos-latest
output_name: clipmorph
asset_name: clipmorph-macos
ffmpeg_dir: mac
- os: ubuntu-latest
output_name: clipmorph
asset_name: clipmorph-linux
ffmpeg_dir: linux
steps:
- uses: actions/checkout@v3
with:
lfs: true
- name: Checkout LFS objects
run: git lfs pull
- name: Free up disk space (Linux only)
if: runner.os == 'Linux'
run: |
echo "=== Before cleanup ==="
df -h
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/share/boost
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo apt-get clean
docker system prune -af
echo "=== After cleanup ==="
df -h
shell: bash
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Set execute permissions for FFmpeg binaries (Unix only)
if: runner.os != 'Windows'
run: |
if [ "$RUNNER_OS" == "macOS" ]; then
chmod +x clipmorph/ffmpeg/mac/ffmpeg
chmod +x clipmorph/ffmpeg/mac/ffprobe
echo "Set permissions for macOS binaries"
elif [ "$RUNNER_OS" == "Linux" ]; then
chmod +x clipmorph/ffmpeg/linux/ffmpeg
chmod +x clipmorph/ffmpeg/linux/ffprobe
echo "Set permissions for Linux binaries"
fi
shell: bash
- name: Remove unnecessary FFmpeg binaries to save space
run: |
if [ "$RUNNER_OS" == "macOS" ]; then
rm -rf clipmorph/ffmpeg/windows clipmorph/ffmpeg/linux
echo "Removed Windows and Linux FFmpeg binaries"
elif [ "$RUNNER_OS" == "Linux" ]; then
rm -rf clipmorph/ffmpeg/windows clipmorph/ffmpeg/mac
echo "Removed Windows and macOS FFmpeg binaries"
elif [ "$RUNNER_OS" == "Windows" ]; then
rm -rf clipmorph/ffmpeg/mac clipmorph/ffmpeg/linux
echo "Removed macOS and Linux FFmpeg binaries"
fi
shell: bash
- name: Verify FFmpeg binaries
run: |
if [ "$RUNNER_OS" == "macOS" ]; then
ls -lh clipmorph/ffmpeg/mac/
./clipmorph/ffmpeg/mac/ffmpeg -version
elif [ "$RUNNER_OS" == "Linux" ]; then
ls -lh clipmorph/ffmpeg/linux/
./clipmorph/ffmpeg/linux/ffmpeg -version
elif [ "$RUNNER_OS" == "Windows" ]; then
ls clipmorph/ffmpeg/windows/
./clipmorph/ffmpeg/windows/ffmpeg.exe -version
fi
shell: bash
- name: Install dependencies
run: |
python -m pip install --upgrade pip --no-cache-dir
pip install pyinstaller --no-cache-dir
pip install . --no-cache-dir
- name: Create PyInstaller Spec
run: |
if [ "$RUNNER_OS" == "Windows" ] || [ "$RUNNER_OS" == "macOS" ]; then
# Single executable for Windows and macOS
cat > clipmorph.spec << 'EOF'
# -*- mode: python ; coding: utf-8 -*-
import sys
excludes = [
'pytest', 'unittest', 'nose', 'hypothesis',
'sphinx', 'docutils', 'IPython', 'jupyter',
'tkinter', 'PyQt5', 'PyQt6', 'PySide2', 'PySide6',
'tensorflow', 'tensorboard', 'jax', 'flax',
'scipy.spatial', 'scipy.ndimage', 'scipy.signal.windows',
'matplotlib.backends.backend_qt5agg',
'matplotlib.backends.backend_tkagg',
'matplotlib.backends.backend_wxagg',
'sympy', 'bokeh', 'dask', 'distributed',
'setuptools', 'pip', 'wheel', 'distutils',
]
a = Analysis(
['clipmorph/__main__.py'],
pathex=[],
binaries=[],
datas=[
('clipmorph/ffmpeg/${{ matrix.ffmpeg_dir }}', 'clipmorph/ffmpeg/${{ matrix.ffmpeg_dir }}'),
('clipmorph/resources', 'clipmorph/resources'),
],
hiddenimports=['whisper', 'whisperx', 'torch', 'torchaudio'],
hookspath=[],
hooksconfig={'matplotlib': {'backends': ['Agg']}},
runtime_hooks=[],
excludes=excludes,
noarchive=False,
)
seen = set()
filtered_binaries = []
for binary in a.binaries:
if binary[0] not in seen:
seen.add(binary[0])
filtered_binaries.append(binary)
a.binaries = filtered_binaries
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='${{ matrix.output_name }}',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
EOF
else
# Directory-based build for Linux
cat > clipmorph.spec << 'EOF'
# -*- mode: python ; coding: utf-8 -*-
import sys
excludes = [
'pytest', 'unittest', 'nose', 'hypothesis',
'sphinx', 'docutils', 'IPython', 'jupyter',
'tkinter', 'PyQt5', 'PyQt6', 'PySide2', 'PySide6',
'tensorflow', 'tensorboard', 'jax', 'flax',
'scipy.spatial', 'scipy.ndimage', 'scipy.signal.windows',
'matplotlib.backends.backend_qt5agg',
'matplotlib.backends.backend_tkagg',
'matplotlib.backends.backend_wxagg',
'sympy', 'bokeh', 'dask', 'distributed',
'setuptools', 'pip', 'wheel', 'distutils',
]
a = Analysis(
['clipmorph/__main__.py'],
pathex=[],
binaries=[],
datas=[
('clipmorph/ffmpeg/${{ matrix.ffmpeg_dir }}', 'clipmorph/ffmpeg/${{ matrix.ffmpeg_dir }}'),
('clipmorph/resources', 'clipmorph/resources'),
],
hiddenimports=['whisper', 'whisperx', 'torch', 'torchaudio'],
hookspath=[],
hooksconfig={'matplotlib': {'backends': ['Agg']}},
runtime_hooks=[],
excludes=excludes,
noarchive=False,
)
seen = set()
filtered_binaries = []
for binary in a.binaries:
if binary[0] not in seen:
seen.add(binary[0])
filtered_binaries.append(binary)
a.binaries = filtered_binaries
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='${{ matrix.output_name }}',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name='clipmorph',
)
EOF
fi
shell: bash
- name: Build with PyInstaller
run: pyinstaller clipmorph.spec
- name: Analyze build size
run: |
echo "=== Build size analysis ==="
if [ "$RUNNER_OS" == "Windows" ] || [ "$RUNNER_OS" == "macOS" ]; then
du -sh dist/${{ matrix.output_name }} || true
else
du -sh dist/clipmorph || true
fi
echo ""
echo "=== Disk space after build ==="
df -h
shell: bash
continue-on-error: true
- name: Package distribution
run: |
if [ "$RUNNER_OS" == "Windows" ] || [ "$RUNNER_OS" == "macOS" ]; then
# Single executable - just move it
mv dist/${{ matrix.output_name }} ${{ matrix.asset_name }}
else
# Linux: Create self-extracting archive
cd dist
tar -czf payload.tar.gz clipmorph/
# Create self-extracting shell script
cat > ../clipmorph-linux << 'SELFEXTRACT'
#!/bin/bash
# Self-extracting installer for ClipMorph
EXTRACT_DIR="$HOME/.clipmorph"
PAYLOAD_LINE=$(awk '/^__PAYLOAD_BEGINS__/ { print NR + 1; exit 0; }' "$0")
# Extract on first run or if extraction directory doesn't exist
if [ ! -d "$EXTRACT_DIR" ] || [ ! -f "$EXTRACT_DIR/clipmorph/clipmorph" ]; then
echo "Extracting ClipMorph (first run only)..."
mkdir -p "$EXTRACT_DIR"
tail -n +$PAYLOAD_LINE "$0" | tar -xzf - -C "$EXTRACT_DIR"
echo "Extraction complete!"
fi
# Run the actual executable with all passed arguments
exec "$EXTRACT_DIR/clipmorph/clipmorph" "$@"
__PAYLOAD_BEGINS__
SELFEXTRACT
# Append the compressed payload
cat payload.tar.gz >> ../clipmorph-linux
chmod +x ../clipmorph-linux
fi
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.asset_name }}
path: ${{ matrix.asset_name }}
- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: ${{ matrix.asset_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}