OpenTranscode/CHANGELOG.md

26 KiB
Raw Blame History

Changelog

All notable changes to OpenTranscode. Versions follow semantic versioning.

[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.