Skip to content

5.25.2 - Same-PTS subtitle retention

Choose a tag to compare

@superuser404notfound superuser404notfound released this 28 Jul 10:52

Patch release. Two subtitle fixes: a packet store that treated a timestamp as a unique key, and the last readers still letting the video stream decide where a positioning seek lands.

Fixed

Subtitle packets sharing a PTS are all retained instead of overwriting one another. (#235)

SubtitlePacketStore placed a harvested packet by PTS and, on finding that timestamp already occupied, replaced what was there:

let insertAt = entries.firstIndex { $0.ptsSeconds >= ptsSeconds } ?? entries.count
if insertAt < entries.count, entries[insertAt].ptsSeconds == ptsSeconds {
    bytes -= entries[insertAt].payload.count
    entries[insertAt] = entry
}

The premise was that a repeated PTS could only mean the demux pump and the forward prefetcher (#151) re-harvesting the same packet. That overlap is real and does need collapsing. It is not the only way two packets carry one timestamp.

ASS/SSA authors overlapping lines on identical Start/End as a matter of course, and a karaoke or layered-style track puts a whole burst of distinct Dialogue events on a single one. The #56 measurement of a real track found 1534 packets on exactly pts=5.207000. Every member of such a burst but the last was discarded at the point of storage, before any decoder saw it, so a heavily styled track reached the renderer with most of its events missing. The report measured 245 processed events where the track carries 2054 in the same range, a count that tracked the 268 distinct timestamp pairs in that range rather than the events themselves.

A re-harvest is byte-identical to the packet it duplicates. That, rather than the bare timestamp match, is the signature the collapse now tests. Anything else joins the run at its end.

The end of the run matters as much as the retention. The store's array order carries through the drainer, which decodes a window in array order, into subtitleCues, where insertCueSorted keeps same-start text cues in arrival order. Inserting at the lower bound would retain every event but hand the host a burst in reverse authoring order.

The insert position is now found by binary search rather than scanned from the front of the array. That scan made one append linear in retained packets and a session's harvest quadratic, which cost little while a burst collapsed to a single entry and is load-bearing now that a dense track keeps all of them.

Bitmap subtitles are unaffected in practice: a PGS display set has a unique start PTS, and split-PES sets already assemble per writer before they are stored.

Reported and fixed by fivepandasna, traced from processed-event counts in a host wrapper against the source file. Follow-up hardening on the run ordering, the search, and two test fixtures that had asserted the collapse using two different payloads on one PTS.

The native subtitle readers anchor their positioning seek on the subtitle axis too. (#234)

The same defect 5.25.1 fixed in the forward prefetcher, reached from the other direction, and not part of that regression. These readers seek before they set their discard flags, so at seek time every stream is still AVDISCARD_DEFAULT, every stream collects av_find_default_stream_index's +200 for discard != AVDISCARD_ALL, and video takes the reference on its own +75. There was never an accidental subtitle anchor here to lose, so this predates #230 entirely.

The consequence is the one #234 describes: on Matroska the seek lands in the cluster holding the last video keyframe, and a cue starting further back is behind the read head before the first packet arrives. These readers feed the native WebVTT renditions, so what went missing was the line that should be on screen where a seek lands.

The anchor is now the lowest routed subtitle stream, and the native subtitle readers started line carries anchor=<index>. A whole-program read starts at 0 and is unaffected either way.

Notes

No API changes.