Skip to content

Fix Auto-Clip toggle #58

Fix Auto-Clip toggle

Fix Auto-Clip toggle #58

Workflow file for this run

name: Build SmartEye
on:
workflow_dispatch:
inputs:
create_installer:
description: "Build an NSIS installer in addition to the portable app zip"
required: true
type: boolean
default: true
create_release:
description: "Create or update a GitHub Release with the production artifacts"
required: true
type: boolean
default: false
compress_with_upx:
description: "Compress SmartEye.exe with UPX before packaging"
required: true
type: boolean
default: false
pull_request:
push:
branches:
- main
- dev
jobs:
validate:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
architecture: "x64"
cache: "pip"
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install validation dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Ruff checks
run: |
python -m ruff check backend frontend utils scripts main.py tests --select F401,F821,F823,F811,F841,E722,B006,B007,B008,B023,B904,RUF012,RUF034
- name: Pytest
run: |
python -m pytest -q
build:
if: github.event_name == 'workflow_dispatch'
needs: validate
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
architecture: "x64"
cache: "pip"
cache-dependency-path: requirements.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Remove duplicate top-level insightface path
shell: pwsh
run: |
$pythonRoot = (python -c "import sys; print(sys.prefix)").Trim()
$topLevelInsightface = Join-Path $pythonRoot "insightface"
if (Test-Path $topLevelInsightface) {
Write-Host "Removing duplicate package path: $topLevelInsightface"
Remove-Item -Path $topLevelInsightface -Recurse -Force
Write-Host "Done."
} else {
Write-Host "No duplicate top-level insightface path detected"
}
python -c "import insightface, sys; print('Python prefix:', sys.prefix); print('insightface module:', insightface.__file__)"
- name: Ensure setuptools is installed for Nuitka
run: |
pip install --upgrade setuptools
pip show setuptools
- name: Collect PySide6 plugins
shell: pwsh
run: |
$p = (python -c "import PySide6, os; print(os.path.join(os.path.dirname(PySide6.__file__), 'plugins'))").Trim()
if (Test-Path $p) {
Write-Host "Copying PySide6 plugins from $p"
Remove-Item -Path "$PWD\frontend\pyside6_plugins" -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item -Path $p -Destination "$PWD\frontend\pyside6_plugins" -Recurse -Force
Write-Host "PySide6 plugins copied to frontend/pyside6_plugins"
} else {
Write-Host "PySide6 plugins path not found: $p"
}
- name: Download model bundle (optional)
shell: pwsh
run: |
if (-not (Test-Path "data/models")) {
if ($env:MODEL_BUNDLE_URL) {
Write-Host "MODEL_BUNDLE_URL set - downloading model bundle..."
Invoke-WebRequest -Uri $env:MODEL_BUNDLE_URL -OutFile models.zip -UseBasicParsing
Expand-Archive models.zip -DestinationPath data/models
Remove-Item models.zip -Force
Write-Host "Model bundle downloaded and extracted to data/models"
} else {
Write-Host "MODEL_BUNDLE_URL not set; skipping model download"
}
} else {
Write-Host "data/models already exists - skipping download"
}
- name: Cache Nuitka build cache
uses: actions/cache@v4
with:
path: C:/Users/runneradmin/AppData/Local/Nuitka/Nuitka
key: nuitka-${{ runner.os }}-${{ hashFiles('requirements.txt', 'main.py') }}
restore-keys: |
nuitka-${{ runner.os }}-
- name: Stage scientific runtime libs for packaging
shell: pwsh
run: |
@'
import importlib.util
import pathlib
import shutil
root = pathlib.Path(".").resolve()
out_root = root / "build_assets"
out_root.mkdir(exist_ok=True)
def stage_lib_dir(pkg_name: str):
spec = importlib.util.find_spec(pkg_name)
if not spec or not spec.origin:
print(f"[warn] could not resolve {pkg_name}")
return
pkg_dir = pathlib.Path(spec.origin).resolve().parent
libs_dir = pkg_dir.parent / f"{pkg_name}.libs"
if not libs_dir.exists():
print(f"[info] no {pkg_name}.libs at {libs_dir}")
return
dst = out_root / f"{pkg_name}.libs"
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(libs_dir, dst)
print(f"[ok] staged {libs_dir} -> {dst}")
stage_lib_dir("scipy")
stage_lib_dir("numpy")
'@ | python -
Get-ChildItem -Path "$PWD\build_assets" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host "Staged runtime dir: $($_.FullName)"
}
- name: Check for model bundle
id: check_models
shell: pwsh
run: |
$hasModels = (Get-ChildItem -Path "data/models" -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0
"has_models=$($hasModels.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Check for object model file
id: check_obj
shell: pwsh
run: |
$hasObj = Test-Path "data/models/Obj-Detection.onnx"
"has_obj=$($hasObj.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Build with Nuitka
uses: Nuitka/Nuitka-Action@main
with:
nuitka-version: 2.7.11
script-name: main.py
mode: standalone
output-dir: build
output-file: SmartEye
jobs: "2"
lto: "yes"
windows-console-mode: disable
windows-icon-from-ico: frontend/assets/icons/icon.ico
enable-plugins: pyside6
show-scons: true
include-package: |
psutil
PySide6
shiboken6
reportlab
requests
pyqtgraph
onnx
onnxruntime
insightface
GPUtil
wmi
backend
frontend
utils
streamlink
numpy
scipy
skimage
albumentations
include-module: |
scipy._cyutility
scipy._lib._ccallback_c
include-package-data: |
reportlab
pyqtgraph
insightface
onnxruntime
streamlink
numpy
scipy
skimage
albumentations
include-data-dir: |
frontend/assets=frontend/assets
frontend/pyside6_plugins=PySide6/plugins
build_assets/scipy.libs=scipy.libs
build_assets/numpy.libs=numpy.libs
include-data-files: |
app_info.json=app_info.json
backend/database/schema.sql=backend/database/schema.sql
${{ steps.check_obj.outputs.has_obj == 'true' && 'data/models/Obj-Detection.onnx=data/models/Obj-Detection.onnx' || '' }}
nofollow-import-to: |
numpy.distutils
numpy.random.tests
numpy.random.tests.test_extending
scipy.tests
scipy.stats.tests
scipy.stats.tests.test_censored_data
scipy.stats.tests.test_continuous
sympy.polys.polyquinticconst
trio.testing
onnxruntime.training
onnxruntime.tools
onnx.backend.test
cv2.samples
pyqtgraph.tests
pyqtgraph.opengl
pyqtgraph.exporters
PySide6.QtWebEngine
PySide6.QtWebEngineWidgets
PySide6.QtWebEngineCore
PySide6.Qt3DCore
PySide6.Qt3DRender
PySide6.Qt3DInput
PySide6.Qt3DLogic
PySide6.Qt3DAnimation
PySide6.Qt3DExtras
PySide6.QtCharts
PySide6.QtDataVisualization
PySide6.QtLocation
PySide6.QtPositioning
PySide6.QtRemoteObjects
PySide6.QtSensors
PySide6.QtSerialPort
PySide6.QtTextToSpeech
PySide6.QtBluetooth
PySide6.QtNfc
unittest
test
distutils
setuptools
pkg_resources
pip
doctest
noinclude-data-files: |
qt6webenginecore.dll
qt6webenginequick.dll
qt6quick.dll
qt6quick3d.dll
qt6quick3druntimerender.dll
qt6quick3dutils.dll
qt6quickcontrols2.dll
qt6quickshapes.dll
qt6quicktemplates2.dll
qt6quicktest.dll
qt6quickwidgets.dll
qt6qml.dll
qt6qmlmeta.dll
qt6qmlmodels.dll
qt6qmlworkerscript.dll
qt6scxml.dll
qt6spatialaudio.dll
skimage/data/*
pyqtgraph/examples/*
assume-yes-for-downloads: true
- name: Compress executable with UPX
if: ${{ inputs.compress_with_upx }}
shell: pwsh
run: |
choco install upx -y
$exe = Get-ChildItem -Path "$PWD\build" -Filter *.exe -File -Recurse | Select-Object -First 1
if ($exe) {
Write-Host "Compressing $($exe.FullName) with UPX..."
upx -9 $exe.FullName
Write-Host "Compression complete!"
} else {
Write-Host "Warning: No .exe file found to compress"
}
- name: Create NSIS Installer
if: ${{ inputs.create_installer }}
shell: pwsh
run: |
choco install nsis -y
$appInfo = Get-Content app_info.json | ConvertFrom-Json
$appVersion = $appInfo.version
$distDir = Get-ChildItem -Path (Join-Path $PWD 'build') -Directory -Filter *.dist | Select-Object -First 1
if (-not $distDir) {
throw "No .dist folder found"
}
$nsiContent = @"
!define APPNAME "SmartEye"
!define VERSION "$appVersion"
!define EXEFILE "SmartEye.exe"
SetCompressor /SOLID lzma
SetCompressorDictSize 64
Name "`${APPNAME} Setup"
RequestExecutionLevel admin
!define MUI_ICON "frontend\assets\icons\icon.ico"
!define MUI_UNICON "frontend\assets\icons\icon.ico"
!include "MUI2.nsh"
OutFile "build\SmartEyeInstaller.exe"
InstallDir "`$PROGRAMFILES\`${APPNAME}"
Caption "`${APPNAME} Installer"
BrandingText "`${APPNAME}"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section "Install"
SetOutPath "`$INSTDIR"
File /r "$($distDir.FullName)\*"
CreateDirectory "`$SMPROGRAMS\`${APPNAME}"
CreateShortcut "`$SMPROGRAMS\`${APPNAME}\`${APPNAME}.lnk" "`$INSTDIR\`${EXEFILE}"
CreateShortcut "`$DESKTOP\`${APPNAME}.lnk" "`$INSTDIR\`${EXEFILE}"
WriteUninstaller "`$INSTDIR\Uninstall.exe"
CreateShortcut "`$SMPROGRAMS\`${APPNAME}\Uninstall.lnk" "`$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "DisplayName" "`${APPNAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "UninstallString" "`$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "DisplayIcon" "`$INSTDIR\`${EXEFILE}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}" "DisplayVersion" "`${VERSION}"
SectionEnd
Section "Uninstall"
RMDir /r "`$INSTDIR"
Delete "`$DESKTOP\`${APPNAME}.lnk"
Delete "`$SMPROGRAMS\`${APPNAME}\`${APPNAME}.lnk"
Delete "`$SMPROGRAMS\`${APPNAME}\Uninstall.lnk"
RMDir "`$SMPROGRAMS\`${APPNAME}"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\`${APPNAME}"
SectionEnd
"@
$nsiContent | Out-File -FilePath "SmartEye.nsi" -Encoding UTF8
Write-Host "Compiling installer with LZMA compression (this may take a few minutes)..."
& "C:\Program Files (x86)\NSIS\makensis.exe" SmartEye.nsi
if ($LASTEXITCODE -ne 0) {
throw "NSIS compilation failed with exit code $LASTEXITCODE"
}
Write-Host "Installer created successfully!"
- name: Collect production artifacts
id: production_artifacts
shell: pwsh
run: |
$appInfo = Get-Content app_info.json | ConvertFrom-Json
$appName = $appInfo.name
$appVersion = $appInfo.version
$appBuild = Get-Date -Format "yyyyMMdd"
$artifactDir = Join-Path $PWD "build\artifacts"
New-Item -Path $artifactDir -ItemType Directory -Force | Out-Null
$distDir = Get-ChildItem -Path (Join-Path $PWD 'build') -Directory -Filter *.dist | Select-Object -First 1
if (-not $distDir) {
throw "No .dist folder found"
}
$portableStageRoot = Join-Path $env:RUNNER_TEMP "smarteye-portable"
$portableAppRoot = Join-Path $portableStageRoot "SmartEye"
Remove-Item -Path $portableStageRoot -Recurse -Force -ErrorAction SilentlyContinue
New-Item -Path $portableAppRoot -ItemType Directory -Force | Out-Null
Copy-Item -Path (Join-Path $distDir.FullName '*') -Destination $portableAppRoot -Recurse -Force
@"
SmartEye portable production build
Run SmartEye.exe from this folder.
Keep the files and folders together; the executable depends on the bundled runtime files.
If Windows SmartScreen appears, choose More info, then Run anyway for your own test build.
"@ | Out-File -FilePath (Join-Path $portableAppRoot "README_FIRST.txt") -Encoding UTF8
$portablePath = Join-Path $artifactDir "$appName-$appVersion-$appBuild-windows-portable.zip"
Compress-Archive -Path $portableAppRoot -DestinationPath $portablePath -Force
$installerPath = Join-Path $PWD "build\SmartEyeInstaller.exe"
$packagedInstallerPath = ""
if (Test-Path $installerPath) {
$packagedInstallerPath = Join-Path $artifactDir "$appName-$appVersion-$appBuild-windows-installer.exe"
Copy-Item -LiteralPath $installerPath -Destination $packagedInstallerPath -Force
} else {
Write-Host "Installer not found; portable zip will still be uploaded."
}
"app_name=$appName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"app_version=$appVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"app_build=$appBuild" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"portable_path=$portablePath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"installer_path=$packagedInstallerPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
Write-Host "Production artifacts:"
Get-ChildItem -Path $artifactDir -File | ForEach-Object {
Write-Host " - $($_.FullName) ($($_.Length) bytes)"
}
- name: Upload production artifacts
uses: actions/upload-artifact@v4
with:
name: SmartEye-windows-production-${{ steps.production_artifacts.outputs.app_version }}-${{ steps.production_artifacts.outputs.app_build }}
path: build/artifacts/*
if-no-files-found: error
retention-days: 14
- name: Create or update release
if: ${{ inputs.create_release }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
run: |
$appName = "${{ steps.production_artifacts.outputs.app_name }}"
$appVersion = "${{ steps.production_artifacts.outputs.app_version }}"
$appBuild = "${{ steps.production_artifacts.outputs.app_build }}"
$tag = "v$appVersion"
$name = "$appName v$appVersion (build $appBuild)"
$repo = "${{ github.repository }}"
$assets = @("${{ steps.production_artifacts.outputs.portable_path }}")
$installer = "${{ steps.production_artifacts.outputs.installer_path }}"
if ($installer -and (Test-Path $installer)) {
$assets += $installer
}
gh release view $tag --repo $repo *> $null
if ($LASTEXITCODE -ne 0) {
gh release create $tag $assets --repo $repo --title $name --generate-notes
} else {
gh release upload $tag $assets --repo $repo --clobber
}