OpenTranscode/CHANGELOG.md

674 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog
All notable changes to OpenTranscode. Versions follow semantic versioning.
## [4.11.0] — 2026-09-27
### Changed — the application is one file
- **``open-transcode.py`` is the program — the ``opentranscode/``
package directory is removed.** One copy, no synchronization
obligation, no drift possible. The file is fully self-contained
(every module's logic inlined in dependency order, external imports
hoisted) and carries the project metadata (version, author,
contact, license) itself. It keeps the original direct-GUI entry:
``python open-transcode.py`` opens the window and ignores CLI
arguments, exactly as the launcher always did. The inlined
``build_parser`` / ``main`` / ``launch_gui`` remain for library
access (and the test suite exercises them).
- **Test suite loads the single module**: conftest exposes
``load_program_module()`` (registered once per session in
``sys.modules`` — one module instance shared by fixtures and direct
callers). All tests that previously imported ``opentranscode.*``
now resolve symbols from the single module; string-form
monkeypatch targets follow it. ``test_package_structure`` pins the
arrangement: the package directory must NOT reappear, and a stray
``import opentranscode`` inside the program file fails the build.
- **pyproject.toml is metadata + tool config only** — no build
system, no console script, nothing to pip-install. pytest
configuration stays in pytest.ini.
- The launch-crash fix (CLI overrides via the pending store flushed
after the async env probe) is part of the single file; README and
architecture docs describe the one-file layout.
## [4.10.0] — 2026-09-26
### Fixed — GUI launch crash (AttributeError on every launch via main())
- ``launch_gui()`` wrote CLI overrides onto ``window.env.av1an_flags``
immediately — but the environment probe runs ~500ms after the event
loop starts (async by design, so the GPU live-encode test never
freezes the UI), so ``env`` was still ``None`` and every GUI launch
through ``main()`` died with ``AttributeError: 'NoneType' object
has no attribute 'av1an_flags'``. Both entry paths were affected
(``python -m opentranscode``; the standalone launcher once it routed
through ``main()`` for CLI-flag support). The original 4.8.1
standalone never hit it only because its entry point bypassed
``launch_gui`` entirely.
- Fix: overrides now land in the window's pending store
(``_set_cli_flag`` / ``_clear_cli_flag``) and ``_probe_and_init``
flushes them into the live ``av1an_flags`` the moment the probe
finishes — including a delete sentinel so ``--chunk-method auto``
still clears probe-set overrides. Falsy values (``False``, ``0``,
``""``) survive the flush. The auto-engine status line no longer
claims a GPU verdict before the probe has run. Verified by driving
the real ``launch_gui`` offscreen on both entry paths and asserting
the flags landed; hermetic semantics pinned in
``tests/test_launch_env_pending.py`` and covered by launcher parity.
### Added — title-artifact scrubbing for output filenames
- **New `title_clean.py` module.** Output names drop the source
release's stale claims before the app's own markers are appended:
`Movie.x264.1080p.WEBRip.x265-GRP.mkv` archives as
`Movie.1080p.WEBRip.GRP_archived.mkv`. A title that still says
x264/h.265/webm on an AV1/Opus/MKV (or Theora/OGV) output
misdescribes the archive.
- Scrub classes: codec tags (video, audio, container — matched on
token boundaries so `MP4Box`/`Aviator` pass through), this app's
own earlier suffixes (`_archived`, `_<w>x<h>` — re-encodes never
stack markers), and separator residue (runs collapse to their
first separator, emptied brackets drop, edges trim). A stem that
scrubs to nothing keeps its original name. Title rewrites log under
`--verbose`.
### QA pass — production readiness (multi-discipline review)
- **Standalone launcher synchronized + parity-guarded.**
``open-transcode.py`` remains the self-contained single-file
distribution (runs with only Python + PySide6 — verified by
executing it in an isolated directory with the package absent). It
is rebuilt from the current package sources, so it now carries every
4.8.2–4.10.0 change the old copy had drifted past (CRF defaults,
Theora/OGV, title scrubbing, the filter row, the QA refactors below).
New ``tests/test_launcher_parity.py`` compares the two artifacts —
profile tables, encoder-arg builders, the Theora quality map, title
scrubbing, and the UI compat rules — and fails the build if either
copy changes without the other.
- **Behavioral drift between the two copies resolved** (the old
launcher and package had diverged; the package now carries the
user-facing behavior and the launcher mirrors it):
- Force-mode validation overrides now log a user-facing WARN per
file (were verbose-only — a silent skip of the validation gate).
- The PRE-FLIGHT VALIDATION table prints whenever any file is
invalid (was verbose-only; clean runs stay quiet).
- av1an failure DIAGNOSIS blocks are user-facing again (they explain
a failed encode attempt), and the av1an→ffmpeg fallback retry
logs one user-facing line — quiet mode previously reported only
the eventual OK, hiding per-file av1an failures entirely.
- The y4m-guard assertion in the retry tests matches the
`combined_out` (stderr+stdout) check both copies now use.
- **Deduplicated the post-probe status bar** (SEI CERT MSC04-C): the
probe path and the post-rebuild path now compose their line through
one `_status_summary()` helper (previously two hand-maintained
copies), with the fallback-encoder scan as a comprehension.
- **Unified availability gating** (`_disable_unavailable_codecs`):
audio and ffmpeg-only video entries now come from one row table
assembled from the profile tables, applied by a single pass — no
per-combo branching (profiles remain the single source of truth).
- **`_parse_extensions`** drops its accumulation loop for a
comprehension; empty parts (trailing/doubled commas) now filter out
before dot-prefixing and can never contribute a bare `.`.
- **Comment voice**: restore-flavored wording ("restores", "brought
back") is gone from code and docs; comments state decisions, not
history. Runtime degradation wording ("falls back to") is retained
deliberately — it describes the graceful step-down paths.
- **Metadata**: author contact standardized to Jeremy Anderson /
dcos.net / info@dcos.net across `__init__`, pyproject, the UI footer
and the README; pyproject version un-stuck from 4.8.1 to match the
release.
- **Docs**: README re-derives from the source — codec/container lists,
version example, libtheora requirement, title-scrub section,
architecture tree, test counts, coding-standards additions
(Unix-philosophy step-down, comment voice), project contact. The
source code is the source of truth; docs describe it.
## [4.9.0] — 2026-09-26
### Added — OGV output target (Theora) with the inverted quality-scale wrapper
- **New "Theora (OGV)" video codec + "OGV (Ogg)" container.** The legacy
open-source pairing (Ogg Theora + Vorbis/Opus/FLAC) is now a first-class
output target for compatibility with old players/portals.
- **Quality-knob wrapper (`theora_quality_from_crf`)** — libtheora grades
quality with `-q:v 0..31` where HIGHER is better, the exact opposite of
the CRF knob every other codec family uses. Theora keeps the shared
knob and its 18-52 scale; the encoder args translate through a linear
inversion (knob 18 → q 31 best, 52 → q 0, default 26 → q 24). The
mapped value is echoed in the log when the knob moves so the number
libtheora actually receives is never a mystery. Scale and saturation
verified empirically against the project ffmpeg build.
- **PRESET combo maps onto libtheora's real `-speed_level 0..2`**
(Best/Medium/Fast; out-of-range values clamp instead of erroring).
- **ffmpeg-only by construction**: av1an has no Theora encoder, so the
chunk-parallel path is force-disabled for the codec (log explains
why), and the `params_fn` guard raises a clear RuntimeError if a
regression ever reaches it. No GPU family — every engine selection
resolves to CPU for Theora.
- **Availability gating**: libtheora is probed with the other encoder
libraries; a missing library logs a probe warning and greys out the
codec entry in the UI (it has no av1an fallback, so it would only
fail at encode time).
- **Container-compatibility rules** (all verified with live ffmpeg 7.x
mux attempts): Theora + MP4/WebM → INCOMPATIBLE (the muxers reject
it); Theora + MKV → WARNING (valid but OGV is canonical — a real
encode was confirmed to work); any other video codec + OGV →
INCOMPATIBLE (the Ogg muxer only takes Theora video). Opus, Vorbis
and FLAC all mux cleanly into OGG; IAMF stays INCOMPATIBLE via the
existing non-MP4 rule.
- Skip-existing detection works for the target (`ffprobe` reports
codec_name `theora`).
## [4.8.2] — 2026-09-26
### Changed — FILTER gets its own line in the UI
- **The extension filter row was split off the options row.** The
FILTER field holds a long comma-separated extension list; sharing a
single row with four checkboxes and the GPU/ENGINE combos squeezed it
into a sliver. It now sits on its own line (with the delete-source
toggle), and Force / av1an / inline-scale / GPU / ENGINE moved down
to a second options row.
### Changed — archival-quality default CRF values
- **AV1 (SVT-AV1) 32 → 26, VP9 32 → 28, x265 28 → 24.** The old
defaults produced xvid-tier output (blocky shadows, smeared grain)
on every engine path, since CPU (`-crf`), NVENC (`-cq`), QSV
(`-global_quality`) and VAAPI all take their quality number from the
same CRF knob. The knob's start position follows the AV1 profile
default (26). Expect roughly +50-60% bitrate versus 4.8.1 defaults;
turn the knob up if you want the smaller files back.
### Added — legacy source extensions in the default filter
- **.vob (DVD rips), .xvid (AVI/ASP rips) and .ogv (Ogg/Theora
archives) are now in `DEFAULT_INPUT_EXTENSIONS`** — legacy formats
this tool exists to re-encode were silently skipped before. There is
still no .ogv OUTPUT target: the Ogg muxer only takes Theora video,
and there is no Theora encoder profile (VP9/AV1/HEVC cannot be
muxed into Ogg), so old .ogv files re-encode to MKV/WebM instead.
## [4.8.1] — 2026-09-25
### Changed — GPU dropdown / ENGINE interaction
- **ENGINE = CPU now disables the GPU dropdown** (greyed out with an
explanatory tooltip) — the choice would have no effect there.
- **"None (CPU-only encode)" remains an explicit option** in the GPU
dropdown for the opposite case: a GPU present in the box that the
user doesn't want encoding on (e.g. it's the display card). The two
controls can no longer contradict each other: GPU engines + "None"
resolve to the CPU path by design, and CPU engine ignores the GPU
choice entirely.
## [4.8.0] — 2026-09-25
### Added — GPU capability profiles (combined generations, incl. oddballs)
- **New `gpu_profiles.py` + GPU dropdown** (UI, next to ENGINE; CLI
`--gpu-profile`). Entries combine whole card generations into single
capability classes — same silicon, same encoding behaviour:
- NVIDIA Kepler/Maxwell (H.264 only), Pascal (GTX 10-series + Tesla
P40/P4/P100 — H.264+HEVC 8/10-bit), Turing (RTX 20 / GTX 16 /
Tesla T4 / **CMP 30/40/50HX** — +B-frames), Ampere (RTX 30 /
A10/A40 / **CMP 90HX** — no AV1 encode), Ada/Blackwell (RTX 40/50,
L4/L40 — +AV1 10-bit).
- Oddballs: **NVIDIA data-center compute (V100/A100/H100, CMP
170HX) has NO NVENC silicon** — the profile routes to CPU instead
of failing. CMP 170HX is GA100-based: the fastest mining card that
cannot hardware-encode.
- Intel Arc (QSV: H.264+HEVC+AV1) and Iris/UHD; AMD RDNA 3 (VAAPI:
+AV1 encode), RDNA 1/2, and the crypto-era GCN 4/5 + Vega cards.
- **Auto-detection**: the probe matches the detected GPU name
(nvidia-smi / lspci, vendor-aware) to a profile; the live encode
smoke test decides what actually works. VAAPI encodes via
`-vaapi_device` + `hwupload`, QSV via `-init_hw_device qsv=hw`.
- **Profile-driven encoding**: `resolve_gpu_encoder()` now returns
(encoder, api) and the ffmpeg command is built per API (device init
args, hwupload filter chains, per-API quality args — nvenc
`-rc vbr -cq`, QSV `-global_quality`, VAAPI `-rc_mode CQP`).
- **Rebuild-from-git builds for the selected GPU profile**: the dep
tree extends with the vendor's packages (arch: nv-codec-headers /
libva+libdrm+mesa / intel-media-driver+onevpl; debian/redhat/suse
equivalents), so pressing REBUILD on a bare system generates the GPU
dependency tree too.
- Forced profiles let a user pin a capability class even when name
auto-match fails; "Auto-detect" stays the default.
## [4.7.1] — 2026-09-25
### Added — always-usable REBUILD FROM GIT (self-generating dep tree)
- **The REBUILD FROM GIT button is now ALWAYS enabled.** It no longer
depends on a successful environment probe — a failed probe (missing
binaries, broken VSScript, missing av1an) is exactly when the rebuild
is needed, so the button works from first launch on a bare system.
- **The build generates its own dependency tree.** Instead of the old
pacman-only toolchain list, `build_dep_plan()` maps the detected distro
family to the right package set and installer: arch (pacman), debian
(apt-get), redhat (dnf), suse (zypper) — including the previously
missing **zimg** (VapourSynth's one hard library dependency, which
made the meson step fail on bare systems), meson/ninja/cmake/nasm and
rust for av1an. Unsupported families get an explicit manual-install
note. Critical tools are re-verified after install; the build aborts
with the exact list if anything is still missing.
- **BestSource plugin now builds from git as part of the VapourSynth
rebuild** (cloned with its libp2p submodule, compiled against the
freshly installed git-VS headers via PYTHONPATH/PKG_CONFIG_PATH, into
the user site-packages plugins dir). This closes the av1an chunking
gap: with BestSource present, av1an auto-selects the fast chunk
method instead of quadratic-decode `select`. The full chain was
verified live on this machine: VS git (Core R80) → BestSource →
`av1an --chunk-method bestsource` → rc=0 output.
- **Runtime env follows the git stack**: `_av1an_env()` and the plugin
probe now include the python user-site vapoursynth dir (module + libs
+ plugins), so av1an loads the freshly built VS instead of the system
one after a rebuild.
## [4.7.0] — 2026-09-25
### Added — Hybrid GPU + CPU scheduling
- **New engine: Hybrid (UI ENGINE combo / `--engine hybrid`).** The
queue is scanned once, then split between two CONCURRENT lanes: a GPU
lane (NVENC via single-pass ffmpeg) and a CPU lane (the family's
software encoder, or av1an chunk-parallel when opted in — so NVENC +
chunk workers + software encoders can all run at the same time on
multi-core boxes with an NVIDIA card).
- **LPT load balancing** (`hybrid_scheduler.plan_hybrid`): files sorted
by size descending, each assigned to the lane with the lower
estimated load using a GPU:CPU speed ratio (default 8:1) — both lanes
finish at roughly the same time.
- **CPU lane thread budget**: the CPU lane's topology is reduced by a
2-thread reserve for the GPU lane's decode/scale/mux before the
intelligent worker math runs; the lane's software ffmpeg encodes are
additionally capped with `-threads N`. NVENC jobs are never
thread-capped (silicon-bound).
- **Per-lane temp dirs** (`worker-<pid>-gpu` / `worker-<pid>-cpu`):
both lanes share one process, so the PID alone no longer separates
them — a lane finishing early can no longer sweep the other lane's
intermediates.
- STOP stops both lanes; the final summary aggregates both lanes'
results. Hybrid needs 2+ encodable files and a functional GPU encoder
and otherwise falls back to a single CPU queue with a logged reason.
Lanes split FILES, never one file across encoders (mixed-encoder
chunks would produce visibly inconsistent quality within a file, and
av1an cannot drive NVENC).
### Fixed — during hybrid hardening
- **libx265 rejects large `-threads` values** ("frameNumThreads must be
[0 .. X265_MAX_FRAME_THREADS)"): the CPU lane's injected thread cap is
clamped to 16 for libx265; SVT-AV1 and libvpx keep the full budget.
- **Failed ffmpeg encodes now delete their partial output.** A failed
encode used to leave a truncated file that ffprobe still parses as the
right codec/resolution — skip-existing would then treat it as a
finished archive forever. The output is unlinked on the ffmpeg error
path (the av1an and STOP paths already cleaned up).
### Fixed — launcher crash on STARTUP
- `OpenCodecMaster._build_ui()` read `self.env.av1an_flags` while
pre-selecting the ENGINE combo, but `env` is None/absent until
`_probe_and_init` runs after the UI build — instantiating the window
crashed with `AttributeError: ... has no attribute 'env'` (launcher)
or on `NoneType` (package). The pre-select now reads defensively.
Verified by offscreen-instantiating the real launcher window.
### Tests
- `tests/test_hybrid_scheduler.py` (19): LPT split, degenerate cases,
thread budget, `file_subset` end-to-end, per-lane temp dirs, ffmpeg
thread cap (CPU yes / GPU no), `scan_input_files`, CLI `--engine
hybrid`, launcher parity.
## [4.6.0] — 2026-09-25
### Overview
GPU (NVENC) encoding with auto GPU/CPU engine selection, the root-cause
fix for the av1an chunking failures on ffmpeg 7+, and the remaining
large-file failure modes (pre-scale timeout, silent disk exhaustion,
whole-video loudnorm decode).
### Added — GPU (NVENC) support + auto engine
- **Engine selector** (UI: Auto/GPU/CPU combo; CLI: `--engine
{auto,gpu,cpu}`). Auto uses the NVENC hardware encoder for the
selected codec family (x265 → `hevc_nvenc`, AV1 → `av1_nvenc` on
RTX 40+) when it actually works; VP9 has no NVENC encoder and stays
on CPU. GPU encodes run via single-pass ffmpeg with `-rc vbr -cq N`
(CPU decode), so av1an chunk-parallel is not needed — one NVENC
process outruns chunk-parallel CPU workers.
- **`env_probe.GpuInfo` + `_probe_gpu()`**: two-stage NVENC probe —
compiled-in encoder list, then a LIVE encode smoke test per encoder.
The live gate catches the real-world failure mode where ffmpeg lists
`hevc_nvenc` but the installed NVIDIA driver is older than the NVENC
API the build targets ("Driver does not support the required nvenc
API version. Required: 13.1 Found: 13.0" — observed on GTX 1070 +
driver 580 + ffmpeg 9.0.2), reports the driver fix, and the engine
falls back to CPU automatically.
- Skip-existing is engine-agnostic: `hevc_nvenc` outputs the same
ffprobe codec_name (`hevc`) as libx265, so switching engines never
re-encodes finished files.
- `--dry-run` prints the GPU verdict (usable encoders or the exact
failure detail).
- **`scripts/build-ffmpeg-nvenc-matched.sh`** — fixes the
driver/ffmpeg NVENC API mismatch without touching the system:
builds ffmpeg against the nv-codec-headers gen the installed driver
actually provides (e.g. 580 driver → gen 13.0) with the distro
build's exact feature set, installing to `~/.local` with
`--enable-rpath` (critical — without rpath the binary silently
loads the distro libavcodec and keeps demanding the newer API).
Verified on GTX 1070 + driver 580.178 + ffmpeg 9.0.2: hevc_nvenc
and h264_nvenc functional, av1_nvenc correctly reported as
unavailable (no Pascal AV1 hardware) and auto stays on SVT-AV1.
### Fixed — av1an chunking (root cause on ffmpeg 7+)
- **ffmpeg ≥ 7 removed `-vsync`, which av1an's segment/hybrid chunk
extraction passes to ffmpeg.** Every segment-based chunk died
instantly ("Unrecognized option 'vsync'" → broken y4m pipe → chunk
fails 3×) — this produced the y4m pipe-break storm in the 2026-07-13
av1an log. Verified end-to-end on ffmpeg 9.0.2 + av1an 0.5.2:
`select` still works (it uses the ffmpeg frame server, not
segmenting), so the plugin-less `select` override remains, the
failure is now diagnosed with an actionable block (install
bestsource/ffms2/lsmash, or stay on the ffmpeg-only path), and the
select retry now also triggers on it.
- **FRAME MISMATCH ("encoder crashed: exit status: 0")** — chunk
manifest vs encoded frame-count drift on sparse-keyframe sources
(20 worker shutdowns in the same log) fell through to "Unknown av1an
failure". Now a recognized per-file pattern that retries with
`select` (exact frame ranges can't drift). Diagnostics now scan
av1an's stdout too, since FRAME MISMATCH lines land there.
### Fixed — large-file failures
- **The ffmpeg path no longer pre-scales.** The CRF-16 intermediate
existed only for VapourSynth source-plugin compatibility; the
ffmpeg/GPU path scales inline via `-vf`. This removes the whole
class of failures: the 0.5-0.8× source-size temp file, the extra
full encode pass, and the flat **1800s pre-scale timeout** that
killed long/high-bitrate sources at exactly 30 minutes. The av1an
path keeps the intermediate but now runs under the per-file timeout
with STOP-button support ("FAIL: pre-scale timeout" on expiry).
- **Severe disk-space warnings are user-facing.** "free < source size"
on the output or temp partition was verbose-only — quiet mode gave
zero notice before "No space left on device". Marginal advice stays
verbose-only.
- **Loudnorm analysis is audio-only (`-vn`).** It previously decoded
the entire video stream to measure audio loudness, pushing large
files past the 120s analysis timeout and silently degrading them to
the static knob gain.
### Tests
- `tests/test_gpu_engine.py` (32): GpuInfo, the live-encode GPU gate
(driver-mismatch case included), the resolve matrix, NVENC vargs,
CLI `--engine`, worker engine plumbing, launcher parity.
- `tests/test_large_file_fixes.py` (11): pre-scale gating + timeout +
STOP handling, user-facing disk-space warnings, `-vn` loudnorm,
FRAME MISMATCH / vsync select retries.
- Harness: EncoderWorker has class-level defaults for `verbose` /
`_current_*` so `__new__`-built test instances match the v4.4.3
contract; tests replace the Qt signal with `conftest.capture_signal()`
instead of patching read-only `SignalInstance` attributes (fixes all
24 order-dependent failures on machines with a real PySide6).
## [4.5.0] — 2026-07-26 (master)
### Overview
Master release consolidating the v4.4.4 large-file fix with all prior
v4.4.x stability work. Targets the **"every large file fails"** symptom
reported on files from 1.1GB to 20GB, where the lossless pre-scale
intermediate was exhausting the temp partition and presenting as cryptic
`ffmpeg error (rc=234)` messages (the `rc=234` was a truncated 300-char
stderr snippet — the real error was "No space left on device").
### Fixed — "ffmpeg error (rc=234)" on 10GB+ source files with scaling
- **Pre-scale intermediate changed from CRF 0 to CRF 16.** The old CRF-0
(mathematically lossless) libx265 intermediate produced 2-4× source
size temp files: a 20GB BluRay rip generated a 60-80GB intermediate,
exhausted the temp partition, and crashed. CRF 16 is visually lossless
for archival purposes and produces 0.5-0.8× source size intermediates
(a 20GB source → ~10-15GB intermediate instead of 60GB). The single
1.6GB→768MB file that succeeded in the user's batch was the only one
small enough that the lossless intermediate fit on disk.
- **New `--inline-scale` flag** skips the pre-scale intermediate
entirely. The scale/pad filter chain is passed directly to av1an via
`--ffmpeg-filter-args`. Zero intermediate file, one fewer encode pass.
Toggleable via the new "Inline scale (no intermediate)" checkbox in
the UI options row. Default OFF — the intermediate path is more
robust against av1an/VapourSynth filter-arg quirks on older builds.
Enable when scaling large files (≥10GB) to save disk and time.
- The disk-space pre-check (`_check_disk_space`) now correctly handles
the inline-scale path: no intermediate is created, so the 2-3× source
temp-space warning is suppressed.
### Carried forward from v4.4.x
- v4.4.3: `AttributeError: 'EncoderWorker' object has no attribute 'verbose'`
crash on START in the launcher script. Added UI toggle for av1an
(checkbox in the options row, equivalent to `--use-av1an`).
- v4.4.2: Live tail of av1an/ffmpeg stderr was spamming the log in
quiet mode. "FAIL: av1an exit code 1" appeared even when the ffmpeg
fallback succeeded (confusing "FAIL then OK" double-status).
- v4.4.1: Default log output reduced to two lines per file (start
banner + finish status). Heartbeat and disk-space warnings require
`--verbose`.
- v4.4.0: Per-file timeout raised from 2h to 24h (configurable via
`--timeout`). 5%-of-source integrity check replaced with absolute
1KB minimum (false-positived on high-bitrate BluRay sources). Disk-
space pre-check warns (not aborts) when free space < source size.
### Tests added
- `tests/test_inline_scale.py` (13 tests): verifies the CLI flag, the
`launch_gui` signature, the `EncoderWorker.inline_scale` attribute
flow, the `_prepare_input` gating, the `_encode_one`
`--ffmpeg-filter-args` injection, the CRF-16 (not CRF-0) intermediate,
and that the launcher script mirror stays in sync.
### Files touched in v4.4.4 (carried into 4.5.0)
- `opentranscode/encoder_worker.py` — CRF 16 + `inline_scale` gate +
`--ffmpeg-filter-args` injection in av1an cmd.
- `open-transcode.py` — mirror of all the above (launcher script).
- `opentranscode/cli.py` — `--inline-scale` flag.
- `opentranscode/ui_window.py` — UI checkbox + `launch_gui` signature +
state plumbing through `env.av1an_flags["inline_scale"]`.
- `opentranscode/__init__.py` — `launch_gui` wrapper signature updated.
### Upgrade notes
- Default behavior for files **without** a target resolution is
unchanged — no intermediate is created either way.
- Default behavior for files **with** a target resolution is now
CRF-16 intermediate (was CRF-0). Output quality is unchanged for
archival purposes; intermediate size drops ~60-75%.
- For maximum speed on large files with scaling, enable `--inline-scale`
or check the "Inline scale (no intermediate)" box in the UI. Test on
a small file first if you're on an older av1an build (pre-0.5.2) to
confirm `--ffmpeg-filter-args` is accepted.
## [4.4.4] — 2026-07-26
### Fixed — "ffmpeg error (rc=234)" on 10GB+ source files with scaling
- **Pre-scale intermediate changed from CRF 0 to CRF 16.** The old CRF-0
(mathematically lossless) libx265 intermediate produced 2-4× source
size temp files: a 20GB BluRay rip generated a 60-80GB intermediate,
exhausted the temp partition, and crashed with cryptic
`ffmpeg error (rc=234)` messages (the rc=234 came from a truncated
300-char stderr snippet — the real error was "No space left on
device"). CRF 16 is visually lossless for archival purposes and
produces 0.5-0.8× source size intermediates (a 20GB source →
~10-15GB intermediate instead of 60GB).
- **New `--inline-scale` flag** skips the pre-scale intermediate
entirely. The scale/pad filter chain is passed directly to av1an via
`--ffmpeg-filter-args`. Zero intermediate file, one fewer encode pass.
Toggleable via the new "Inline scale (no intermediate)" checkbox in
the UI options row. Default OFF — the intermediate path is more
robust against av1an/VapourSynth filter-arg quirks on older builds.
Enable when scaling large files (≥10GB) to save disk and time.
- The disk-space pre-check (`_check_disk_space`) now correctly handles
the inline-scale path: no intermediate is created, so the 2-3× source
temp-space warning is suppressed.
### Tests added
- `tests/test_inline_scale.py` (13 tests): verifies the CLI flag, the
`launch_gui` signature, the `EncoderWorker.inline_scale` attribute
flow, the `_prepare_input` gating, the `_encode_one`
`--ffmpeg-filter-args` injection, the CRF-16 (not CRF-0) intermediate,
and that the launcher script mirror stays in sync.
## [4.4.3] — 2026-07-25
### Fixed
- `AttributeError: 'EncoderWorker' object has no attribute 'verbose'` crash
on START in the launcher script's `EncoderWorker.__init__`. The launcher
script now sets `self.verbose` from `env.av1an_flags["verbose"]`, matching
the package's behavior.
### Added
- UI toggle for av1an: a new "av1an (chunk-parallel)" checkbox in the
options row. Default OFF = ffmpeg-only. The CLI flag `--use-av1an`
still works; the UI toggle takes precedence when set.
### Changed
- Documentation terminology: `open-transcode.py` is consistently called
"the launcher script" (not "single-file script"). It mirrors the
16-module `opentranscode/` package; calling it "single-file" was
misleading.
## [4.4.2] — 2026-07-25
### Fixed
- Live tail of av1an/ffmpeg stderr (`│ Encoding: 1373/1376 Frames @ 51.70 fps...`)
was spamming the log in quiet mode in the launcher script. The package
had this gated behind `--verbose` since v4.1.1; the launcher script
now matches.
- "FAIL: av1an exit code 1" appeared in the log even when the ffmpeg
fallback succeeded, producing a confusing "FAIL then OK" double-status.
The av1an failure line now goes to `_vlog` (verbose only); the user
sees only the final outcome (OK or `FAIL: av1an + ffmpeg both failed`).
## [4.4.1] — 2026-07-25
### Changed
- Default log output reduced to two lines per file: start banner + finish
status. Heartbeat (`... 30s elapsed`) and disk-space warnings now
require `--verbose`. The user asked for "start + finish, nothing else";
this delivers exactly that.
## [4.4.0] — 2026-07-25
### Added — massive-file support (30GB+ BluRay rips)
- **Per-file timeout raised from 2h to 24h**, configurable via
`--timeout SECONDS`. A 30GB 1080p BluRay rip at SVT-AV1 preset 6
takes 4-10 hours; the old 2h timeout killed massive-file encodes
partway through.
- **5%-of-source integrity check replaced with absolute 1KB minimum**.
The old check false-positived on high-bitrate sources (50GB BluRay →
5% = 2.5GB, but valid AV1 at CRF 32 produces 1-2GB for a 2-hour movie).
The real integrity gate is the duration check (≥95% of source).
- **Disk-space pre-check** warns (not aborts) if free space < source size.
When scaling, also checks the temp partition (lossless intermediate
can be 2-3x source size).
### Changed — log noise reduction
- Combined `[N/total] filename` banner + status into a single line:
`[1/180] filename.mkv — OK: 1.6MB -> 1.3MB (81%)` (was two lines).
- Disk-space warnings no longer fire for skipped files (the check now
runs after the skip-existing check).
## [4.3.0] — 2026-07-25
### Added — skip-existing detection
- Probes the output file with ffprobe before encoding. If the output
exists with a matching video+audio codec (and matching resolution when
scaling is requested), the file is skipped. Default ON; use
`--force-reencode` to disable.
- Added `ffprobe_codec_name` field to `VideoCodecProfile` and
`AudioProfile` (av1/vp9/hevc, opus/vorbis/flac/iamf).
- Final summary now includes `Skipped: N` count.
### Fixed — heartbeat regression
- v4.2.1 gated the 30-second heartbeat behind `--verbose`, causing the
"hangs on first transcode, forever timer" symptom in quiet mode. The
heartbeat is now always user-facing (one line per 30 seconds during
long encodes). The live tail of `frame= 67 fps= 12...` stays gated.
## [4.2.1] — 2026-07-25
### Changed — quiet mode by default
- Tech-detail log lines gated behind `--verbose`. Default output is
two lines per file: start banner + finish status.
- Gated: CMD: lines, live tail of av1an/ffmpeg stderr, DIAGNOSIS blocks,
resolution map, pre-flight validation table, heartbeat, disk-space
warnings, RETRY messages, file-type detection details.
## [4.2.0] — 2026-07-25
### Changed — ffmpeg is the default encode path
- av1an chunk-parallel was too fragile across distros (y4m pipe breaks,
SvtAv1EncApp CLI rejects `--threads`, VapourSynth plugin issues,
output buffering making it look hung). The default encode path is now
ffmpeg-only. av1an is opt-in via `--use-av1an`.
- The av1an pre-flight smoke test is skipped entirely when av1an is
not requested. `_on_run_clicked` sets `use_ffmpeg_fallback = True`
directly, short-circuiting the av1an code path.
## [4.1.2] — 2026-07-25
### Fixed
- Removed the `--threads N` injection into av1an's `--video-params`
string (introduced in v4.1.0). `SvtAv1EncApp` (the standalone CLI
av1an invokes per-chunk) does not accept `--threads` — only `--lp`
(logical processors). The result was `Unprocessed tokens: --threads`
→ every chunk failed 3x → no av1an output. Thread capping now lives
in av1an's `--workers` flag (chunk-parallel count) and in `-threads`
for the ffmpeg fallback path (where libsvtav1 is a library).
- `params_fn` signature returned to `(crf, preset) -> str` (v4.0.0 form).
## [4.1.1] — 2026-07-25
### Added
- **Live progress tail** — av1an's stdout/stderr emits to the GUI log
as it arrives (handles both `\n` log lines and `\r` progress bar
updates as line boundaries).
- **30-second heartbeat** — `... still encoding (Xs elapsed)` every
30 seconds so the user knows the encode is alive.
### Changed
- `IDEAL_THREADS_PER_WORKER` raised from 4 to 6 for better per-chunk
SVT-AV1 throughput. On a 28-thread Xeon, the split changed from
6×4=24 to 4×6=24 (same total, better per-chunk latency).
## [4.1.0] — 2026-07-25
### Added — intelligent chunking
- `_compute_intelligent_worker_count()` computes `(worker_count,
threads_per_worker)` such that `worker_count * threads_per_worker <=
logical_threads - 1`. Prevents thread oversubscription on high-core-
count machines (13 workers × 28 threads = 364 active on 28 logical CPUs
→ kernel scheduler drowned → hard lock).
- Per-encoder `--threads N` cap injected into `--video-params`.
- CLI flags `--max-workers N` and `--threads-per-worker N` for overrides.
## [4.0.0] — 2026-07-25
### Fixed — "works up until near the end, never saves chunks into a full file"
- av1an auto-selects the Hybrid chunk method when no VapourSynth source
plugins are installed. Hybrid fails on phone-recorded MP4s with sparse
keyframes (scene boundaries rarely align with I-frames → segment muxer
splits mid-GOP → decoder errors → y4m pipe breaks → encoder reads EOF
→ every chunk fails after 3 retries → no output file).
- `env_probe` now probes for VapourSynth source plugins (`lsmash`,
`ffms2`, `bestsource`, `dgdecnv`). When none are found, pre-sets
`chunk_method_override = "select"` to avoid the wasted first attempt.
- `_encode_one` accepts a `chunk_method` parameter for retry. When av1an
fails with the y4m break pattern, it recursively retries with
`--chunk-method select` and caches that choice for subsequent files.
- `--chunk-method {auto,select,hybrid,segment,ffms2,lsmash,bestsource,dgdecnv}`
CLI flag for forcing a specific chunk method.
### Package split
- Refactored the monolithic `open-transcode.py` into a 16-module
`opentranscode/` package. The launcher script is preserved for
backwards compatibility and as the test target for mocked tests.
- `pyproject.toml` for `pip install -e .` and `python -m build`.
---
[4.5.0]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.5.0
[4.4.4]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.4.4
[4.4.3]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.4.3
[4.4.2]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.4.2
[4.4.1]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.4.1
[4.4.0]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.4.0
[4.3.0]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.3.0
[4.2.1]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.2.1
[4.2.0]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.2.0
[4.1.2]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.1.2
[4.1.1]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.1.1
[4.1.0]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.1.0
[4.0.0]: https://git.dcos.net/dcosnet/OpenTranscode/releases/tag/v4.0.0