Sweep the pinned toolchain to Rust 1.97
The Windows UI needs current winsafe, whose real minimum is 1.89 (its manifest under-declares 1.87 while it uses NonNull::from_ref). Rather than stop at the minimum, this goes to current stable and fixes what that costs. The counter-intuitive result: 1.97 is CHEAPER than 1.89. libfreemkv had 54 clippy errors at 1.89 and 6 at 1.97, because clippy tightened the noisy collapsible_if lint in between. Stopping at the minimum would have been the most expensive choice available. Roughly 47 lints across the eight repos, the large majority auto-fixed: libfreemkv 6, freemkv-engine 14, bdemu 8, freemkv-keysources 7, autorip 6, freemkv-unlock 3, freemkv-i18n 3. The hand-fixed ones are a descending sort to sort_by_key(Reverse), four manual checked-division sites, a loop counter replaced by enumerate, and a loop whose first let-else became a while-let. Worth recording for whoever bumps next: clippy is MSRV-AWARE. Those 54 lints only appear once the crate DECLARES 1.89 or later, because let-chains become available. A bare `cargo +1.89 clippy` against a manifest still pinned at 1.87 reports clean and is meaningless — gate with the real precommit script, which is also the only thing that covers build scripts. The pin still sits below the Mac default, so it keeps doing its job: catching lint drift locally before CI sees it.
This commit is contained in:
@@ -195,11 +195,11 @@ impl Ac3Parser {
|
||||
// First access unit that starts in this PES's own bytes: adopt
|
||||
// this PES's timestamp so a genuine PTS jump is followed instead
|
||||
// of the running cadence drifting past it.
|
||||
if let Some(a) = &anchor {
|
||||
if start >= a.at {
|
||||
frame_pts_ns = a.pts_ns;
|
||||
anchor = None;
|
||||
}
|
||||
if let Some(a) = &anchor
|
||||
&& start >= a.at
|
||||
{
|
||||
frame_pts_ns = a.pts_ns;
|
||||
anchor = None;
|
||||
}
|
||||
let duration_ns = frame_duration_ns(remaining, bsid);
|
||||
pending = Some(PendingAu {
|
||||
|
||||
+11
-11
@@ -365,17 +365,17 @@ impl CodecParser for H264Parser {
|
||||
const HIGH_PROFILES: [u8; 14] = [
|
||||
100, 110, 122, 144, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135,
|
||||
];
|
||||
if HIGH_PROFILES.contains(&profile_idc) {
|
||||
if let Some((chroma_fmt, depth_luma, depth_chroma)) = parse_sps_high_profile_ext(sps) {
|
||||
// byte 0: 111111xx — reserved(6) + chroma_format_idc(2)
|
||||
record.push(0xFC | (chroma_fmt & 0x03));
|
||||
// byte 1: 11111xxx — reserved(5) + bit_depth_luma_minus8(3)
|
||||
record.push(0xF8 | (depth_luma & 0x07));
|
||||
// byte 2: 11111xxx — reserved(5) + bit_depth_chroma_minus8(3)
|
||||
record.push(0xF8 | (depth_chroma & 0x07));
|
||||
// byte 3: num_of_sequence_parameter_set_ext (0 = none)
|
||||
record.push(0x00);
|
||||
}
|
||||
if HIGH_PROFILES.contains(&profile_idc)
|
||||
&& let Some((chroma_fmt, depth_luma, depth_chroma)) = parse_sps_high_profile_ext(sps)
|
||||
{
|
||||
// byte 0: 111111xx — reserved(6) + chroma_format_idc(2)
|
||||
record.push(0xFC | (chroma_fmt & 0x03));
|
||||
// byte 1: 11111xxx — reserved(5) + bit_depth_luma_minus8(3)
|
||||
record.push(0xF8 | (depth_luma & 0x07));
|
||||
// byte 2: 11111xxx — reserved(5) + bit_depth_chroma_minus8(3)
|
||||
record.push(0xF8 | (depth_chroma & 0x07));
|
||||
// byte 3: num_of_sequence_parameter_set_ext (0 = none)
|
||||
record.push(0x00);
|
||||
}
|
||||
|
||||
Some(record)
|
||||
|
||||
+18
-19
@@ -307,11 +307,10 @@ impl HevcParser {
|
||||
};
|
||||
let rbsp = strip_emulation_prevention(raw);
|
||||
let mut i = 0usize;
|
||||
loop {
|
||||
// payloadType: sum of 0xFF run + final byte.
|
||||
let Some(payload_type) = read_sei_ff_value(&rbsp, &mut i) else {
|
||||
break;
|
||||
};
|
||||
// payloadType: sum of 0xFF run + final byte. Exhausting the RBSP ends the
|
||||
// walk; the remaining `let ... else break` arms below handle a TRUNCATED
|
||||
// message, which is a different condition from a clean end.
|
||||
while let Some(payload_type) = read_sei_ff_value(&rbsp, &mut i) {
|
||||
// payloadSize: same ff-extension coding.
|
||||
let Some(payload_size) = read_sei_ff_value(&rbsp, &mut i) else {
|
||||
break;
|
||||
@@ -504,11 +503,11 @@ impl CodecParser for HevcParser {
|
||||
// 33-bit counter wrapped: add another period and re-check, rather
|
||||
// than treat the wrap as a backward clip reset.
|
||||
let mut unwrapped = raw_pts + self.pts_wrap_offset;
|
||||
if let Some(high) = self.high_pts {
|
||||
if high - unwrapped > PTS_WRAP_PERIOD / 2 {
|
||||
self.pts_wrap_offset += PTS_WRAP_PERIOD;
|
||||
unwrapped += PTS_WRAP_PERIOD;
|
||||
}
|
||||
if let Some(high) = self.high_pts
|
||||
&& high - unwrapped > PTS_WRAP_PERIOD / 2
|
||||
{
|
||||
self.pts_wrap_offset += PTS_WRAP_PERIOD;
|
||||
unwrapped += PTS_WRAP_PERIOD;
|
||||
}
|
||||
match self.high_pts {
|
||||
Some(high) if unwrapped < high - BACKSTEP_TICKS => {
|
||||
@@ -564,18 +563,18 @@ impl CodecParser for HevcParser {
|
||||
// `num_extra_slice_header_bits` — and thus the bit offset to
|
||||
// `slice_type` — is EXACT. With no PPS we decline rather than
|
||||
// guess, leaving coding `None` (honestly absent).
|
||||
if coding_type.is_none() && nal_type <= NAL_VCL_MAX {
|
||||
if let Some(num_extra) = self
|
||||
if coding_type.is_none()
|
||||
&& nal_type <= NAL_VCL_MAX
|
||||
&& let Some(num_extra) = self
|
||||
.cur_pps
|
||||
.as_deref()
|
||||
.and_then(hevc_num_extra_slice_header_bits)
|
||||
{
|
||||
coding_type = hevc_first_slice_coding_type(
|
||||
&data[nal_start..end],
|
||||
nal_type,
|
||||
num_extra,
|
||||
);
|
||||
}
|
||||
{
|
||||
coding_type = hevc_first_slice_coding_type(
|
||||
&data[nal_start..end],
|
||||
nal_type,
|
||||
num_extra,
|
||||
);
|
||||
}
|
||||
|
||||
match nal_type {
|
||||
|
||||
@@ -196,10 +196,10 @@ impl Mpeg2Parser {
|
||||
if let Some(h) = extract_seq_header(&data) {
|
||||
self.progressive_sequence = parse_progressive_sequence(&h);
|
||||
self.seq_header = Some(h);
|
||||
if let Some((num, den)) = self.frame_rate() {
|
||||
if num > 0 {
|
||||
self.frame_duration_ns = 1_000_000_000i64 * den as i64 / num as i64;
|
||||
}
|
||||
if let Some((num, den)) = self.frame_rate()
|
||||
&& num > 0
|
||||
{
|
||||
self.frame_duration_ns = 1_000_000_000i64 * den as i64 / num as i64;
|
||||
}
|
||||
}
|
||||
// A GOP header (0xB8) or a fresh sequence header (0xB3) starts a new GOP,
|
||||
|
||||
@@ -168,12 +168,12 @@ impl SparsePtsReorder {
|
||||
// (assumes both anchors sit at a similar relative display slot),
|
||||
// but each GOP re-locks its own origin, so the estimate only sets
|
||||
// intra-GOP spacing.
|
||||
if self.dur_ns == 0 {
|
||||
if let (Some((p_held, _)), Some((p_next, _))) = (held.anchor, gop.anchor) {
|
||||
let span = p_next - p_held;
|
||||
if span > 0 && held.count > 0 {
|
||||
self.dur_ns = (span / held.count).max(1);
|
||||
}
|
||||
if self.dur_ns == 0
|
||||
&& let (Some((p_held, _)), Some((p_next, _))) = (held.anchor, gop.anchor)
|
||||
{
|
||||
let span = p_next - p_held;
|
||||
if span > 0 && held.count > 0 {
|
||||
self.dur_ns = (span / held.count).max(1);
|
||||
}
|
||||
}
|
||||
out = self.emit_gop(held);
|
||||
|
||||
+43
-43
@@ -344,49 +344,49 @@ impl CodecParser for TrueHdParser {
|
||||
// mid-AU would snap that AU's PTS backward/forward and break the
|
||||
// monotonic +AU_DURATION_NS cadence (A/V drift). Once the buffer is empty
|
||||
// the next PES legitimately begins a new AU and seeds the base.
|
||||
if self.buf.is_empty() {
|
||||
if let Some(pts) = pes.pts {
|
||||
// Resync to the authoritative PES PTS. TrueHD AUs are a fixed
|
||||
// sample count (40 @ 48 kHz), so the per-AU `+AU_DURATION_NS`
|
||||
// cadence is sample-accurate — more so than the disc's per-PES
|
||||
// PTS, which carries the source muxer's own rounding jitter.
|
||||
//
|
||||
// Two distinct backward steps must be handled OPPOSITELY:
|
||||
//
|
||||
// 1. Small backward jitter (sub-second PES rounding): when the
|
||||
// buffer empties exactly on a PES boundary and that PES's PTS
|
||||
// lands a few ticks *below* the running cadence, an
|
||||
// unconditional reset would set the next AU's timestamp below
|
||||
// the AU just emitted, producing non-monotonic block
|
||||
// timestamps a muxer rejects. CLAMP to the running position so
|
||||
// output stays strictly monotonic.
|
||||
//
|
||||
// 2. Large backward step (> DISCONTINUITY_BACKSTEP_NS): this is a
|
||||
// clip-boundary PTS reset — the title's clips are read as one
|
||||
// concatenated stream and a non-seamless boundary resets the
|
||||
// source PES PTS near zero. This is NOT jitter and must NOT be
|
||||
// clamped: clamping strands the audio at the previous clip's
|
||||
// tail cadence, so when `TimelineContinuity` later bumps the
|
||||
// global offset for the new epoch (driven by the video
|
||||
// back-jump) the stranded-high audio PTS is flung ~a whole
|
||||
// clip past the frontier, producing the non-monotonic
|
||||
// audio-DTS band on multi-clip titles (Dune: Part Two, Top
|
||||
// Gun). ADOPT the raw reset so the per-track raw PTS that
|
||||
// reaches `TimelineContinuity` carries the true boundary, and
|
||||
// the corrector rebases it exactly as it already does for the
|
||||
// DTS / AC-3 parsers (which never clamp). Same threshold the
|
||||
// timeline corrector uses to classify a discontinuity.
|
||||
//
|
||||
// A genuine forward gap/discontinuity is always adopted by the
|
||||
// `.max()`.
|
||||
let new = pts_to_ns(pts);
|
||||
if new < self.next_pts_ns - DISCONTINUITY_BACKSTEP_NS {
|
||||
// Clip-boundary reset: take the raw PTS, restart the cadence.
|
||||
self.next_pts_ns = new;
|
||||
} else {
|
||||
// Within-clip jitter (or forward progression): stay monotonic.
|
||||
self.next_pts_ns = self.next_pts_ns.max(new);
|
||||
}
|
||||
if self.buf.is_empty()
|
||||
&& let Some(pts) = pes.pts
|
||||
{
|
||||
// Resync to the authoritative PES PTS. TrueHD AUs are a fixed
|
||||
// sample count (40 @ 48 kHz), so the per-AU `+AU_DURATION_NS`
|
||||
// cadence is sample-accurate — more so than the disc's per-PES
|
||||
// PTS, which carries the source muxer's own rounding jitter.
|
||||
//
|
||||
// Two distinct backward steps must be handled OPPOSITELY:
|
||||
//
|
||||
// 1. Small backward jitter (sub-second PES rounding): when the
|
||||
// buffer empties exactly on a PES boundary and that PES's PTS
|
||||
// lands a few ticks *below* the running cadence, an
|
||||
// unconditional reset would set the next AU's timestamp below
|
||||
// the AU just emitted, producing non-monotonic block
|
||||
// timestamps a muxer rejects. CLAMP to the running position so
|
||||
// output stays strictly monotonic.
|
||||
//
|
||||
// 2. Large backward step (> DISCONTINUITY_BACKSTEP_NS): this is a
|
||||
// clip-boundary PTS reset — the title's clips are read as one
|
||||
// concatenated stream and a non-seamless boundary resets the
|
||||
// source PES PTS near zero. This is NOT jitter and must NOT be
|
||||
// clamped: clamping strands the audio at the previous clip's
|
||||
// tail cadence, so when `TimelineContinuity` later bumps the
|
||||
// global offset for the new epoch (driven by the video
|
||||
// back-jump) the stranded-high audio PTS is flung ~a whole
|
||||
// clip past the frontier, producing the non-monotonic
|
||||
// audio-DTS band on multi-clip titles (Dune: Part Two, Top
|
||||
// Gun). ADOPT the raw reset so the per-track raw PTS that
|
||||
// reaches `TimelineContinuity` carries the true boundary, and
|
||||
// the corrector rebases it exactly as it already does for the
|
||||
// DTS / AC-3 parsers (which never clamp). Same threshold the
|
||||
// timeline corrector uses to classify a discontinuity.
|
||||
//
|
||||
// A genuine forward gap/discontinuity is always adopted by the
|
||||
// `.max()`.
|
||||
let new = pts_to_ns(pts);
|
||||
if new < self.next_pts_ns - DISCONTINUITY_BACKSTEP_NS {
|
||||
// Clip-boundary reset: take the raw PTS, restart the cadence.
|
||||
self.next_pts_ns = new;
|
||||
} else {
|
||||
// Within-clip jitter (or forward progression): stay monotonic.
|
||||
self.next_pts_ns = self.next_pts_ns.max(new);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -216,12 +216,11 @@ impl CodecParser for Vc1Parser {
|
||||
let end = find_next_sc(data, i + 4).unwrap_or(data.len());
|
||||
let sh = &data[i..end];
|
||||
// Try to parse resolution from advanced profile sequence header
|
||||
if self.seq_header.is_none() {
|
||||
if let Some((w, h)) = parse_vc1_resolution(sh) {
|
||||
if self.seq_header.is_none()
|
||||
&& let Some((w, h)) = parse_vc1_resolution(sh) {
|
||||
self.width = w;
|
||||
self.height = h;
|
||||
}
|
||||
}
|
||||
// Collect into a scratch Vec so handle_header can
|
||||
// append; we discard the Vec and only keep the flag.
|
||||
let mut scratch = Vec::new();
|
||||
@@ -250,12 +249,11 @@ impl CodecParser for Vc1Parser {
|
||||
}
|
||||
has_entry_point = true;
|
||||
}
|
||||
SC_FRAME => {
|
||||
SC_FRAME
|
||||
// Frame data starts at this start code
|
||||
if frame_start.is_none() {
|
||||
if frame_start.is_none() => {
|
||||
frame_start = Some(i);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
i += 4;
|
||||
|
||||
+43
-44
@@ -512,16 +512,16 @@ impl DiscStream {
|
||||
// priority, mirroring the multipass sweep's transport-failure rule
|
||||
// in `read_error::handle_read_error`. The CLI/UX surfaces this so the
|
||||
// user power-cycles the drive (or switches to multipass recovery).
|
||||
if let Some(e) = res.as_ref().err() {
|
||||
if e.is_scsi_transport_failure() {
|
||||
let (status, sense) = extract_scsi_context(e);
|
||||
return Err(crate::error::Error::DiscRead {
|
||||
sector: lba as u64,
|
||||
status: Some(status),
|
||||
sense,
|
||||
}
|
||||
.into());
|
||||
if let Some(e) = res.as_ref().err()
|
||||
&& e.is_scsi_transport_failure()
|
||||
{
|
||||
let (status, sense) = extract_scsi_context(e);
|
||||
return Err(crate::error::Error::DiscRead {
|
||||
sector: lba as u64,
|
||||
status: Some(status),
|
||||
sense,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
if (sectors as u32) <= align {
|
||||
@@ -571,16 +571,16 @@ impl DiscStream {
|
||||
// a dead bridge as a skippable unit and marching the whole disc
|
||||
// at one bridge-recovery per probe (hard rule #2, "runs forever,
|
||||
// no MKV"). Re-check `rec` and abort, mirroring line 442.
|
||||
if let Some(e) = rec.as_ref().err() {
|
||||
if e.is_scsi_transport_failure() {
|
||||
let (status, sense) = extract_scsi_context(e);
|
||||
return Err(crate::error::Error::DiscRead {
|
||||
sector: lba as u64,
|
||||
status: Some(status),
|
||||
sense,
|
||||
}
|
||||
.into());
|
||||
if let Some(e) = rec.as_ref().err()
|
||||
&& e.is_scsi_transport_failure()
|
||||
{
|
||||
let (status, sense) = extract_scsi_context(e);
|
||||
return Err(crate::error::Error::DiscRead {
|
||||
sector: lba as u64,
|
||||
status: Some(status),
|
||||
sense,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Recovery read also failed. Skip the WHOLE failed unit or bail.
|
||||
@@ -725,29 +725,27 @@ impl crate::pes::Stream for DiscStream {
|
||||
for pes in &demuxer.flush() {
|
||||
if let Some((_, track)) =
|
||||
self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid)
|
||||
{
|
||||
if let Some((_, parser)) =
|
||||
&& let Some((_, parser)) =
|
||||
self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid)
|
||||
{
|
||||
let resync = &mut self.resync;
|
||||
let is_video = &self.is_video;
|
||||
let pending = &mut self.pending_frames;
|
||||
for frame in parser.parse(pes) {
|
||||
// Same B1 gate — a concealed gap can leave a
|
||||
// post-gap frame in the demuxer's final flush.
|
||||
let emit = match resync.get_mut(*track) {
|
||||
Some(gate) => gate.admit(
|
||||
is_video.get(*track).copied().unwrap_or(false),
|
||||
frame.discontinuity,
|
||||
frame.keyframe,
|
||||
),
|
||||
None => true,
|
||||
};
|
||||
if emit {
|
||||
pending.push_back(crate::pes::PesFrame::from_codec_frame(
|
||||
*track, frame,
|
||||
));
|
||||
}
|
||||
{
|
||||
let resync = &mut self.resync;
|
||||
let is_video = &self.is_video;
|
||||
let pending = &mut self.pending_frames;
|
||||
for frame in parser.parse(pes) {
|
||||
// Same B1 gate — a concealed gap can leave a
|
||||
// post-gap frame in the demuxer's final flush.
|
||||
let emit = match resync.get_mut(*track) {
|
||||
Some(gate) => gate.admit(
|
||||
is_video.get(*track).copied().unwrap_or(false),
|
||||
frame.discontinuity,
|
||||
frame.keyframe,
|
||||
),
|
||||
None => true,
|
||||
};
|
||||
if emit {
|
||||
pending.push_back(crate::pes::PesFrame::from_codec_frame(
|
||||
*track, frame,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1021,10 +1019,11 @@ impl crate::pes::Stream for DiscStream {
|
||||
return true;
|
||||
}
|
||||
for (idx, s) in self.title.streams.iter().enumerate() {
|
||||
if let crate::disc::Stream::Video(v) = s {
|
||||
if !v.secondary && self.codec_private(idx).is_none() {
|
||||
return false;
|
||||
}
|
||||
if let crate::disc::Stream::Video(v) = s
|
||||
&& !v.secondary
|
||||
&& self.codec_private(idx).is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
|
||||
@@ -436,7 +436,7 @@ impl<W: Write> M2tsMux<W> {
|
||||
}
|
||||
|
||||
fn maybe_emit_psi(&mut self) -> io::Result<()> {
|
||||
if self.packets_written == 0 || self.packets_written % PSI_INTERVAL_PACKETS == 0 {
|
||||
if self.packets_written == 0 || self.packets_written.is_multiple_of(PSI_INTERVAL_PACKETS) {
|
||||
self.emit_pat()?;
|
||||
self.emit_pmt()?;
|
||||
}
|
||||
|
||||
+32
-33
@@ -1583,10 +1583,11 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
// track: the ReferenceBlock above is emitted for any video track, so a
|
||||
// single global slot produced cross-track references on a multi-video-track
|
||||
// title (MVC base + secondary view, or a disc with two angles).
|
||||
if keyframe && is_video {
|
||||
if let Some(slot) = self.last_video_keyframe_ticks.get_mut(track_idx) {
|
||||
*slot = Some(pts_ticks);
|
||||
}
|
||||
if keyframe
|
||||
&& is_video
|
||||
&& let Some(slot) = self.last_video_keyframe_ticks.get_mut(track_idx)
|
||||
{
|
||||
*slot = Some(pts_ticks);
|
||||
}
|
||||
self.frame_count += 1;
|
||||
|
||||
@@ -1600,29 +1601,29 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
// (it claims 5.1 on a 2.0 stream); the bitstream acmod is authoritative.
|
||||
// Only the first frame triggers it; the byte width is unchanged so the
|
||||
// patch is a single-byte in-place rewrite (then restore position).
|
||||
if let Some(fixup) = self.ac3_channel_fixups.get_mut(&track_idx) {
|
||||
if !fixup.corrected {
|
||||
match super::codec::ac3::acmod_channels(data) {
|
||||
Some(actual) if actual > 0 => {
|
||||
if actual != fixup.claimed {
|
||||
tracing::warn!(
|
||||
target: "mux",
|
||||
"AC-3 track {track_idx}: IFO claimed {} channels but bitstream acmod says {}; trusting the bitstream (possible wrong-stream selection)",
|
||||
fixup.claimed,
|
||||
actual,
|
||||
);
|
||||
let here = self.writer.stream_position()?;
|
||||
self.writer
|
||||
.seek(std::io::SeekFrom::Start(fixup.value_offset))?;
|
||||
self.writer.write_all(&[actual])?;
|
||||
self.writer.seek(std::io::SeekFrom::Start(here))?;
|
||||
}
|
||||
fixup.corrected = true;
|
||||
if let Some(fixup) = self.ac3_channel_fixups.get_mut(&track_idx)
|
||||
&& !fixup.corrected
|
||||
{
|
||||
match super::codec::ac3::acmod_channels(data) {
|
||||
Some(actual) if actual > 0 => {
|
||||
if actual != fixup.claimed {
|
||||
tracing::warn!(
|
||||
target: "mux",
|
||||
"AC-3 track {track_idx}: IFO claimed {} channels but bitstream acmod says {}; trusting the bitstream (possible wrong-stream selection)",
|
||||
fixup.claimed,
|
||||
actual,
|
||||
);
|
||||
let here = self.writer.stream_position()?;
|
||||
self.writer
|
||||
.seek(std::io::SeekFrom::Start(fixup.value_offset))?;
|
||||
self.writer.write_all(&[actual])?;
|
||||
self.writer.seek(std::io::SeekFrom::Start(here))?;
|
||||
}
|
||||
// Frame too short to carry the BSI bits — keep the passed
|
||||
// (IFO) value and try again on the next frame.
|
||||
_ => {}
|
||||
fixup.corrected = true;
|
||||
}
|
||||
// Frame too short to carry the BSI bits — keep the passed
|
||||
// (IFO) value and try again on the next frame.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1767,14 +1768,12 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
// with a 1-byte size VINT covering the remaining 19 bytes occupies
|
||||
// exactly 1 + 1 + 19 = 21 bytes, overwriting the entry in place without
|
||||
// shifting any following element.
|
||||
if !have_cues {
|
||||
if let Some(entry_pos) = self.cues_seek_entry_pos {
|
||||
self.writer.seek(std::io::SeekFrom::Start(entry_pos))?;
|
||||
ebml::write_id(&mut self.writer, ebml::VOID)?;
|
||||
// 19 = 21-byte entry minus the Void ID (1) and size (1) bytes.
|
||||
ebml::write_size(&mut self.writer, 19)?;
|
||||
self.writer.write_all(&[0u8; 19])?;
|
||||
}
|
||||
if !have_cues && let Some(entry_pos) = self.cues_seek_entry_pos {
|
||||
self.writer.seek(std::io::SeekFrom::Start(entry_pos))?;
|
||||
ebml::write_id(&mut self.writer, ebml::VOID)?;
|
||||
// 19 = 21-byte entry minus the Void ID (1) and size (1) bytes.
|
||||
ebml::write_size(&mut self.writer, 19)?;
|
||||
self.writer.write_all(&[0u8; 19])?;
|
||||
}
|
||||
self.writer.seek(std::io::SeekFrom::End(0))?;
|
||||
|
||||
|
||||
@@ -254,11 +254,11 @@ impl MvcMerge {
|
||||
.front()
|
||||
.map(|pb| pb.additional.is_some())
|
||||
.unwrap_or(false);
|
||||
if front_ready || self.pending_base.len() > MVC_PAIR_WINDOW {
|
||||
if let Some(pb) = self.pending_base.pop_front() {
|
||||
out.push((pb.frame, pb.additional));
|
||||
continue;
|
||||
}
|
||||
if (front_ready || self.pending_base.len() > MVC_PAIR_WINDOW)
|
||||
&& let Some(pb) = self.pending_base.pop_front()
|
||||
{
|
||||
out.push((pb.frame, pb.additional));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -159,11 +159,9 @@ fn parse_eac3(f: &[u8]) -> Option<DolbyConfig> {
|
||||
// samples at sample_rate. rate = bytes·8·sr / samples / 1000.
|
||||
let frame_bytes = (frmsiz as u64 + 1) * 2;
|
||||
let samples = blocks as u64 * 256;
|
||||
let data_rate_kbps = if samples > 0 {
|
||||
((frame_bytes * 8 * sample_rate as u64) / samples / 1000) as u16
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let data_rate_kbps = (frame_bytes * 8 * sample_rate as u64)
|
||||
.checked_div(samples)
|
||||
.map_or(0, |r| (r / 1000) as u16);
|
||||
|
||||
Some(DolbyConfig {
|
||||
fscod,
|
||||
|
||||
+11
-8
@@ -285,11 +285,13 @@ impl<W: Write + Seek> Mp4Sink<W> {
|
||||
|
||||
let mut tracks = Vec::new();
|
||||
let mut route = vec![None; title.streams.len()];
|
||||
let mut next_id = 1u32;
|
||||
let mut video_codec = Codec::Hevc;
|
||||
for &i in &report.included {
|
||||
let track_id = next_id;
|
||||
next_id += 1;
|
||||
// Track ids are 1-based and assigned in inclusion order. `moov`'s
|
||||
// next_track_id is NOT derived from this counter — it is max(track_id) + 1
|
||||
// computed after the sample-less retain, since ids are handed out here
|
||||
// before any track is dropped.
|
||||
for (n, &i) in report.included.iter().enumerate() {
|
||||
let track_id = n as u32 + 1;
|
||||
route[i] = Some(tracks.len());
|
||||
match &title.streams[i] {
|
||||
DiscStream::Video(v) => {
|
||||
@@ -440,10 +442,11 @@ impl<W: Write + Seek + Send> Stream for Mp4Sink<W> {
|
||||
// cost us the frame. Dropping leading frames here lost audio silently, and
|
||||
// a track whose frames never parsed vanished from the output entirely with
|
||||
// no report; finish() now decides that case loudly instead.
|
||||
if self.tracks[slot].media == Media::Audio && self.tracks[slot].audio_entry.is_none() {
|
||||
if let Some(entry) = audio::dolby_sample_entry(self.tracks[slot].codec, &frame.data) {
|
||||
self.tracks[slot].audio_entry = Some(entry);
|
||||
}
|
||||
if self.tracks[slot].media == Media::Audio
|
||||
&& self.tracks[slot].audio_entry.is_none()
|
||||
&& let Some(entry) = audio::dolby_sample_entry(self.tracks[slot].codec, &frame.data)
|
||||
{
|
||||
self.tracks[slot].audio_entry = Some(entry);
|
||||
}
|
||||
let pts_ns = frame.pts;
|
||||
let offset = self.mdat_start + 16 + self.mdat_payload;
|
||||
|
||||
@@ -420,10 +420,11 @@ impl Stream for PipelinedPesStream {
|
||||
return true;
|
||||
}
|
||||
for (idx, s) in self.title.streams.iter().enumerate() {
|
||||
if let crate::disc::Stream::Video(v) = s {
|
||||
if !v.secondary && self.codec_private(idx).is_none() {
|
||||
return false;
|
||||
}
|
||||
if let crate::disc::Stream::Video(v) = s
|
||||
&& !v.secondary
|
||||
&& self.codec_private(idx).is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
|
||||
+15
-16
@@ -193,10 +193,10 @@ pub fn parse_url(url: &str) -> StreamUrl {
|
||||
return StreamUrl::Null;
|
||||
}
|
||||
}
|
||||
if let Some(rest) = url.strip_prefix("stdio://") {
|
||||
if rest.is_empty() {
|
||||
return StreamUrl::Stdio;
|
||||
}
|
||||
if let Some(rest) = url.strip_prefix("stdio://")
|
||||
&& rest.is_empty()
|
||||
{
|
||||
return StreamUrl::Stdio;
|
||||
}
|
||||
if let Some(rest) = url.strip_prefix("iso://") {
|
||||
return StreamUrl::Iso {
|
||||
@@ -1646,20 +1646,19 @@ pub(crate) fn resolve_mux_key_map_cached(
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let mut idx = pick(&samples, &pool);
|
||||
if idx.is_none() {
|
||||
if let Some(f) = fetch {
|
||||
if !samples.is_empty() {
|
||||
let fresh = f.unit_keys(&samples);
|
||||
if let crate::decrypt::DecryptKeys::Aacs { unit_keys, .. } = keys {
|
||||
for k in fresh {
|
||||
if !unit_keys.iter().any(|(_, h)| *h == k) {
|
||||
let i = unit_keys.len() as u32;
|
||||
unit_keys.push((i, k));
|
||||
}
|
||||
}
|
||||
idx = pick(&samples, unit_keys);
|
||||
if idx.is_none()
|
||||
&& let Some(f) = fetch
|
||||
&& !samples.is_empty()
|
||||
{
|
||||
let fresh = f.unit_keys(&samples);
|
||||
if let crate::decrypt::DecryptKeys::Aacs { unit_keys, .. } = keys {
|
||||
for k in fresh {
|
||||
if !unit_keys.iter().any(|(_, h)| *h == k) {
|
||||
let i = unit_keys.len() as u32;
|
||||
unit_keys.push((i, k));
|
||||
}
|
||||
}
|
||||
idx = pick(&samples, unit_keys);
|
||||
}
|
||||
}
|
||||
// `sample_units` draws REAL content (not authored-bad units), so a sample
|
||||
|
||||
+6
-6
@@ -53,12 +53,12 @@ impl StdioStream {
|
||||
/// zero-frame output stream still emits the magic + metadata header,
|
||||
/// keeping the wire protocol symmetric with the read side's read_header().
|
||||
fn ensure_header_written(&mut self) -> io::Result<()> {
|
||||
if let Some(w) = &mut self.writer {
|
||||
if !self.header_written {
|
||||
let m = meta::M2tsMeta::from_title(&self.disc_title);
|
||||
meta::write_header(w, &m)?;
|
||||
self.header_written = true;
|
||||
}
|
||||
if let Some(w) = &mut self.writer
|
||||
&& !self.header_written
|
||||
{
|
||||
let m = meta::M2tsMeta::from_title(&self.disc_title);
|
||||
meta::write_header(w, &m)?;
|
||||
self.header_written = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+8
-8
@@ -140,14 +140,14 @@ impl TimelineContinuity {
|
||||
// overflow: a panic out of the public `Stream::write` path in an
|
||||
// overflow-checked build, and in release a wrap to the opposite sign
|
||||
// that fires the straggler clamp on essentially every passive frame.
|
||||
if let Some(high) = self.high_ns {
|
||||
if mapped > high.saturating_add(DISCONTINUITY_BACKSTEP_NS) {
|
||||
let prev_mapped = raw_pts_ns.saturating_add(self.prev_offset_ns);
|
||||
if prev_mapped <= high
|
||||
&& prev_mapped >= high.saturating_sub(DISCONTINUITY_BACKSTEP_NS)
|
||||
{
|
||||
return prev_mapped;
|
||||
}
|
||||
if let Some(high) = self.high_ns
|
||||
&& mapped > high.saturating_add(DISCONTINUITY_BACKSTEP_NS)
|
||||
{
|
||||
let prev_mapped = raw_pts_ns.saturating_add(self.prev_offset_ns);
|
||||
if prev_mapped <= high
|
||||
&& prev_mapped >= high.saturating_sub(DISCONTINUITY_BACKSTEP_NS)
|
||||
{
|
||||
return prev_mapped;
|
||||
}
|
||||
}
|
||||
return mapped;
|
||||
|
||||
Reference in New Issue
Block a user