1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use super::find_last_page;
use crate::error::{FileDecodingError, Result};
use crate::file::FileType;
use crate::properties::FileProperties;

use std::io::{Read, Seek, SeekFrom};
use std::time::Duration;

use byteorder::{LittleEndian, ReadBytesExt};
use ogg_pager::Page;

#[derive(Debug, Copy, Clone, PartialEq, Default)]
#[non_exhaustive]
/// An Opus file's audio properties
pub struct OpusProperties {
	pub(crate) duration: Duration,
	pub(crate) overall_bitrate: u32,
	pub(crate) audio_bitrate: u32,
	pub(crate) channels: u8,
	pub(crate) version: u8,
	pub(crate) input_sample_rate: u32,
}

impl From<OpusProperties> for FileProperties {
	fn from(input: OpusProperties) -> Self {
		Self {
			duration: input.duration,
			overall_bitrate: Some(input.overall_bitrate),
			audio_bitrate: Some(input.audio_bitrate),
			sample_rate: Some(input.input_sample_rate),
			bit_depth: None,
			channels: Some(input.channels),
		}
	}
}

impl OpusProperties {
	/// Duration
	pub fn duration(&self) -> Duration {
		self.duration
	}

	/// Overall bitrate (kbps)
	pub fn overall_bitrate(&self) -> u32 {
		self.overall_bitrate
	}

	/// Audio bitrate (kbps)
	pub fn audio_bitrate(&self) -> u32 {
		self.audio_bitrate
	}

	/// Channel count
	pub fn channels(&self) -> u8 {
		self.channels
	}

	/// Opus version
	pub fn version(&self) -> u8 {
		self.version
	}

	/// Input sample rate
	pub fn input_sample_rate(&self) -> u32 {
		self.input_sample_rate
	}
}

pub(in crate::ogg) fn read_properties<R>(data: &mut R, first_page: &Page) -> Result<OpusProperties>
where
	R: Read + Seek,
{
	let (stream_len, file_length) = {
		let current = data.stream_position()?;
		let end = data.seek(SeekFrom::End(0))?;
		data.seek(SeekFrom::Start(current))?;

		(end - first_page.start, end)
	};

	let mut properties = OpusProperties::default();

	let first_page_abgp = first_page.abgp;

	// Skip identification header
	let first_page_content = &mut &first_page.content()[8..];

	properties.version = first_page_content.read_u8()?;
	properties.channels = first_page_content.read_u8()?;

	let pre_skip = first_page_content.read_u16::<LittleEndian>()?;

	properties.input_sample_rate = first_page_content.read_u32::<LittleEndian>()?;

	let _output_gain = first_page_content.read_u16::<LittleEndian>()?;

	let channel_mapping_family = first_page_content.read_u8()?;

	// https://datatracker.ietf.org/doc/html/rfc7845.html#section-5.1.1
	if (channel_mapping_family == 0 && properties.channels > 2)
		|| (channel_mapping_family == 1 && properties.channels > 8)
	{
		return Err(FileDecodingError::new(
			FileType::Opus,
			"Invalid channel count for mapping family",
		)
		.into());
	}

	// Subtract the identification and metadata packet length from the total
	let audio_size = stream_len - data.stream_position()?;

	let last_page = find_last_page(data)?;
	let last_page_abgp = last_page.abgp;

	if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp + u64::from(pre_skip)) {
		let length = frame_count * 1000 / 48000;
		properties.duration = Duration::from_millis(length);

		properties.overall_bitrate = ((file_length * 8) / length) as u32;
		properties.audio_bitrate = (audio_size * 8 / length) as u32;
	}

	Ok(properties)
}