v0.11.15: lint cleanup — fmt + clippy clean

This commit is contained in:
Matt Jackson
2026-04-21 18:52:55 +00:00
parent b8ba5ea308
commit d4818ad88e
16 changed files with 108 additions and 63 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog # Changelog
## 0.11.15 (2026-04-21)
### Lint cleanup
- Fix all `cargo fmt` and `cargo clippy -D warnings` across codebase.
- Remove unused imports (Codec, HdrFormat, ScanOptions, detect_max_batch_sectors, Extent).
- Fix CSS tuple pattern deref, collapsible if-statement, div_ceil reimplementation.
## 0.11.14 (2026-04-21) ## 0.11.14 (2026-04-21)
### Audit fixes: read recovery, verify, SCSI ### Audit fixes: read recovery, verify, SCSI
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "libfreemkv" name = "libfreemkv"
version = "0.11.14" version = "0.11.15"
edition = "2021" edition = "2021"
rust-version = "1.86" rust-version = "1.86"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
+2 -1
View File
@@ -348,7 +348,7 @@ fn read_disc_key(drive: &mut Drive, agid: u8, bus_key: &[u8; 5]) -> Result<[u8;
let candidate = super::lfsr::decrypt_key(0x00, player_key, &enc); let candidate = super::lfsr::decrypt_key(0x00, player_key, &enc);
// Check if any previous candidate matches (same disc key from different entry/pk) // Check if any previous candidate matches (same disc key from different entry/pk)
for &(ref prev, _, _) in &candidates { for (prev, _, _) in &candidates {
if *prev == candidate { if *prev == candidate {
return Ok(candidate); return Ok(candidate);
} }
@@ -391,6 +391,7 @@ fn read_raw_title_key(drive: &mut Drive, agid: u8, lba: u32) -> Result<[u8; 5]>
Ok(key) Ok(key)
} }
#[allow(dead_code)]
fn read_title_key( fn read_title_key(
drive: &mut Drive, drive: &mut Drive,
agid: u8, agid: u8,
+7 -9
View File
@@ -874,8 +874,8 @@ impl KeySource {
/// Standard KEYDB.cfg search locations (compatible with libaacs). /// Standard KEYDB.cfg search locations (compatible with libaacs).
const KEYDB_SEARCH_PATHS: &[&str] = &[ const KEYDB_SEARCH_PATHS: &[&str] = &[
".config/aacs/KEYDB.cfg", // libaacs standard path ".config/aacs/KEYDB.cfg", // libaacs standard path
".config/freemkv/keydb.cfg", // freemkv download path ".config/freemkv/keydb.cfg", // freemkv download path
]; ];
const KEYDB_SYSTEM_PATH: &str = "/etc/aacs/KEYDB.cfg"; const KEYDB_SYSTEM_PATH: &str = "/etc/aacs/KEYDB.cfg";
@@ -938,9 +938,7 @@ pub struct DiscId {
impl DiscId { impl DiscId {
/// Best available name: meta_title, then formatted volume_id. /// Best available name: meta_title, then formatted volume_id.
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
self.meta_title self.meta_title.as_deref().unwrap_or(&self.volume_id)
.as_deref()
.unwrap_or(&self.volume_id)
} }
} }
@@ -1031,10 +1029,10 @@ impl Disc {
{ {
let lba = disc.titles[0].extents.iter().find_map(|ext| { let lba = disc.titles[0].extents.iter().find_map(|ext| {
let mut buf = vec![0u8; 2048]; let mut buf = vec![0u8; 2048];
if session.read_sectors(ext.start_lba, 1, &mut buf).is_ok() { if session.read_sectors(ext.start_lba, 1, &mut buf).is_ok()
if crate::css::is_scrambled(&buf) { && crate::css::is_scrambled(&buf)
return Some(ext.start_lba); {
} return Some(ext.start_lba);
} }
None None
}); });
+7 -1
View File
@@ -714,7 +714,13 @@ impl SectorReader for Drive {
self.read(lba, count, buf, true) self.read(lba, count, buf, true)
} }
fn read_sectors_recover(&mut self, lba: u32, count: u16, buf: &mut [u8], recovery: bool) -> Result<usize> { fn read_sectors_recover(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
self.read(lba, count, buf, recovery) self.read(lba, count, buf, recovery)
} }
} }
+3 -10
View File
@@ -81,20 +81,13 @@ pub enum EventKind {
}, },
/// Binary search isolated and recovered a marginal sector. /// Binary search isolated and recovered a marginal sector.
SectorRecovered { SectorRecovered { sector: u64 },
sector: u64,
},
/// Sector unreadable, zero-filled (skip mode). /// Sector unreadable, zero-filled (skip mode).
SectorSkipped { SectorSkipped { sector: u64 },
sector: u64,
},
/// Binary search activated — batch failed, isolating bad sector. /// Binary search activated — batch failed, isolating bad sector.
BinarySearch { BinarySearch { sector: u64, batch_size: u16 },
sector: u64,
batch_size: u16,
},
/// Operation complete. /// Operation complete.
Complete { Complete {
+3 -1
View File
@@ -564,7 +564,9 @@ fn parse_pgc(data: &[u8], pgc_offset: usize, chapters: u16) -> Result<DvdTitle>
} }
// Program map: each byte is the first cell number (1-based) for that program // Program map: each byte is the first cell number (1-based) for that program
for p in 0..nr_of_programs { for p in 0..nr_of_programs {
if pgm_base + p >= data.len() { break; } if pgm_base + p >= data.len() {
break;
}
let first_cell = data[pgm_base + p] as usize; let first_cell = data[pgm_base + p] as usize;
// Chapter time = sum of cell durations before this program's first cell // Chapter time = sum of cell durations before this program's first cell
let time: f64 = cell_durations[..first_cell.saturating_sub(1)].iter().sum(); let time: f64 = cell_durations[..first_cell.saturating_sub(1)].iter().sum();
+3 -2
View File
@@ -142,7 +142,7 @@ pub fn apply(reader: &mut dyn SectorReader, udf: &UdfFs, titles: &mut [DiscTitle
/// Runs after BD-J label extraction — fills gaps with codec + channel descriptions. /// Runs after BD-J label extraction — fills gaps with codec + channel descriptions.
/// This is the central place for all fallback label generation. /// This is the central place for all fallback label generation.
pub fn fill_defaults(titles: &mut [crate::disc::DiscTitle]) { pub fn fill_defaults(titles: &mut [crate::disc::DiscTitle]) {
use crate::disc::{Codec, HdrFormat, Stream}; use crate::disc::Stream;
for title in titles.iter_mut() { for title in titles.iter_mut() {
for stream in &mut title.streams { for stream in &mut title.streams {
@@ -151,7 +151,8 @@ pub fn fill_defaults(titles: &mut [crate::disc::DiscTitle]) {
a.label = generate_audio_label(&a.codec, &a.channels, a.secondary); a.label = generate_audio_label(&a.codec, &a.channels, a.secondary);
} }
Stream::Video(v) if v.label.is_empty() => { Stream::Video(v) if v.label.is_empty() => {
v.label = generate_video_label(&v.codec, v.resolution.pixels(), &v.hdr, v.secondary); v.label =
generate_video_label(&v.codec, v.resolution.pixels(), &v.hdr, v.secondary);
} }
Stream::Subtitle(s) if s.forced => { Stream::Subtitle(s) if s.forced => {
// Ensure forced subs are labeled even if BD-J didn't set a name // Ensure forced subs are labeled even if BD-J didn't set a name
+1 -1
View File
@@ -90,9 +90,9 @@ pub(crate) mod platform;
pub mod profile; pub mod profile;
pub mod scsi; pub mod scsi;
pub mod sector; pub mod sector;
pub mod verify;
pub(crate) mod speed; pub(crate) mod speed;
pub(crate) mod udf; pub(crate) mod udf;
pub mod verify;
pub use drive::capture::{ pub use drive::capture::{
capture_drive_data, mask_bytes, mask_string, CapturedFeature, DriveCapture, capture_drive_data, mask_bytes, mask_string, CapturedFeature, DriveCapture,
+1 -1
View File
@@ -153,7 +153,7 @@ impl CodecParser for Mpeg2Parser {
bits += 64 * 8; bits += 64 * 8;
} }
} }
let total_bytes = 4 + ((bits + 7) / 8) as usize; let total_bytes = 4 + bits.div_ceil(8) as usize;
(sc + total_bytes).min(data.len()) (sc + total_bytes).min(data.len())
} }
} }
+9 -12
View File
@@ -50,30 +50,27 @@ impl TrueHdParser {
0 => { 0 => {
// 48 kHz // 48 kHz
static SIZES: [usize; 38] = [ static SIZES: [usize; 38] = [
64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 160, 160, 192, 192, 224, 224, 256,
160, 160, 192, 192, 224, 224, 256, 256, 320, 320, 256, 320, 320, 384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 896, 896,
384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 1024, 1024, 1152, 1152, 1280, 1280,
896, 896, 1024, 1024, 1152, 1152, 1280, 1280,
]; ];
SIZES.get(frmsizecod).copied().unwrap_or(0) SIZES.get(frmsizecod).copied().unwrap_or(0)
} }
1 => { 1 => {
// 44.1 kHz // 44.1 kHz
static SIZES: [usize; 38] = [ static SIZES: [usize; 38] = [
69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 174, 175, 208, 209, 243, 244,
174, 175, 208, 209, 243, 244, 278, 279, 348, 349, 278, 279, 348, 349, 417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 975, 976,
417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 1114, 1115, 1253, 1254, 1393, 1394,
975, 976, 1114, 1115, 1253, 1254, 1393, 1394,
]; ];
SIZES.get(frmsizecod).copied().unwrap_or(0) SIZES.get(frmsizecod).copied().unwrap_or(0)
} }
2 => { 2 => {
// 32 kHz // 32 kHz
static SIZES: [usize; 38] = [ static SIZES: [usize; 38] = [
96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 240, 240, 288, 288, 336, 336,
240, 240, 288, 288, 336, 336, 384, 384, 480, 480, 384, 384, 480, 480, 576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344,
576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344, 1536, 1536, 1728, 1728, 1920, 1920,
1344, 1344, 1536, 1536, 1728, 1728, 1920, 1920,
]; ];
SIZES.get(frmsizecod).copied().unwrap_or(0) SIZES.get(frmsizecod).copied().unwrap_or(0)
} }
+7 -2
View File
@@ -5,7 +5,7 @@
//! //!
//! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`. //! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`.
use crate::disc::{detect_max_batch_sectors, Disc, DiscTitle, Extent, ScanOptions}; use crate::disc::{Disc, DiscTitle, Extent};
use crate::event::{Event, EventKind}; use crate::event::{Event, EventKind};
use crate::sector::SectorReader; use crate::sector::SectorReader;
use std::io; use std::io;
@@ -171,7 +171,12 @@ impl DiscStream {
let offset = self.buf_valid; let offset = self.buf_valid;
if self if self
.reader .reader
.read_sectors_recover(lba, count, &mut self.read_buf[offset..offset + bytes], false) .read_sectors_recover(
lba,
count,
&mut self.read_buf[offset..offset + bytes],
false,
)
.is_ok() .is_ok()
{ {
self.buf_valid += bytes; self.buf_valid += bytes;
+28 -11
View File
@@ -5,7 +5,9 @@
//! cues and seek head are finalized at the end. //! cues and seek head are finalized at the end.
use super::ebml; use super::ebml;
use crate::disc::{AudioStream, Chapter, Codec, ColorSpace, HdrFormat, SubtitleStream, VideoStream}; use crate::disc::{
AudioStream, Chapter, Codec, ColorSpace, HdrFormat, SubtitleStream, VideoStream,
};
use std::io::{self, Seek, SeekFrom, Write}; use std::io::{self, Seek, SeekFrom, Write};
/// MKV track definition (built from disc stream metadata). /// MKV track definition (built from disc stream metadata).
@@ -24,10 +26,10 @@ pub struct MkvTrack {
pub display_width: u32, // display aspect ratio width (0 = same as pixel) pub display_width: u32, // display aspect ratio width (0 = same as pixel)
pub display_height: u32, // display aspect ratio height (0 = same as pixel) pub display_height: u32, // display aspect ratio height (0 = same as pixel)
// HDR colour metadata // HDR colour metadata
pub colour_matrix: u8, // MatrixCoefficients (9=bt2020nc) pub colour_matrix: u8, // MatrixCoefficients (9=bt2020nc)
pub colour_transfer: u8, // TransferCharacteristics (16=smpte2084/PQ) pub colour_transfer: u8, // TransferCharacteristics (16=smpte2084/PQ)
pub colour_primaries: u8, // Primaries (9=bt2020) pub colour_primaries: u8, // Primaries (9=bt2020)
pub colour_range: u8, // Range (1=tv/limited) pub colour_range: u8, // Range (1=tv/limited)
// Audio-specific // Audio-specific
pub sample_rate: f64, pub sample_rate: f64,
pub channels: u8, pub channels: u8,
@@ -52,7 +54,7 @@ impl MkvTrack {
}; };
let (matrix, transfer, primaries, range) = match v.color_space { let (matrix, transfer, primaries, range) = match v.color_space {
ColorSpace::Bt2020 => (9, 16, 9, 1), // bt2020nc, PQ, bt2020, limited ColorSpace::Bt2020 => (9, 16, 9, 1), // bt2020nc, PQ, bt2020, limited
ColorSpace::Bt709 => (1, 1, 1, 1), // bt709 ColorSpace::Bt709 => (1, 1, 1, 1), // bt709
ColorSpace::Unknown => (0, 0, 0, 0), ColorSpace::Unknown => (0, 0, 0, 0),
}; };
// Override transfer for non-PQ HDR // Override transfer for non-PQ HDR
@@ -268,7 +270,11 @@ impl<W: Write + Seek> MkvMuxer<W> {
// DefaultDuration — frame duration in nanoseconds // DefaultDuration — frame duration in nanoseconds
if track.default_duration_ns > 0 { if track.default_duration_ns > 0 {
ebml::write_uint(&mut writer, ebml::DEFAULT_DURATION, track.default_duration_ns)?; ebml::write_uint(
&mut writer,
ebml::DEFAULT_DURATION,
track.default_duration_ns,
)?;
} }
// Video-specific // Video-specific
@@ -278,13 +284,25 @@ impl<W: Write + Seek> MkvMuxer<W> {
ebml::write_uint(&mut writer, ebml::PIXEL_HEIGHT, track.pixel_height as u64)?; ebml::write_uint(&mut writer, ebml::PIXEL_HEIGHT, track.pixel_height as u64)?;
if track.display_width > 0 && track.display_height > 0 { if track.display_width > 0 && track.display_height > 0 {
ebml::write_uint(&mut writer, ebml::DISPLAY_WIDTH, track.display_width as u64)?; ebml::write_uint(&mut writer, ebml::DISPLAY_WIDTH, track.display_width as u64)?;
ebml::write_uint(&mut writer, ebml::DISPLAY_HEIGHT, track.display_height as u64)?; ebml::write_uint(
&mut writer,
ebml::DISPLAY_HEIGHT,
track.display_height as u64,
)?;
} }
// Colour metadata (HDR) // Colour metadata (HDR)
if track.colour_matrix > 0 || track.colour_transfer > 0 { if track.colour_matrix > 0 || track.colour_transfer > 0 {
let col_pos = ebml::start_master(&mut writer, ebml::COLOUR)?; let col_pos = ebml::start_master(&mut writer, ebml::COLOUR)?;
ebml::write_uint(&mut writer, ebml::MATRIX_COEFFICIENTS, track.colour_matrix as u64)?; ebml::write_uint(
ebml::write_uint(&mut writer, ebml::TRANSFER_CHARACTERISTICS, track.colour_transfer as u64)?; &mut writer,
ebml::MATRIX_COEFFICIENTS,
track.colour_matrix as u64,
)?;
ebml::write_uint(
&mut writer,
ebml::TRANSFER_CHARACTERISTICS,
track.colour_transfer as u64,
)?;
ebml::write_uint(&mut writer, ebml::PRIMARIES, track.colour_primaries as u64)?; ebml::write_uint(&mut writer, ebml::PRIMARIES, track.colour_primaries as u64)?;
ebml::write_uint(&mut writer, ebml::RANGE, track.colour_range as u64)?; ebml::write_uint(&mut writer, ebml::RANGE, track.colour_range as u64)?;
ebml::end_master(&mut writer, col_pos)?; ebml::end_master(&mut writer, col_pos)?;
@@ -845,4 +863,3 @@ mod tests {
); );
} }
} }
+2 -3
View File
@@ -246,7 +246,6 @@ impl SgIoTransport {
device.to_path_buf() device.to_path_buf()
} }
} }
impl Drop for SgIoTransport { impl Drop for SgIoTransport {
@@ -335,8 +334,8 @@ impl ScsiTransport for SgIoTransport {
// Wait for completion with enforceable timeout. // Wait for completion with enforceable timeout.
// Retry on EINTR (signal interrupted poll) with remaining time. // Retry on EINTR (signal interrupted poll) with remaining time.
let deadline = std::time::Instant::now() let deadline =
+ std::time::Duration::from_millis(timeout_ms as u64); std::time::Instant::now() + std::time::Duration::from_millis(timeout_ms as u64);
let pr = loop { let pr = loop {
let remaining = deadline let remaining = deadline
.saturating_duration_since(std::time::Instant::now()) .saturating_duration_since(std::time::Instant::now())
+7 -1
View File
@@ -16,7 +16,13 @@ pub trait SectorReader: Send {
/// Read with explicit recovery flag. /// Read with explicit recovery flag.
/// true = full retry/reset loop (for ripping). false = single attempt, fast fail (for verify). /// true = full retry/reset loop (for ripping). false = single attempt, fast fail (for verify).
/// Default: delegates to read_sectors (recovery=true behavior). /// Default: delegates to read_sectors (recovery=true behavior).
fn read_sectors_recover(&mut self, lba: u32, count: u16, buf: &mut [u8], recovery: bool) -> Result<usize> { fn read_sectors_recover(
&mut self,
lba: u32,
count: u16,
buf: &mut [u8],
recovery: bool,
) -> Result<usize> {
// Default ignores flag — file-backed readers don't have recovery // Default ignores flag — file-backed readers don't have recovery
let _ = recovery; let _ = recovery;
self.read_sectors(lba, count, buf) self.read_sectors(lba, count, buf)
+20 -7
View File
@@ -1,6 +1,6 @@
//! Disc sector verification — read every sector and classify health. //! Disc sector verification — read every sector and classify health.
use crate::disc::{Chapter, DiscTitle, Extent}; use crate::disc::{Chapter, DiscTitle};
use crate::sector::SectorReader; use crate::sector::SectorReader;
use std::time::Instant; use std::time::Instant;
@@ -55,7 +55,12 @@ impl VerifyResult {
} }
/// Map a bad sector range to a chapter timestamp. /// Map a bad sector range to a chapter timestamp.
pub fn chapter_at_offset(chapters: &[Chapter], byte_offset: u64, duration_secs: f64, total_bytes: u64) -> Option<(usize, f64)> { pub fn chapter_at_offset(
chapters: &[Chapter],
byte_offset: u64,
duration_secs: f64,
total_bytes: u64,
) -> Option<(usize, f64)> {
if total_bytes == 0 || chapters.is_empty() { if total_bytes == 0 || chapters.is_empty() {
return None; return None;
} }
@@ -150,7 +155,12 @@ pub fn verify_title(
let s1 = Instant::now(); let s1 = Instant::now();
let first_ok = reader let first_ok = reader
.read_sectors_recover(sector_lba, 1, &mut buf[sector_offset..sector_offset + 2048], false) .read_sectors_recover(
sector_lba,
1,
&mut buf[sector_offset..sector_offset + 2048],
false,
)
.is_ok(); .is_ok();
let s1_ms = s1.elapsed().as_millis(); let s1_ms = s1.elapsed().as_millis();
@@ -164,7 +174,12 @@ pub fn verify_title(
// Retry once more after brief pause // Retry once more after brief pause
std::thread::sleep(std::time::Duration::from_secs(2)); std::thread::sleep(std::time::Duration::from_secs(2));
if reader if reader
.read_sectors_recover(sector_lba, 1, &mut buf[sector_offset..sector_offset + 2048], false) .read_sectors_recover(
sector_lba,
1,
&mut buf[sector_offset..sector_offset + 2048],
false,
)
.is_ok() .is_ok()
{ {
recovered += 1; recovered += 1;
@@ -178,9 +193,7 @@ pub fn verify_title(
if status != SectorStatus::Good { if status != SectorStatus::Good {
// Merge with previous range if contiguous and same status // Merge with previous range if contiguous and same status
if let Some(last) = ranges.last_mut() { if let Some(last) = ranges.last_mut() {
if last.status == status if last.status == status && last.start_lba + last.count == sector_lba {
&& last.start_lba + last.count == sector_lba
{
last.count += 1; last.count += 1;
} else { } else {
ranges.push(SectorRange { ranges.push(SectorRange {