Skip to content

Commit 2b7508c

Browse files
committed
Add desktop packaging with PyInstaller and electron-builder
1 parent 0bd2a58 commit 2b7508c

13 files changed

Lines changed: 3286 additions & 64 deletions

File tree

.github/workflows/build.yml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: Build & Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
build:
10+
strategy:
11+
matrix:
12+
include:
13+
- os: windows-latest
14+
platform: win
15+
- os: macos-latest
16+
platform: mac
17+
- os: ubuntu-latest
18+
platform: linux
19+
20+
runs-on: ${{ matrix.os }}
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Set up Python 3.12
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: '3.12'
29+
30+
- name: Set up Node.js
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: '20'
34+
35+
- name: Install Python dependencies
36+
working-directory: backend
37+
run: |
38+
pip install --no-cache-dir torch==2.8.0 torchaudio==2.8.0 --index-url https://download.pytorch.org/whl/cpu
39+
pip install --no-cache-dir -r requirements.txt
40+
pip install pyinstaller
41+
42+
- name: Install ffmpeg (Windows)
43+
if: matrix.platform == 'win'
44+
run: choco install ffmpeg -y
45+
46+
- name: Install ffmpeg (macOS)
47+
if: matrix.platform == 'mac'
48+
run: brew install ffmpeg
49+
50+
- name: Install ffmpeg (Linux)
51+
if: matrix.platform == 'linux'
52+
run: sudo apt-get update && sudo apt-get install -y ffmpeg
53+
54+
- name: Build backend with PyInstaller
55+
working-directory: backend
56+
run: pyinstaller modsquad-backend.spec
57+
58+
- name: Bundle ffmpeg into backend (Windows)
59+
if: matrix.platform == 'win'
60+
shell: pwsh
61+
run: |
62+
Copy-Item (Get-Command ffmpeg).Source "backend/dist/modsquad-backend/"
63+
Copy-Item (Get-Command ffprobe).Source "backend/dist/modsquad-backend/"
64+
65+
- name: Bundle ffmpeg into backend (macOS/Linux)
66+
if: matrix.platform != 'win'
67+
run: |
68+
cp $(which ffmpeg) backend/dist/modsquad-backend/
69+
cp $(which ffprobe) backend/dist/modsquad-backend/
70+
71+
- name: Install frontend dependencies
72+
working-directory: frontend
73+
run: npm ci
74+
75+
- name: Build frontend
76+
working-directory: frontend
77+
run: npm run build
78+
79+
- name: Build Electron app (Windows)
80+
if: matrix.platform == 'win'
81+
working-directory: frontend
82+
run: npx electron-builder --win
83+
84+
- name: Build Electron app (macOS)
85+
if: matrix.platform == 'mac'
86+
working-directory: frontend
87+
run: npx electron-builder --mac
88+
89+
- name: Build Electron app (Linux)
90+
if: matrix.platform == 'linux'
91+
working-directory: frontend
92+
run: npx electron-builder --linux
93+
94+
- name: Upload artifacts
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: modsquad-${{ matrix.platform }}
98+
path: |
99+
frontend/release/*.exe
100+
frontend/release/*.dmg
101+
frontend/release/*.AppImage
102+
103+
release:
104+
needs: build
105+
runs-on: ubuntu-latest
106+
permissions:
107+
contents: write
108+
109+
steps:
110+
- name: Download all artifacts
111+
uses: actions/download-artifact@v4
112+
with:
113+
path: artifacts
114+
115+
- name: Create GitHub Release
116+
uses: softprops/action-gh-release@v2
117+
with:
118+
files: artifacts/**/*
119+
generate_release_notes: true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ dist-ssr
1515
__pycache__/
1616
*.pyc
1717
venv/
18+
build/
19+
*.spec.swp
20+
21+
# Electron builder output
22+
release/
1823

1924
# Uploaded videos go to this folder
2025
uploads/

backend/data/filtered_words.json

Lines changed: 0 additions & 20 deletions
This file was deleted.

backend/main.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44
import shutil
55
from pathlib import Path
66
import logging
7+
import os
8+
import sys
9+
10+
# When running as a PyInstaller bundle, add the bundle dir to PATH
11+
# so that bundled ffmpeg and other binaries are found.
12+
if getattr(sys, "frozen", False):
13+
bundle_dir = Path(sys._MEIPASS)
14+
os.environ["PATH"] = str(bundle_dir) + os.pathsep + os.environ.get("PATH", "")
715

816
from utils.transcribe import transcribe_audio
917
from utils.settings import router as settings_router
@@ -19,21 +27,16 @@
1927

2028
app = FastAPI(title="ModSquad API", version="1.0.0")
2129

22-
origins = [
23-
"http://localhost:5173",
24-
"localhost:5173"
25-
]
26-
2730
app.add_middleware(
2831
CORSMiddleware,
29-
allow_origins=origins,
32+
allow_origins=["*"],
3033
allow_credentials=True,
3134
allow_methods=["*"],
32-
allow_headers=["*"]
35+
allow_headers=["*"],
3336
)
3437

35-
UPLOAD_DIR = Path("uploads")
36-
UPLOAD_DIR.mkdir(exist_ok=True)
38+
UPLOAD_DIR = Path(os.environ.get("UPLOAD_DIR", "uploads"))
39+
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
3740

3841
ALLOWED_EXTENSIONS = {".mp4", ".mov", ".avi", ".mp3", ".wav", ".m4a"}
3942

@@ -107,4 +110,9 @@ async def download_file(filename: str):
107110
file_path = UPLOAD_DIR / filename
108111
if not file_path.exists():
109112
raise HTTPException(status_code=404, detail="File not found")
110-
return FileResponse(path=str(file_path), filename=filename, media_type="video/mp4")
113+
return FileResponse(path=str(file_path), filename=filename, media_type="video/mp4")
114+
115+
if __name__ == "__main__":
116+
import uvicorn
117+
port = int(os.environ.get("MODSQUAD_PORT", "8000"))
118+
uvicorn.run(app, host="127.0.0.1", port=port)

backend/modsquad-backend.spec

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
3+
from PyInstaller.utils.hooks import collect_all
4+
5+
block_cipher = None
6+
7+
extra_datas = [('data', 'data')]
8+
extra_binaries = []
9+
extra_hiddenimports = [
10+
'uvicorn.logging',
11+
'uvicorn.lifespan',
12+
'uvicorn.lifespan.on',
13+
'uvicorn.lifespan.off',
14+
'uvicorn.protocols',
15+
'uvicorn.protocols.http',
16+
'uvicorn.protocols.http.auto',
17+
'uvicorn.protocols.http.h11_impl',
18+
'uvicorn.protocols.http.httptools_impl',
19+
'uvicorn.protocols.websockets',
20+
'uvicorn.protocols.websockets.auto',
21+
'uvicorn.protocols.websockets.wsproto_impl',
22+
'uvicorn.loops',
23+
'uvicorn.loops.auto',
24+
'uvicorn.loops.asyncio',
25+
]
26+
27+
for pkg in ['whisperx', 'torch', 'torchaudio', 'faster_whisper', 'ctranslate2', 'transformers', 'torchcodec', 'pyannote.audio', 'pyannote.core', 'pyannote.pipeline', 'speechbrain', 'imageio', 'imageio_ffmpeg', 'moviepy']:
28+
try:
29+
tmp_datas, tmp_binaries, tmp_hiddenimports = collect_all(pkg)
30+
extra_datas += tmp_datas
31+
extra_binaries += tmp_binaries
32+
extra_hiddenimports += tmp_hiddenimports
33+
except Exception:
34+
pass
35+
36+
a = Analysis(
37+
['main.py'],
38+
pathex=[],
39+
binaries=extra_binaries,
40+
datas=extra_datas,
41+
hiddenimports=extra_hiddenimports,
42+
hookspath=[],
43+
hooksconfig={},
44+
runtime_hooks=[],
45+
excludes=[],
46+
noarchive=False,
47+
)
48+
49+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
50+
51+
exe = EXE(
52+
pyz,
53+
a.scripts,
54+
[],
55+
exclude_binaries=True,
56+
name='modsquad-backend',
57+
debug=False,
58+
bootloader_ignore_signals=False,
59+
strip=False,
60+
upx=True,
61+
console=True,
62+
)
63+
64+
coll = COLLECT(
65+
exe,
66+
a.binaries,
67+
a.zipfiles,
68+
a.datas,
69+
strip=False,
70+
upx=True,
71+
upx_exclude=[],
72+
name='modsquad-backend',
73+
)

0 commit comments

Comments
 (0)