diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 5de72aec..00000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "permissions": { - "allow": [ - "WebSearch", - "WebFetch(domain:doc.qt.io)", - "WebFetch(domain:forum.qt.io)", - "WebFetch(domain:github.com)", - "Bash(uv run ruff check:*)", - "Bash(uv run:*)", - "Bash(python:*)", - "Bash(git log:*)", - "Bash(git show:*)", - "Bash(git status:*)", - "Bash(git diff:*)", - "Bash(git blame:*)", - "Bash(git ls-files:*)", - "Bash(git ls-tree:*)", - "Bash(git rev-parse:*)", - "Bash(git reflog:*)", - "Bash(git describe:*)", - "Bash(git shortlog:*)", - "Bash(git stash list:*)", - "Bash(git cat-file:*)", - "Bash(git remote -v:*)", - "Bash(git config --get:*)", - "Bash(git config --list:*)", - "Bash(gh pr list:*)", - "Bash(gh pr view:*)", - "Bash(gh pr status:*)", - "Bash(gh pr checks:*)", - "Bash(gh pr diff:*)", - "Bash(gh issue list:*)", - "Bash(gh issue view:*)", - "Bash(gh issue status:*)", - "Bash(gh repo view:*)", - "Bash(gh repo list:*)", - "Bash(gh release list:*)", - "Bash(gh release view:*)", - "Bash(gh run list:*)", - "Bash(gh run view:*)", - "Bash(gh workflow list:*)", - "Bash(gh workflow view:*)", - "Bash(gh search:*)", - "Bash(gh status:*)", - "Bash(gh auth status:*)", - "Bash(gh label list:*)", - "Bash(gh gist list:*)", - "Bash(gh gist view:*)", - "Bash(gh cache list:*)", - "Bash(gh ruleset list:*)", - "Bash(gh ruleset view:*)", - "Bash(gh variable list:*)", - "Bash(gh secret list:*)", - "Bash(gh project list:*)", - "Bash(gh project view:*)", - "Bash(dir:*)", - "Bash(find:*)", - "Bash(findstr:*)", - "Bash(type:*)", - "Bash(where:*)", - "Bash(tree:*)", - "Bash(whoami:*)", - "Bash(hostname:*)", - "Bash(ver:*)", - "Bash(fc:*)", - "Bash(echo:*)", - "Bash(Get-Content:*)", - "Bash(Get-ChildItem:*)", - "Bash(Get-Item:*)", - "Bash(Get-ItemProperty:*)", - "Bash(Get-Location:*)", - "Bash(Get-Process:*)", - "Bash(Get-Command:*)", - "Bash(Get-Help:*)", - "Bash(Get-Date:*)", - "Bash(Get-Host:*)", - "Bash(Get-Module:*)", - "Bash(Get-Variable:*)", - "Bash(Get-Alias:*)", - "Bash(Get-Service:*)", - "Bash(Select-String:*)", - "Bash(Select-Object:*)", - "Bash(Where-Object:*)", - "Bash(Sort-Object:*)", - "Bash(Format-Table:*)", - "Bash(Format-List:*)", - "Bash(Measure-Object:*)", - "Bash(Compare-Object:*)", - "Bash(Test-Path:*)", - "Bash(Resolve-Path:*)", - "Bash(Split-Path:*)", - "Bash(Join-Path:*)", - "Bash(ls:*)", - "Bash(xargs:*)", - "WebFetch(domain:ffmpeg.org)", - "Bash(ffmpeg -h:*)", - "Bash(ffmpeg:*)", - "WebFetch(domain:gitlab.com)", - "WebFetch(domain:wiki.x266.mov)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(grep:*)", - "WebFetch(domain:docs.rs)", - "WebFetch(domain:gist.github.com)", - "WebFetch(domain:manpages.debian.org)", - "WebFetch(domain:api.github.com)", - "WebFetch(domain:codecalamity.com)", - "WebFetch(domain:www.mail-archive.com)", - "WebFetch(domain:trac.ffmpeg.org)", - "WebFetch(domain:dev.to)", - "WebFetch(domain:www.ffmpeg.org)", - "WebFetch(domain:www.phoronix.com)", - "Bash(copy:*)", - "Bash(ffprobe:*)", - "Bash(gh api:*)", - "Bash(git:*)", - "WebFetch(domain:docs.nvidia.com)", - "Bash(gh discussion:*)" - ] - } -} diff --git a/.claude/skills/build-installer.md b/.claude/skills/build-installer.md new file mode 100644 index 00000000..67d5479c --- /dev/null +++ b/.claude/skills/build-installer.md @@ -0,0 +1,68 @@ +--- +description: Build the FastFlix installer, distribution, launcher, and uninstaller +--- + +# Building the FastFlix Installer & Distribution + +## Prerequisites + +- Go 1.22+ installed +- `go-winres` installed (pinned): `go install github.com/tc-hib/go-winres@v0.3.3`, with `%GOPATH%\bin` on PATH +- Python 3.13+ with `uv` +- Internet access (first build downloads embeddable Python and wheels) + +`zstandard` is not a project dependency; run the script through `uv run --with zstandard`. + +## Full Build (everything from scratch) + +```bash +uv run --with zstandard python scripts/build_distribution.py --archive --installer +``` + +This produces `dist/FastFlix_installer.exe`. The script: +- Downloads the embeddable Python (version constant `DEFAULT_PYTHON_VERSION` in the script) +- Builds a fresh FastFlix wheel (wipes `dist/wheels`, requires exactly one wheel) +- Installs dependencies pinned from `uv.lock` for the bundled CPython 3.13 / win_amd64 (`--arch arm64` for ARM64) +- Syncs `cmd/*/winres/winres.json` with `fastflix/version.py` (versions + copyright year) +- Runs `go-winres make` in each `cmd/*` and builds `FastFlix.exe` + `uninstall.exe` (+ installer with `--installer`) + with `-X main.Version=... -X main.BuildDate=...` +- Trims unused PySide6 modules (~527 MB savings), keeping third-party licence files +- Creates `fastflix_dist.tar.zst`, copies `licenses.txt`, generates `terms_translations.json` + +Use `--archive` alone to stop before building the installer. + +## Manual rebuilds (after Go code changes) + +Run `go-winres make` in the `cmd/` directory first (the `.syso` files are gitignored), then: + +```bash +go build -ldflags="-s -w -X main.Version=6.3.0 -X main.BuildDate=2026-01-01" -o dist/FastFlix/FastFlix.exe ./cmd/launcher +go build -ldflags="-s -w -H windowsgui -X main.Version=6.3.0" -o dist/FastFlix/uninstall.exe ./cmd/uninstaller +go build -ldflags="-s -w -H windowsgui -X main.Version=6.3.0 -X main.BuildDate=2026-01-01" -o dist/FastFlix_installer.exe ./cmd/installer # needs the archive +``` + +## Checks + +```bash +GOOS=windows go vet ./... +go test ./cmd/internal/... +``` + +## Important Notes + +- **Launcher**: Built WITHOUT `-H windowsgui` (needs console for logs); passes all arguments through to `python -m fastflix` +- **Installer/Uninstaller**: Built WITH `-H windowsgui` (no console) +- **Shared logic** (path safety, manifest-based removal, registry, self-delete batch) is in `cmd/internal/ffinstall`; never delete directories with `RemoveAll` on a registry-derived path without `ffinstall.SafeToRemove` +- **Uninstall** removes only files listed by `install_manifest.txt` (top-level entries) and keeps user files like a portable `fastflix.yaml` +- **DO NOT** use `CREATE_NO_WINDOW` flag on the launcher's child process - breaks FFmpeg pipe handles +- **DO NOT** use `walk` or other Go GUI frameworks - they have `TTM_ADDTOOL` failures on modern Windows. Use raw Win32 API. +- For `WM_DRAWITEM`-style lParam casts use `*(**T)(unsafe.Pointer(&lParam))` (keeps `go vet` clean) + +## Testing + +```bash +dist/FastFlix/FastFlix.exe --version +dist/FastFlix/FastFlix.exe --test +dist/FastFlix_installer.exe +"%LOCALAPPDATA%\Programs\FastFlix\uninstall.exe" +``` diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec21ab99..d13f108e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -96,41 +96,41 @@ jobs: run: | mkdir dist New-Item -Path Env: -Name VERSION -Value $(python.exe scripts\get_version.py) - New-Item -Path Env: -Name EXACT_VERSION -Value $(python.exe scripts\get_version.py exact) - New-Item -Path Env: -Name PRODUCT_VERSION -Value $(python.exe scripts\get_version.py nsis) echo "Building branch $env:GITHUB_REF - version $env:VERSION" echo "::set-env name=VERSION::$env:VERSION" - echo "::set-env name=EXACT_VERSION::$env:EXACT_VERSION" - echo "::set-env name=PRODUCT_VERSION::$env:PRODUCT_VERSION" env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + cache-dependency-path: go.sum + + - name: Install go-winres (icon / manifest / version resources) + shell: bash + run: | + go install github.com/tc-hib/go-winres@v0.3.3 + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + - name: Install Python Dependencies run: uv sync --frozen - - name: Build single executable + # Builds python runtime + dependencies (pinned by uv.lock), runs go-winres for the launcher and + # uninstaller, builds FastFlix.exe + uninstall.exe, writes cmd/installer/fastflix_dist.tar.zst, then + # runs go-winres for the installer and builds dist\FastFlix_installer.exe with + # -ldflags "-s -w -H windowsgui -X main.Version= -X main.BuildDate=". + - name: Build distribution and installer shell: cmd - run: uv run pyinstaller FastFlix_Windows_OneFile.spec + run: uv run --with zstandard python scripts\build_distribution.py --archive --installer - - name: Build installer executable + - name: Go vet shell: cmd - run: uv run pyinstaller FastFlix_Windows_Installer.spec - -# - uses: skymatic/code-sign-action@v1 -# with: -# certificate: '${{ secrets.CODE_CERT_B64 }}' -# password: '${{ secrets.CODE_CERT_PASS }}' -# certificatesha1: '${{ secrets.CODE_CERT_THUMB }}' -# description: 'FastFlix' -# timestampUrl: 'http://timestamp.sectigo.com' -# folder: 'dist\FastFlix' -# recursive: false + run: go vet ./... - - name: Package installer + - name: Go tests shell: cmd - run: | - makensis.exe /INPUTCHARSET UTF8 /DVERSION=${{ env.EXACT_VERSION }} /DPRODUCT_VERSION=${{ env.PRODUCT_VERSION }} FastFlix.nsi - move FastFlix_installer.exe dist\FastFlix_${{ env.VERSION }}_installer.exe + run: go test ./cmd/internal/... # - uses: skymatic/code-sign-action@v1 # with: @@ -140,29 +140,29 @@ jobs: # description: 'FastFlix' # timestampUrl: 'http://timestamp.sectigo.com' # folder: 'dist' -# recursive: false +# recursive: true - - name: Test executable + - name: Test launcher + shell: cmd run: | - dist\FastFlix.exe --version - dist\FastFlix.exe --test + dist\FastFlix\FastFlix.exe --version + dist\FastFlix\FastFlix.exe --test - - name: Package single executable + - name: Package installer and portable archive shell: cmd run: | - move dist\*.exe . - move docs\build-licenses.txt LICENSE + move dist\FastFlix_installer.exe dist\FastFlix_${{ env.VERSION }}_installer.exe + copy docs\build-licenses.txt dist\FastFlix\LICENSE + 7z a -mm=Deflate -mfb=258 -mpass=15 dist\FastFlix_${{ env.VERSION }}_win64.zip .\dist\FastFlix\* - - name: Upload standalone executable artifact + - name: Upload portable archive artifact uses: actions/upload-artifact@v4 with: name: FastFlix_${{ env.VERSION }}_win64 - path: | - FastFlix.exe - LICENSE + path: dist/FastFlix_${{ env.VERSION }}_win64.zip - name: Upload installer artifact uses: actions/upload-artifact@v4 with: name: FastFlix_${{ env.VERSION }}_installer - path: FastFlix_${{ env.VERSION }}_installer.exe + path: dist/FastFlix_${{ env.VERSION }}_installer.exe diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f45a8437..0e22c01f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -48,4 +48,5 @@ jobs: - name: Run tests env: PYTEST_QT_API: pyside6 + QT_QPA_PLATFORM: offscreen run: uv run pytest tests -v diff --git a/.gitignore b/.gitignore index 07eb55e2..c002adbd 100644 --- a/.gitignore +++ b/.gitignore @@ -125,4 +125,13 @@ iso-639-3.* build-dir/ benchmarking/ -./.claude/settings.local.json +.claude/settings.local.json + +# Go installer build artifacts +cmd/installer/fastflix_dist.* +cmd/*/*.syso +cmd/installer/licenses.txt +cmd/installer/terms_translations.json + +# Build script cache (downloaded Python embeddable, get-pip.py) +scripts/cache/ diff --git a/CHANGES b/CHANGES index 407c2d1d..ec5abf6c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,125 @@ # Changelog +## Version 6.3.0 + +* Adding #202 new video filters to the Advanced panel: vibrance, color temperature, curves presets, colorbalance presets, unsharp mask, deflicker, pad/letterbox, LUT3D file loading, and nlmeans_opencl GPU-accelerated denoise — with rigaya hardware encoder support for curves (--vpp-curves), unsharp (--vpp-unsharp), pad (--vpp-pad), LUT3D (--vpp-colorspace lut3d), and nlmeans_opencl (--vpp-nlmeans) +* Adding #212 full source and output file path tooltip on hover for queue items, with responsive text eliding for long file names (thanks to bmcassagne) +* Adding #344 lossless encoding option for AVC (x264), AV1 (AOM), VP9, and AV1 (SVT-AV1) encoders +* Adding #377 #550 #694 gamma and hue options to the Equalizer in the Advanced panel (FFmpeg eq/hue filters for software encoders, --vpp-tweak for rigaya) (thanks to lincsat, DCNerds, and Patrick Isbendjian) +* Adding #405 sharpen filter (CAS) to the Advanced panel (FFmpeg cas filter for software encoders, --vpp-unsharp for rigaya) +* Adding #447 GOP length setting to the Advanced panel (FFmpeg -g, rigaya --gop-len) (thanks to colemar) +* Adding #457 QSVEncC --tune parameter support (default, psnr, ssim, ms_ssim, vmaf, perceptual) for HEVC, AV1, and AVC encoders (thanks to Marco Ravich) +* Adding #480 2-pass CRF encoding for the AOM AV1 encoder for improved quality (thanks to jimbow973) +* Adding #498 deinterlace method and mode options (yadif, bwdif, w3fdif, estdif with send_frame/send_field modes) to the Advanced panel, with persistence in profiles +* Adding #520 "Source" option for the output file extension, matching the output container to the source format for batch encoding mixed MKV/MP4 files (thanks to x9sim9) +* Adding #523 drag and drop folder support in the Concatenation Builder (thanks to GokieKS) +* Adding #526 -movflags +faststart toggle for MP4/MOV containers, enabled by default (thanks to Aria) +* Adding #594 codec and codec+profile matching options for audio profile pattern matching, allowing users to distinguish between e.g. DTS-HD MA and regular DTS tracks (thanks to Damundai) +* Adding #665 profile-level control for subtitle "default" and "forced" disposition flags with Keep Source, Clear All, and Set on First Track modes (thanks to legoleigh) +* Adding #677 "Keep source loaded after adding to queue" setting to allow tweaking and re-queuing the same source without reloading (thanks to Mine181) +* Adding #682 HE-AAC and HE-AAC v2 audio profile selection for the libfdk_aac codec, including in profile audio matching and for rigaya encoders (HE-AAC v2 falls back to HE-AAC for non-stereo output) (thanks to Augusto7743) +* Adding Reverse Video option in the Advanced panel (applies FFmpeg reverse video filter and areverse audio filter for converted tracks, always seeking on the input side so trimmed segments stay aligned) +* Adding rigaya encoder support for Equalizer (--vpp-tweak), Denoise (--vpp-nlmeans/knn/pmd), Deblock (--vpp-deblock), Output FPS (--vpp-fps), and Video Track Title — only Video Speed and Reverse Video remain unsupported +* Adding #544 Download HDR10 Metadata button in Source Details tab for exporting mastering display and content light level data in x265-compatible format (thanks to yundebian918) +* Adding structured table view to Source Details tab with grouped sections, human-readable value formatting, Download JSON export, and full path tooltips on source/output filename fields +* Adding warning in Data & Attachments tab and reason text when tracks are incompatible with the output format (e.g. data streams in MKV) +* Adding Go-based Windows installer, launcher and uninstaller using an embeddable Python distribution (per-user and all-users installs, Add/Remove Programs integration, upgrades from previous NSIS installs) replacing the PyInstaller and NSIS Windows builds, plus a portable zip release +* Changing Advanced panel layout to use QGroupBox sections (Video Details, Frame Rate, Video Processing, Color & Appearance, Color, Output) with restoration filters separated from color correction filters +* Fixing #766 FFmpeg and other external tools failing on Linux PyInstaller builds (e.g. AUR fastflix-bin) with library symbol errors due to inherited bundled `LD_LIBRARY_PATH` (thanks to anne-o-pixel) +* Fixing #741 VideoToolbox HEVC and H264 encoders passing invalid numeric profile index instead of profile name, causing "incorrect parameters" errors on macOS (thanks to enaveso) +* Fixing #560 queue items could not be removed while encoding — now allows removing non-running items and grays out unavailable controls (move, reload, load queue) during encoding (thanks to 54x1) +* Fixing #736 thumbnail and crop preview generation failing with FFmpeg 8.0+ by adding `-update 1` flag required by the image2 muxer for single-image output (thanks to kliffgomel) +* Fixing #738 conversion error with Google Pixel videos by disabling data stream mapping for Matroska containers which do not support data streams (attachments like fonts are still allowed) (thanks to Hankuuuu) +* Fixing attachment streams (fonts, etc.) losing mimetype/filename metadata during conversion due to `-map_metadata -1` stripping per-stream tags required by the muxers +* Fixing cover image metadata targeting wrong output stream when data/attachment tracks are present (FFmpeg places `-map` streams before `-attach` streams in output order) +* Fixing data streams being auto-included by FFmpeg when encoding to MKV, causing "Only audio, video, and subtitles are supported" errors — now explicitly excludes unmapped data with `-dn` and rechecks compatibility when output format changes +* Fixing #739 unable to add more than 1 video per session due to AttributeError when closing audio track widgets (thanks to mrdav1) +* Fixing #130 auto-crop producing invalid crop rectangles for non-standard aspect ratios (4:3 FHD, 2.76:1 ultra-wide) due to independent dimension tracking across cropdetect frames and Python falsy-zero bug (thanks to hallmansm) +* Fixing HDR tonemapping failing with zscale "no path between colorspaces" by passing complete source colorspace parameters (primaries, matrix, transfer) to the zscale filter, with fallback retry for crop previews when tonemapping still fails +* Fixing bitrate combobox being unreadably small on x264 and x265 encoders — combo now sizes to fit its widest item +* Fixing rigaya encoder pad/letterbox calculation ignoring crop dimensions due to incorrect type check +* Fixing rigaya encoders (NVEncC, QSVEncC, VCEEncC) crashing with no output when data streams are copied to non-MKV containers — `--data-copy` and `--attachment-copy` are now only emitted for Matroska output +* Fixing startup crash when queue file exists but is missing the 'queue' key +* Fixing FFmpeg HEVC and AV1 NVENC bitrate mode failing with FFmpeg 8 due to removed `-2pass` option and unsupported `-pass` two-pass mode — now a single encode using NVENC `-multipass` +* Fixing FFmpeg HEVC NVENC passing invalid `-spatial_aq` option instead of `-spatial-aq` +* Fixing auto-detected interlaced sources having deinterlacing turned off by profile defaults on load and profile change +* Fixing bwdif, w3fdif and estdif deinterlace send_frame mode doubling the frame rate (filters default to one frame per field) +* Fixing duplicate queue entry ids and work folders when keeping the source loaded after adding to queue +* Fixing custom resolution values such as `1280:-2` / `-1:720` disabling scaling, and auto-calculated dimensions rounding down instead of to the nearest multiple of 8 +* Fixing auto-crop being thrown off by fully black frames and over-cropping mixed letterbox/pillarbox samples by using the union of detected content per edge +* Fixing x264 lossless failing when a profile other than high444 is selected, and SVT-AV1 / AOM lossless encodes still applying film grain and denoise +* Fixing pad/letterbox being applied before rotation (FFmpeg and rigaya encoders) +* Fixing duplicate `--vpp-colorspace` arguments when combining HDR to SDR with a 3D LUT on rigaya encoders +* Fixing nlmeans_opencl denoise failing without HDR removal and in previews, and truncating 10-bit sources to 8-bit +* Fixing data, attachment and cover stream metadata being written to the wrong output stream after toggling tracks or burning in subtitles +* Fixing Copy encoder reversing converted audio when Reverse Video is enabled, and burned-in text subtitles playing forward over reversed video +* Fixing GOP length of 0 producing all-intra encodes and being lost on reload +* Fixing video speed and reverse warning dialogs popping up while loading videos, profiles or queue items +* Fixing Data & Attachments track selection being lost after switching to an incompatible output format and back +* Fixing Source Details tab leaving the HDR10 button visible after clearing the video, rendering tag text as HTML, and silently ignoring save errors +* Fixing missing word wrap in yes/no dialogs, queue titles not eliding, and disabled toggle switches not looking disabled +* Fixing missing and English-only translations for new strings +* Adding SVT-AV1 tune options 3 (IQ), 4 (MS-SSIM) and 5 (VMAF) (IQ and MS-SSIM for AVIF), Variance Boost (strength and octile), temporal filtering, startup mini-gop size and quantization matrix (min/max) options to the SVT-AV1 encoder + +* Fixing config file being written non-atomically; it is now replaced atomically with a .bak kept, and a corrupt or truncated config falls back to the backup (or defaults, keeping the bad file aside) instead of preventing startup +* Fixing a single invalid profile or wrongly-typed config value preventing FastFlix from starting +* Fixing legacy fastflix.json upgrade setting the ffmpeg path from the ffprobe value +* Fixing a single unreadable queue item discarding the entire saved queue; bad items are skipped and the original queue file is kept aside +* Fixing queue file lock handling and replacing the queue file atomically on Windows, and cleaning up leftover temporary queue files +* Fixing a corrupt history file being overwritten with an empty history (it is now moved aside to history.yaml.corrupt-