feat(ui): add Clip Deduplicator and Overlap Manager for lossless clip merging #136
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: CI | |
| on: | |
| push: | |
| branches: [main, master, linux, 'feat/**', 'fix/**'] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main, master, linux] | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| # Keep CI read-only and free of secrets; release verification scans untrusted changes. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Python — the UI and its tests, on both platforms that ship a build. | |
| python: | |
| name: Python ${{ matrix.python-version }} (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| # 3.14 is the interpreter the alpha is actually built with (the shipped | |
| # bundle contains python314.dll). 3.12 is the floor we claim to support. | |
| python-version: ["3.12", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: | | |
| requirements-alpha.txt | |
| requirements-dev.txt | |
| - name: Install system dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -q -y \ | |
| cmake g++ pkg-config libpulse0 libpulse-dev libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 \ | |
| libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \ | |
| libxcb-xinerama0 libxkbcommon-x11-0 libx11-dev libxext-dev libxfixes-dev \ | |
| libwayland-dev wayland-protocols libpipewire-0.3-dev libdbus-1-dev | |
| - name: Install pinned dependencies | |
| # The alpha lock is the ONLY install path. If it does not resolve on a | |
| # supported interpreter, that is a release bug, not a CI inconvenience. | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements-alpha.txt -r requirements-dev.txt | |
| - name: Assert the environment matches the lock | |
| # Catches a transitive dependency that resolved to something other than | |
| # what requirements-alpha.txt pins — the exact AUDIT-009 failure mode. | |
| run: python -m pip check | |
| - name: Assert imageio-ffmpeg is absent (AUDIT-005) | |
| shell: bash | |
| run: | | |
| if python -m pip show imageio-ffmpeg > /dev/null 2>&1; then | |
| echo "::error::imageio-ffmpeg is installed. It bundles a GPLv3 FFmpeg" | |
| echo "::error::and must never enter a build environment." | |
| exit 1 | |
| fi | |
| echo "imageio-ffmpeg absent." | |
| - name: Install Windows FFmpeg runtime for media tests | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| python tools/fetch_third_party.py --ffmpeg | |
| $ffmpeg = "${{ github.workspace }}\\FTHRcapture\\FTHRclips\\third_party\\ffmpeg\\bin" | |
| "Path=$ffmpeg;$env:Path" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 | |
| - name: Prepare Linux test artifacts | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get install -q -y cmake g++ pkg-config libpulse-dev libx11-dev libxext-dev libxfixes-dev libwayland-dev wayland-protocols libpipewire-0.3-dev libdbus-1-dev | |
| python tools/fetch_third_party.py --ffmpeg-linux | |
| echo "LD_LIBRARY_PATH=${{ github.workspace }}/FTHRcapture_linux/third_party/ffmpeg/lib:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV" | |
| echo "PATH=${{ github.workspace }}/FTHRcapture_linux/third_party/ffmpeg/bin:${PATH}" >> "$GITHUB_ENV" | |
| cmake -B FTHRcapture_linux/build -S FTHRcapture_linux -DCMAKE_BUILD_TYPE=Release -DFTHR_FFMPEG_ROOT=${{ github.workspace }}/FTHRcapture_linux/third_party/ffmpeg | |
| cmake --build FTHRcapture_linux/build -j"$(nproc)" | |
| - name: Byte-compile all sources | |
| # A syntax error in a rarely imported module otherwise only shows up at | |
| # runtime, inside a frozen bundle, on a user's machine. | |
| run: python -m compileall -q FTHR_UI tests tools | |
| - name: Lint | |
| run: python -m ruff check . | |
| - name: Tests | |
| shell: bash | |
| env: | |
| QT_QPA_PLATFORM: offscreen | |
| run: | | |
| if [ "${{ runner.os }}" = "Linux" ]; then | |
| python -m pytest tests/ -v --tb=short \ | |
| --ignore=tests/test_linux_appimage_report.py \ | |
| --ignore=tests/test_linux_runtime.py \ | |
| --ignore=tests/test_linux_tools.py \ | |
| --ignore=tests/test_capture_backend_detection.py \ | |
| --ignore=tests/test_windows_monitor.py \ | |
| --ignore=tests/test_windows_native_hotkeys.py \ | |
| --ignore=tests/test_windows_autostart.py \ | |
| --ignore=tests/test_windows_installer_contract.py \ | |
| --ignore=tests/test_windows_lifecycle_contract.py \ | |
| --ignore=tests/test_windows_hardware_qualification_args.py \ | |
| --ignore=tests/test_windows_microphone_devices.py | |
| else | |
| python -m pytest tests/ -v --tb=short \ | |
| --ignore=tests/test_linux_appimage_report.py \ | |
| --ignore=tests/test_linux_runtime.py \ | |
| --ignore=tests/test_linux_tools.py \ | |
| --ignore=tests/test_capture_backend_detection.py \ | |
| --ignore=tests/test_license_gate_linux.py \ | |
| --ignore=tests/test_windows_monitor.py \ | |
| --ignore=tests/test_windows_native_hotkeys.py \ | |
| --ignore=tests/test_windows_autostart.py \ | |
| --ignore=tests/test_windows_installer_contract.py \ | |
| --ignore=tests/test_windows_lifecycle_contract.py \ | |
| --ignore=tests/test_windows_hardware_qualification_args.py \ | |
| --ignore=tests/test_windows_microphone_devices.py | |
| fi | |
| # Linux capture engine | |
| linux-engine: | |
| name: Linux engine (CMake) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -q -y \ | |
| cmake g++ pkg-config \ | |
| libpulse-dev libx11-dev libxext-dev libxfixes-dev \ | |
| libwayland-dev wayland-protocols libwayland-bin \ | |
| libpipewire-0.3-dev libdbus-1-dev | |
| - name: Fetch pinned Linux FFmpeg | |
| run: python tools/fetch_third_party.py --ffmpeg-linux | |
| - name: Configure | |
| run: > | |
| cmake -B FTHRcapture_linux/build -S FTHRcapture_linux | |
| -DCMAKE_BUILD_TYPE=Release | |
| -DFTHR_FFMPEG_ROOT=${{ github.workspace }}/FTHRcapture_linux/third_party/ffmpeg | |
| - name: Build | |
| # Warnings are surfaced but not fatal yet: the engine has never been | |
| # built warning-clean and turning them into errors here would make the | |
| # first green run impossible. Tighten once the backlog is cleared. | |
| run: | | |
| cmake --build FTHRcapture_linux/build -j"$(nproc)" 2>&1 | tee build.log | |
| echo "--- warning summary ---" | |
| grep -c "warning:" build.log || echo "0 warnings" | |
| - name: Fail on compile errors | |
| run: | | |
| if grep -q "error:" build.log; then | |
| echo "::error::compile errors in the Linux engine" | |
| exit 1 | |
| fi | |
| - name: Verify the binary exists | |
| run: test -x FTHRcapture_linux/build/FTHRclips | |
| - name: Native contract tests | |
| run: ctest --test-dir FTHRcapture_linux/build --output-on-failure | |
| - name: Verify licences and a clean source tree | |
| run: | | |
| python tools/verify_release_licenses.py --tree . | |
| git diff --exit-code | |
| # Deliberately NOT run: the engine's own capture path. A GitHub runner | |
| # has no compositor, no DRM device and no audio server. Starting it here | |
| # would test the failure path and prove nothing about capture. | |
| # Windows capture engine | |
| windows-engine: | |
| name: Windows engine (MSBuild) | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| cache-dependency-path: | | |
| requirements-alpha.txt | |
| requirements-dev.txt | |
| - uses: microsoft/setup-msbuild@v2 | |
| - name: Fetch FFmpeg runtime | |
| # Headers and import libs are tracked; the 152 MB of DLLs are not. | |
| # The fetch verifies every file against tools/ffmpeg_manifest.json. | |
| run: python tools/fetch_third_party.py --ffmpeg | |
| - name: Report missing third-party files clearly | |
| shell: bash | |
| run: | | |
| missing=0 | |
| for f in include/libavcodec/avcodec.h lib/avcodec.lib bin/avcodec-62.dll; do | |
| p="FTHRcapture/FTHRclips/third_party/ffmpeg/$f" | |
| if [ ! -f "$p" ]; then | |
| echo "::error::missing third-party file: $p" | |
| missing=1 | |
| fi | |
| done | |
| [ "$missing" -eq 0 ] || { | |
| echo "::error::Run: python tools/fetch_third_party.py --ffmpeg" | |
| exit 1 | |
| } | |
| - name: Build (Release | x64) | |
| run: > | |
| msbuild FTHRcapture\FTHRcapture.sln | |
| -p:Configuration=Release | |
| -p:Platform=x64 | |
| -m | |
| -verbosity:minimal | |
| - name: Verify the binary exists and is x64 | |
| shell: pwsh | |
| run: | | |
| $exe = "FTHRcapture\x64\Release\FTHRclips.exe" | |
| if (-not (Test-Path $exe)) { throw "engine binary not produced" } | |
| # PE header: 0x8664 = x86-64. A silent Win32 build would link but | |
| # then fail to load the x64 FFmpeg DLLs at runtime. | |
| $fs = [IO.File]::OpenRead($exe) | |
| $br = New-Object IO.BinaryReader($fs) | |
| $fs.Position = 0x3C | |
| $fs.Position = $br.ReadInt32() + 4 | |
| $machine = $br.ReadUInt16() | |
| $br.Close() | |
| if ($machine -ne 0x8664) { throw ("wrong architecture: 0x{0:X}" -f $machine) } | |
| "engine OK, x64" | |
| - name: Install pinned bundle dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements-alpha.txt -r requirements-dev.txt | |
| python -m pip check | |
| - name: Build sealed optional upload packages | |
| run: python tools/build_optional_uploaders.py | |
| - name: Test built optional upload packages | |
| run: > | |
| python -m pytest tests/test_uploader_plugin_boundary.py | |
| -k "test_built_" --tb=short | |
| - name: Build and verify the Windows bundle | |
| shell: pwsh | |
| run: | | |
| python -m PyInstaller FTHR.spec --clean --noconfirm --log-level WARN | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| if (-not (Test-Path "dist\FTHRClips\FTHRClips.exe")) { throw "UI binary not produced" } | |
| if (-not (Test-Path "dist\FTHRClips\_internal\engine\FTHRClips.exe")) { throw "bundled engine not produced" } | |
| python tools/verify_release_licenses.py --windows-dist dist\FTHRClips | |
| # Release verification — the gates that decide whether a tag is allowed. | |
| release-verification: | |
| name: Release verification | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # the large-file check inspects history | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| - name: Licences (AUDIT-005) | |
| # Refuses any tree that reintroduces GPL FFmpeg components or drops | |
| # the third-party licence paperwork. | |
| run: python tools/verify_release_licenses.py --tree . | |
| - name: Version consistency (AUDIT-008) | |
| run: python tools/verify_version_consistency.py | |
| - name: Shared-memory contract | |
| run: python tools/verify_shared_memory_contract.py | |
| - name: Engine response contract | |
| run: python tools/verify_engine_response_contract.py | |
| - name: Exception-handling ratchet | |
| run: python tools/verify_exception_handling.py | |
| - name: Secrets, user data and oversized binaries | |
| run: python tools/scan_repo_hygiene.py | |
| - name: No large blobs in history | |
| # A 150 MB DLL committed once stays in the pack forever, even after a | |
| # later delete. Cheaper to catch it on the PR that introduces it. | |
| run: | | |
| limit=$((2 * 1024 * 1024)) | |
| bad=$(git rev-list --objects --all \ | |
| | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | |
| | awk -v lim="$limit" '$1 == "blob" && $3 > lim {print $3, $4}' \ | |
| | sort -rn | head -20) | |
| if [ -n "$bad" ]; then | |
| echo "::error::blobs above 2 MB found in history:" | |
| echo "$bad" | |
| exit 1 | |
| fi | |
| echo "No oversized blobs in history." | |
| - name: Licence claim sanity (AUDIT-013) | |
| # FTHR's project licence is GPL-3.0-only. Generated assets may retain | |
| # their explicit MIT grant, so only current application claims belong | |
| # in this consistency check. | |
| run: | | |
| grep -q 'GNU GENERAL PUBLIC LICENSE' LICENSE | |
| grep -q 'Version 3, 29 June 2007' LICENSE | |
| if grep -nE "SOURCE_LICENSE = 'MIT'|FTHR Clips.+source.+MIT|application code remains MIT" \ | |
| README.md THIRD_PARTY_NOTICES.md FTHR_UI/version.py FTHR_UI/main.py; then | |
| echo "::error::FTHR's application licence drifted back to MIT." | |
| exit 1 | |
| fi | |
| echo "GPL-3.0-only project licence declaration is consistent." | |
| # Tag guard | |
| tag-guard: | |
| name: Tag guard | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| needs: [python, linux-engine, linux-appimage-package, windows-engine, release-verification] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| - name: Tag must match FTHR_UI/version.py | |
| run: | | |
| tag="${GITHUB_REF#refs/tags/v}" | |
| src="$(python -c "import sys; sys.path.insert(0,'FTHR_UI'); import version; print(version.__version__)")" | |
| if [ "$tag" != "$src" ]; then | |
| echo "::error::tag v$tag does not match FTHR_UI/version.py ($src)" | |
| exit 1 | |
| fi | |
| echo "Tag v$tag matches the source version." | |
| # Publish artifacts manually after install, launch, and uninstall qualification. | |
| # Linux AppImage compatibility check. Build on the oldest supported runner | |
| # and validate the result, but never upload or publish the image from CI. | |
| linux-appimage-package: | |
| name: Linux AppImage (glibc baseline) | |
| runs-on: ubuntu-22.04 | |
| needs: [linux-engine, release-verification] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| cache: pip | |
| cache-dependency-path: requirements-alpha.txt | |
| - name: Install packaging dependencies | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -q -y \ | |
| build-essential cmake pkg-config patchelf curl file binutils \ | |
| libavcodec-dev libavformat-dev libavutil-dev libavdevice-dev \ | |
| libswscale-dev libswresample-dev libpulse-dev libportaudio2 libx11-dev \ | |
| libgl1 libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 \ | |
| libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \ | |
| libxcb-shape0 libxcb-xinerama0 libxkbcommon0 libxkbcommon-x11-0 \ | |
| libxext-dev libxfixes-dev libwayland-dev wayland-protocols libwayland-bin \ | |
| libpipewire-0.3-dev libdbus-1-dev | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements-alpha.txt -r requirements-dev.txt | |
| python -m pip check | |
| - name: Build AppImage | |
| run: bash build_linux.sh | |
| - name: Verify AppImage artifact and checksum | |
| run: | | |
| image=(build_output/FTHRClips-*-x86_64.AppImage) | |
| [ "${#image[@]}" -eq 1 ] | |
| checksum="${image[0]}.sha256" | |
| test -f "$checksum" | |
| (cd "$(dirname "${image[0]}")" && sha256sum --check "$(basename "$checksum")") | |
| python tools/verify_linux_glibc.py \ | |
| --manifest tools/linux_release_baseline.json \ | |
| --target build_output/AppDir \ | |
| --target "${image[0]}" | |
| # The artifact remains a CI-local compatibility result. Users build | |
| # and verify their own AppImage; GitHub does not publish this file. |