Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f4390eb
CHANGES: fix typo (#688)
luzpaz Sep 6, 2025
f451d21
Fix process priority (#708)
JacobDev1 Nov 25, 2025
e8b28a3
Add function to find Rigaya encoder binaries (#707)
anne-o-pixel Nov 25, 2025
7edb262
Package updates
cdgriffith Jan 18, 2026
dd0df90
Audio and GIF fixes
cdgriffith Jan 19, 2026
3768b75
* Fixing crash when dragging non-video content over the main window
cdgriffith Jan 19, 2026
72a6786
- Adding async queue saving to prevent GUI blocking during queue op…
cdgriffith Jan 20, 2026
c3bf659
* Fixing window resizing beyond screen boundaries when switching prof…
cdgriffith Jan 21, 2026
428eb08
Add audio profile title options and fix dropdown styling
cdgriffith Feb 3, 2026
ecd37f0
* Adding Start/End Time tab to right-side options panel between Size …
cdgriffith Feb 3, 2026
5131e26
* Adding Keyframe checkbox to Settings menu as "Use keyframes for pre…
cdgriffith Feb 3, 2026
568e35a
* Adding Keyframe checkbox to Settings menu as "Use keyframes for pre…
cdgriffith Feb 3, 2026
124e4e9
Beta Version 1
cdgriffith Feb 5, 2026
f7323b0
Fix CI issues
cdgriffith Feb 5, 2026
57701f9
* Fixing #695 Qt6 decimal input fields enforcing locale-specific deci…
cdgriffith Feb 6, 2026
5e0cc5f
* Fixing #337 #700 #597 refactoring all FFmpeg command building from …
cdgriffith Feb 6, 2026
7bcb819
Add PGS to SRT OCR subtitle extraction feature (#709)
mikeSGman Feb 7, 2026
66934c2
Improving subtitle extraction
cdgriffith Feb 8, 2026
49ba817
Update version and picture
cdgriffith Feb 9, 2026
87770b4
Lots of fixes
cdgriffith Feb 9, 2026
a59ffd7
Adding gifski, version bump
cdgriffith Feb 9, 2026
7e03779
Adding gifski code
cdgriffith Feb 9, 2026
f217db5
* Adding video info bar below file area showing bit depth, color spac…
cdgriffith Feb 10, 2026
915afed
vp9 and vvc tests
cdgriffith Feb 10, 2026
d9cf067
* Fixing encoding incorrectly reported as successful when FFmpeg fail…
cdgriffith Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ jobs:
build-nix:
strategy:
matrix:
os: [ ubuntu-22.04, ubuntu-24.04, macos-13, macos-14 ]
os: [ ubuntu-22.04, ubuntu-24.04, macos-14 ]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install the latest version of uv and activate the environment
uses: astral-sh/setup-uv@v6
Expand Down Expand Up @@ -84,7 +84,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install the latest version of uv and activate the environment
uses: astral-sh/setup-uv@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pythonpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.13'

- name: Install Dependencies
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install the latest version of uv and activate the environment
uses: astral-sh/setup-uv@v6
Expand All @@ -30,7 +30,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install the latest version of uv and activate the environment
uses: astral-sh/setup-uv@v6
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,5 @@ test.html
iso-639-3.*
build-dir/
benchmarking/

./.claude/settings.local.json
68 changes: 67 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,5 +1,71 @@
# Changelog

## Version 6.0.0

* Adding #709 PGS to SRT OCR subtitle extraction feature (thanks to mikeSGman)
* Adding #712 audio profile title options: No Title, Generate Title, and Custom Title (thanks to gaalos)
* Adding gifski GIF encoder support with automatic PATH detection and manual path configuration
* Adding external subtitle file support - import .srt, .ass, .ssa, .vtt, and .sup files into the subtitle track list
* Adding AVC (x264) encoder options: AQ Mode, Psy-RD, Level, and custom x264 params
* Adding "Detected External Programs" section to Settings showing status of NVEncC, QSVEncC, VCEEncC, HDR10+ Parser, and PGS OCR tools
* Adding resizable columns to Concatenation Builder window with minimum widths based on header text
* Adding Keyframe checkbox to Settings menu as "Use keyframes for preview images" option (default enabled)
* Adding video info bar below file area showing bit depth, color space, chroma subsampling, HDR10, and HDR10+ at a glance
* Adding HDR10+ passthrough awareness for AOM AV1 encoder with FFmpeg 8.0+ version detection and UI indicators
* Adding HDR10+ tool auto-download on Windows startup and Help menu
* Adding AOM AV1 encoder options: Tune (psnr/ssim), Denoise, AQ Mode, allintra usage mode, and custom aom-params pass-through
* Adding rav1e encoder options: Tune (Psychovisual/Psnr), Photon Noise (film grain synthesis), Scene Detection toggle, HDR10 metadata passthrough, and custom rav1e-params pass-through
* Adding Dolby Vision copy support for NVEncC, QSVEncC, and VCEEncC HEVC/AV1 encoders
* Adding preview time slider to overlay at bottom of preview image
* Adding time display next to preview slider in H:MM:SS format
* Adding async queue saving to prevent GUI blocking during queue operations
* Adding atomic file writes for queue to prevent corruption from interrupted saves
* Adding file-based locking for queue operations to prevent race conditions between instances
* Adding graceful shutdown handling for worker process and background threads
* Adding Start/End Time tab to right-side options panel between Size and Crop tabs with compact 3-column layout
* Adding save dialog for subtitle extraction allowing users to choose output location, defaulting to the output directory
* Adding VVC encoder options: Perceptual QPA toggle, Intra Period, Threads, IFP (inter-frame parallelism), 8-bit pixel format support, and HDR10 metadata passthrough (mastering display, content light level, color signaling, chroma location)
* Adding VP9 encoder options: Auto Alt-Ref Frames, Lag in Frames, Tune Content, AQ Mode, and Sharpness
* Fixing #337 #700 #597 refactoring all FFmpeg command building from string concatenation to List[str] to fix shlex.split() failures with quotes, special characters in titles, and Windows path handling issues (thanks to Xoanon88 and Buzz0016)
* Fixing #481 MP4 subtitle copy mode using wrong codec - now uses mov_text instead of copy for MP4 containers (thanks to wmonte75)
* Fixing #687 dialogs staying on top of all windows and unable to minimize by removing WindowStaysOnTopHint flag (thanks to Krawk)
* Fixing #688 typo in changelog (thanks to luzpaz)
* Fixing #695 Qt6 decimal input fields enforcing locale-specific decimal separators (comma vs dot) by forcing C locale (thanks to isben)
* Fixing #696 RAW command display boxes too small on macOS by using actual document size for height calculation (thanks to isben)
* Fixing #707 Rigaya encoder binary detection with case-insensitive search (thanks to anne-o-pixel)
* Fixing #708 process priority niceness values inverted on Linux and removed Realtime option on non-Windows (thanks to JacobDev1)
* Fixing encoding incorrectly reported as successful when FFmpeg fails immediately (e.g. VAAPI on Windows) due to race condition between worker and output reader thread
* Fixing copy encoder failing when OpenCL is enabled by not adding hardware device flags when filters are disabled
* Fixing zscale "no path between colorspaces" error during HDR tonemapping by explicitly specifying input transfer characteristic
* Fixing thumbnail generation failing on non-full-range YUV sources (e.g. HDR AV1) with FFmpeg 8.0+ by adding -strict unofficial to mjpeg encoder
* Fixing FFmpeg download failing when destination directories (e.g. presets) already exist from a previous install
* Fixing HDR10+ detection to use ffprobe for non-HEVC codecs (AV1, VP9, etc.) and as fallback when hdr10plus_tool is unavailable
* Fixing Windows taskbar showing generic icon instead of FastFlix icon by setting AppUserModelID and deferring blocking version check until after event loop starts
* Fixing Settings browse buttons column being too wide by setting fixed width and column stretch
* Fixing PGS OCR requiring manual checkbox enable - now auto-detects availability from tesseract and pgsrip
* Fixing PGS OCR subtitle extraction using pgsrip.Sup to process already-extracted .sup file instead of re-extracting from MKV (fixes wrong file path and mkvextract failures)
* Fixing extract dropdown arrow being oversized by scaling the menu-indicator to match up/down button proportions
* Fixing QFont::setPointSize warnings in PyInstaller executables by using pt instead of px for stylesheet font sizes
* Fixing video track selector showing unnecessarily when source video has only one video track
* Fixing rav1e and AOM AV1 custom params list being corrupted to "[]" string on profile load/reload, breaking HDR10+ metadata passthrough
* Fixing visual border between filename area and video track selector
* Fixing test suite hanging due to missing QApplication in PySide6 widget tests
* Fixing window resizing beyond screen boundaries when switching profiles on macOS
* Fixing potential GUI freeze when log queue fills up during encoding
* Fixing file handle leaks in command runner when process startup fails
* Fixing crash when dragging non-video content over the main window
* Fixing rotation setting not being applied correctly when loading profiles
* Fixing flip settings not restoring correctly when returning video from queue
* Fixing GIF encoder not applying resolution scaling
* Fixing audio profile loading - validator returned wrong enum type (MatchType instead of MatchItem)
* Fixing "monoo" typo in downmix mapping that caused mono downmix to fail
* Fixing undefined variable crash when creating duplicate audio tracks via profile filters
* Fixing IndexError when applying profile audio filters to videos with non-sequential track indices
* Fixing TypeError crash with Rigaya encoders when audio quality not explicitly set
* Fixing AttributeError crash when audio track metadata is incomplete
* Fixing queue file generation mismatch errors due to redundant save calls on startup and when adding to queue


## Version 5.12.4

* Fixing #675 "Default Source Folder" not used when adding Complete Folders (thanks to Krawk)
Expand All @@ -10,7 +76,7 @@

## Version 5.12.3

* Fixing #673 changing subtitle langauge in the UI did not take effect in the command (thanks to danielly2020)
* Fixing #673 changing subtitle language in the UI did not take effect in the command (thanks to danielly2020)
* Fixing #673 extract subtitle command was looking for subtitle index, not absolute index (thanks to danielly2020)

## Version 5.12.2
Expand Down
113 changes: 113 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

FastFlix is a Python GUI application for video encoding/transcoding using PySide6 (Qt6). It wraps FFmpeg and supports 25+ encoder backends including x264, x265, AV1 variants, VP9, VVC, and hardware encoders (NVIDIA NVEncC, Intel QSVEncC, AMD VCEEncC).

**Requirements:** Python 3.13+, FFmpeg 4.3+ (5.0+ recommended)

## Build & Development Commands

```bash
# Install dependencies
uv sync --frozen

# Lint and format
uv run ruff check # Check for violations
uv run ruff check --fix # Auto-fix issues
uv run ruff format # Format code

# Run tests
uv run pytest tests -v
PYTEST_QT_API=pyside6 uv run pytest tests -v # Linux with Qt

# Run specific test file
uv run pytest tests/encoders/test_hevc_x265_command_builder.py -v

# Run the application
python -m fastflix

# Build executables
uv run pyinstaller FastFlix_Windows_OneFile.spec
uv run pyinstaller FastFlix_Nix_OneFile.spec
```

## Code Style

- Line length: 120 characters
- Double quotes for strings
- Ruff for linting and formatting (Black-compatible)
- Type hints via Pydantic models

## Architecture

### Multi-Process Design
- **Main process** (`entry.py`): Sets up queues and spawns worker subprocess
- **GUI process** (`application.py`): Qt application (prevents UI blocking)
- **Worker process** (`conversion_worker.py`): Processes conversion queue
- Queue communication between processes for status/logging

### Encoder Plugin System
Each encoder lives in `fastflix/encoders/{encoder_name}/` with:
- `__init__.py`: Encoder metadata/registration
- `command_builder.py`: Implements `build(fastflix) -> List[Command]`
- Settings model in `models/encode.py` (Pydantic)
- UI panel in `widgets/panels/{encoder_name}/`

### Key Modules
| Module | Purpose |
|--------|---------|
| `flix.py` | Core FFmpeg/FFprobe interaction |
| `widgets/main.py` | Main GUI window |
| `models/config.py` | Configuration management |
| `models/encode.py` | Encoder settings models |
| `encoders/common/helpers.py` | Shared command building utilities |

### Data Flow
1. User loads video → `parse()` via FFprobe
2. Encoding options set → Pydantic model validation
3. Video queued → Added to `conversion_list`
4. Worker processes → Encoder's `build()` generates FFmpeg commands
5. `command_runner.py` executes → Progress streamed to GUI

## Adding a New Encoder

1. Create directory: `fastflix/encoders/{encoder_name}/`
2. Add settings class to `models/encode.py`
3. Implement `command_builder.py` with `build(fastflix) -> List[Command]`
4. Create UI panel in `widgets/panels/{encoder_name}/`
5. Add tests in `tests/encoders/test_{encoder_name}_command_builder.py`

## Configuration

- Config file: `~/.config/FastFlix/fastflix.yaml`
- Portable mode: Place `fastflix.yaml` in app directory
- Environment overrides: `FF_FFMPEG`, `FF_FFPROBE`, `FF_HDR10PLUS`, `FF_CONFIG`

## FFmpeg Command Research

**IMPORTANT:** Always research FFmpeg commands online before implementing or modifying encoder command builders. FFmpeg options and filter syntax can change between versions.

Resources to consult:
- Official FFmpeg documentation: https://ffmpeg.org/ffmpeg.html
- FFmpeg filters documentation: https://ffmpeg.org/ffmpeg-filters.html
- FFmpeg wiki: https://trac.ffmpeg.org/wiki

Key considerations:
- Filter order matters (e.g., scale before palettegen for GIFs)
- Use appropriate scale flags (lanczos/bicubic over bilinear)
- Verify filter_complex syntax for multi-input/output chains
- Check encoder-specific options in official docs

## Changelog

**IMPORTANT:** Always update the `CHANGES` file when making significant additions or bug fixes during a session. Add entries under the current version section at the top of the file using the format:
- `* Adding {feature description}` for new features
- `* Fixing {bug description}` for bug fixes

## Branching

- `master`: Release branch
- `develop`: Development branch (PRs merge here)
7 changes: 5 additions & 2 deletions FastFlix_Nix_OneFile.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_submodules
from PyInstaller.utils.hooks import collect_submodules, copy_metadata, collect_data_files
import toml
import os
import platform
Expand All @@ -26,9 +26,12 @@ all_imports.remove("python-box")
all_imports.append("box")
all_imports.append("iso639")

# Add pgsrip for OCR support
all_imports.extend(["pgsrip", "pytesseract", "cv2", "numpy", "pysrt", "babelfish", "babelfish.converters", "babelfish.converters.alpha2", "babelfish.converters.alpha3b", "babelfish.converters.alpha3t", "babelfish.converters.name", "babelfish.converters.opensubtitles", "cleanit"])

a = Analysis(['fastflix/__main__.py'],
binaries=[],
datas=[('CHANGES', 'fastflix/.'), ('docs/build-licenses.txt', 'docs')] + all_fastflix_files,
datas=[('CHANGES', 'fastflix/.'), ('docs/build-licenses.txt', 'docs')] + all_fastflix_files + copy_metadata('pgsrip') + copy_metadata('pytesseract') + copy_metadata('babelfish') + copy_metadata('cleanit') + copy_metadata('trakit') + collect_data_files('babelfish') + collect_data_files('cleanit'),
hiddenimports=all_imports,
hookspath=[],
runtime_hooks=[],
Expand Down
7 changes: 5 additions & 2 deletions FastFlix_Windows_Installer.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_submodules
from PyInstaller.utils.hooks import collect_submodules, copy_metadata, collect_data_files
import toml

block_cipher = None
Expand All @@ -24,9 +24,12 @@ all_imports.remove("python-box")
all_imports.append("box")
all_imports.append("iso639")

# Add pgsrip for OCR support
all_imports.extend(["pgsrip", "pytesseract", "cv2", "numpy", "pysrt", "babelfish", "babelfish.converters", "babelfish.converters.alpha2", "babelfish.converters.alpha3b", "babelfish.converters.alpha3t", "babelfish.converters.name", "babelfish.converters.opensubtitles", "cleanit"])

a = Analysis(['fastflix\\__main__.py'],
binaries=[],
datas=[('CHANGES', 'fastflix\\.'), ('docs\\build-licenses.txt', 'docs')] + all_fastflix_files,
datas=[('CHANGES', 'fastflix\\.'), ('docs\\build-licenses.txt', 'docs')] + all_fastflix_files + copy_metadata('pgsrip') + copy_metadata('pytesseract') + copy_metadata('babelfish') + copy_metadata('cleanit') + copy_metadata('trakit') + collect_data_files('babelfish') + collect_data_files('cleanit'),
hiddenimports=all_imports,
hookspath=[],
runtime_hooks=[],
Expand Down
7 changes: 5 additions & 2 deletions FastFlix_Windows_OneFile.spec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import toml

from PyInstaller.utils.hooks import collect_submodules
from PyInstaller.utils.hooks import collect_submodules, copy_metadata, collect_data_files

block_cipher = None

Expand All @@ -27,13 +27,16 @@ all_imports.remove("python-box")
all_imports.append("box")
all_imports.append("iso639")

# Add pgsrip for OCR support
all_imports.extend(["pgsrip", "pytesseract", "cv2", "numpy", "pysrt", "babelfish", "babelfish.converters", "babelfish.converters.alpha2", "babelfish.converters.alpha3b", "babelfish.converters.alpha3t", "babelfish.converters.name", "babelfish.converters.opensubtitles", "cleanit"])

portable_file = "fastflix\\portable.py"
with open(portable_file, "w") as portable:
portable.write(" ")

a = Analysis(['fastflix\\__main__.py'],
binaries=[],
datas=[('CHANGES', 'fastflix\\.'), ('docs\\build-licenses.txt', 'docs')] + all_fastflix_files,
datas=[('CHANGES', 'fastflix\\.'), ('docs\\build-licenses.txt', 'docs')] + all_fastflix_files + copy_metadata('pgsrip') + copy_metadata('pytesseract') + copy_metadata('babelfish') + copy_metadata('cleanit') + copy_metadata('trakit') + collect_data_files('babelfish') + collect_data_files('cleanit'),
hiddenimports=all_imports,
hookspath=[],
runtime_hooks=[],
Expand Down
Loading