1use anyhow::Result;
2
3pub mod ogg;
4pub mod ogg_vorbis;
5pub mod vorbis;
6
7pub use ogg_vorbis::OggVorbisReader;
8
9#[derive(Debug, Clone)]
10pub struct Entry {
11 pub file_pos: u64,
12 pub granule_position: u64,
13}
14
15#[derive(Debug, Clone)]
16pub struct TableOfContent {
17 pub entries: Vec<Entry>,
18}
19
20impl TableOfContent {
21 pub fn from_ogg_reader<R: std::io::Read + std::io::Seek>(rdr: &mut R) -> Result<Self> {
23 let all_headers = ogg::all_headers(rdr)?;
24 let entries: Vec<_> = all_headers
25 .into_iter()
26 .map(|(file_pos, hdr)| Entry { file_pos, granule_position: hdr.granule_position })
27 .collect();
28 Ok(Self { entries })
29 }
30
31 pub fn from_reader<R: std::io::Read>(rdr: &mut R) -> Result<Self> {
35 use byteorder::{LittleEndian, ReadBytesExt};
36
37 let mut entries = Vec::new();
38 while let Ok(file_pos) = rdr.read_u64::<LittleEndian>() {
39 let granule_position = rdr.read_u64::<LittleEndian>().unwrap();
40 entries.push(Entry { file_pos, granule_position });
41 }
42 Ok(Self { entries })
43 }
44
45 pub fn write<W: std::io::Write>(&self, w: &mut W) -> Result<()> {
46 use byteorder::{LittleEndian, WriteBytesExt};
47 for entry in self.entries.iter() {
48 w.write_u64::<LittleEndian>(entry.file_pos)?;
49 w.write_u64::<LittleEndian>(entry.granule_position)?;
50 }
51 Ok(())
52 }
53
54 pub fn last_entry_before(&self, start_pos: u64) -> Option<&Entry> {
55 let packet_idx = self.entries.partition_point(|entry| entry.granule_position < start_pos);
56 let mut packet_idx = packet_idx.saturating_sub(1);
57 while packet_idx < self.entries.len() && self.entries[packet_idx].granule_position == 0 {
58 packet_idx += 1;
59 }
60 self.entries.get(packet_idx)
61 }
62}
63
64pub fn read_ogg_vorbis_sample<P: AsRef<std::path::Path>>(
67 p: P,
68 start_time_sec: f64,
69 duration_sec: f64,
70) -> Result<(Vec<Vec<f32>>, u32)> {
71 let mut ovr = {
72 let file = std::fs::File::open(p.as_ref())?;
73 let reader = std::io::BufReader::new(file);
74 OggVorbisReader::new(reader)?
75 };
76 let sample_rate = ovr.sample_rate();
77 let start_pos = (start_time_sec * sample_rate as f64) as u64;
78 let duration = (duration_sec * sample_rate as f64) as u64;
79
80 let table_path = p.as_ref().with_extension("ogg_table");
81 let granule_pos = if table_path.is_file() {
82 let table_file = std::fs::File::open(&table_path)?;
83 let mut table_reader = std::io::BufReader::new(table_file);
84 let toc = TableOfContent::from_reader(&mut table_reader)?;
85 let entry = match toc.last_entry_before(start_pos) {
86 None => anyhow::bail!("not enough audio packets in file"),
87 Some(entry) => entry,
88 };
89 ovr.seek(entry.file_pos, true)?
90 } else {
91 ovr.seek_granule_position(start_pos, true)?
92 };
93 let to_skip = start_pos.saturating_sub(granule_pos);
94 let data = ovr.decode(to_skip as usize, duration as usize)?;
95 Ok((data, sample_rate))
96}