# 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 5–10 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

```bash
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

### Option A: Install as a package (recommended)

```bash
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)

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

### Option C: Verify your environment without encoding

```bash
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

```bash
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)

```bash
# 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:

```bash
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.
