lofty/ogg/speex/
properties.rs1use crate::error::Result;
2use crate::macros::decode_err;
3use crate::ogg::find_last_page;
4use crate::properties::FileProperties;
5use crate::util::math::RoundedDivision;
6
7use std::io::{Read, Seek, SeekFrom};
8use std::time::Duration;
9
10use byteorder::{LittleEndian, ReadBytesExt};
11use ogg_pager::{Packets, PageHeader};
12
13#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
15#[non_exhaustive]
16pub struct SpeexProperties {
17 pub(crate) duration: Duration,
18 pub(crate) version: u32,
19 pub(crate) sample_rate: u32,
20 pub(crate) mode: u32,
21 pub(crate) channels: u8,
22 pub(crate) vbr: bool,
23 pub(crate) overall_bitrate: u32,
24 pub(crate) audio_bitrate: u32,
25 pub(crate) nominal_bitrate: i32,
26}
27
28impl From<SpeexProperties> for FileProperties {
29 fn from(input: SpeexProperties) -> Self {
30 Self {
31 duration: input.duration,
32 overall_bitrate: Some(input.overall_bitrate),
33 audio_bitrate: Some(input.audio_bitrate),
34 sample_rate: Some(input.sample_rate),
35 bit_depth: None,
36 channels: Some(input.channels),
37 channel_mask: None,
38 }
39 }
40}
41
42impl SpeexProperties {
43 pub fn duration(&self) -> Duration {
45 self.duration
46 }
47
48 pub fn version(&self) -> u32 {
50 self.version
51 }
52
53 pub fn sample_rate(&self) -> u32 {
55 self.sample_rate
56 }
57
58 pub fn mode(&self) -> u32 {
60 self.mode
61 }
62
63 pub fn channels(&self) -> u8 {
65 self.channels
66 }
67
68 pub fn vbr(&self) -> bool {
70 self.vbr
71 }
72
73 pub fn overall_bitrate(&self) -> u32 {
75 self.overall_bitrate
76 }
77
78 pub fn audio_bitrate(&self) -> u32 {
80 self.audio_bitrate
81 }
82
83 pub fn nominal_bitrate(&self) -> i32 {
85 self.nominal_bitrate
86 }
87}
88
89pub(in crate::ogg) fn read_properties<R>(
90 data: &mut R,
91 first_page_header: &PageHeader,
92 packets: &Packets,
93) -> Result<SpeexProperties>
94where
95 R: Read + Seek,
96{
97 log::debug!("Reading Speex properties");
98
99 let identification_packet = packets.get(0).unwrap();
102
103 if identification_packet.len() < 80 {
104 decode_err!(@BAIL Speex, "Header packet too small");
105 }
106
107 let mut properties = SpeexProperties::default();
108
109 let identification_packet_reader = &mut &identification_packet[28..];
115
116 properties.version = identification_packet_reader.read_u32::<LittleEndian>()?;
117 if properties.version > 1 {
118 decode_err!(@BAIL Speex, "Unknown Speex stream version");
119 }
120
121 let _header_size = identification_packet_reader.read_u32::<LittleEndian>()?;
123
124 properties.sample_rate = identification_packet_reader.read_u32::<LittleEndian>()?;
125 properties.mode = identification_packet_reader.read_u32::<LittleEndian>()?;
126
127 let _mode_bitstream_version = identification_packet_reader.read_u32::<LittleEndian>()?;
129
130 let channels = identification_packet_reader.read_u32::<LittleEndian>()?;
131
132 if channels != 1 && channels != 2 {
133 decode_err!(@BAIL Speex, "Found invalid channel count, must be mono or stereo");
134 }
135
136 properties.channels = channels as u8;
137 properties.nominal_bitrate = identification_packet_reader.read_i32::<LittleEndian>()?;
138
139 let _frame_size = identification_packet_reader.read_u32::<LittleEndian>()?;
141
142 properties.vbr = identification_packet_reader.read_u32::<LittleEndian>()? == 1;
143
144 let last_page = find_last_page(data);
145 let file_length = data.seek(SeekFrom::End(0))?;
146
147 let metadata_packets_length = packets.iter().take(2).map(<[u8]>::len).sum::<usize>();
149 let stream_length = file_length.saturating_sub(metadata_packets_length as u64);
150
151 let mut length = 1000;
154 if let Ok(last_page) = last_page {
155 let first_page_abgp = first_page_header.abgp;
156 let last_page_abgp = last_page.header().abgp;
157
158 if properties.sample_rate > 0 {
159 let total_samples = last_page_abgp.saturating_sub(first_page_abgp);
160
161 if total_samples > 0 {
163 length = (total_samples * 1000).div_round(u64::from(properties.sample_rate));
164 properties.duration = Duration::from_millis(length);
165 } else {
166 log::warn!(
167 "Speex: The file contains invalid PCM values, unable to calculate length"
168 );
169 }
170 } else {
171 log::warn!("Speex: Sample rate = 0, unable to calculate length");
172 }
173 }
174
175 if properties.nominal_bitrate > 0 {
176 properties.overall_bitrate = (file_length.saturating_mul(8) / length) as u32;
177 properties.audio_bitrate = (properties.nominal_bitrate as u64 / 1000) as u32;
178 } else {
179 log::warn!("Nominal bitrate = 0, estimating bitrate from file length");
180
181 properties.overall_bitrate = file_length.saturating_mul(8).div_round(length) as u32;
182 properties.audio_bitrate = stream_length.saturating_mul(8).div_round(length) as u32;
183 }
184
185 Ok(properties)
186}