Skip to content

Commit 9f035e8

Browse files
authored
Version 6.2.0 (#733)
* Adding AV1 (NVENC) encoder for FFmpeg-based AV1 hardware encoding on NVIDIA GPUs (RTX 4000+) with quality-focused defaults including spatial/temporal AQ, lookahead, and multipass support * Adding #724 "exit" option to the After Conversion dropdown, which closes FastFlix after all queue items complete (thanks to jrff123) * Adding #731 OpenCL Support setting (Auto/Disable) with re-detection button in Application Locations settings (thanks to sks2012) * Adding favicon to root of repo so it shows up on fastflix.org (thanks to Balthazar) * Adding encoding history feature with browsable history window, "Apply Last Used Settings" menu action, and startup opt-in prompt * Adding FFmpeg 8.0+ version check on startup with option to download latest FFmpeg on Windows * Adding "Keep source format" option to Audio Normalize, which detects and uses the same audio codec and bitrate as the source video * Adding Audio Encoders tab in Settings to view and select which FFmpeg audio encoders appear in audio codec dropdowns * Adding Data tab to profile settings with passthrough all or remove all options for data and attachment streams * Adding clear current video X button next to source path and "Clear Current Video" option in File menu * Adding rotation and flip buttons to the visual crop window, allowing users to change rotation (0/90/180/270) and toggle horizontal/vertical flip without leaving the crop view * Changing visual crop window to show the video frame with rotation and flip applied, matching the final output so crop edges can be set intuitively in the rotated view * Changing Copy encoder to use modern FFmpeg display_rotation, display_hflip, and display_vflip for lossless rotation and flip metadata instead of deprecated rotate metadata tag, with support for MP4, MOV, MKV, and M4V containers * Changing non-copy encoder rotation handling to use FFmpeg's built-in auto-rotation instead of manual display_rotation overrides, which also properly handles source flips from the display matrix * Changing -fps_mode to be used instead of deprecated -vsync for frame rate control * Fixing #725 encoder detection to check `ffmpeg -encoders` output in addition to compilation flags, so encoders like VAAPI are shown even when the build flag is absent (thanks to Davius and Generator) * Fixing #728 rigaya encoders (NVEncC, QSVEncC, VCEEncC) now pass --dolby-vision-rpu-prm crop=true when Dolby Vision RPU copy is enabled and a crop is applied (thanks to izzy697) * Fixing #730 6.1.1 arm no subtitles tab with VideoToolBox (Apple M1 and above) HEVC & H264 (thanks to enaveso) * Fixing page_update() busy-wait that could deadlock the GUI thread when called reentrantly * Fixing shutdown-while-encoding bug where the worker would lose the shutdown intent after the current encode finished, requiring a forceful GUI kill instead of graceful shutdown * Fixing visual crop window showing incorrect bounds and dimensions when user rotation is applied, by showing the frame in pre-rotation space where crop actually operates * Fixing video crop and dimension detection for rotated videos where display matrix rotation was not found when other side data (e.g., HDR mastering display) preceded it * Fixing cover extraction to not be during video load and blocking, but a background task * Fixing 6.1.1 > 6.2.0 mixed queue saving bug (thanks to Norbert) * Removing -strict experimental from SVT-AV1 encoders (no longer needed with FFmpeg 8+)
1 parent 10a6955 commit 9f035e8

64 files changed

Lines changed: 4385 additions & 1009 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"WebSearch",
5+
"WebFetch(domain:doc.qt.io)",
6+
"WebFetch(domain:forum.qt.io)",
7+
"WebFetch(domain:github.com)",
8+
"Bash(uv run ruff check:*)",
9+
"Bash(uv run:*)",
10+
"Bash(python:*)",
11+
"Bash(git log:*)",
12+
"Bash(git show:*)",
13+
"Bash(git status:*)",
14+
"Bash(git diff:*)",
15+
"Bash(git blame:*)",
16+
"Bash(git ls-files:*)",
17+
"Bash(git ls-tree:*)",
18+
"Bash(git rev-parse:*)",
19+
"Bash(git reflog:*)",
20+
"Bash(git describe:*)",
21+
"Bash(git shortlog:*)",
22+
"Bash(git stash list:*)",
23+
"Bash(git cat-file:*)",
24+
"Bash(git remote -v:*)",
25+
"Bash(git config --get:*)",
26+
"Bash(git config --list:*)",
27+
"Bash(gh pr list:*)",
28+
"Bash(gh pr view:*)",
29+
"Bash(gh pr status:*)",
30+
"Bash(gh pr checks:*)",
31+
"Bash(gh pr diff:*)",
32+
"Bash(gh issue list:*)",
33+
"Bash(gh issue view:*)",
34+
"Bash(gh issue status:*)",
35+
"Bash(gh repo view:*)",
36+
"Bash(gh repo list:*)",
37+
"Bash(gh release list:*)",
38+
"Bash(gh release view:*)",
39+
"Bash(gh run list:*)",
40+
"Bash(gh run view:*)",
41+
"Bash(gh workflow list:*)",
42+
"Bash(gh workflow view:*)",
43+
"Bash(gh search:*)",
44+
"Bash(gh status:*)",
45+
"Bash(gh auth status:*)",
46+
"Bash(gh label list:*)",
47+
"Bash(gh gist list:*)",
48+
"Bash(gh gist view:*)",
49+
"Bash(gh cache list:*)",
50+
"Bash(gh ruleset list:*)",
51+
"Bash(gh ruleset view:*)",
52+
"Bash(gh variable list:*)",
53+
"Bash(gh secret list:*)",
54+
"Bash(gh project list:*)",
55+
"Bash(gh project view:*)",
56+
"Bash(dir:*)",
57+
"Bash(find:*)",
58+
"Bash(findstr:*)",
59+
"Bash(type:*)",
60+
"Bash(where:*)",
61+
"Bash(tree:*)",
62+
"Bash(whoami:*)",
63+
"Bash(hostname:*)",
64+
"Bash(ver:*)",
65+
"Bash(fc:*)",
66+
"Bash(echo:*)",
67+
"Bash(Get-Content:*)",
68+
"Bash(Get-ChildItem:*)",
69+
"Bash(Get-Item:*)",
70+
"Bash(Get-ItemProperty:*)",
71+
"Bash(Get-Location:*)",
72+
"Bash(Get-Process:*)",
73+
"Bash(Get-Command:*)",
74+
"Bash(Get-Help:*)",
75+
"Bash(Get-Date:*)",
76+
"Bash(Get-Host:*)",
77+
"Bash(Get-Module:*)",
78+
"Bash(Get-Variable:*)",
79+
"Bash(Get-Alias:*)",
80+
"Bash(Get-Service:*)",
81+
"Bash(Select-String:*)",
82+
"Bash(Select-Object:*)",
83+
"Bash(Where-Object:*)",
84+
"Bash(Sort-Object:*)",
85+
"Bash(Format-Table:*)",
86+
"Bash(Format-List:*)",
87+
"Bash(Measure-Object:*)",
88+
"Bash(Compare-Object:*)",
89+
"Bash(Test-Path:*)",
90+
"Bash(Resolve-Path:*)",
91+
"Bash(Split-Path:*)",
92+
"Bash(Join-Path:*)",
93+
"Bash(ls:*)",
94+
"Bash(xargs:*)",
95+
"WebFetch(domain:ffmpeg.org)",
96+
"Bash(ffmpeg -h:*)",
97+
"Bash(ffmpeg:*)",
98+
"WebFetch(domain:gitlab.com)",
99+
"WebFetch(domain:wiki.x266.mov)",
100+
"WebFetch(domain:raw.githubusercontent.com)",
101+
"Bash(grep:*)",
102+
"WebFetch(domain:docs.rs)",
103+
"WebFetch(domain:gist.github.com)",
104+
"WebFetch(domain:manpages.debian.org)",
105+
"WebFetch(domain:api.github.com)",
106+
"WebFetch(domain:codecalamity.com)",
107+
"WebFetch(domain:www.mail-archive.com)",
108+
"WebFetch(domain:trac.ffmpeg.org)",
109+
"WebFetch(domain:dev.to)",
110+
"WebFetch(domain:www.ffmpeg.org)",
111+
"WebFetch(domain:www.phoronix.com)",
112+
"Bash(copy:*)",
113+
"Bash(ffprobe:*)",
114+
"Bash(gh api:*)",
115+
"Bash(git:*)",
116+
"WebFetch(domain:docs.nvidia.com)"
117+
]
118+
}
119+
}

.claude/skills/changelog.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
name: changelog
3+
description: Update the CHANGES changelog file with new entries. MUST be consulted whenever adding, modifying, or removing entries in the CHANGES file, including when referencing GitHub issues.
4+
user_invocable: true
5+
trigger: Always read this skill BEFORE writing any changelog entry. Triggered by any task that involves updating the CHANGES file, adding a fix/feature note, or referencing a GitHub issue in the changelog.
6+
---
7+
8+
# Changelog Skill
9+
10+
When updating the `CHANGES` file, follow these rules:
11+
12+
## Entry Format
13+
14+
Each entry is a single bullet point starting with `* `:
15+
16+
```
17+
* {Verb} {description}
18+
```
19+
20+
## Verbs and Ordering
21+
22+
Entries MUST use one of these four starting verbs, and MUST appear in this order within each version section:
23+
24+
1. **Adding** — new features
25+
2. **Changing** — modifications to existing behavior
26+
3. **Fixing** — bug fixes
27+
4. **Removing** — removed features or deprecated items
28+
29+
## GitHub Issue Entries
30+
31+
- Entries that reference a GitHub issue include the issue number after the verb: `* Fixing #725 description...`
32+
- Within each verb group, entries WITH issue numbers come FIRST, sorted by issue number ascending (smallest to largest)
33+
- Entries WITHOUT issue numbers follow after
34+
35+
## Thanks Attribution
36+
37+
- When an entry references a GitHub issue, thank the issue author by their **GitHub display name** (not username)
38+
- Look up the display name via `gh api users/{username} --jq '.name // .login'`
39+
- Format: `(thanks to {display name})`
40+
- If multiple people contributed (e.g., reporter and commenter with the fix), thank all of them
41+
- The thanks attribution goes at the end of the entry
42+
43+
## Example
44+
45+
```
46+
## Version 6.2.0
47+
48+
* Adding #731 OpenCL Support setting (thanks to sks2012)
49+
* Adding FFmpeg 8.0+ version check on startup
50+
* Adding "Keep source format" option to Audio Normalize
51+
* Changing visual crop window to show rotated frame
52+
* Changing -fps_mode to be used instead of deprecated -vsync
53+
* Fixing #725 encoder detection to use ffmpeg -encoders (thanks to Davius and Generator)
54+
* Fixing #730 subtitles tab missing on ARM (thanks to enaveso)
55+
* Fixing cover extraction blocking video load
56+
* Removing -strict experimental from SVT-AV1 encoders
57+
```

CHANGES

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
# Changelog
22

3+
## Version 6.2.0
4+
5+
* Adding AV1 (NVENC) encoder for FFmpeg-based AV1 hardware encoding on NVIDIA GPUs (RTX 4000+) with quality-focused defaults including spatial/temporal AQ, lookahead, and multipass support
6+
* Adding #724 "exit" option to the After Conversion dropdown, which closes FastFlix after all queue items complete (thanks to jrff123)
7+
* Adding #731 OpenCL Support setting (Auto/Disable) with re-detection button in Application Locations settings (thanks to sks2012)
8+
* Adding favicon to root of repo so it shows up on fastflix.org (thanks to Balthazar)
9+
* Adding encoding history feature with browsable history window, "Apply Last Used Settings" menu action, and startup opt-in prompt
10+
* Adding FFmpeg 8.0+ version check on startup with option to download latest FFmpeg on Windows
11+
* Adding "Keep source format" option to Audio Normalize, which detects and uses the same audio codec and bitrate as the source video
12+
* Adding Audio Encoders tab in Settings to view and select which FFmpeg audio encoders appear in audio codec dropdowns
13+
* Adding Data tab to profile settings with passthrough all or remove all options for data and attachment streams
14+
* Adding clear current video X button next to source path and "Clear Current Video" option in File menu
15+
* Adding rotation and flip buttons to the visual crop window, allowing users to change rotation (0/90/180/270) and toggle horizontal/vertical flip without leaving the crop view
16+
* Changing visual crop window to show the video frame with rotation and flip applied, matching the final output so crop edges can be set intuitively in the rotated view
17+
* Changing Copy encoder to use modern FFmpeg display_rotation, display_hflip, and display_vflip for lossless rotation and flip metadata instead of deprecated rotate metadata tag, with support for MP4, MOV, MKV, and M4V containers
18+
* Changing non-copy encoder rotation handling to use FFmpeg's built-in auto-rotation instead of manual display_rotation overrides, which also properly handles source flips from the display matrix
19+
* Changing -fps_mode to be used instead of deprecated -vsync for frame rate control
20+
* Fixing #725 encoder detection to check `ffmpeg -encoders` output in addition to compilation flags, so encoders like VAAPI are shown even when the build flag is absent (thanks to Davius and Generator)
21+
* Fixing #728 rigaya encoders (NVEncC, QSVEncC, VCEEncC) now pass --dolby-vision-rpu-prm crop=true when Dolby Vision RPU copy is enabled and a crop is applied (thanks to izzy697)
22+
* Fixing #730 6.1.1 arm no subtitles tab with VideoToolBox (Apple M1 and above) HEVC & H264 (thanks to enaveso)
23+
* Fixing page_update() busy-wait that could deadlock the GUI thread when called reentrantly
24+
* Fixing shutdown-while-encoding bug where the worker would lose the shutdown intent after the current encode finished, requiring a forceful GUI kill instead of graceful shutdown
25+
* Fixing visual crop window showing incorrect bounds and dimensions when user rotation is applied, by showing the frame in pre-rotation space where crop actually operates
26+
* Fixing video crop and dimension detection for rotated videos where display matrix rotation was not found when other side data (e.g., HDR mastering display) preceded it
27+
* Fixing cover extraction to not be during video load and blocking, but a background task
28+
* Fixing 6.1.1 > 6.2.0 mixed queue saving bug (thanks to Norbert)
29+
* Removing -strict experimental from SVT-AV1 encoders (no longer needed with FFmpeg 8+)
30+
331
## Version 6.1.1
432

533
* Adding "Show completion popup message" (default off) and "Show error popup message" (default on) settings, replacing the old "Disable completion and error messages" toggle (thanks to Balthazar)

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
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).
88

9-
**Requirements:** Python 3.13+, FFmpeg 4.3+ (5.0+ recommended)
9+
**Requirements:** Python 3.13+, FFmpeg 8.0+
1010

1111
## Build & Development Commands
1212

fastflix/application.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
import reusables
88
from PySide6 import QtGui, QtWidgets, QtCore
99

10-
from fastflix.flix import ffmpeg_audio_encoders, ffmpeg_configuration, ffprobe_configuration, ffmpeg_opencl_support
10+
from fastflix.flix import (
11+
ffmpeg_audio_encoders,
12+
ffmpeg_video_encoders,
13+
ffmpeg_configuration,
14+
ffprobe_configuration,
15+
ffmpeg_opencl_support,
16+
)
1117
from fastflix.language import t
1218
from fastflix.models.config import Config, MissingFF
1319
from fastflix.models.fastflix import FastFlix
@@ -86,6 +92,7 @@ def init_encoders(app: FastFlixApp, **_):
8692
from fastflix.encoders.gif import main as gif_plugin
8793
from fastflix.encoders.gifski import main as gifski_plugin
8894
from fastflix.encoders.ffmpeg_hevc_nvenc import main as nvenc_plugin
95+
from fastflix.encoders.ffmpeg_av1_nvenc import main as ffmpeg_av1_nvenc_plugin
8996
from fastflix.encoders.hevc_x265 import main as hevc_plugin
9097
from fastflix.encoders.rav1e import main as rav1e_plugin
9198
from fastflix.encoders.svt_av1 import main as svt_av1_plugin
@@ -115,6 +122,7 @@ def init_encoders(app: FastFlixApp, **_):
115122
nvenc_plugin,
116123
hevc_videotoolbox_plugin,
117124
h264_videotoolbox_plugin,
125+
ffmpeg_av1_nvenc_plugin,
118126
av1_plugin,
119127
rav1e_plugin,
120128
svt_av1_plugin,
@@ -171,10 +179,23 @@ def init_encoders(app: FastFlixApp, **_):
171179
# if "H.264/AVC" in app.fastflix.config.vceencc_encoders:
172180
encoders.insert(encoders.index(avc_plugin), vceencc_avc_plugin)
173181

182+
# Mapping from requires values to search terms for ffmpeg -encoders output.
183+
# Most requires values (e.g. "vaapi", "libx264") appear directly in encoder names,
184+
# but some compilation flags don't match encoder names and need explicit mapping.
185+
requires_to_encoder = {
186+
"cuda-llvm": "nvenc",
187+
}
188+
189+
def _encoder_available(requires: str) -> bool:
190+
if requires in app.fastflix.ffmpeg_config:
191+
return True
192+
search_term = requires_to_encoder.get(requires, requires)
193+
return any(search_term in enc for enc in (app.fastflix.video_encoders or []))
194+
174195
app.fastflix.encoders = {
175196
encoder.name: encoder
176197
for encoder in encoders
177-
if (not getattr(encoder, "requires", None)) or encoder.requires in app.fastflix.ffmpeg_config or DEVMODE
198+
if (not getattr(encoder, "requires", None)) or _encoder_available(encoder.requires) or DEVMODE
178199
}
179200

180201

@@ -183,6 +204,58 @@ def init_fastflix_directories(app: FastFlixApp):
183204
app.fastflix.log_path.mkdir(parents=True, exist_ok=True)
184205

185206

207+
def _handle_ffmpeg_version_warning_windows(app: FastFlixApp, container: Container):
208+
msg = QtWidgets.QMessageBox(container)
209+
msg.setIcon(QtWidgets.QMessageBox.Warning)
210+
msg.setWindowTitle(t("FFmpeg Version Warning"))
211+
msg.setText(
212+
t(
213+
"Your FFmpeg (libavcodec {version}) is older than the required FFmpeg 8.0+ (libavcodec 62+)."
214+
" Some features may not work correctly."
215+
).format(version=app.fastflix.libavcodec_version)
216+
)
217+
cb = QtWidgets.QCheckBox(t("Don't show this warning again"))
218+
msg.setCheckBox(cb)
219+
download_btn = msg.addButton(t("Download Latest"), QtWidgets.QMessageBox.AcceptRole)
220+
msg.addButton(t("Ignore"), QtWidgets.QMessageBox.RejectRole)
221+
msg.exec()
222+
223+
if msg.clickedButton() == download_btn:
224+
try:
225+
container.status_bar.run_tasks(
226+
[Task(t("Downloading FFmpeg"), grab_stable_ffmpeg)],
227+
signal_task=True,
228+
can_cancel=True,
229+
)
230+
ffmpeg_configuration(app)
231+
except Exception:
232+
logger.exception("Failed to download FFmpeg")
233+
234+
if cb.isChecked():
235+
app.fastflix.config.suppress_ffmpeg_version_warning = True
236+
app.fastflix.config.save()
237+
238+
239+
def _handle_ffmpeg_version_warning_other(app: FastFlixApp):
240+
msg = QtWidgets.QMessageBox()
241+
msg.setIcon(QtWidgets.QMessageBox.Warning)
242+
msg.setWindowTitle(t("FFmpeg Version Warning"))
243+
msg.setText(
244+
t(
245+
"Your FFmpeg (libavcodec {version}) is older than the required FFmpeg 8.0+ (libavcodec 62+)."
246+
" Please update FFmpeg. Visit https://ffmpeg.org/download.html"
247+
).format(version=app.fastflix.libavcodec_version)
248+
)
249+
cb = QtWidgets.QCheckBox(t("Don't show this warning again"))
250+
msg.setCheckBox(cb)
251+
msg.addButton(t("OK"), QtWidgets.QMessageBox.AcceptRole)
252+
msg.exec()
253+
254+
if cb.isChecked():
255+
app.fastflix.config.suppress_ffmpeg_version_warning = True
256+
app.fastflix.config.save()
257+
258+
186259
def app_setup(
187260
enable_scaling: bool = True,
188261
portable_mode: bool = False,
@@ -347,13 +420,30 @@ def app_setup(
347420
except Exception:
348421
logger.exception("Failed to download HDR10+ tool")
349422

423+
if app.fastflix.config.enable_history is None:
424+
history_choice = yes_no_message(
425+
t("Would you like to enable encoding history?")
426+
+ "\n\n"
427+
+ t(
428+
"This keeps a local record of your completed encodings, letting you review the settings used for any past video and quickly re-apply them to new ones."
429+
)
430+
+ "\n\n"
431+
+ t("All data is stored locally on your computer. Nothing is sent to the internet."),
432+
title=t("Enable Encoding History"),
433+
)
434+
if history_choice is not None:
435+
app.fastflix.config.enable_history = history_choice
436+
if history_choice:
437+
container.rebuild_menu()
438+
350439
app.fastflix.config.save()
351440

352441
# Run startup tasks (FFmpeg config, encoder init) through status bar
353442
startup_tasks = [
354443
Task(t("Gather FFmpeg version"), ffmpeg_configuration),
355444
Task(t("Gather FFprobe version"), ffprobe_configuration),
356445
Task(t("Gather FFmpeg audio encoders"), ffmpeg_audio_encoders),
446+
Task(t("Gather FFmpeg video encoders"), ffmpeg_video_encoders),
357447
Task(t("Determine OpenCL Support"), ffmpeg_opencl_support),
358448
Task(t("Initialize Encoders"), init_encoders),
359449
]
@@ -366,6 +456,13 @@ def app_setup(
366456
container.setEnabled(True)
367457
return app
368458

459+
# Check FFmpeg version (libavcodec 62 = FFmpeg 8.x)
460+
if not app.fastflix.config.suppress_ffmpeg_version_warning and 0 < app.fastflix.libavcodec_version < 62:
461+
if reusables.win_based:
462+
_handle_ffmpeg_version_warning_windows(app, container)
463+
else:
464+
_handle_ffmpeg_version_warning_other(app)
465+
369466
# Encoders are now populated — initialize the encoder UI
370467
container.main.init_encoders_ui()
371468

fastflix/command_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ def change_priority(
133133
def _safe_log_put(self, msg):
134134
"""Put message to log queue with timeout to prevent blocking if GUI is dead."""
135135
try:
136-
self.log_queue.put(msg, timeout=1.0)
136+
self.log_queue.put(msg, timeout=0.1)
137137
except Full:
138-
pass # GUI likely dead, ignore
138+
pass # GUI likely dead or log queue full, skip
139139

140140
def read_output(self):
141141
with (

0 commit comments

Comments
 (0)