A batch transcoding GUI for Linux built with PySide6.
Go to file
Jeremy Anderson 48f7816ca6 A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
logs A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
opentranscode A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
tests A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
LICENSE A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
README.md A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
open-transcode.py A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
pyproject.toml A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00
pytest.ini A single-file, batch transcoding GUI for Linux built with PySide6. Wraps av1an for chunk-parallel AV1/VP9/x265 encoding with a retro-futuristic MMD3 media console aesthetic. 2026-07-24 21:39:41 -04:00

README.md

OpenTranscode v4.0.0 — Production Release

v4.0.0 resolves the "works up until near the end, never saves chunks into a full file" bug that affected phone-recorded MP4s with sparse keyframes.

The v4.0.0 fix (in detail)

The bug

When no VapourSynth source plugins are installed (the common case on most distros — lsmash, ffms2, bestsource are all separate packages), av1an auto-selects the Hybrid chunk method. Hybrid does:

  1. ffmpeg -c copy -f segment to split the source at scene boundaries
  2. Re-decode each segment to y4m via a second ffmpeg invocation
  3. Pipe the y4m to the encoder (SvtAv1EncApp / vpxenc / x265)

Phone-recorded MP4s (the 20190707_112725.11b774bacde3.mp4 files in the production log) only have I-frames every 510 seconds. Scene boundaries detected by av1an's av_scenechange rarely align with those sparse keyframes. The segment muxer can only split on keyframes, so the segment for scene N actually starts somewhere inside scene N-1's GOP.

The result: the decoder has no I-frame reference → errors with [h264 @ 0x...] error while decoding MB 35 25 → the y4m pipe breaks → the encoder reads EOF mid-frame → Failed to read y4m frame delimiter. Read broken. EOF: 1 → every chunk fails after 3 retries → no chunks to concat → no output file.

The previous ffmpeg fallback rescued the file, but it was slow (single-pass, no chunk-parallel) and the diagnostic was misleading ("concat failure" when it was actually a chunk-extraction failure).

The fix (three parts)

  1. _encode_one now accepts a chunk_method parameter. When av1an fails with the y4m break pattern, it recursively retries with --chunk-method select. Select uses VapourSynth's select() filter to extract frames one-by-one — slower than Hybrid but reliable for any file VapourSynth can open. This is faster than the ffmpeg fallback (chunk-parallel still works) and produces identical-quality output (same encoder, same params).

  2. The working chunk_method is cached in env.av1an_flags["chunk_method_override"] so subsequent files skip the wasted first attempt.

  3. env_probe now probes for VapourSynth source plugins via _probe_vs_source_plugins(). When NONE are found, it pre-sets chunk_method_override = "select" to avoid the wasted first attempt entirely. The probe checks ~/.local/lib/vapoursynth/, /usr/lib/vapoursynth/, /usr/local/lib/vapoursynth/, and the Debian multiarch path.

Also fixed

The "SUMMARY block + non-zero exit" diagnostic previously misdiagnosed y4m break failures as "concat failure" (because SVT-AV1 prints a SUMMARY block per-chunk before the pipe breaks). The check is now guarded by "Failed to read y4m frame delimiter" not in stderr_full so it only fires for true concat failures.

New CLI flag

opentranscode --chunk-method {auto,select,hybrid,segment,ffms2,lsmash,bestsource,dgdecnv}

Lets the user force a specific chunk method. Useful for debugging or for environments where the probe picks the wrong default. auto clears any override the probe set.

Quick start

cd /path/to/this/directory
pip install -e .                   # editable install (dev)
# OR
pip install -e ".[dev]"            # with pytest + build tools

# Now you can run it three ways:
opentranscode                       # console entry point
python -m opentranscode             # module entry point
python -m opentranscode --version   # → opentranscode 4.0.0

Option B: Run the single-file script (backwards compat)

python open-transcode.py            # the single-file version still works

Option C: Verify your environment without encoding

opentranscode --dry-run             # probe env + smoke test, no GUI, no encode
opentranscode --dry-run --chunk-method select  # preview a forced chunk method
opentranscode --verify-only /path/to/existing_output.mkv   # re-verify an output

Files

Path Description
opentranscode/ Package — 16 modules. Importable as import opentranscode.
open-transcode.py Single-file script (~6,100 lines). Kept for backwards compat + as the test target for the mocked tests. Mirrors the package behavior.
pyproject.toml PEP 621 build config. Entry point: opentranscode = opentranscode.__main__:main. Ready for pip install -e . and python -m build.
tests/ 103 tests across 12 files. All pass in ~10 seconds.
pytest.ini pytest config (also in pyproject.toml).
README.md This file.
logs/av1an.log.2026-07-13 The production log that revealed the y4m break bug. Kept for reference.

Test suite

cd /path/to/this/directory
python -m pytest tests/ -v

# 103 tests, ~10 seconds:
#   25 mocked unit tests (smoke test, encoder pipeline, audio loudnorm, etc.)
#   12 real-ffmpeg e2e tests — requires ffmpeg + ffprobe
#   36 package-structure tests
#   13 chunk-method retry tests
#    1 chunk-method e2e recovery test

The e2e tests generate a real 2-second test video with ffmpeg, run the full EncoderWorker pipeline on it (AV1→MKV, x265→MKV, VP9→WebM), and verify the output file exists, is non-empty, has the correct codec, and has the expected duration. The chunk-method e2e test additionally simulates the y4m break pattern and verifies the retry-with-select produces a valid output file.

Verification (run these to confirm v4.0.0 works)

# 1. Package imports cleanly
python -c "import opentranscode; print(opentranscode.__version__)"  # → 4.0.0

# 2. CLI works
python -m opentranscode --version        # → opentranscode 4.0.0
python -m opentranscode --help           # → usage (includes --chunk-method)
python -m opentranscode --dry-run        # → env probe report (shows VS plugins + chunk method)

# 3. All tests pass
python -m pytest tests/ -q               # → 103 passed in ~10s

# 4. Install works
pip install -e .                         # → installs opentranscode + PySide6
opentranscode --version                  # → opentranscode 4.0.0

# 5. Single-file script still works (backwards compat)
python open-transcode.py                 # → launches GUI (if PySide6 + display)

Publishing to PyPI

v4.0.0 is the production release. To publish:

python -m build               # produces dist/opentranscode-4.0.0.tar.gz + .whl
twine upload dist/*           # publishes to PyPI

The production log that revealed the bug

The file logs/av1an.log.2026-07-13 is the actual av1an log from the user's production run that revealed the y4m break bug. Key markers:

INFO encode_file: Input: 1920x1080 @ 29.763 fps, YUVJ420P, SDR
INFO encode_file: scenecut: found 8 scene(s) [with extra_splits: 16 scene(s)]
DEBUG encode_file: Segmenting video
DEBUG encode_file: Segment done
INFO encode_file:
        Encoding          Failed to read y4m frame delimiter. Read broken. EOF: 1
        [h264 @ 0x55da365b30c0] error while decoding MB 35 25
WARN encode_chunk: Encoder failed (on chunk 11):
        Encoding          Failed to read y4m frame delimiter. Read broken. EOF: 1
        SUMMARY -----------------------------------------------------------------
        Average Speed:		2.501 fps
ERROR av1an_core::broker: [chunk 4] encoder failed 3 times, shutting down worker

The smoke test (line 1 of the log) succeeds because it uses chunk_method: Select — but the real encode (line 4 onward) uses chunk_method: Hybrid (av1an's auto-selection when no VS plugins are installed) and fails on every chunk.