The DVD seek-index concern is that the mux highway
(PsDemuxer -> PipelinedPesStream codec parse -> frame out -> muxer)
might drop the keyframe flag or per-frame duration that Mpeg2Parser sets
on each Frame, which would stop the muxer's cluster/cue open
(keyframe && track 0) from ever firing and leave a DVD MKV with many
clusters and zero cues.
Add dvd_highway_preserves_video_keyframe_and_duration: drives a real
Mpeg2Parser through PipelinedPesStream via DemuxBatch::Ps batches (one
PTS-stamped MPEG-2 PS video PES per GOP, decode-order I + P/B), reads the
frames back through the highway's read(), and asserts the GOP-opening
I-frames arrive as keyframes, every frame keeps its duration (BlockGroup
path), and video routes to track 0. Guards from_codec_frame's
keyframe/duration propagation across the demux-thread + recycled-buffer
highway, which the prior cue tests (codec parser straight into the muxer)
did not exercise.
Verified against the real current-VFR-build output
(Greenland-feature.mkv, today): 3365 clusters / 3365 CuePoints, all
BlockGroups, cues resolve to clusters and track the video — the highway
preserves the flags and the seek index is complete.
The existing cue tests (cue_count_equals_cluster_count,
cue_positions_resolve_to_clusters, cue_times_match_cluster_timestamps)
all feed frames with duration_ns=None, so they exercise only the
SimpleBlock write path (UHD/HEVC). DVD MPEG-2 video is now VFR: every
coded picture carries a per-frame duration_ns=Some(..), so it is written
as a BlockGroup, not a SimpleBlock. That cue path was untested.
Add cue_count_equals_cluster_count_blockgroup_vfr, which drives the real
Mpeg2Parser end-to-end (decode-order frames, non-monotonic B-frame
display PTS, telecine field durations) into the muxer and asserts the
output is wholly BlockGroup (no SimpleBlock) and that the Cues index has
exactly one cue per cluster, with every cue resolving to a real cluster.
Guards the DVD seek index (scrub/fast-forward) against regressing to the
chapter-seek-only, zero-cue state.
New write-only pes::Stream sink that taps the per-track PesFrame stream
(the seam right before MKV muxing) and writes each track to its own
elementary-stream file, plus chapters and per-audio-track delay metadata.
Purely additive — the MKV mux path is untouched.
- mux/demux_sink.rs: DemuxSink + EsWriter dispatch. Pass-through for
codecs whose Frame.data is already standalone ES (MPEG-2, VC-1, AC3/
E-AC3, DTS/DTS-HD, TrueHD, LPCM). Non-trivial writers:
- AnnexBWriter: reframes hvcC/avcC 4-byte-length-prefixed NALs to
Annex-B and prepends VPS/SPS/PPS parsed out of the codec_private
configuration record (HEVC .hevc / H.264 .h264).
- PgsSupWriter: rebuilds the HDMV 'PG' segment framing the parser
strips, with 90kHz PTS/DTS (.sup).
- VobSubWriter: writes raw SPUs to .sub and synthesizes the .idx
sidecar (palette + per-SPU timestamp/filepos).
- Delay-in-filename (mkvmerge-readable 'DELAY <n>ms') + chapter XML/OGM
export. TimelineRebase ports the MKV muxer's seamless-branch epoch
logic so per-track ES timestamps stay continuous across clip joins.
- mux/resolve.rs: StreamUrl::Demux variant + scheme/path_str/parse_url/
input(write-only)/output arms.
- mux/mod.rs: module + public type re-exports.
16 unit tests: Annex-B reframing, hvcC/avcC param extraction, delay
sign/rounding + mkvmerge-regex match, PGS .sup framing, VobSub .idx
synthesis, chapter XML/OGM, timeline rebase, and end-to-end file-keying
by track + track selection.
A genuinely-clear or uncrackable extra title (a tiny menu/nav stub) no
longer poisons a multi-title rip with a false CssKeyMissing (E7023).
- decrypt_keys_for_title_checked: re-crack a non-overlapping VTS via
crack_key_outcome and report title_is_clear when the title's own
extents show no scrambling. A genuinely-clear stub on an otherwise-CSS
disc needs no key.
- ensure_title_decryptable: pass a clear stub without a key; a scrambled-
but-uncrackable title still hard-fails with CssKeyMissing.
- is_scrambled_pack: hardened scramble-evidence gate for the crack scan —
requires the MPEG-PS pack-start signature before trusting the 0x14
scramble bits, so a clear stub with stray 0x14 bits can't flip
saw_scrambled. The descramble loop keeps the looser is_scrambled.
- mux/resolve: ISO per-title gate routes through the clear-aware check.
Add PassProgress::bytes_retryable_total (NonTrimmed/NonScraped — failed
and awaiting retry), distinct from bytes_pending_total which also folds
in not-yet-attempted (NonTried) bytes. Set it at every construction site
(Sweep from the snapshot, Patch from stats, 0 for sequential/placeholder
paths). The disc-level 'lost' display in the CLI can now use
unreadable+retryable instead of unreadable+pending, so a healthy
in-progress rip no longer reports its unread remainder as lost.
Disc::copy gated its entire mapfile-resume dispatch behind
`if opts.multipass`, so a plain (non-multipass) `disc:// iso://`
copy always called sweep_internal(resume=false) — wiping the
mapfile + ISO and re-sweeping from sector 0. The CLI help and
rip_iso examples promise "auto-resumes if interrupted", and the
sweep already flushes a crash-safe mapfile per block, so the only
missing piece was consulting it on re-run.
Lift the mapfile-resume dispatch out of the multipass guard:
clean->no-op, size-mismatch->fresh sweep, NonTried tail->resume
sweep now apply to both modes. The patch (Pass N) dispatch on
retryable bytes stays multipass-only; a plain copy with a
fully-attempted mapfile returns a terminal result instead of
restarting. autorip (always multipass=true) is unchanged.
Add a regression test that an interrupted plain copy resumes the
NonTried tail and does NOT re-read the Finished prefix.
Targeted tests for the rc.6 surfaces, strengthening (not duplicating) the
regression tests the rc.6 commits already shipped. No production code changes.
aacs/keys.rs — resolve_keys_with_reason / classify_resolve_failure:
The E7021/E7022 split is already proven end-to-end through the
ensure_decryptable gate (disc/mod.rs). These pin the classifier directly at
the keys.rs seam for the branches the gate test does not reach:
- processing-keys-only + zero VID -> VidUnavailable (the gate test only
exercises the device-keys arm of has_derivation_material).
- VID PRESENT + material -> NoMaterial: a non-zero VID must never be
reported as VidUnavailable however much material is on hand (the has_vid
short-circuit; the gate test only uses the zero-VID sentinel).
- VID present + no material -> NoMaterial.
- version dispatch: version 1 routes the V10 resolver (stamps V10), any
other value routes the V20->V21 chain; a resolved disc returns Ok, never
Err(ResolveFailure).
unlock.rs — Unlocker seam introspection + ordering:
- matching_name reports the first matching unlocker without running it, and
is None for an unsupported drive; registered_count grows after a
registration (monotonic check — the registry is process-wide and shared
across the unlock tests, so no exact-delta assertion).
- route_unlock first-registered-match-wins: two unlockers matching the same
identity, the earlier-registered one runs and the later is never consulted.
The dir:// extractor set the AACS unit-alignment base ONCE to the first
extent's start, then read every extent against that single base. For a
multi-extent (fragmented / Long-AD / continuation-ICB) file the second
and later extents start at arbitrary LBAs whose offset from the first
extent is generally not a multiple of 3 sectors, so the first read of
each later extent failed the decrypt-on-read gate
(is_unit_aligned(lba, unit_base)), returned DecryptFailed, and recorded
the whole extent as a zero-filled hole even though the data was readable.
Re-anchor the unit base PER extent (matching mux/disc.rs and
sector/prefetched.rs), so each clip's encrypted region gates on its own
unit grid. Same bug class as the rc.5.2 clip-anchor fix.
Also harden the extract + keydb write paths:
- finalize_file: fsync the .partial after set_len (the truncation runs
on a second handle the content fsync never touched) and fsync the
parent dir after rename so the new dirent is crash-durable.
- keydb write_atomic: fsync the parent dir after rename (POSIX dirent
durability), matching the finalize_file pattern.
- AACS tail batch: document that decrypt_sectors' trailing-partial
contract already handles the short final unit; no math change.
- decrypt-loss delta loads use Acquire (defensive happens-before if
file extraction is ever parallelised).
- is_windows_reserved: add CONIN$/CONOUT$/CLOCK$; reserved names are
now substituted (prefix _) instead of aborting the whole tree walk, so
a legal Linux-authored NUL.cfg extracts.
- http_get header cap: >= MAX_HEADER_BYTES (was > , one byte over).
Regression tests: multi-extent AACS file (Δ4-sector extents) extracts
both extents with zero loss; focused per-extent alignment-arithmetic
test; reserved-name substitution assertions.
When key resolution had derivation material (device or processing keys)
but no Volume ID was available to derive the unit key, surface
Error::AacsVidUnavailable instead of the generic NoDiscKey. When there
was no usable key material at all, keep NoDiscKey.
resolve_keys_classical / resolve_keys_v21 still return a bare
Option<ResolvedKeys> (all existing callers unchanged); a new
resolve_keys_with_reason wrapper threads the typed ResolveFailure
(VidUnavailable | NoMaterial) out. decrypt_with uses it; the
ensure_decryptable_keys gate maps a captured AacsVidUnavailable reason to
E7021, otherwise E7022. No decryption math, key derivation, or descramble
logic changed -- only the reason reported on a resolution failure.
Adds ensure_decryptable_aacs_vid_unavailable_vs_no_key proving both
branches (device-keys + zero VID -> E7021; no keys -> E7022).
Rewrite the AACS scan/VID trace lines so a reader understands them without
opening the source: name the real thing (AACS host certificate, Volume ID,
decryption key), say "key source" not "keydb", and describe what happened.
The VID flow is unchanged (unlocker OEM VID → cert handshake → continue); a
missing VID is logged, never fatal. All strings are in tracing macros (the
sanctioned debug-log channel) — no English added to any Error.
default_path() now resolves <dir of current exe>/keydb.cfg with no OS-config
fallback — freemkv is a portable standalone binary, so the keydb lives next to
it. --keydb still overrides.
Sibling of Disc::copy specialized to write per-file instead of a whole ISO
image, decrypting on the way out: walk the UDF tree, read each file's extents
through the shared DecryptingSectorSource (AACS unit-aligned, CSS per-VTS),
strip AACS/, sanitize host paths per component, .partial+rename, 1-shot with
per-file loss accounting (no mapfile; recovery stays the iso:// multipass
path). Reuses UdfFs + the decrypt seam; only the per-file orchestration is new.
Adds Disc::ensure_decryptable / ensure_decryptable_keys, the single decrypt
gate consulted before any copy or mux. When the source is encrypted and no
key resolved (and not --raw), abort with a typed error and write nothing,
instead of silently emitting ciphertext at exit 0. Unifies the prior ad-hoc
CSS/AACS checks.
Three Silence-of-the-Lambs (R2 PAL SD-DVD) follow-ups for rc.5.2.
SUB-TASK 1 — Windows Explorer showed 12.5 fps (half) for the 576i25 track.
Root cause: the DefaultDecodedFieldDuration (20 ms field) element rc.5.1
added to "fix" Windows fps did the opposite. With FlagInterlaced=1 +
DefaultDuration=40 ms + DefaultDecodedFieldDuration=20 ms, Explorer halved
to 12.5 fps and MediaInfo flipped to VFR. MakeMKV's correct rip omits the
field-duration element, keeps FlagInterlaced=1 + FieldOrder=TFF +
full-frame DefaultDuration (40 ms), and Explorer shows 25 fps / MediaInfo
CFR. Fix: MkvTrack::video now passes field_duration_ns == 0 so the element
is no longer written; the 1/DefaultDuration = 25 fps signal (the only one
tools trust) is the full-frame value. Interlace signalling (FlagInterlaced,
FieldOrder=TFF) is retained — MediaInfo reads scan type from the MPEG-2 ES
picture coding extension, so it still reports Interlaced / Top Field First.
Tests pin the new TrackEntry elements (element present/absent + values).
SUB-TASK 2 — opening "menu"/still-frame video. Traced the MPEG-2
opening-GOP path; the wrong/last seq header and PTS-floor-to-0 hypotheses
are RULED OUT with file:line evidence: codecPrivate is the FIRST sequence
header (read once at headers-ready, mkvstream.rs:115 + pipelined_stream.rs:289),
DVD VOBU structure guarantees each title opens on seq header + I-frame (no
mid-GOP open), the parser back-anchors leading still-frames to the disc's
real timeline (mpeg2.rs:296-303), and the muxer anchors base on the opening
keyframe's real PTS so the t=0 floor (mkv.rs:963) never corrupts it.
Regression tests pin all three (parser + muxer level).
SUB-TASK 3 — make --log-level 3 self-sufficient (diag.rs + minimal hooks).
(a) dump the ACTUAL MKV TrackEntry elements written per track
(tag=mkv.track: FlagInterlaced, FieldOrder, DefaultDuration, field duration,
Display dims, codecPrivate hex) so Windows-fps-class metadata is verifiable
from a log alone. (b) capture the first ~100 coded frames per track (raw)
to <output>.opening.bin with a per-frame summary line (tag=mkv.opening.frame:
track, key/delta, size, PTS) so opening-GOP/menu issues are diagnosable from
a future log without the disc. Both gated to log-level 3; normal runs open
no side file and record nothing.
CI gate (Rust 1.86): fmt --check, clippy -D warnings, and test --tests all
green.
The DVD AC-3 sub-stream probe recorded the FIRST decodable frame of each
physical 0x8x sub-stream as its channel count. A DVD feature opens with
logos/warnings whose audio is often a thin 2.0 bed on 0x80 before the
real 5.1 main mix begins a fraction of a second later. The probe locked
onto that opening 2.0 frame and reported 0x80=2, missing the 5.1
entirely (confirmed on Greenland: 0x80's head frames are acmod=2, then
acmod=7+lfe). With no 6-channel sub-stream found, channel-match routing
fell back to the ordinal map — harmless on Greenland, but on a disc where
the 5.1 lives on a non-ordinal sub-stream the wrong-substream bug stays
unfixed.
Fix: scan EVERY 0x0B77 frame of each sub-stream in the probe window and
keep the MAXIMUM channel count (the sub-stream's real main-mix
capability), advancing frame-by-frame via ac3_frame_size so a frame body
can't be mistaken for a new sync. Also bump PROBE_SECTORS 512->1024: the
1 MiB head window saw ONLY 0x80; 2 MiB reliably contains a frame of every
physical sub-stream.
Greenland tag=dvd.substream: before 0x80=2 (only); after 0x80=6, 0x81=2,
0x82=2 — matching the IFO and the decoded output.
Adds probe_reads_max_channels_no_cross_contamination regression test.
1. HEVC CRA->BLA false-trigger on 33-bit PTS wraparound
(src/mux/codec/hevc.rs): the clip-boundary auto-detect compared the
RAW 33-bit PES PTS against the high-water mark, so a single-clip title
crossing 2^33->0 (~26.5h) false-armed pending_clip_boundary and rewrote
a legitimate in-clip CRA(21)->BLA_W_LP(16), dropping valid RASL pictures
(visible corruption) and breaking the single-clip byte-identical
guarantee. Now unwrap the PTS onto a monotonic 64-bit timeline first
(a near-full-period backstep is a wrap: add 2^33, update the watermark,
do not arm). Regression test cra_after_33bit_pts_wrap_not_rewritten;
the genuine-clip-join test still passes.
2. Single-pass recovery read bypassed the transport-failure abort
(src/mux/disc.rs): the line-442 short-circuit only inspected the 10s
read res. A transport failure (status 0xFF, wedged USB bridge) on the
60s recovery read fell into the skip_errors branch and zero-filled/
advanced, marching the disc at one bridge-recovery per probe
(run-forever, hard rule #2). Re-check the recovery error for
is_scsi_transport_failure() before the skip block and abort with
Error::DiscRead. Test transport_failure_on_recovery_read_aborts_even_with_skip_errors.
3. Recovery-read SUCCESS branch had no coverage (src/mux/disc.rs tests):
added RecoverableReader (errors when recovery=false, succeeds when
recovery=true) and test recovery_read_success_muxes_recovered_data_no_skip
driving fill_extents to the size-1 bottom-out and asserting the recovered
data is muxed (counters advance, no skip).
4. TrueHD channel-correction probe omitted set_unit_base
(src/disc/mod.rs correct_truehd_channels): the probe read via a
DecryptingSectorSource without anchoring the AACS unit-alignment gate,
so it degraded to absolute start_lba % 3 and returned DecryptFailed on a
non-3-aligned extent, silently understating Atmos/7.1 as 5.1. Now call
set_unit_base(ext.start_lba) before the probe read (no-op for CSS/None).
5. is_unit_aligned lba<unit_base latent trap (src/aacs/decrypt.rs):
wrapping_sub mis-gated when lba < unit_base (2^32 == 1 mod 3). Switched
to saturating_sub (clamps offset to 0, a unit boundary) and pinned the
contract with is_unit_aligned_lba_below_base_is_well_defined plus
is_unit_aligned_relative_to_base.
cargo +1.86 fmt --check / clippy -D warnings / test --tests all green.
Fixes the "Silence of the Lambs" R2 PAL wrong-substream rip: the feature's
IFO declares one 5.1 AC-3 stream, but the scan assigned it the on-wire
sub-stream id 0x80 purely by per-codec ordinal (ifo::assign_audio_sub_stream_ids).
On this disc the physical 0x80 carries the 2.0 down-mix and the 5.1 main mix
lives at a different 0x8x sub-stream, so the rip muxed 2.0 while labelling it
"Dolby Digital 5.1" (the acmod fixup in mkv.rs then corrected only the Channels
element, surfacing the mismatch as the "IFO claimed 6 but acmod says 2" warning
— too late to re-route).
New src/disc/dvd_audio_probe.rs probes each physical AC-3 sub-stream's real
channel count from the head of the feature (the acmod/lfeon of its first frame
after the 0x0B77 sync) and re-routes each IFO-declared AC-3 stream onto the
physical sub-stream whose actual channel count matches the declared count,
instead of trusting the ordinal. Wired into both mux demux paths
(DiscStream::new and resolve::build_iso_pipeline) over the decrypting reader,
so it works on CSS discs and the autorip ISO-remux path alike. Bounded
512-sector best-effort read; an empty/unreadable probe degrades to the original
ordinal mapping (no regression on normal discs).
The cell selection is left unchanged: the feature's cell 0 (cat=0x02, 302.4s)
is chapter 1 of the movie (matches MakeMKV's chapter map and 1h53 duration
exactly), so it must NOT be dropped — the perceived "wrong video at the start"
was the wrong 2.0 audio over the opening, the same root cause.
Diagnostics (--log-level 3): new tag=dvd.substream rows dump the ACTUAL acmod
channel count of each physical 0x8x sub-stream read from the VOB, and the
per-cell tag=dvd.cell verdict now spells out the keep/skip reason. With the
existing tag=dvd.aattr (IFO declared sub_id + channels) a bug log alone now
shows whether the ordinal 0x80 really carries the declared layout — no disc
needed to diagnose this class.
expose ac3::find_ac3_sync as pub(crate) for the probe.
The "TopGun bug" (Top Gun 1986 UHD, DV Profile 7 dual-layer): the auditor
flags the rip `corrupt` with a flood of HEVC "Could not find ref with POC N"
decode errors (POC 114/210/228/234/240/246/252/318/336/354/372/399 — one
cluster per clip join).
Root cause: the title is multiple .m2ts clips joined at non-seamless
boundaries (mpls connection_condition 0x05/0x06), read as one concatenated
stream. Each next clip opens with a CRA whose RASL leading pictures reference
pre-join frames gone after concatenation. hevc.rs already has the spec remedy
(rewrite splice CRA_NUT 21 → BLA_W_LP 16 so a decoder sets NoRaslOutput and
drops the dangling RASL), behind mark_clip_boundary() — but nothing ever
called it: connection_condition is not plumbed through the threaded mux
pipeline, so the whole CRA→BLA mechanism was dead code.
Fix: detect the boundary inside the parser from the bitstream. Each clip
carries its own PES PTS base, so a non-seamless join is a large backward
PTS reset. HevcParser::parse now tracks a PTS high-water mark and, on a
backward step beyond 3 s (270000 ticks @ 90 kHz — mirroring the mux-side
DISCONTINUITY_BACKSTEP_NS), arms the existing CRA→BLA rewrite for the new
clip's first IRAP. Self-contained: works for every mux path, no cross-thread
plumbing. The 3 s threshold sits above any HEVC B-frame reorder dip and far
below any clip duration, so it never false-triggers in-clip; single-clip /
seamless titles never arm it and stay byte-identical.
Adds regression test cra_at_auto_detected_pts_backstep_rewritten_to_bla
(in-clip dip must not trigger; splice CRA after a backward reset must become
BLA; one-shot). hevc suite 55/55, mux suite 780/780.
Implements the rc.5.2 quick-units list from the DVD coverage audit and
corrects the "passes-but-encodes-the-bug" tests that could not
distinguish correct from wrong behaviour.
New tests (each with the bug it guards):
mux/mkv.rs
- field_duration_is_direct_trackentry_child_not_in_video: depth-aware
check that DefaultDecodedFieldDuration (and DefaultDuration) are direct
TrackEntry children, NOT nested in the Video master. Replaces the flat
find_id byte-scan that passed either way. Adds master_children /
first_track_entry depth-walking helpers.
- pal_576i_emits_bt470bg_colour_codes / ntsc_480i_emits_smpte170m_colour_codes:
assert the actual CICP tuples written into the MKV Colour master —
PAL (5,5,5,1) vs NTSC (6,6,6,1) — not just stream-layer ColorSpace.
- ntsc_480i_field_order_is_tff_and_encoded: pins NTSC 480i hardcoded TFF
and its ~33.37ms/16.68ms frame/field durations, asserting the encoded
FlagInterlaced/FieldOrder bytes (480i was never exercised before).
ifo.rs
- video_attr_absolute_bytes_pin_real_layout: drives parse_video_attr with
HARDCODED real DVD-Video bytes (PAL/NTSC x 4:3/16:9, plus mpeg_version
in bits 7-6) instead of v_atr_byte, so a co-edit of the shift constants
can't re-seed the PAL-as-NTSC bug. Anchors that permitted_df bits (1-0)
are not read as the TV system.
disc/dvd.rs
- scan_dvd_titles_lpcm_routes_to_a0_pid_range: LPCM (coding 4) → sub-id
0xA0 → PID 0xBDA0, disjoint from the AC-3 0xBD8x space, channels kept.
- scan_dvd_titles_multiple_vobsub_tracks_distinct_pids: three VobSub
tracks → distinct 0x20+ordinal PIDs, per-language, shared palette.
css/mod.rs
- crack_outcome_reaches_cracked_with_span: drives the full crack scan to
CrackOutcome::Cracked via a Stevenson-crackable synthetic sector and
asserts crack_span recording (the Cracked branch was never exercised).
- recrack_succeeds_on_other_vts_extents: per-VTS re-crack SUCCESS path.
- all_locked_synthetic_iso_yields_css_key_missing_signal: all-locked
multi-extent ISO → ScrambledUncracked, the signal the scan converts to
css_error = Some(CssKeyMissing).
Corrected fixtures (passes-but-encodes-the-bug):
- scan_dvd_titles_mixed_audio_codecs_distinct_pids: real channel nibbles
(AC-3 5.1 = 6ch, DTS 2.0 = 2ch) replacing the 1ch placeholders; asserts
channel counts and exact canonical PIDs (0xBD80 / 0xBD88).
- ebml.rs FieldOrder comment: drop the stale "PAL DVD (576i) is
bottom-field-first" line that contradicted the TFF-for-all code.
The AACS unit-alignment gate measured `lba % 3` against absolute disc LBA 0,
but aligned units are anchored at each clip's encrypted-region start. A clip
whose start_lba is not 3-aligned had its readable units wrongly rejected with
"Decryption failed" (the big-title-only failure on some Blu-rays). One
canonical clip-anchored helper (`aacs::is_unit_aligned`) is now the single
source of truth for the decrypt-on-read gate; both mux read paths set the
per-extent `unit_base = start_lba` via a new `SectorSource::set_unit_base`.
Also moves key *mechanism* into the library: the encrypted sample reader
(`read_encrypted_units`) and the candidate-key resolution loop
(`resolve_and_apply`) now live here, so a key source is purely a lookup.
Regression test covers a clip based at a non-3-aligned LBA.
Single-pass disc->MKV has no Pass N, so its read bottom-out now issues one
bounded recovery read (recovery=true, ~60s ECC) before skipping or aborting,
matching the multipass patch. Fixes a transient/marginal sector surfaced as a
read failure direct-to-MKV while multipass recovered it. One read, not a loop
(hard rule #2); recovered data is used so no bogus-status hole reopens.
- CSS: unlock scrambled-sector reads on enforcing drives via bus-auth
only; classify sense 6F/03 as CSS-locked; early-bail on a fully locked
scan; gate the AACS handshake off DVD discs.
- DVD first-play menu no longer prepended to the feature: read the title
VOBS base from vtstt_vobs (0xC4), not the menu VOBS vtsm_vobs (0xC0).
- Interlaced field-duration (DefaultDecodedFieldDuration) written as a
direct TrackEntry child rather than inside Video, so Windows reports
the correct frame rate.
- Audio channel count read from the AC-3 bitstream; FieldOrder set to
TFF; per-track BPS tags.
- Structured disc diagnostics at --log-level 3; reduced per-operation
log spam.
Interlacing is detected upstream (PAL DVD -> R576i) but was dropped in
two places: the video label hardcoded a 'p' suffix, and the muxer never
wrote any scan-type flag, so MediaInfo inferred progressive and reported
576p for a 576i source.
- Add Resolution::is_interlaced() for the R*i variants.
- generate_video_label now branches i/p for the heights that can be
interlaced (1080, 576, 480) instead of always emitting 'p'.
- MkvTrack carries interlaced + field_order; the video serializer emits
FlagInterlaced (0x9A; 1=interlaced, 2=progressive) and, for interlaced
content, FieldOrder (0x9D) - bottom-field-first for PAL 576i,
top-field-first otherwise. Adds the EBML constants.
The VTS_AST_ATR byte 1 stores (channels - 1) in the low 3 bits, but we
were reading the high nibble. A normal 2.0 AC-3 track has 0x1 in the low
nibble and 0x0 in the high nibble, so we computed 0+1=1 and mislabeled
every stereo track as mono in the muxed Channels element.
Read the low 3 bits instead, and correct the two audio_attr tests that
had encoded (channels - 1) in the high nibble to match the buggy reader.
Drop rx and recycle_tx (now Option fields) before joining the producer
thread in BytePrefetcher::Drop. Without this, a non-EOF source fills the
depth-2 forward channel then spins in send_timeout(POLL_INTERVAL) forever
because rx is never drained; join() deadlocks. Adds a regression test
(drop_endless_prefetcher_joins_cleanly) that directly exercises the path.
When a keyframe AU carried an unchanged seq_header (stripped) but a
redefined entry_point (appended), the old append-then-reassert path
produced [entry_point, seq_header] — entry_point before seq_header,
violating SMPTE 421M which requires seq+entry before every RAP.
Replace the single shared prefix Vec + reassert_active() with per-type
temporaries (redefined_seq / redefined_ep) collected during the scan,
then assembled in canonical seq-then-entry order at keyframe time.
Non-keyframes still emit only genuine redefinitions, also seq-before-ep.
Removes the now-unused reassert_active() helper. Adds a regression test
covering the seq-unchanged / entry-redefined trigger case.
Comments at lines ~63 and ~102 misidentified 0x01 (first-item/seamless) as the
non-seamless trigger and labelled 0x05/0x06 as seamless — inverted vs the BD-ROM
spec and mpls.rs (which documents 1=seamless, 5/6=non-seamless). Corrected all
affected doc blocks; no logic change.
The comment at line 404 claimed "every non-Finished range" but the
immediately-following ranges_with call lists only NonTrimmed,
NonScraped, and Unreadable — deliberately omitting NonTried.
Update the comment to accurately reflect the actual status list and
explain that NonTried is excluded because it is handled by a preceding
sweep pass, not by patch.