lofty/ogg/vorbis/
properties.rs

1use super::find_last_page;
2use crate::error::Result;
3use crate::properties::FileProperties;
4use crate::util::math::RoundedDivision;
5
6use std::io::{Read, Seek, SeekFrom};
7use std::time::Duration;
8
9use byteorder::{LittleEndian, ReadBytesExt};
10use ogg_pager::{Packets, PageHeader};
11
12/// An OGG Vorbis file's audio properties
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
14#[non_exhaustive]
15pub struct VorbisProperties {
16	pub(crate) duration: Duration,
17	pub(crate) overall_bitrate: u32,
18	pub(crate) audio_bitrate: u32,
19	pub(crate) sample_rate: u32,
20	pub(crate) channels: u8,
21	pub(crate) version: u32,
22	pub(crate) bitrate_maximum: i32,
23	pub(crate) bitrate_nominal: i32,
24	pub(crate) bitrate_minimum: i32,
25}
26
27impl From<VorbisProperties> for FileProperties {
28	fn from(input: VorbisProperties) -> Self {
29		Self {
30			duration: input.duration,
31			overall_bitrate: Some(input.overall_bitrate),
32			audio_bitrate: Some(input.audio_bitrate),
33			sample_rate: Some(input.sample_rate),
34			bit_depth: None,
35			channels: Some(input.channels),
36			channel_mask: None,
37		}
38	}
39}
40
41impl VorbisProperties {
42	/// Duration of the audio
43	pub fn duration(&self) -> Duration {
44		self.duration
45	}
46
47	/// Overall bitrate (kbps)
48	pub fn overall_bitrate(&self) -> u32 {
49		self.overall_bitrate
50	}
51
52	/// Audio bitrate (kbps)
53	pub fn audio_bitrate(&self) -> u32 {
54		self.audio_bitrate
55	}
56
57	/// Sample rate (Hz)
58	pub fn sample_rate(&self) -> u32 {
59		self.sample_rate
60	}
61
62	/// Channel count
63	pub fn channels(&self) -> u8 {
64		self.channels
65	}
66
67	/// Vorbis version
68	pub fn version(&self) -> u32 {
69		self.version
70	}
71
72	/// Maximum bitrate (bps)
73	pub fn bitrate_max(&self) -> i32 {
74		self.bitrate_maximum
75	}
76
77	/// Nominal bitrate (bps)
78	pub fn bitrate_nominal(&self) -> i32 {
79		self.bitrate_nominal
80	}
81
82	/// Minimum bitrate (bps)
83	pub fn bitrate_min(&self) -> i32 {
84		self.bitrate_minimum
85	}
86}
87
88pub(in crate::ogg) fn read_properties<R>(
89	data: &mut R,
90	first_page_header: &PageHeader,
91	packets: &Packets,
92) -> Result<VorbisProperties>
93where
94	R: Read + Seek,
95{
96	let mut properties = VorbisProperties::default();
97
98	// It's impossible to get this far without the identification packet, safe to unwrap
99	let first_packet = packets.get(0).expect("Identification packet expected");
100
101	// Skip identification header
102	let first_page_content = &mut &first_packet[7..];
103
104	properties.version = first_page_content.read_u32::<LittleEndian>()?;
105
106	properties.channels = first_page_content.read_u8()?;
107	properties.sample_rate = first_page_content.read_u32::<LittleEndian>()?;
108
109	properties.bitrate_maximum = first_page_content.read_i32::<LittleEndian>()?;
110	properties.bitrate_nominal = first_page_content.read_i32::<LittleEndian>()?;
111	properties.bitrate_minimum = first_page_content.read_i32::<LittleEndian>()?;
112
113	let last_page = find_last_page(data);
114	let file_length = data.seek(SeekFrom::End(0))?;
115
116	// This is used for bitrate calculation, it should be the length in
117	// milliseconds, but if we can't determine it then we'll just use 1000.
118	let mut length = 1000;
119	if let Ok(last_page) = last_page {
120		let first_page_abgp = first_page_header.abgp;
121		let last_page_abgp = last_page.header().abgp;
122
123		if properties.sample_rate > 0 {
124			let total_samples = u128::from(last_page_abgp.saturating_sub(first_page_abgp));
125
126			// Best case scenario
127			if total_samples > 0 {
128				length =
129					(total_samples * 1000).div_round(u128::from(properties.sample_rate)) as u64;
130				properties.duration = Duration::from_millis(length);
131			} else {
132				log::warn!(
133					"Vorbis: The file contains invalid PCM values, unable to calculate length"
134				);
135			}
136		} else {
137			log::warn!("Vorbis: Sample rate = 0, unable to calculate length");
138		}
139	}
140
141	if length > 0 {
142		properties.overall_bitrate = (file_length.saturating_mul(8) / length) as u32;
143	}
144
145	if properties.bitrate_nominal > 0 {
146		properties.audio_bitrate = (properties.bitrate_nominal as u64 / 1000) as u32;
147	}
148
149	Ok(properties)
150}