Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Unreleased

- **refactor(rewriter): orthogonal HW-decode-text branch** (#40). The
- **refactor(rewriter): orthogonal HW-decode-text branch** (#41). The
HW-decode-text path now runs through `extractGraphFacts +
composeBurn(burnSpec{vaResident:true, …})` — the same orthogonal core the
SW-reshape (#39) and HW-decode-bitmap (#37) branches already use. The
Expand All @@ -21,7 +21,52 @@
`-map [4]` / `-map [6]` is retargeted via the shared `retargetMapLabel`.
Orthogonal parity harness 1369/1369 over the 1583-entry argv corpus, no
drift; 4 of the original `reFilter*` regex zoo gone in v1.6.1, last 2 gone
now. No fork change.
now.

- **fix(ffmpeg): sched start_prepare per-output sinkless gate** (#41, patch
`0122`). Patch `0120` skipped `dst_finished` allocation in `start_prepare()`
for any decoder marked `sink_less` — correct when the decoder has only a
sink-less side-channel output, broken when the decoder has BOTH a
sink-less side-channel AND a real OST consumer (the dual-consumer case
exercised when an argv keeps Plex's `-map <spec> -f null -codec ass
nullfile` decode-sink alongside `-map_inlineass <spec>`). In that case,
the OST output's `dst_finished` stayed NULL while `nb_dst=1`, and the
first `sch_dec_send` for the OST dereferenced `&dst_finished[0]` →
SIGSEGV in `dec1:0:srt`. Fix: move the sink-less gate INSIDE the
per-output loop so `dst_finished` is allocated for real outputs and only
the genuinely-unconnected outputs are skipped. Live-validated on
plex-test with a minimal lavfi+SRT gdb repro (pre-patch SIGSEGV →
post-patch clean decode, 2 packets read, 2 frames decoded).

- **fix(rewriter): keep inlineass decode-sink for sidecar bindings** (#41).
Patch `0120`'s sink-less self-decode (via `ist_inlineass_add`) works for
embedded subtitle streams — they share the demuxer with main video,
which is pumped by the scheduler fast enough to feed the sink-less
decoder. For sidecar inputs (`-i temp-0.srt -map_inlineass 1:s:0`) the
separate sidecar demuxer thread has no other downstream consumer; the
scheduler chokes after the first packet (1 packet / 7 bytes from a
134 KiB SRT file, 0 frames decoded, libass track empty, subs render
blank). Fix: `stripInlineassDecodeSink` now gates on the `-map_inlineass`
binding's `file_idx == 0`. Sidecar bindings (`file_idx >= 1`) keep
Plex's emitted `-map <spec> -f null -codec ass nullfile` so the sidecar
demuxer has a real downstream consumer. Combined with patch `0122`
(above) the dual-consumer state is safe; sidecar SRT renders. Embedded
bindings still get the original `0120` strip (no extra null-mux
competing for the NFS read during pre-throttle buffer fill).

- **refactor(rewriter): fold tryOptimizeRemux into Rewrite()** (#43). The
Optimize remux fast-path was a parallel function that re-ordered the
same helpers the main rewriter's tail already calls. Collapsed into one
path: detect `isRemux := isOptimizeRemux(args)` up-front, gate the
transcode block (decoder upgrade, init_hw_device, sub detection, filter
rewrite, encoder swap, SEI inject) and the VAAPI-only env writes (LIBVA
driver, inlineass-decode-sink strip) on `!isRemux`. Common tail (EAE
swap, manifest_name / segment_list / progressurl rewrites, loglevel /
nostats / env scrubs) runs unconditionally — already shared via
helpers, order-independent. Drops `decode:remux:<short>` and
`encode:copy(passthrough)` change tags (helper-emitted tags suffice).
Parity-validated on 720 corpus entries (real-prod + Optimize-sweep
matrix): 0 argv / env / progressurl divergence. Net −47 LOC.

## v1.6.1 — 2026-05-26

Expand Down
13 changes: 10 additions & 3 deletions docs/PACED_SELF_DECODE.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,16 @@ So the scheduler gains an explicit *sink-less* concept.
### `fftools/ffmpeg_sched.{c,h}`
- `SchDec` gains `int sink_less;`.
- New API `void sch_dec_set_sinkless(Scheduler *sch, unsigned dec_idx);`.
- `start_prepare()`: skip the per-output "not connected to any sink" check when
`dec->sink_less` (the source check still applies — a sink-less decoder must
still be fed by the demuxer).
- `start_prepare()`: skip the per-output "not connected to any sink" check
when `dec->sink_less` AND the output has `nb_dst == 0` (the source check
still applies — a sink-less decoder must still be fed by the demuxer).
**Per-output gate** — patch `0122` refines this from the original
whole-decoder `if (dec->sink_less) continue` which skipped `dst_finished`
allocation for ALL outputs of the decoder, including real OST consumers
in the dual-consumer case (sidecar SRT path where the rewriter keeps
Plex's null-mux decode-sink). The original variant SIGSEGV'd in
`sch_dec_send` on the first decoded frame when `dst_finished` was NULL
for the real output.
- `sch_dec_send()`: when the target output has `nb_dst == 0`, `av_frame_unref`
the frame and `return 0` (consumed, discarded) instead of falling through to
the `AVERROR_EOF` result. Universally safe — only sink-less outputs ever have
Expand Down
20 changes: 15 additions & 5 deletions docs/REWRITER.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,21 @@ construction, and the OpenCL detour collapses into the canonical
resolution tier below `render_height`. Static cues are unaffected. Same knob
applies on the SW-reshape path (`composeBurn`'s `animatedTierDown` axis).
- Plex's `-map_inlineass <spec>` is **kept** — it drives the libass feed.
- Plex's `-map <spec> -f null -codec ass` decode-sink is **stripped**
(`stripInlineassDecodeSink`, gated on `-map_inlineass` still being present).
As of fork patch `0120` (v1.5.0) the `-map_inlineass` binding self-decodes
via a sink-less decoder paced by the demux, so the null-mux is redundant —
see `docs/PACED_SELF_DECODE.md`.
- Plex's `-map <spec> -f null -codec ass` decode-sink is **conditionally
stripped** by `stripInlineassDecodeSink`, gated on (a) `-map_inlineass`
still being present AND (b) the binding's `file_idx == 0` (embedded sub
stream — shares the main demuxer with video). As of fork patch `0120`
(v1.5.0) the `-map_inlineass` binding self-decodes via a sink-less
decoder paced by the demux; for embedded subs the shared demuxer is
pumped by main video, so the null-mux is redundant and competes with the
NFS read during the pre-throttle buffer fill (the original reason to
strip it). For **sidecar bindings** (`file_idx >= 1`, e.g.
`-map_inlineass 1:s:0` pointing at a PMS-staged `temp-0.srt`) the
sidecar has its own demuxer thread with no other downstream consumer; the
scheduler chokes after the first packet and the binding sees zero cues.
There the decode-sink is **kept** so the sidecar demuxer has a real
consumer (patch `0122` makes the dual-consumer state safe for
`dst_finished` allocation). See `docs/PACED_SELF_DECODE.md`.
- Plex's full `inlineass` node is passed through **verbatim** — including
the styling keys `language`/`overrides`/`outline`/`shadow`. As of patch
`0119` the fork's `inlineass` parses them (`overrides` →
Expand Down