Fix truncated rips on dual-layer discs, propagate read errors

Use UDF file_extents() to read actual allocation descriptors instead
of assuming m2ts files are contiguous from file_start_lba. Dual-layer
UHD discs split large files across 70+ extents (~1 GB each) — the old
code created one extent from packet count which only covered the first
chunk, causing silent truncation at ~37%.

Also changed fill_extents() to return io::Result<bool> so read errors
propagate instead of being silently treated as EOF.
This commit is contained in:
Matt Jackson
2026-04-18 02:08:34 +00:00
parent a328ad3cd8
commit fba1eb189c
2 changed files with 22 additions and 27 deletions
+9 -8
View File
@@ -66,20 +66,21 @@ impl Disc {
pkt_count = clip_info.source_packet_count; pkt_count = clip_info.source_packet_count;
total_size += pkt_count as u64 * 192; total_size += pkt_count as u64 * 192;
// Get m2ts file start LBA and compute extent from packet count. // Get m2ts file extents from UDF allocation descriptors.
// BD-ROM m2ts files are contiguous on disc (mastering requirement). // Dual-layer discs split files across layers — UDF knows the real layout.
let m2ts_path = format!("/BDMV/STREAM/{}.m2ts", play_item.clip_id); let m2ts_path = format!("/BDMV/STREAM/{}.m2ts", play_item.clip_id);
let file_lba = udf_fs.file_start_lba(reader, &m2ts_path).unwrap_or(0); if let Ok(file_exts) = udf_fs.file_extents(reader, &m2ts_path) {
let total_bytes = pkt_count as u64 * 192; for (lba, sectors) in file_exts {
let total_sectors = total_bytes.div_ceil(2048) as u32; if sectors > 0 && lba > 0 {
if total_sectors > 0 && file_lba > 0 {
extents.push(Extent { extents.push(Extent {
start_lba: file_lba, start_lba: lba,
sector_count: total_sectors, sector_count: sectors,
}); });
} }
} }
} }
}
}
clips.push(Clip { clips.push(Clip {
clip_id: play_item.clip_id.clone(), clip_id: play_item.clip_id.clone(),
+6 -12
View File
@@ -117,9 +117,9 @@ impl DiscStream {
self.disc.as_ref() self.disc.as_ref()
} }
fn fill_extents(&mut self) -> bool { fn fill_extents(&mut self) -> io::Result<bool> {
if self.current_extent >= self.extents.len() { if self.current_extent >= self.extents.len() {
return false; return Ok(false);
} }
let ext_start = self.extents[self.current_extent].start_lba; let ext_start = self.extents[self.current_extent].start_lba;
let ext_sectors = self.extents[self.current_extent].sector_count; let ext_sectors = self.extents[self.current_extent].sector_count;
@@ -137,21 +137,15 @@ impl DiscStream {
let bytes = sectors as usize * 2048; let bytes = sectors as usize * 2048;
self.read_buf.resize(bytes, 0); self.read_buf.resize(bytes, 0);
match self self.reader
.reader .read_sectors(lba, sectors, &mut self.read_buf[..bytes])?;
.read_sectors(lba, sectors, &mut self.read_buf[..bytes])
{
Ok(_) => {
self.buf_valid = bytes; self.buf_valid = bytes;
self.current_offset += sectors as u32; self.current_offset += sectors as u32;
if self.current_offset >= ext_sectors { if self.current_offset >= ext_sectors {
self.current_extent += 1; self.current_extent += 1;
self.current_offset = 0; self.current_offset = 0;
} }
true Ok(true)
}
Err(_) => false,
}
} }
} }
@@ -166,7 +160,7 @@ impl crate::pes::Stream for DiscStream {
} }
loop { loop {
if !self.fill_extents() { if !self.fill_extents()? {
self.eof = true; self.eof = true;
// Flush demuxer — last PES packet may still be in the assembler // Flush demuxer — last PES packet may still be in the assembler
if let Some(ref mut demuxer) = self.ts_demuxer { if let Some(ref mut demuxer) = self.ts_demuxer {