Skip to content

Commit 3e1f5e4

Browse files
committed
EBML: Start parsing \Ebml\Segment\Tags\Tag\Targets
1 parent b79a19c commit 3e1f5e4

File tree

5 files changed

+224
-5
lines changed

5 files changed

+224
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ needless_return = "allow" # Explicit returns are needed from time to time
5555
redundant_guards = "allow" # Currently broken for some cases, might enable later
5656
into_iter_without_iter = "allow" # This is only going to fire on some internal types, doesn't matter much
5757
struct_excessive_bools = "allow" # I have yet to find one case of this being useful
58+
struct_field_names = "allow"
5859

5960
[workspace.lints.rustdoc]
6061
broken_intra_doc_links = "deny"

lofty/src/ebml/read/segment_tags.rs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::config::ParseOptions;
22
use crate::ebml::element_reader::{ElementChildIterator, ElementIdent, ElementReaderYield};
3-
use crate::ebml::EbmlTag;
3+
use crate::ebml::{EbmlTag, TargetType};
44
use crate::error::Result;
55

6+
use crate::macros::decode_err;
67
use std::io::{Read, Seek};
78

89
pub(super) fn read_from<R>(
@@ -32,9 +33,9 @@ where
3233
while let Some(child) = children_reader.next()? {
3334
match child {
3435
ElementReaderYield::Master((ElementIdent::Targets, _size)) => {
35-
read_targets(&mut children_reader.children())?
36+
let _ = read_targets(&mut children_reader.children())?;
3637
},
37-
ElementReaderYield::Master((ElementIdent::Tag, _size)) => {
38+
ElementReaderYield::Master((ElementIdent::SimpleTag, _size)) => {
3839
read_simple_tag(&mut children_reader.children())?
3940
},
4041
_ => unimplemented!("Unhandled child element in \\Ebml\\Segment\\Tags: {child:?}"),
@@ -44,11 +45,75 @@ where
4445
Ok(())
4546
}
4647

47-
fn read_targets<R>(_children_reader: &mut ElementChildIterator<'_, R>) -> Result<()>
48+
struct Target {
49+
target_type_value: TargetType,
50+
target_type: Option<String>,
51+
track_uid: Vec<u64>,
52+
edition_uid: Vec<u64>,
53+
chapter_uid: Vec<u64>,
54+
attachment_uid: Vec<u64>,
55+
}
56+
57+
fn read_targets<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<Target>
4858
where
4959
R: Read + Seek,
5060
{
51-
unimplemented!("\\Ebml\\Segment\\Tags\\Targets")
61+
let mut target_type_value = None;
62+
let mut target_type = None;
63+
let mut track_uid = Vec::new();
64+
let mut edition_uid = Vec::new();
65+
let mut chapter_uid = Vec::new();
66+
let mut attachment_uid = Vec::new();
67+
68+
while let Some(child) = children_reader.next()? {
69+
match child {
70+
ElementReaderYield::Child((child, size)) => match child.ident {
71+
ElementIdent::TargetTypeValue => {
72+
target_type_value = Some(children_reader.read_unsigned_int(size.value())?);
73+
},
74+
ElementIdent::TargetType => {
75+
target_type = Some(children_reader.read_string(size.value())?);
76+
},
77+
ElementIdent::TagTrackUID => {
78+
track_uid.push(children_reader.read_unsigned_int(size.value())?);
79+
},
80+
ElementIdent::TagEditionUID => {
81+
edition_uid.push(children_reader.read_unsigned_int(size.value())?);
82+
},
83+
ElementIdent::TagChapterUID => {
84+
chapter_uid.push(children_reader.read_unsigned_int(size.value())?);
85+
},
86+
ElementIdent::TagAttachmentUID => {
87+
attachment_uid.push(children_reader.read_unsigned_int(size.value())?);
88+
},
89+
_ => unreachable!(
90+
"Unhandled child element in \\Ebml\\Segment\\Tags\\Targets: {child:?}"
91+
),
92+
},
93+
ElementReaderYield::Eof => break,
94+
_ => {
95+
unreachable!("Unhandled child element in \\Ebml\\Segment\\Tags\\Targets: {child:?}")
96+
},
97+
}
98+
}
99+
100+
let target_type_value = match target_type_value {
101+
// Casting the `u64` to `u8` is safe because the value is checked to be within
102+
// the range of `TargetType` anyway.
103+
Some(value) => TargetType::try_from(value as u8)?,
104+
// The spec defines TargetType 50 (Album) as the default value, as it is the most
105+
// common grouping level.
106+
None => TargetType::Album,
107+
};
108+
109+
Ok(Target {
110+
target_type_value,
111+
target_type,
112+
track_uid,
113+
edition_uid,
114+
chapter_uid,
115+
attachment_uid,
116+
})
52117
}
53118

54119
fn read_simple_tag<R>(_children_reader: &mut ElementChildIterator<'_, R>) -> Result<()>

lofty/src/ebml/tag/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
pub(crate) mod attached_file;
2+
pub(crate) mod simple_tag;
3+
pub(crate) mod target_type;
4+
25
pub use attached_file::*;
6+
pub use simple_tag::*;
7+
pub use target_type::*;
38

49
use crate::config::WriteOptions;
510
use crate::error::LoftyError;

lofty/src/ebml/tag/simple_tag.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use crate::tag::ItemValue;
2+
3+
/// The language of a [`SimpleTag`] or chapter
4+
///
5+
/// Notes:
6+
///
7+
/// - ISO-639-2 was the original language code used in Matroska.
8+
/// - BCP-47 is the newer, **recommended** language option.
9+
/// - The ISO-639-2 language code allows for an optional country code, so the [Lang] type cannot be used.
10+
///
11+
/// [Lang]: crate::tag::items::Lang
12+
pub enum Language {
13+
/// An ISO-639-2 language code
14+
Iso639_2(String),
15+
/// A BCP-47 language code (recommended)
16+
Bcp47(String),
17+
}
18+
19+
/// The type of content stored in a [`SimpleTag`]
20+
///
21+
/// Matroska allows two different types of content to be stored in tags: UTF-8 strings and binary data.
22+
///
23+
/// ## Conversions with [`ItemValue`]
24+
///
25+
/// A `TagValue` can be converted to and from an [`ItemValue`] with the following conversions:
26+
///
27+
/// ### To [`ItemValue`]
28+
///
29+
/// - [`TagValue::String`] -> [`ItemValue::Text`]
30+
/// - [`TagValue::Binary`] -> [`ItemValue::Binary`]
31+
///
32+
/// ### From [`ItemValue`]
33+
///
34+
/// - [`ItemValue::Text`] | [`ItemValue::Locator`] -> [`TagValue::String`]
35+
/// - [`ItemValue::Binary`] -> [`TagValue::Binary`]
36+
pub enum TagValue {
37+
/// A UTF-8 string tag value
38+
String(String),
39+
/// A binary tag value
40+
Binary(Vec<u8>),
41+
}
42+
43+
impl From<TagValue> for ItemValue {
44+
fn from(value: TagValue) -> Self {
45+
match value {
46+
TagValue::String(s) => ItemValue::Text(s),
47+
TagValue::Binary(b) => ItemValue::Binary(b),
48+
}
49+
}
50+
}
51+
52+
impl From<ItemValue> for TagValue {
53+
fn from(value: ItemValue) -> Self {
54+
match value {
55+
ItemValue::Text(s) | ItemValue::Locator(s) => TagValue::String(s),
56+
ItemValue::Binary(b) => TagValue::Binary(b),
57+
}
58+
}
59+
}
60+
61+
/// General information about the target
62+
///
63+
/// Notes on how `SimpleTag`s work:
64+
///
65+
/// - Multiple [`SimpleTag`]s can exist in a file.
66+
/// - They each describe a single [`Target`].
67+
/// - This also means that multiple tags can describe the same target.
68+
/// - They **do not** need to have a value.
69+
pub struct SimpleTag {
70+
/// The name of the tag as it is stored
71+
///
72+
/// This field can essentially contain anything, but the following conditions are recommended:
73+
///
74+
/// - It **SHOULD** consist of capital letters, numbers and the underscore character ‘_’.
75+
/// - It **SHOULD NOT** contain any space.
76+
///
77+
/// When in doubt, the [`TagName`] enum can be used, which covers all specified tags.
78+
pub name: String,
79+
/// The language of the tag
80+
///
81+
/// See [`Language`] for more information.
82+
pub language: Option<Language>,
83+
/// Whether [`language`] is the default/original langauge to use
84+
///
85+
/// This is used when multiple languages are present in a file. This field will be ignored
86+
/// if [`language`] is `None`.
87+
///
88+
/// [`language`]: #structfield.language
89+
pub default: bool,
90+
/// The actual tag value
91+
///
92+
/// For more information, see [`TagValue`]
93+
pub value: Option<TagValue>,
94+
}

lofty/src/ebml/tag/target_type.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
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+
Album = 50,
30+
/// This is used to represent the following:
31+
///
32+
/// - Audio: EDITION / ISSUE / VOLUME / OPUS
33+
/// - Video: SEASON / SEQUEL / VOLUME
34+
Edition = 60,
35+
/// For both audio and video, this represents: COLLECTION
36+
Collection = 70,
37+
}
38+
39+
impl TryFrom<u8> for TargetType {
40+
type Error = LoftyError;
41+
42+
fn try_from(value: u8) -> Result<Self> {
43+
match value {
44+
10 => Ok(Self::Shot),
45+
20 => Ok(Self::Scene),
46+
30 => Ok(Self::Track),
47+
40 => Ok(Self::Part),
48+
50 => Ok(Self::Album),
49+
60 => Ok(Self::Edition),
50+
70 => Ok(Self::Collection),
51+
_ => decode_err!(@BAIL Ebml, "TargetType value out of range"),
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)