pub struct CdReader { /* private fields */ }Expand description
Helper struct to interact with the audio CD. Internally it holds a platform-specific handle to the open CD drive to read from it and it is correctly closed when CDReader is dropped.
Implementations§
Source§impl CdReader
impl CdReader
Sourcepub fn detect_track_format(
&self,
track: &Track,
) -> Result<SectorReadFormat, CdReaderError>
pub fn detect_track_format( &self, track: &Track, ) -> Result<SectorReadFormat, CdReaderError>
Detect the default read format for a track.
Audio tracks are identified directly from the TOC. Data tracks are queried with MMC READ TRACK INFORMATION. If its Data Mode is inconclusive, one raw sector is inspected as a fallback.
§Errors
- Returns
CdReaderError::CannotDetectTrackFormatif neither the track metadata nor a raw sector identifies the data format. - Returns
CdReaderError::Io,CdReaderError::Scsi, orCdReaderError::Parseif querying the drive fails.
Examples found in repository?
More examples
26fn main() -> Result<(), Box<dyn std::error::Error>> {
27 let output_dir = common::fresh_output_dir("save_data_track")?;
28 let reader = CdReader::open_default()?;
29 let toc = reader.read_toc()?;
30
31 // There is no `find_data_track` helper in the crate — the idiom is a plain
32 // filter on the TOC, since "data track" is simply `!is_audio`.
33 let data_track = toc
34 .tracks
35 .iter()
36 .find(|track| !track.is_audio)
37 .ok_or("no data track on this disc (need a mixed-mode / enhanced CD)")?;
38
39 let format = reader.detect_track_format(data_track)?;
40 println!("Data track #{} detected as {format:?}\n", data_track.number);
41
42 match format {
43 SectorReadFormat::Mode1Cooked => {
44 // Cooked Mode 1 strips sync/header/EDC/ECC, leaving exactly the
45 // 2048-byte user data per sector — i.e. the raw ISO 9660 image.
46 let iso_path = output_dir.join(format!("track{:02}.iso", data_track.number));
47 let bytes = stream_track_to_file(&reader, &toc, data_track.number, format, &iso_path)?;
48
49 println!(
50 "Wrote {} ({bytes} bytes, {} sectors)\n",
51 iso_path.display(),
52 bytes / format.sector_size() as u64
53 );
54 print_mount_hint(&iso_path.display().to_string());
55 }
56 SectorReadFormat::Mode2Raw => {
57 // Mode 2 forms are a per-sector property; producing a clean cooked
58 // payload requires inspecting each sector's XA subheader, which is
59 // left to the consumer. We save the complete raw sectors so nothing
60 // is lost.
61 let bin_path = output_dir.join(format!("track{:02}.mode2.bin", data_track.number));
62 let bytes = stream_track_to_file(&reader, &toc, data_track.number, format, &bin_path)?;
63
64 println!(
65 "This is a Mode 2 track. Saved complete raw sectors to {} \
66 ({bytes} bytes, {} sectors).",
67 bin_path.display(),
68 bytes / format.sector_size() as u64
69 );
70 println!(
71 "Extracting a mountable filesystem from Mode 2 is consumer territory: \
72 each sector's XA subheader decides which bytes are user data."
73 );
74 }
75 other => {
76 return Err(format!(
77 "data track #{} detected as {other:?}, which is unexpected for a data track",
78 data_track.number
79 )
80 .into());
81 }
82 }
83
84 Ok(())
85}Source§impl CdReader
impl CdReader
Sourcepub fn list_drives() -> Result<Vec<DriveInfo>, CdReaderError>
pub fn list_drives() -> Result<Vec<DriveInfo>, CdReaderError>
Enumerate candidate optical drives and probe whether they currently have an audio CD.
§Errors
Returns CdReaderError::Io if platform drive enumeration fails.
Errors while probing an individual drive are represented by
DriveInfo::has_audio_cd being false instead.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let drives = CdReader::list_drives()?;
6
7 if drives.is_empty() {
8 println!("No optical drives found.");
9 return Ok(());
10 }
11
12 println!("Found {} drive(s):\n", drives.len());
13 for drive in &drives {
14 let status = if drive.has_audio_cd {
15 "audio CD inserted"
16 } else {
17 "no audio CD"
18 };
19 println!("Drive: {}, status: [{status}]", drive.path);
20 }
21
22 Ok(())
23}Sourcepub fn open_default() -> Result<Self, CdReaderError>
pub fn open_default() -> Result<Self, CdReaderError>
Open the first discovered drive that currently has an audio CD.
§Errors
- Returns
CdReaderError::NoUsableDriveif no discovered drive has a readable audio CD. - Returns
CdReaderError::Ioif drive enumeration or opening the selected drive fails.
Examples found in repository?
More examples
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("read_first_track")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let first_audio = toc
12 .tracks
13 .iter()
14 .find(|t| t.is_audio)
15 .ok_or("no audio tracks found")?;
16
17 println!("Reading track {}...", first_audio.number);
18 let data = reader.read_track(&toc, first_audio.number)?;
19
20 let wav = create_wav(data);
21 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
22 std::fs::write(&output_path, wav)?;
23 println!("Saved {}", output_path.display());
24
25 Ok(())
26}6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("stream_last_track")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let last_audio = toc
12 .tracks
13 .iter()
14 .rev()
15 .find(|t| t.is_audio)
16 .ok_or("no audio tracks found")?;
17
18 println!("Streaming track {}...", last_audio.number);
19 let mut stream = reader.open_track_stream(&toc, last_audio.number)?;
20
21 let mut pcm = Vec::new();
22 while let Some(chunk) = stream.next_chunk()? {
23 pcm.extend_from_slice(&chunk);
24 }
25
26 let wav = create_wav(pcm);
27 let output_path = output_dir.join(format!("track{:02}.wav", last_audio.number));
28 std::fs::write(&output_path, wav)?;
29 println!("Saved {}", output_path.display());
30
31 Ok(())
32}6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let reader = CdReader::open_default()?;
8 let toc = reader.read_toc()?;
9
10 println!("Table of Contents\n");
11
12 println!(
13 "Tracks {}-{} ({} total), lead-out at LBA {}\n",
14 toc.first_track,
15 toc.last_track,
16 toc.tracks.len(),
17 toc.leadout_lba,
18 );
19
20 for track in &toc.tracks {
21 let kind = if track.is_audio { "audio" } else { "data " };
22 let (m, s, f) = track.start_msf;
23 let sectors = track_end_lba(&toc, track.number) - track.start_lba;
24 let duration_secs = sectors as f64 / 75.0;
25 let mins = (duration_secs / 60.0) as u32;
26 let secs = (duration_secs % 60.0) as u32;
27
28 println!(
29 " #{:>2} {} LBA {:>6} MSF {:02}:{:02}.{:02} duration: {:02}:{:02}",
30 track.number, kind, track.start_lba, m, s, f, mins, secs,
31 );
32 }
33
34 Ok(())
35}6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("read_all_tracks")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let audio_tracks: Vec<_> = toc.tracks.iter().filter(|t| t.is_audio).collect();
12 println!("Found {} audio track(s)\n", audio_tracks.len());
13
14 let mut failed = Vec::new();
15
16 for track in &audio_tracks {
17 print!("Reading track {:>2}... ", track.number);
18 match reader.read_track(&toc, track.number) {
19 Ok(data) => {
20 let wav = create_wav(data);
21 let output_path = output_dir.join(format!("track{:02}.wav", track.number));
22 std::fs::write(&output_path, wav)?;
23 println!("saved {}", output_path.display());
24 }
25 Err(e) => {
26 println!("FAILED: {}", e);
27 failed.push(track.number);
28 }
29 }
30 }
31
32 if !failed.is_empty() {
33 eprintln!("\nFailed to read tracks: {:?}", failed);
34 }
35
36 Ok(())
37}12fn main() -> Result<(), Box<dyn std::error::Error>> {
13 let output_dir = common::fresh_output_dir("custom_retry")?;
14 let reader = CdReader::open_default()?;
15 let toc = reader.read_toc()?;
16
17 let first_audio = toc
18 .tracks
19 .iter()
20 .find(|t| t.is_audio)
21 .ok_or("no audio tracks found")?;
22
23 // More attempts, longer backoff, and sector reduction down to 1
24 // for maximum resilience on scratched media.
25 let retry = RetryConfig::default()
26 .with_max_attempts(8)
27 .with_initial_backoff(Duration::from_millis(50))
28 .with_max_backoff(Duration::from_secs(1))
29 .with_chunk_reduction(true)
30 .with_min_sectors_per_read(1);
31 let options = ReadOptions::default().with_retry(retry);
32
33 println!(
34 "Reading track {} with aggressive retry...",
35 first_audio.number
36 );
37 let data = reader.read_track_with_options(&toc, first_audio.number, &options)?;
38
39 let wav = create_wav(data);
40 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
41 std::fs::write(&output_path, wav)?;
42 println!("Saved {}", output_path.display());
43
44 Ok(())
45}Source§impl CdReader
impl CdReader
Sourcepub fn open_track_stream<'a>(
&'a self,
toc: &Toc,
track_no: u8,
) -> Result<TrackStream<'a>, CdReaderError>
pub fn open_track_stream<'a>( &'a self, toc: &Toc, track_no: u8, ) -> Result<TrackStream<'a>, CdReaderError>
Open a streaming reader for an audio track using the default options.
§Errors
Returns the same errors as CdReader::open_track_stream_with_options.
Examples found in repository?
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("stream_last_track")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let last_audio = toc
12 .tracks
13 .iter()
14 .rev()
15 .find(|t| t.is_audio)
16 .ok_or("no audio tracks found")?;
17
18 println!("Streaming track {}...", last_audio.number);
19 let mut stream = reader.open_track_stream(&toc, last_audio.number)?;
20
21 let mut pcm = Vec::new();
22 while let Some(chunk) = stream.next_chunk()? {
23 pcm.extend_from_slice(&chunk);
24 }
25
26 let wav = create_wav(pcm);
27 let output_path = output_dir.join(format!("track{:02}.wav", last_audio.number));
28 std::fs::write(&output_path, wav)?;
29 println!("Saved {}", output_path.display());
30
31 Ok(())
32}More examples
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("stream_with_progress")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let first_audio = toc
12 .tracks
13 .iter()
14 .find(|t| t.is_audio)
15 .ok_or("no audio tracks found")?;
16
17 let mut stream = reader.open_track_stream(&toc, first_audio.number)?;
18
19 let total_secs = stream.total_seconds();
20 println!(
21 "Track {} — {} sectors ({:.0}s)\n",
22 first_audio.number,
23 stream.total_sectors(),
24 total_secs,
25 );
26
27 let mut pcm = Vec::new();
28 while let Some(chunk) = stream.next_chunk()? {
29 pcm.extend_from_slice(&chunk);
30
31 let cur = stream.current_seconds();
32 let pct = cur / total_secs * 100.0;
33 eprint!("\r [{:>5.1}s / {:.1}s] {:5.1}%", cur, total_secs, pct,);
34 }
35 eprintln!("\r [{:.1}s / {:.1}s] 100.0%", total_secs, total_secs);
36
37 let wav = create_wav(pcm);
38 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
39 std::fs::write(&output_path, wav)?;
40 println!("\nSaved {}", output_path.display());
41
42 Ok(())
43}Sourcepub fn open_track_stream_with_options<'a>(
&'a self,
toc: &Toc,
track_no: u8,
options: &ReadOptions,
) -> Result<TrackStream<'a>, CdReaderError>
pub fn open_track_stream_with_options<'a>( &'a self, toc: &Toc, track_no: u8, options: &ReadOptions, ) -> Result<TrackStream<'a>, CdReaderError>
Open a streaming reader using explicit read options.
Use TrackStream::next_chunk to pull sector-aligned chunks in the
selected format. The requested read speed is applied once before the
stream is returned. To override the default
chunk size, call TrackStream::with_sectors_per_chunk on the returned
stream.
§Errors
- Returns
CdReaderError::TrackFormatMismatchif the selected format is incompatible with the track. - Returns
CdReaderError::Ioif the track is absent, its bounds are invalid, or the read-speed request fails.
Examples found in repository?
92fn stream_track_to_file(
93 reader: &CdReader,
94 toc: &Toc,
95 track_no: u8,
96 format: SectorReadFormat,
97 path: &Path,
98) -> Result<u64, Box<dyn std::error::Error>> {
99 let options = ReadOptions::default().with_format(format);
100 let mut stream = reader.open_track_stream_with_options(toc, track_no, &options)?;
101
102 let total_sectors = stream.total_sectors();
103 let mut writer = BufWriter::new(File::create(path)?);
104 let mut written = 0u64;
105
106 while let Some(chunk) = stream.next_chunk()? {
107 writer.write_all(&chunk)?;
108 written += chunk.len() as u64;
109
110 let done = stream.current_sector();
111 let pct = done as f32 / total_sectors as f32 * 100.0;
112 eprint!("\r {done}/{total_sectors} sectors ({pct:5.1}%)");
113 }
114 eprintln!("\r {total_sectors}/{total_sectors} sectors (100.0%)");
115
116 writer.flush()?;
117 Ok(written)
118}Source§impl CdReader
impl CdReader
Sourcepub fn open(drive: &DriveInfo) -> Result<Self, CdReaderError>
pub fn open(drive: &DriveInfo) -> Result<Self, CdReaderError>
Opens a drive returned by CdReader::list_drives.
The reader owns the opened drive until it is dropped.
§Errors
Returns CdReaderError::Io if the discovered drive path cannot be
opened with the access required for raw drive commands.
Sourcepub fn open_path(path: &str) -> Result<Self, CdReaderError>
pub fn open_path(path: &str) -> Result<Self, CdReaderError>
Opens a CD drive at a platform-specific device path.
Example paths are /dev/sr0 on Linux, disk6 on macOS, and
\\.\E: on Windows. The reader owns the opened drive until it is dropped.
§Errors
Returns CdReaderError::Io if path is invalid or the operating
system cannot open it with the required access.
Sourcepub fn from_file(file: File) -> Self
pub fn from_file(file: File) -> Self
Builds a reader from an already-open handle to the drive’s device node, instead of opening the path ourselves.
This exists for privileged access. Reading a raw optical device can
require more rights than the calling process has, and there is no way to
gain them after the fact — the descriptor has to come from somewhere
else. A caller that hits EPERM / EACCES from CdReader::open_path
can obtain one through a privilege-escalation helper (macOS
/usr/libexec/authopen, a setuid helper, a launchd service) and hand it
over here.
The handle must refer to the drive’s device node — /dev/rdiskN on
macOS, /dev/srN on Linux — and the reader takes ownership of it,
closing it on drop.
On Linux the handle must have been opened O_RDWR: the SG_IO ioctls
this crate issues are rejected on a read-only descriptor. On macOS
O_RDONLY is correct and preferred, since a write-capable open of an
optical device demands exclusivity.
Sourcepub fn read_toc(&self) -> Result<Toc, CdReaderError>
pub fn read_toc(&self) -> Result<Toc, CdReaderError>
Read Table of Contents for the opened drive. You’ll likely only need to access
tracks from the returned value in order to iterate and read each track’s raw data.
Please note that each track in the vector has number property, which you should use
when calling read_track, as it doesn’t start with 0. It is important to do so,
because in the future it might include 0 for the hidden track.
§Errors
Returns CdReaderError::Io or CdReaderError::Scsi if the drive
command fails, and CdReaderError::Parse if the returned TOC is
malformed.
Examples found in repository?
More examples
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("read_first_track")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let first_audio = toc
12 .tracks
13 .iter()
14 .find(|t| t.is_audio)
15 .ok_or("no audio tracks found")?;
16
17 println!("Reading track {}...", first_audio.number);
18 let data = reader.read_track(&toc, first_audio.number)?;
19
20 let wav = create_wav(data);
21 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
22 std::fs::write(&output_path, wav)?;
23 println!("Saved {}", output_path.display());
24
25 Ok(())
26}6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("stream_last_track")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let last_audio = toc
12 .tracks
13 .iter()
14 .rev()
15 .find(|t| t.is_audio)
16 .ok_or("no audio tracks found")?;
17
18 println!("Streaming track {}...", last_audio.number);
19 let mut stream = reader.open_track_stream(&toc, last_audio.number)?;
20
21 let mut pcm = Vec::new();
22 while let Some(chunk) = stream.next_chunk()? {
23 pcm.extend_from_slice(&chunk);
24 }
25
26 let wav = create_wav(pcm);
27 let output_path = output_dir.join(format!("track{:02}.wav", last_audio.number));
28 std::fs::write(&output_path, wav)?;
29 println!("Saved {}", output_path.display());
30
31 Ok(())
32}6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let reader = CdReader::open_default()?;
8 let toc = reader.read_toc()?;
9
10 println!("Table of Contents\n");
11
12 println!(
13 "Tracks {}-{} ({} total), lead-out at LBA {}\n",
14 toc.first_track,
15 toc.last_track,
16 toc.tracks.len(),
17 toc.leadout_lba,
18 );
19
20 for track in &toc.tracks {
21 let kind = if track.is_audio { "audio" } else { "data " };
22 let (m, s, f) = track.start_msf;
23 let sectors = track_end_lba(&toc, track.number) - track.start_lba;
24 let duration_secs = sectors as f64 / 75.0;
25 let mins = (duration_secs / 60.0) as u32;
26 let secs = (duration_secs % 60.0) as u32;
27
28 println!(
29 " #{:>2} {} LBA {:>6} MSF {:02}:{:02}.{:02} duration: {:02}:{:02}",
30 track.number, kind, track.start_lba, m, s, f, mins, secs,
31 );
32 }
33
34 Ok(())
35}6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("read_all_tracks")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let audio_tracks: Vec<_> = toc.tracks.iter().filter(|t| t.is_audio).collect();
12 println!("Found {} audio track(s)\n", audio_tracks.len());
13
14 let mut failed = Vec::new();
15
16 for track in &audio_tracks {
17 print!("Reading track {:>2}... ", track.number);
18 match reader.read_track(&toc, track.number) {
19 Ok(data) => {
20 let wav = create_wav(data);
21 let output_path = output_dir.join(format!("track{:02}.wav", track.number));
22 std::fs::write(&output_path, wav)?;
23 println!("saved {}", output_path.display());
24 }
25 Err(e) => {
26 println!("FAILED: {}", e);
27 failed.push(track.number);
28 }
29 }
30 }
31
32 if !failed.is_empty() {
33 eprintln!("\nFailed to read tracks: {:?}", failed);
34 }
35
36 Ok(())
37}12fn main() -> Result<(), Box<dyn std::error::Error>> {
13 let output_dir = common::fresh_output_dir("custom_retry")?;
14 let reader = CdReader::open_default()?;
15 let toc = reader.read_toc()?;
16
17 let first_audio = toc
18 .tracks
19 .iter()
20 .find(|t| t.is_audio)
21 .ok_or("no audio tracks found")?;
22
23 // More attempts, longer backoff, and sector reduction down to 1
24 // for maximum resilience on scratched media.
25 let retry = RetryConfig::default()
26 .with_max_attempts(8)
27 .with_initial_backoff(Duration::from_millis(50))
28 .with_max_backoff(Duration::from_secs(1))
29 .with_chunk_reduction(true)
30 .with_min_sectors_per_read(1);
31 let options = ReadOptions::default().with_retry(retry);
32
33 println!(
34 "Reading track {} with aggressive retry...",
35 first_audio.number
36 );
37 let data = reader.read_track_with_options(&toc, first_audio.number, &options)?;
38
39 let wav = create_wav(data);
40 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
41 std::fs::write(&output_path, wav)?;
42 println!("Saved {}", output_path.display());
43
44 Ok(())
45}Sourcepub fn read_track(
&self,
toc: &Toc,
track_no: u8,
) -> Result<Vec<u8>, CdReaderError>
pub fn read_track( &self, toc: &Toc, track_no: u8, ) -> Result<Vec<u8>, CdReaderError>
Read an audio track using the default options.
It returns raw PCM data, but if you want to save it directly and make it playable,
wrap the result with create_wav.
§Errors
Returns the same errors as CdReader::read_track_with_options.
Examples found in repository?
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("read_first_track")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let first_audio = toc
12 .tracks
13 .iter()
14 .find(|t| t.is_audio)
15 .ok_or("no audio tracks found")?;
16
17 println!("Reading track {}...", first_audio.number);
18 let data = reader.read_track(&toc, first_audio.number)?;
19
20 let wav = create_wav(data);
21 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
22 std::fs::write(&output_path, wav)?;
23 println!("Saved {}", output_path.display());
24
25 Ok(())
26}More examples
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let output_dir = common::fresh_output_dir("read_all_tracks")?;
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let audio_tracks: Vec<_> = toc.tracks.iter().filter(|t| t.is_audio).collect();
12 println!("Found {} audio track(s)\n", audio_tracks.len());
13
14 let mut failed = Vec::new();
15
16 for track in &audio_tracks {
17 print!("Reading track {:>2}... ", track.number);
18 match reader.read_track(&toc, track.number) {
19 Ok(data) => {
20 let wav = create_wav(data);
21 let output_path = output_dir.join(format!("track{:02}.wav", track.number));
22 std::fs::write(&output_path, wav)?;
23 println!("saved {}", output_path.display());
24 }
25 Err(e) => {
26 println!("FAILED: {}", e);
27 failed.push(track.number);
28 }
29 }
30 }
31
32 if !failed.is_empty() {
33 eprintln!("\nFailed to read tracks: {:?}", failed);
34 }
35
36 Ok(())
37}Sourcepub fn read_track_with_options(
&self,
toc: &Toc,
track_no: u8,
options: &ReadOptions,
) -> Result<Vec<u8>, CdReaderError>
pub fn read_track_with_options( &self, toc: &Toc, track_no: u8, options: &ReadOptions, ) -> Result<Vec<u8>, CdReaderError>
Read a complete track using explicit read options.
§Errors
- Returns
CdReaderError::TrackFormatMismatchif the selected sector format is incompatible with the track. - Returns
CdReaderError::Ioif the track is absent, its bounds are invalid, or an operating-system drive operation fails. - Returns
CdReaderError::Scsiif the drive rejects a read command.
Examples found in repository?
12fn main() -> Result<(), Box<dyn std::error::Error>> {
13 let output_dir = common::fresh_output_dir("custom_retry")?;
14 let reader = CdReader::open_default()?;
15 let toc = reader.read_toc()?;
16
17 let first_audio = toc
18 .tracks
19 .iter()
20 .find(|t| t.is_audio)
21 .ok_or("no audio tracks found")?;
22
23 // More attempts, longer backoff, and sector reduction down to 1
24 // for maximum resilience on scratched media.
25 let retry = RetryConfig::default()
26 .with_max_attempts(8)
27 .with_initial_backoff(Duration::from_millis(50))
28 .with_max_backoff(Duration::from_secs(1))
29 .with_chunk_reduction(true)
30 .with_min_sectors_per_read(1);
31 let options = ReadOptions::default().with_retry(retry);
32
33 println!(
34 "Reading track {} with aggressive retry...",
35 first_audio.number
36 );
37 let data = reader.read_track_with_options(&toc, first_audio.number, &options)?;
38
39 let wav = create_wav(data);
40 let output_path = output_dir.join(format!("track{:02}.wav", first_audio.number));
41 std::fs::write(&output_path, wav)?;
42 println!("Saved {}", output_path.display());
43
44 Ok(())
45}More examples
7fn main() -> Result<(), Box<dyn std::error::Error>> {
8 let reader = CdReader::open_default()?;
9 let toc = reader.read_toc()?;
10
11 let first_audio = toc
12 .tracks
13 .iter()
14 .find(|track| track.is_audio)
15 .ok_or("no audio tracks found")?;
16
17 // Keep "unchanged" immediately after 1x so it tests whether the previous speed
18 // request remains in effect when no new speed command is sent.
19 let speed_tests = [
20 ("1x", ReadSpeed::CustomMultiplier(1)),
21 ("unchanged (after 1x)", ReadSpeed::Unchanged),
22 ("10x", ReadSpeed::CustomMultiplier(10)),
23 ("30x", ReadSpeed::CustomMultiplier(30)),
24 ("optimal", ReadSpeed::Optimal),
25 ];
26
27 println!(
28 "Reading audio track {} {} times\n",
29 first_audio.number,
30 speed_tests.len()
31 );
32
33 let mut timings = Vec::with_capacity(speed_tests.len());
34
35 for (label, read_speed) in speed_tests {
36 let options = ReadOptions::default().with_read_speed(read_speed);
37
38 println!("Reading at {label}...");
39 let started = Instant::now();
40 let data = reader.read_track_with_options(&toc, first_audio.number, &options)?;
41 let elapsed = started.elapsed();
42
43 println!(
44 "Read {} bytes in {:.3} seconds\n",
45 data.len(),
46 elapsed.as_secs_f64()
47 );
48 timings.push((label, elapsed));
49 }
50
51 println!("Timing summary:");
52 for (label, elapsed) in timings {
53 println!(" {label:<22} {:>10.3} seconds", elapsed.as_secs_f64());
54 }
55
56 Ok(())
57}Sourcepub fn read_sector_range(
&self,
start_lba: u32,
sectors: u32,
options: &ReadOptions,
) -> Result<Vec<u8>, CdReaderError>
pub fn read_sector_range( &self, start_lba: u32, sectors: u32, options: &ReadOptions, ) -> Result<Vec<u8>, CdReaderError>
Read an arbitrary range of sectors using explicit read options.
§Low-level API
Callers are responsible for providing valid sector boundaries and selecting
a format compatible with the sectors on the disc. Prefer CdReader::read_track
or CdReader::read_track_with_options when reading a complete TOC track.
§Errors
- Returns
CdReaderError::Ioif the range is invalid, the read-speed request fails, or an operating-system read fails. - Returns
CdReaderError::Scsiif the drive rejects a read command.
Examples found in repository?
28fn main() -> Result<(), Box<dyn std::error::Error>> {
29 let output_dir = common::fresh_output_dir("play_audio_track")?;
30 let seconds: u32 = match std::env::args().nth(1) {
31 Some(a) => a.parse()?,
32 None => 30,
33 };
34
35 let reader = CdReader::open_default()?;
36 let toc = reader.read_toc()?;
37
38 let track = toc
39 .tracks
40 .iter()
41 .find(|t| t.is_audio)
42 .ok_or("no audio tracks found on this disc")?;
43
44 // Clamp the preview to what the track actually holds: the track ends where
45 // the next track starts, or at the lead-out if it's the last one.
46 let track_end = toc
47 .tracks
48 .iter()
49 .map(|t| t.start_lba)
50 .filter(|&lba| lba > track.start_lba)
51 .min()
52 .unwrap_or(toc.leadout_lba);
53 let track_sectors = track_end - track.start_lba;
54 let sectors = (seconds * SECTORS_PER_SECOND).min(track_sectors);
55 let actual_seconds = sectors / SECTORS_PER_SECOND;
56
57 println!(
58 "Reading first {actual_seconds}s ({sectors} sectors) of audio track #{}...",
59 track.number
60 );
61 let pcm = reader.read_sector_range(track.start_lba, sectors, &ReadOptions::default())?;
62 println!(
63 "Read {} bytes of PCM ({:.1} MiB)",
64 pcm.len(),
65 pcm.len() as f64 / (1024.0 * 1024.0)
66 );
67
68 let output_path = output_dir.join(format!("track{:02}_preview.wav", track.number));
69 std::fs::write(&output_path, create_wav(pcm))?;
70 println!("Saved {}", output_path.display());
71
72 play(&output_path)
73}More examples
17fn main() -> Result<(), Box<dyn std::error::Error>> {
18 let reader = CdReader::open_default()?;
19 let toc = reader.read_toc()?;
20
21 let data_track = toc
22 .tracks
23 .iter()
24 .find(|t| !t.is_audio)
25 .ok_or("no data track on this disc (need a mixed-mode / enhanced CD)")?;
26
27 let pvd_lba = data_track.start_lba + PVD_SECTOR_OFFSET;
28 println!(
29 "Data track #{} starts at LBA {}; reading PVD at LBA {}\n",
30 data_track.number, data_track.start_lba, pvd_lba
31 );
32
33 let mut options = ReadOptions::default().with_format(SectorReadFormat::Mode1Raw);
34
35 // --- raw read (2352 B) -------------------------------------------------
36 let raw = reader.read_sector_range(pvd_lba, 1, &options)?;
37 if raw.len() != 2352 {
38 return Err(format!("raw read returned {} bytes, expected 2352", raw.len()).into());
39 }
40
41 let sync_ok = raw[0] == 0x00 && raw[1..11].iter().all(|&b| b == 0xFF) && raw[11] == 0x00;
42 let mode = raw[15];
43 println!("raw sync pattern : {}", pass(sync_ok));
44 println!("raw sector mode : Mode {mode}");
45
46 // User data sits after sync(12) + header(4) for Mode 1, and additionally
47 // after an 8-byte subheader for Mode 2 Form 1.
48 let user_offset = match mode {
49 1 => 16,
50 2 => 24,
51 other => return Err(format!("unexpected sector mode {other}").into()),
52 };
53 let raw_user = &raw[user_offset..user_offset + 2048];
54
55 let iso_ok = raw_user[0] == 0x01 && &raw_user[1..6] == b"CD001";
56 println!("ISO 9660 'CD001' : {}", pass(iso_ok));
57
58 // --- cooked read (2048 B) ---------------------------------------------
59 // The cooked format is specifically Mode 1, so only cross-check it there.
60 if mode == 1 {
61 options = options.with_format(SectorReadFormat::Mode1Cooked);
62 let cooked = reader.read_sector_range(pvd_lba, 1, &options)?;
63 if cooked.len() != 2048 {
64 return Err(
65 format!("cooked read returned {} bytes, expected 2048", cooked.len()).into(),
66 );
67 }
68 let matches_raw = cooked == raw_user;
69 println!("cooked == raw[16..]: {}", pass(matches_raw));
70
71 if sync_ok && iso_ok && matches_raw {
72 println!("\nALL CHECKS PASSED — cooked and raw data reads are correct.");
73 return Ok(());
74 }
75 } else {
76 println!(
77 "\nData track is Mode {mode} (e.g. CD-Extra / Mode 2 Form 1). The cooked path \
78 targets Mode 1, so only the raw checks apply here."
79 );
80 if sync_ok && iso_ok {
81 println!("Raw read verified against on-disc ISO structure.");
82 return Ok(());
83 }
84 }
85
86 Err("one or more verification checks FAILED — see output above".into())
87}Trait Implementations§
Source§impl AudioSectorReader for CdReader
The physical drive is itself an AudioSectorReader, so drive-backed and
file-backed code can share the generic read_track path. This uses the
default read options (audio sectors, default retry policy); for explicit
control, prefer the inherent CdReader::read_track_with_options.
impl AudioSectorReader for CdReader
The physical drive is itself an AudioSectorReader, so drive-backed and
file-backed code can share the generic read_track path. This uses the
default read options (audio sectors, default retry policy); for explicit
control, prefer the inherent CdReader::read_track_with_options.