# 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`, `_x` — 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--gpu` / `worker--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