Skip to main content

CdReader

Struct CdReader 

Source
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

Source

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
Examples found in repository?
examples/detect_track_formats.rs (line 9)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    let reader = CdReader::open_default()?;
6    let toc = reader.read_toc()?;
7
8    for track in &toc.tracks {
9        let format = reader.detect_track_format(track)?;
10        println!("Track #{:>2}: {format:?}", track.number);
11    }
12
13    Ok(())
14}
More examples
Hide additional examples
examples/save_data_track.rs (line 39)
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

Source

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?
examples/list_drives.rs (line 5)
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}
Source

pub fn open_default() -> Result<Self, CdReaderError>

Open the first discovered drive that currently has an audio CD.

§Errors
Examples found in repository?
examples/detect_track_formats.rs (line 5)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    let reader = CdReader::open_default()?;
6    let toc = reader.read_toc()?;
7
8    for track in &toc.tracks {
9        let format = reader.detect_track_format(track)?;
10        println!("Track #{:>2}: {format:?}", track.number);
11    }
12
13    Ok(())
14}
More examples
Hide additional examples
examples/read_first_track.rs (line 8)
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}
examples/stream_last_track.rs (line 8)
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}
examples/read_toc.rs (line 7)
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}
examples/read_all_tracks.rs (line 8)
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}
examples/custom_retry.rs (line 14)
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

Source

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?
examples/stream_last_track.rs (line 19)
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
Hide additional examples
examples/stream_with_progress.rs (line 17)
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}
Source

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
Examples found in repository?
examples/save_data_track.rs (line 100)
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

Source

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.

Source

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.

Source

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.

Source

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?
examples/detect_track_formats.rs (line 6)
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5    let reader = CdReader::open_default()?;
6    let toc = reader.read_toc()?;
7
8    for track in &toc.tracks {
9        let format = reader.detect_track_format(track)?;
10        println!("Track #{:>2}: {format:?}", track.number);
11    }
12
13    Ok(())
14}
More examples
Hide additional examples
examples/read_first_track.rs (line 9)
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}
examples/stream_last_track.rs (line 9)
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}
examples/read_toc.rs (line 8)
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}
examples/read_all_tracks.rs (line 9)
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}
examples/custom_retry.rs (line 15)
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

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?
examples/read_first_track.rs (line 18)
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
Hide additional examples
examples/read_all_tracks.rs (line 18)
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}
Source

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
Examples found in repository?
examples/custom_retry.rs (line 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}
More examples
Hide additional examples
examples/read_speed.rs (line 40)
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}
Source

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::Io if the range is invalid, the read-speed request fails, or an operating-system read fails.
  • Returns CdReaderError::Scsi if the drive rejects a read command.
Examples found in repository?
examples/play_audio_track.rs (line 61)
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
Hide additional examples
examples/read_data_track.rs (line 36)
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.

Source§

type Error = CdReaderError

Error produced when this backing cannot satisfy a sector read. Read more
Source§

fn read_audio_sectors( &self, start_lba: u32, count: u32, ) -> Result<Vec<u8>, Self::Error>

Read the sector range start_lba..start_lba + count. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.