Skip to content

Commit 78430fc

Browse files
committed
EBML: Finish parsing \Segment\Tags
1 parent c784502 commit 78430fc

File tree

8 files changed

+173
-109
lines changed

8 files changed

+173
-109
lines changed

lofty/src/ebml/read.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ where
2626
// new ones all scattered throughout the file
2727
let mut properties = EbmlProperties::default();
2828

29-
let mut ebml_tag = None;
29+
let ebml_tag;
3030

3131
let mut element_reader = ElementReader::new(reader);
3232

lofty/src/ebml/read/segment_attachments.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ where
1919
{
2020
while let Some(child) = children_reader.next()? {
2121
match child {
22-
ElementReaderYield::Master((ElementIdent::AttachedFile, size)) => {
22+
ElementReaderYield::Master((ElementIdent::AttachedFile, _size)) => {
2323
let attached_file = read_attachment(children_reader)?;
2424
tag.attached_files.push(attached_file);
2525
},

lofty/src/ebml/read/segment_tags.rs

+44-43
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::config::ParseOptions;
22
use crate::ebml::element_reader::{ElementChildIterator, ElementIdent, ElementReaderYield};
3-
use crate::ebml::{EbmlTag, Language, SimpleTag, TagValue, TargetType};
3+
use crate::ebml::{EbmlTag, Language, SimpleTag, Tag, TagValue, Target, TargetType};
44
use crate::error::Result;
55

66
use crate::macros::decode_err;
@@ -17,7 +17,8 @@ where
1717
while let Some(child) = children_reader.next()? {
1818
match child {
1919
ElementReaderYield::Master((ElementIdent::Tag, _size)) => {
20-
read_tag(&mut children_reader.children(), tag)?
20+
let tag_element = read_tag(&mut children_reader.children())?;
21+
tag.tags.push(tag_element);
2122
},
2223
ElementReaderYield::Eof => break,
2324
_ => unimplemented!("Unhandled child element in \\Segment\\Tags: {child:?}"),
@@ -27,10 +28,13 @@ where
2728
Ok(())
2829
}
2930

30-
fn read_tag<R>(children_reader: &mut ElementChildIterator<'_, R>, _tag: &mut EbmlTag) -> Result<()>
31+
fn read_tag<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<Tag>
3132
where
3233
R: Read + Seek,
3334
{
35+
let mut target = None;
36+
let mut simple_tags = Vec::new();
37+
3438
while let Some(child) = children_reader.next()? {
3539
let ElementReaderYield::Master((master, _size)) = child else {
3640
match child {
@@ -43,39 +47,39 @@ where
4347

4448
match master {
4549
ElementIdent::Targets => {
46-
let _ = read_targets(&mut children_reader.children())?;
50+
if target.is_some() {
51+
decode_err!(
52+
@BAIL Ebml,
53+
"Duplicate Targets element found in \\Segment\\Tags\\Tag"
54+
);
55+
}
56+
57+
target = Some(read_targets(&mut children_reader.children())?);
4758
},
4859
ElementIdent::SimpleTag => {
49-
let _ = read_simple_tag(&mut children_reader.children())?;
60+
simple_tags.push(read_simple_tag(&mut children_reader.children())?);
5061
},
5162
_ => {
5263
unimplemented!("Unhandled child element in \\Segment\\Tags\\Tag: {master:?}");
5364
},
5465
}
5566
}
5667

57-
Ok(())
58-
}
68+
let Some(target) = target else {
69+
decode_err!(@BAIL Ebml, "\\Segment\\Tags\\Tag is missing the required `Targets` element");
70+
};
5971

60-
struct Target {
61-
target_type_value: TargetType,
62-
target_type: Option<String>,
63-
track_uid: Vec<u64>,
64-
edition_uid: Vec<u64>,
65-
chapter_uid: Vec<u64>,
66-
attachment_uid: Vec<u64>,
72+
Ok(Tag {
73+
target,
74+
simple_tags,
75+
})
6776
}
6877

6978
fn read_targets<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<Target>
7079
where
7180
R: Read + Seek,
7281
{
73-
let mut target_type_value = None;
74-
let mut target_type = None;
75-
let mut track_uid = Vec::new();
76-
let mut edition_uid = Vec::new();
77-
let mut chapter_uid = Vec::new();
78-
let mut attachment_uid = Vec::new();
82+
let mut target = Target::default();
7983

8084
while let Some(child) = children_reader.next()? {
8185
let ElementReaderYield::Child((child, size)) = child else {
@@ -89,46 +93,43 @@ where
8993

9094
match child.ident {
9195
ElementIdent::TargetTypeValue => {
92-
target_type_value = Some(children_reader.read_unsigned_int(size.value())?);
96+
let value = children_reader.read_unsigned_int(size.value())?;
97+
98+
// Casting the `u64` to `u8` is safe because the value is checked to be within
99+
// the range of `TargetType` anyway.
100+
let target_type = TargetType::try_from(value as u8)?;
101+
target.target_type = target_type;
93102
},
94103
ElementIdent::TargetType => {
95-
target_type = Some(children_reader.read_string(size.value())?);
104+
target.name = Some(children_reader.read_string(size.value())?);
96105
},
97106
ElementIdent::TagTrackUID => {
98-
track_uid.push(children_reader.read_unsigned_int(size.value())?);
107+
let mut track_uids = target.track_uids.unwrap_or_default();
108+
track_uids.push(children_reader.read_unsigned_int(size.value())?);
109+
target.track_uids = Some(track_uids);
99110
},
100111
ElementIdent::TagEditionUID => {
101-
edition_uid.push(children_reader.read_unsigned_int(size.value())?);
112+
let mut edition_uids = target.edition_uids.unwrap_or_default();
113+
edition_uids.push(children_reader.read_unsigned_int(size.value())?);
114+
target.edition_uids = Some(edition_uids);
102115
},
103116
ElementIdent::TagChapterUID => {
104-
chapter_uid.push(children_reader.read_unsigned_int(size.value())?);
117+
let mut chapter_uids = target.chapter_uids.unwrap_or_default();
118+
chapter_uids.push(children_reader.read_unsigned_int(size.value())?);
119+
target.chapter_uids = Some(chapter_uids);
105120
},
106121
ElementIdent::TagAttachmentUID => {
107-
attachment_uid.push(children_reader.read_unsigned_int(size.value())?);
122+
let mut attachment_uids = target.attachment_uids.unwrap_or_default();
123+
attachment_uids.push(children_reader.read_unsigned_int(size.value())?);
124+
target.attachment_uids = Some(attachment_uids);
108125
},
109126
_ => {
110127
unreachable!("Unhandled child element in \\Segment\\Tags\\Tag\\Targets: {child:?}")
111128
},
112129
}
113130
}
114131

115-
let target_type_value = match target_type_value {
116-
// Casting the `u64` to `u8` is safe because the value is checked to be within
117-
// the range of `TargetType` anyway.
118-
Some(value) => TargetType::try_from(value as u8)?,
119-
// The spec defines TargetType 50 (Album) as the default value, as it is the most
120-
// common grouping level.
121-
None => TargetType::Album,
122-
};
123-
124-
Ok(Target {
125-
target_type_value,
126-
target_type,
127-
track_uid,
128-
edition_uid,
129-
chapter_uid,
130-
attachment_uid,
131-
})
132+
Ok(target)
132133
}
133134

134135
fn read_simple_tag<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<SimpleTag>

lofty/src/ebml/tag/mod.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
pub(crate) mod attached_file;
2-
pub(crate) mod simple_tag;
3-
pub(crate) mod target_type;
1+
mod attached_file;
2+
mod simple_tag;
3+
mod tag;
4+
mod target;
45

56
pub use attached_file::*;
67
pub use simple_tag::*;
7-
pub use target_type::*;
8+
pub use tag::*;
9+
pub use target::*;
810

911
use crate::config::WriteOptions;
1012
use crate::error::LoftyError;
1113
use crate::io::{FileLike, Length, Truncate};
12-
use crate::tag::{Accessor, MergeTag, SplitTag, Tag, TagExt, TagType};
14+
use crate::tag::{Accessor, MergeTag, SplitTag, TagExt, TagType};
1315

1416
use std::io::Write;
1517
use std::ops::Deref;
@@ -21,6 +23,7 @@ use lofty_attr::tag;
2123
#[derive(Default, Debug, PartialEq, Eq, Clone)]
2224
#[tag(description = "An `EBML` tag", supported_formats(Ebml))]
2325
pub struct EbmlTag {
26+
pub(crate) tags: Vec<Tag>,
2427
pub(crate) attached_files: Vec<AttachedFile>,
2528
}
2629

@@ -107,27 +110,27 @@ impl Deref for SplitTagRemainder {
107110
impl SplitTag for EbmlTag {
108111
type Remainder = SplitTagRemainder;
109112

110-
fn split_tag(mut self) -> (Self::Remainder, Tag) {
113+
fn split_tag(mut self) -> (Self::Remainder, crate::tag::Tag) {
111114
todo!()
112115
}
113116
}
114117

115118
impl MergeTag for SplitTagRemainder {
116119
type Merged = EbmlTag;
117120

118-
fn merge_tag(self, _tag: Tag) -> Self::Merged {
121+
fn merge_tag(self, _tag: crate::tag::Tag) -> Self::Merged {
119122
todo!()
120123
}
121124
}
122125

123-
impl From<EbmlTag> for Tag {
126+
impl From<EbmlTag> for crate::tag::Tag {
124127
fn from(input: EbmlTag) -> Self {
125128
input.split_tag().1
126129
}
127130
}
128131

129-
impl From<Tag> for EbmlTag {
130-
fn from(input: Tag) -> Self {
132+
impl From<crate::tag::Tag> for EbmlTag {
133+
fn from(input: crate::tag::Tag) -> Self {
131134
SplitTagRemainder::default().merge_tag(input)
132135
}
133136
}

lofty/src/ebml/tag/simple_tag.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::tag::ItemValue;
99
/// - The ISO-639-2 language code allows for an optional country code, so the [Lang] type cannot be used.
1010
///
1111
/// [Lang]: crate::tag::items::Lang
12+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1213
pub enum Language {
1314
/// An ISO-639-2 language code
1415
Iso639_2(String),
@@ -33,6 +34,7 @@ pub enum Language {
3334
///
3435
/// - [`ItemValue::Text`] | [`ItemValue::Locator`] -> [`TagValue::String`]
3536
/// - [`ItemValue::Binary`] -> [`TagValue::Binary`]
37+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3638
pub enum TagValue {
3739
/// A UTF-8 string tag value
3840
String(String),
@@ -66,6 +68,7 @@ impl From<ItemValue> for TagValue {
6668
/// - They each describe a single [`Target`].
6769
/// - This also means that multiple tags can describe the same target.
6870
/// - They **do not** need to have a value.
71+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6972
pub struct SimpleTag {
7073
/// The name of the tag as it is stored
7174
///

lofty/src/ebml/tag/tag.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use super::{SimpleTag, Target};
2+
3+
/// A single metadata descriptor.
4+
///
5+
/// This represents a `\Segment\Tags\Tag` element in the EBML tree. It contains a single [`Target`] and
6+
/// its associated [`SimpleTag`]s.
7+
///
8+
/// This structure is very different from other formats. See [`Target`] and [`SimpleTag`] for more
9+
/// information on how these work.
10+
#[derive(Default, Debug, PartialEq, Eq, Clone)]
11+
pub struct Tag {
12+
/// The target for which the tags are applied.
13+
pub target: Target,
14+
/// General information about the target
15+
pub simple_tags: Vec<SimpleTag>,
16+
}

lofty/src/ebml/tag/target.rs

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::error::{LoftyError, Result};
2+
use crate::macros::decode_err;
3+
4+
/// The type of the target.
5+
///
6+
/// This is used to determine the type of the target that the tag is applied to.
7+
#[repr(u8)]
8+
#[non_exhaustive]
9+
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
10+
pub enum TargetType {
11+
/// For video, this represents: SHOT
12+
Shot = 10,
13+
/// This is used to represent the following:
14+
///
15+
/// - Audio: SUBTRACK / PART / MOVEMENT
16+
/// - Video: SCENE
17+
Scene = 20,
18+
/// This is used to represent the following:
19+
///
20+
/// - Audio: TRACK / SONG
21+
/// - Video: CHAPTER
22+
Track = 30,
23+
/// For both audio and video, this represents: PART / SESSION
24+
Part = 40,
25+
/// This is used to represent the following:
26+
///
27+
/// - Audio: ALBUM / OPERA / CONCERT
28+
/// - Video: MOVIE / EPISODE / CONCERT
29+
// The spec defines TargetType 50 (Album) as the default value, as it is the most
30+
// common grouping level.
31+
#[default]
32+
Album = 50,
33+
/// This is used to represent the following:
34+
///
35+
/// - Audio: EDITION / ISSUE / VOLUME / OPUS
36+
/// - Video: SEASON / SEQUEL / VOLUME
37+
Edition = 60,
38+
/// For both audio and video, this represents: COLLECTION
39+
Collection = 70,
40+
}
41+
42+
impl TryFrom<u8> for TargetType {
43+
type Error = LoftyError;
44+
45+
fn try_from(value: u8) -> Result<Self> {
46+
match value {
47+
10 => Ok(Self::Shot),
48+
20 => Ok(Self::Scene),
49+
30 => Ok(Self::Track),
50+
40 => Ok(Self::Part),
51+
50 => Ok(Self::Album),
52+
60 => Ok(Self::Edition),
53+
70 => Ok(Self::Collection),
54+
_ => decode_err!(@BAIL Ebml, "TargetType value out of range"),
55+
}
56+
}
57+
}
58+
59+
/// The target for which a [`SimpleTag`] is applied.
60+
///
61+
/// In Matroska, tags are specified on the level of targets. For example, there is no "TRACK TITLE"
62+
/// tag, but rather a "TITLE" tag that is applied to a [`TargetType::Track`] target.
63+
///
64+
/// See [`TargetType`] for more information on the types of targets.
65+
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
66+
pub struct Target {
67+
/// The type of the target.
68+
pub target_type: TargetType,
69+
/// An informational string that can be used to display the logical level of the target.
70+
pub name: Option<String>,
71+
/// A unique ID to identify the [Track](s) the tags belong to.
72+
///
73+
/// If the value is 0 at this level, the tags apply to all tracks in the Segment. If set to any
74+
/// other value, it **MUST** match the [`TrackUID`] value of a track found in this Segment.
75+
pub track_uids: Option<Vec<u64>>,
76+
/// A unique ID to identify the [EditionEntry](s) the tags belong to.
77+
///
78+
/// If the value is 0 at this level, the tags apply to all editions in the Segment. If set to
79+
/// any other value, it **MUST** match the [`EditionUID`] value of an edition found in this Segment.
80+
pub edition_uids: Option<Vec<u64>>,
81+
/// A unique ID to identify the [Chapter](s) the tags belong to.
82+
///
83+
/// If the value is 0 at this level, the tags apply to all chapters in the Segment. If set to
84+
/// any other value, it **MUST** match the [`ChapterUID`] value of a chapter found in this Segment.
85+
pub chapter_uids: Option<Vec<u64>>,
86+
/// A unique ID to identify the [`AttachedFile`]\(s) the tags belong to.
87+
///
88+
/// If the value is 0 at this level, the tags apply to all the attachments in the Segment. If
89+
/// set to any other value, it **MUST** match the [`AttachedFile::uid`]) value of an attachment
90+
/// found in this Segment.
91+
///
92+
/// [`AttachedFile`]: crate::ebml::AttachedFile
93+
/// [`AttachedFile::uid`]: crate::ebml::AttachedFile::uid
94+
pub attachment_uids: Option<Vec<u64>>,
95+
}

0 commit comments

Comments
 (0)