Skip to content

Commit 1a2742e

Browse files
committed
EBML: Get MKA properties test passing
1 parent cded302 commit 1a2742e

File tree

4 files changed

+172
-53
lines changed

4 files changed

+172
-53
lines changed

lofty/src/ebml/element_reader.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,17 @@ ebml_master_elements! {
146146
TrackEntry: {
147147
id: 0xAE,
148148
children: [
149+
TrackNumber: { 0xD7, UnsignedInt },
150+
TrackUid: { 0x73C5, UnsignedInt },
149151
TrackType: { 0x83, UnsignedInt },
150152
FlagEnabled: { 0xB9, UnsignedInt },
151153
FlagDefault: { 0x88, UnsignedInt },
152154
DefaultDuration: { 0x23E3_83, UnsignedInt },
153155
TrackTimecodeScale: { 0x2331_59, Float },
154156
Language: { 0x22B5_9C, String },
157+
LanguageBCP47: { 0x22B59D, String },
155158
CodecID: { 0x86, String },
159+
CodecPrivate: { 0x63A2, Binary },
156160
CodecName: { 0x258688, Utf8 },
157161
CodecDelay: { 0x56AA, UnsignedInt },
158162
SeekPreRoll: { 0x56BB, UnsignedInt },
@@ -168,6 +172,7 @@ ebml_master_elements! {
168172
OutputSamplingFrequency: { 0x78B5, Float },
169173
Channels: { 0x9F, UnsignedInt },
170174
BitDepth: { 0x6264, UnsignedInt },
175+
Emphasis: { 0x52F1, UnsignedInt },
171176
],
172177
},
173178

lofty/src/ebml/properties.rs

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::Language;
12
use crate::properties::FileProperties;
23

34
/// Properties from the EBML header
@@ -113,17 +114,41 @@ impl Default for SegmentInfo {
113114
}
114115

115116
/// A full descriptor for an audio track
116-
#[derive(Debug, Clone, PartialEq, Default)]
117+
#[derive(Debug, Clone, PartialEq)]
117118
pub struct AudioTrackDescriptor {
118119
pub(crate) number: u64,
119120
pub(crate) uid: u64,
120-
pub(crate) language: String,
121+
pub(crate) enabled: bool,
122+
pub(crate) default: bool,
123+
pub(crate) language: Language,
121124
pub(crate) default_duration: u64,
122125
pub(crate) codec_id: String,
123-
pub(crate) codec_private: Vec<u8>,
126+
pub(crate) codec_private: Option<Vec<u8>>,
127+
pub(crate) codec_name: Option<String>,
124128
pub(crate) settings: AudioTrackSettings,
125129
}
126130

131+
impl Default for AudioTrackDescriptor {
132+
fn default() -> Self {
133+
AudioTrackDescriptor {
134+
// Note, these values are not spec compliant and will hopefully be overwritten when
135+
// parsing. It doesn't really matter though, since we aren't an encoder.
136+
number: 0,
137+
uid: 0,
138+
default_duration: 0,
139+
codec_id: String::new(),
140+
141+
// Spec-compliant defaults
142+
enabled: true,
143+
default: true,
144+
language: Language::Iso639_2(String::from("eng")),
145+
codec_private: None,
146+
codec_name: None,
147+
settings: AudioTrackSettings::default(),
148+
}
149+
}
150+
}
151+
127152
impl AudioTrackDescriptor {
128153
/// The track number
129154
pub fn number(&self) -> u64 {
@@ -135,10 +160,20 @@ impl AudioTrackDescriptor {
135160
self.uid
136161
}
137162

163+
/// Whether the track is usable
164+
pub fn is_enabled(&self) -> bool {
165+
self.enabled
166+
}
167+
168+
/// Whether the track is eligible for automatic selection
169+
pub fn is_default(&self) -> bool {
170+
self.default
171+
}
172+
138173
/// The language of the track, in the Matroska languages form
139174
///
140175
/// NOTE: See [basics](https://matroska.org/technical/basics.html#language-codes) on language codes.
141-
pub fn language(&self) -> &str {
176+
pub fn language(&self) -> &Language {
142177
&self.language
143178
}
144179

@@ -157,8 +192,13 @@ impl AudioTrackDescriptor {
157192
}
158193

159194
/// Private data only known to the codec
160-
pub fn codec_private(&self) -> &[u8] {
161-
&self.codec_private
195+
pub fn codec_private(&self) -> Option<&[u8]> {
196+
self.codec_private.as_deref()
197+
}
198+
199+
/// A human-readable string for the [codec_id](AudioTrackDescriptor::codec_id)
200+
pub fn codec_name(&self) -> Option<&str> {
201+
self.codec_name.as_deref()
162202
}
163203

164204
/// The audio settings of the track
@@ -170,23 +210,23 @@ impl AudioTrackDescriptor {
170210
/// Settings for an audio track
171211
#[derive(Debug, Clone, PartialEq, Default)]
172212
pub struct AudioTrackSettings {
173-
pub(crate) sampling_frequency: u32,
174-
pub(crate) output_sampling_frequency: u32,
213+
pub(crate) sampling_frequency: f64,
214+
pub(crate) output_sampling_frequency: f64,
175215
pub(crate) channels: u8,
176216
pub(crate) bit_depth: Option<u8>,
177217
pub(crate) emphasis: Option<EbmlAudioTrackEmphasis>,
178218
}
179219

180220
impl AudioTrackSettings {
181221
/// The sampling frequency of the track
182-
pub fn sampling_frequency(&self) -> u32 {
222+
pub fn sampling_frequency(&self) -> f64 {
183223
self.sampling_frequency
184224
}
185225

186226
/// Real output sampling frequency in Hz (used for SBR techniques).
187227
///
188228
/// The default value for `output_sampling_frequency` of the same TrackEntry is equal to the [`Self::sampling_frequency`].
189-
pub fn output_sampling_frequency(&self) -> u32 {
229+
pub fn output_sampling_frequency(&self) -> f64 {
190230
self.output_sampling_frequency
191231
}
192232

@@ -210,7 +250,6 @@ impl AudioTrackSettings {
210250
#[allow(missing_docs)]
211251
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
212252
pub enum EbmlAudioTrackEmphasis {
213-
None = 0,
214253
CdAudio = 1,
215254
Reserved = 2,
216255
CcitJ17 = 3,
@@ -225,6 +264,27 @@ pub enum EbmlAudioTrackEmphasis {
225264
PhonoNartb = 16,
226265
}
227266

267+
impl EbmlAudioTrackEmphasis {
268+
/// Get the audio emphasis from a `u8`
269+
pub fn from_u8(value: u8) -> Option<Self> {
270+
match value {
271+
1 => Some(Self::CdAudio),
272+
2 => Some(Self::Reserved),
273+
3 => Some(Self::CcitJ17),
274+
4 => Some(Self::Fm50),
275+
5 => Some(Self::Fm75),
276+
10 => Some(Self::PhonoRiaa),
277+
11 => Some(Self::PhonoIecN78),
278+
12 => Some(Self::PhonoTeldec),
279+
13 => Some(Self::PhonoEmi),
280+
14 => Some(Self::PhonoColumbiaLp),
281+
15 => Some(Self::PhonoLondon),
282+
16 => Some(Self::PhonoNartb),
283+
_ => None,
284+
}
285+
}
286+
}
287+
228288
/// EBML audio properties
229289
#[derive(Debug, Clone, PartialEq, Default)]
230290
pub struct EbmlProperties {
@@ -248,28 +308,24 @@ impl EbmlProperties {
248308
&self.extensions
249309
}
250310

251-
/// Information from the `\EBML\Segment\Info` element
311+
/// Information from the `\Segment\Info` element
252312
pub fn segment_info(&self) -> &SegmentInfo {
253313
&self.segment_info
254314
}
255315

256316
/// All audio tracks in the file
257317
///
258-
/// This includes all audio tracks in the Matroska `\EBML\Segment\Tracks` element.
259-
///
260-
/// NOTE: The first audio track is **always** the default audio track.
318+
/// This includes all audio tracks in the Matroska `\Segment\Tracks` element.
261319
pub fn audio_tracks(&self) -> &[AudioTrackDescriptor] {
262320
&self.audio_tracks
263321
}
264322

265323
/// Information about the default audio track
266324
///
267325
/// The information is extracted from the first audio track with its default flag set
268-
/// in the `\EBML\Segment\Tracks` element.
269-
///
270-
/// NOTE: This will always return `Some` unless [`ParseOptions::read_properties`](crate::config::ParseOptions::read_properties) is set to `false`.
326+
/// in the `\Segment\Tracks` element.
271327
pub fn default_audio_track(&self) -> Option<&AudioTrackDescriptor> {
272-
self.audio_tracks.first()
328+
self.audio_tracks.iter().find(|track| track.default)
273329
}
274330
}
275331

@@ -283,7 +339,7 @@ impl From<EbmlProperties> for FileProperties {
283339
duration: todo!("Support duration"),
284340
overall_bitrate: todo!("Support bitrate"),
285341
audio_bitrate: todo!("Support bitrate"),
286-
sample_rate: Some(default_audio_track.settings.sampling_frequency),
342+
sample_rate: Some(default_audio_track.settings.sampling_frequency as u32),
287343
bit_depth: default_audio_track.settings.bit_depth,
288344
channels: Some(default_audio_track.settings.channels),
289345
channel_mask: todo!("Channel mask"),

lofty/src/ebml/read/segment_tracks.rs

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,23 @@ use crate::ebml::element_reader::{
33
ChildElementDescriptor, ElementChildIterator, ElementIdent, ElementReaderYield,
44
};
55
use crate::ebml::properties::EbmlProperties;
6+
use crate::ebml::{AudioTrackDescriptor, EbmlAudioTrackEmphasis, Language};
67
use crate::error::Result;
78

89
use std::io::{Read, Seek};
910

1011
pub(super) fn read_from<R>(
1112
children_reader: &mut ElementChildIterator<'_, R>,
1213
parse_options: ParseOptions,
13-
_properties: &mut EbmlProperties,
14+
properties: &mut EbmlProperties,
1415
) -> Result<()>
1516
where
1617
R: Read + Seek,
1718
{
18-
let mut audio_tracks = Vec::new();
19-
2019
while let Some(child) = children_reader.next()? {
2120
match child {
2221
ElementReaderYield::Master((ElementIdent::TrackEntry, _size)) => {
23-
read_track_entry(children_reader, parse_options, &mut audio_tracks)?;
22+
read_track_entry(children_reader, parse_options, &mut properties.audio_tracks)?;
2423
},
2524
ElementReaderYield::Eof => break,
2625
_ => {
@@ -32,30 +31,30 @@ where
3231
Ok(())
3332
}
3433

35-
#[derive(Default)]
36-
struct AudioTrack {
37-
default: bool,
38-
enabled: bool,
39-
codec_id: String,
40-
codec_name: String,
41-
}
42-
4334
const AUDIO_TRACK_TYPE: u64 = 2;
4435

4536
fn read_track_entry<R>(
4637
children_reader: &mut ElementChildIterator<'_, R>,
47-
_parse_options: ParseOptions,
48-
audio_tracks: &mut Vec<AudioTrack>,
38+
parse_options: ParseOptions,
39+
audio_tracks: &mut Vec<AudioTrackDescriptor>,
4940
) -> Result<()>
5041
where
5142
R: Read + Seek,
5243
{
53-
let mut track = AudioTrack::default();
44+
let mut track = AudioTrackDescriptor::default();
5445

5546
while let Some(child) = children_reader.next()? {
5647
match child {
5748
ElementReaderYield::Child((ChildElementDescriptor { ident, .. }, size)) => {
5849
match ident {
50+
ElementIdent::TrackNumber => {
51+
let track_number = children_reader.read_unsigned_int(size.value())?;
52+
track.number = track_number;
53+
},
54+
ElementIdent::TrackUid => {
55+
let track_uid = children_reader.read_unsigned_int(size.value())?;
56+
track.uid = track_uid;
57+
},
5958
ElementIdent::TrackType => {
6059
let track_type = children_reader.read_unsigned_int(size.value())?;
6160
log::trace!("Encountered new track of type: {}", track_type);
@@ -80,18 +79,27 @@ where
8079
let _timecode_scale = children_reader.read_float(size.value())?;
8180
},
8281
ElementIdent::Language => {
83-
let _language = children_reader.read_string(size.value())?;
82+
let language = children_reader.read_string(size.value())?;
83+
track.language = Language::Iso639_2(language);
84+
},
85+
ElementIdent::LanguageBCP47 => {
86+
let language = children_reader.read_string(size.value())?;
87+
track.language = Language::Bcp47(language);
8488
},
8589
ElementIdent::CodecID => {
8690
let codec_id = children_reader.read_string(size.value())?;
8791
track.codec_id = codec_id;
8892
},
93+
ElementIdent::CodecPrivate => {
94+
let codec_private = children_reader.read_binary(size.value())?;
95+
track.codec_private = Some(codec_private);
96+
},
8997
ElementIdent::CodecDelay => {
9098
let _codec_delay = children_reader.read_unsigned_int(size.value())?;
9199
},
92100
ElementIdent::CodecName => {
93101
let codec_name = children_reader.read_utf8(size.value())?;
94-
track.codec_name = codec_name;
102+
track.codec_name = Some(codec_name);
95103
},
96104
ElementIdent::SeekPreRoll => {
97105
let _seek_pre_roll = children_reader.read_unsigned_int(size.value())?;
@@ -101,7 +109,7 @@ where
101109
},
102110
ElementReaderYield::Master((id, size)) => match id {
103111
ElementIdent::Audio => {
104-
children_reader.skip(size.value())?;
112+
read_audio_settings(&mut children_reader.children(), parse_options, &mut track)?
105113
},
106114
_ => {
107115
unreachable!("Unhandled master element in TrackEntry: {:?}", id);
@@ -114,12 +122,59 @@ where
114122
}
115123
}
116124

117-
if !track.enabled {
118-
log::debug!("Skipping disabled track");
119-
return Ok(());
120-
}
121-
122125
audio_tracks.push(track);
123126

124127
Ok(())
125128
}
129+
130+
fn read_audio_settings<R>(
131+
children_reader: &mut ElementChildIterator<'_, R>,
132+
_parse_options: ParseOptions,
133+
audio_track: &mut AudioTrackDescriptor,
134+
) -> Result<()>
135+
where
136+
R: Read + Seek,
137+
{
138+
while let Some(child) = children_reader.next()? {
139+
match child {
140+
ElementReaderYield::Child((ChildElementDescriptor { ident, .. }, size)) => {
141+
match ident {
142+
ElementIdent::SamplingFrequency => {
143+
let sampling_frequency = children_reader.read_float(size.value())?;
144+
audio_track.settings.sampling_frequency = sampling_frequency;
145+
},
146+
ElementIdent::OutputSamplingFrequency => {
147+
let output_sampling_frequency = children_reader.read_float(size.value())?;
148+
audio_track.settings.output_sampling_frequency = output_sampling_frequency;
149+
},
150+
ElementIdent::Channels => {
151+
let channels = children_reader.read_unsigned_int(size.value())? as u8;
152+
audio_track.settings.channels = channels;
153+
},
154+
ElementIdent::BitDepth => {
155+
let bit_depth = children_reader.read_unsigned_int(size.value())? as u8;
156+
audio_track.settings.bit_depth = Some(bit_depth);
157+
},
158+
ElementIdent::Emphasis => {
159+
let emphasis = children_reader.read_unsigned_int(size.value())?;
160+
if emphasis == 0 {
161+
continue; // No emphasis
162+
}
163+
164+
audio_track.settings.emphasis =
165+
EbmlAudioTrackEmphasis::from_u8(emphasis as u8);
166+
},
167+
_ => {
168+
unreachable!("Unhandled child element in Audio: {child:?}");
169+
},
170+
}
171+
},
172+
ElementReaderYield::Eof => break,
173+
_ => {
174+
unreachable!("Unhandled child element in Audio: {child:?}");
175+
},
176+
}
177+
}
178+
179+
Ok(())
180+
}

0 commit comments

Comments
 (0)