Skip to content

Commit 76a6e6a

Browse files
committed
EBML: Handle EbmlTag -> Tag conversion
1 parent 9b710e4 commit 76a6e6a

38 files changed

+1107
-91
lines changed

lofty/src/ape/tag/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ mod tests {
915915
fn skip_reading_cover_art() {
916916
let p = Picture::new_unchecked(
917917
PictureType::CoverFront,
918+
None,
918919
Some(MimeType::Jpeg),
919920
None,
920921
std::iter::repeat(0).take(50).collect::<Vec<u8>>(),

lofty/src/ebml/element_reader.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ ebml_master_elements! {
109109
children: [
110110
// SeekHead: { 0x114D_9B74, Master },
111111
Info: { 0x1549_A966, Master },
112-
Cluster: { 0x1F43_B675, Master },
113112
Tracks: { 0x1654_AE6B, Master },
114113
Tags: { 0x1254_C367, Master },
115114
Attachments: { 0x1941_A469, Master },

lofty/src/ebml/read.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
mod segment;
22
mod segment_attachments;
33
mod segment_chapters;
4-
mod segment_cluster;
54
mod segment_info;
65
mod segment_tags;
76
mod segment_tracks;

lofty/src/ebml/read/segment.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{segment_attachments, segment_cluster, segment_info, segment_tags, segment_tracks};
1+
use super::{segment_attachments, segment_info, segment_tags, segment_tracks};
22
use crate::config::ParseOptions;
33
use crate::ebml::element_reader::{ElementHeader, ElementIdent, ElementReader, ElementReaderYield};
44
use crate::ebml::properties::EbmlProperties;
@@ -30,13 +30,6 @@ where
3030
properties,
3131
)?;
3232
},
33-
ElementIdent::Cluster if parse_options.read_properties => {
34-
segment_cluster::read_from(
35-
&mut children_reader.children(),
36-
parse_options,
37-
properties,
38-
)?;
39-
},
4033
ElementIdent::Tracks if parse_options.read_properties => {
4134
segment_tracks::read_from(
4235
&mut children_reader.children(),

lofty/src/ebml/read/segment_attachments.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::error::Result;
77
use crate::macros::decode_err;
88
use crate::picture::MimeType;
99

10+
use std::borrow::Cow;
1011
use std::io::{Read, Seek};
1112

1213
pub(super) fn read_from<R>(
@@ -31,7 +32,7 @@ where
3132
Ok(())
3233
}
3334

34-
fn read_attachment<R>(element_reader: &mut ElementReader<R>) -> Result<AttachedFile>
35+
fn read_attachment<R>(element_reader: &mut ElementReader<R>) -> Result<AttachedFile<'static>>
3536
where
3637
R: Read + Seek,
3738
{
@@ -105,12 +106,12 @@ where
105106
};
106107

107108
Ok(AttachedFile {
108-
description,
109-
file_name,
109+
description: description.map(Cow::Owned),
110+
file_name: Cow::Owned(file_name),
110111
mime_type,
111-
file_data,
112+
file_data: Cow::Owned(file_data),
112113
uid,
113-
referral,
114+
referral: referral.map(Cow::Owned),
114115
used_start_time,
115116
used_end_time,
116117
})

lofty/src/ebml/read/segment_cluster.rs

Lines changed: 0 additions & 17 deletions
This file was deleted.

lofty/src/ebml/read/segment_tags.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::config::ParseOptions;
22
use crate::ebml::element_reader::{ElementChildIterator, ElementIdent, ElementReaderYield};
33
use crate::ebml::{EbmlTag, Language, SimpleTag, Tag, TagValue, Target, TargetType};
44
use crate::error::Result;
5-
65
use crate::macros::decode_err;
6+
77
use std::io::{Read, Seek};
88

99
pub(super) fn read_from<R>(
@@ -57,7 +57,7 @@ where
5757
target = Some(read_targets(&mut children_reader.children())?);
5858
},
5959
ElementIdent::SimpleTag => {
60-
simple_tags.push(read_simple_tag(&mut children_reader.children())?);
60+
simple_tags.push(read_simple_tag(&mut children_reader.children())?)
6161
},
6262
_ => {
6363
unimplemented!("Unhandled child element in \\Segment\\Tags\\Tag: {master:?}");
@@ -70,7 +70,7 @@ where
7070
};
7171

7272
Ok(Tag {
73-
target,
73+
target: Some(target),
7474
simple_tags,
7575
})
7676
}

lofty/src/ebml/tag/attached_file.rs

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
use crate::error::Result;
22
use crate::macros::encode_err;
3-
use crate::picture::MimeType;
3+
use crate::picture::{MimeType, Picture};
44

5+
use std::borrow::Cow;
56
use std::fmt::Debug;
67

78
/// Some attached file
89
///
910
/// This element contains any attached files, similar to the [GEOB]
1011
/// frame in ID3v2. The difference is, this is *also* used for images.
1112
///
13+
/// **Unsupported in WebM**
14+
///
1215
/// [GEOB]: crate::id3::v2::GeneralEncapsulatedObject
1316
#[derive(Clone, Eq, PartialEq)]
14-
pub struct AttachedFile {
17+
pub struct AttachedFile<'a> {
1518
/// A human-friendly name for the attached file.
16-
pub description: Option<String>,
19+
pub description: Option<Cow<'a, str>>,
1720
/// The actual file name of the attached file.
18-
pub file_name: String,
21+
pub file_name: Cow<'a, str>,
1922
/// Media type of the file following the [RFC6838] format.
2023
///
2124
/// [RFC6838]: https://tools.ietf.org/html/rfc6838
2225
pub mime_type: MimeType,
2326
/// The data of the file.
24-
pub file_data: Vec<u8>,
27+
pub file_data: Cow<'a, [u8]>,
2528
/// Unique ID representing the file, as random as possible.
2629
pub uid: u64,
2730
/// A binary value that a track/codec can refer to when the attachment is needed.
28-
pub referral: Option<Vec<u8>>,
31+
pub referral: Option<Cow<'a, [u8]>>,
2932
/// The timestamp at which this optimized font attachment comes into context.
3033
///
3134
/// This is expressed in Segment Ticks which is based on `TimestampScale`. This element is
@@ -38,7 +41,7 @@ pub struct AttachedFile {
3841
pub used_end_time: Option<u64>,
3942
}
4043

41-
impl Debug for AttachedFile {
44+
impl Debug for AttachedFile<'_> {
4245
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4346
f.debug_struct("AttachedFile")
4447
.field("description", &self.description)
@@ -53,12 +56,85 @@ impl Debug for AttachedFile {
5356
}
5457
}
5558

56-
impl AttachedFile {
59+
impl From<Picture> for AttachedFile<'_> {
60+
fn from(picture: Picture) -> Self {
61+
Self {
62+
description: picture.description,
63+
file_name: picture.file_name.unwrap_or_default(),
64+
mime_type: picture
65+
.mime_type
66+
.unwrap_or(MimeType::Unknown(String::from("image/"))),
67+
file_data: picture.data,
68+
uid: 0,
69+
referral: None,
70+
used_start_time: None,
71+
used_end_time: None,
72+
}
73+
}
74+
}
75+
76+
impl AttachedFile<'_> {
77+
/// Whether this file is an image
78+
///
79+
/// This will check if the [`MimeType`] starts with `image/`.
80+
///
81+
/// # Examples
82+
///
83+
/// ```rust
84+
/// use lofty::ebml::AttachedFile;
85+
/// use lofty::picture::MimeType;
86+
///
87+
/// let file = AttachedFile {
88+
/// description: None,
89+
/// file_name: "something.png".into(),
90+
/// // PNG MIME type
91+
/// mime_type: MimeType::Png,
92+
/// file_data: vec![1, 2, 3].into(),
93+
/// uid: 0,
94+
/// referral: None,
95+
/// used_start_time: None,
96+
/// used_end_time: None
97+
/// };
98+
///
99+
/// assert!(file.is_image());
100+
pub fn is_image(&self) -> bool {
101+
match &self.mime_type {
102+
MimeType::Unknown(mime) if mime.starts_with("image/") => true,
103+
MimeType::Unknown(_) => false,
104+
// `MimeType` is only ever used for `Picture`s outside of Matroska
105+
_ => true,
106+
}
107+
}
108+
57109
pub(crate) fn validate(&self) -> Result<()> {
58110
if self.uid == 0 {
59111
encode_err!(@BAIL Ebml, "The UID of an attachment cannot be 0");
60112
}
61113

62114
Ok(())
63115
}
116+
117+
pub(crate) fn into_owned(self) -> AttachedFile<'static> {
118+
let AttachedFile {
119+
description,
120+
file_name,
121+
mime_type,
122+
file_data,
123+
uid,
124+
referral,
125+
used_start_time,
126+
used_end_time,
127+
} = self;
128+
129+
AttachedFile {
130+
description: description.map(|d| Cow::Owned(d.into_owned())),
131+
file_name: Cow::Owned(file_name.into_owned()),
132+
mime_type,
133+
file_data: Cow::Owned(file_data.into_owned()),
134+
uid,
135+
referral: referral.map(|r| Cow::Owned(r.into_owned())),
136+
used_start_time,
137+
used_end_time,
138+
}
139+
}
64140
}

lofty/src/ebml/tag/generic.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! Conversions to and from generic types
2+
//!
3+
//! NOTE: We can **ONLY** convert `SimpleTags` that come from a target with **NO** uids
4+
5+
use super::{EbmlTag, Language, SimpleTag, TargetType, TOMBSTONE_SIMPLE_TAG};
6+
use crate::tag::items::Lang;
7+
use crate::tag::{ItemKey, Tag, TagItem, TagType};
8+
9+
use std::collections::HashMap;
10+
use std::sync::LazyLock;
11+
12+
macro_rules! matroska_mapping_tables {
13+
(
14+
$($target:ident => [
15+
$($matroska_key:literal <=> $item_key:ident),* $(,)?
16+
]);+ $(;)?
17+
) => {
18+
const _: () = {
19+
match TargetType::Album {
20+
$(
21+
TargetType::$target => {}
22+
),+
23+
}
24+
};
25+
26+
pub(crate) const SUPPORTED_ITEMKEYS: &[ItemKey] = &[
27+
$(
28+
$(
29+
ItemKey::$item_key
30+
),*
31+
)+
32+
];
33+
34+
static MAPPINGS: LazyLock<HashMap<(TargetType, &'static str), ItemKey>> = LazyLock::new(|| {
35+
let mut m = HashMap::new();
36+
$(
37+
$(
38+
m.insert((TargetType::$target, $matroska_key), ItemKey::$item_key);
39+
)*
40+
)+
41+
m
42+
});
43+
44+
static REVERSE_MAPPINGS: LazyLock<HashMap<ItemKey, (TargetType, &'static str)>> = LazyLock::new(|| {
45+
let mut m = HashMap::new();
46+
$(
47+
$(
48+
m.insert(ItemKey::$item_key, (TargetType::$target, $matroska_key));
49+
)*
50+
)+
51+
m
52+
});
53+
};
54+
}
55+
56+
// TODO: Actually define all the mappings
57+
matroska_mapping_tables!(
58+
Shot => [];
59+
Scene => [];
60+
Track => [
61+
"TITLE" <=> TrackTitle,
62+
"ARTIST" <=> TrackArtist,
63+
];
64+
Part => [];
65+
Album => [];
66+
Edition => [];
67+
Collection => [];
68+
);
69+
70+
const TAG_RETAINED: bool = true;
71+
const TAG_CONSUMED: bool = false;
72+
73+
pub(super) fn split_tag(mut ebml_tag: EbmlTag) -> (EbmlTag, Tag) {
74+
let mut tag = Tag::new(TagType::Ebml);
75+
76+
// TODO: Pictures, can they be handled in a generic way?
77+
// What about the uid and referral?
78+
79+
ebml_tag.tags.retain_mut(|t| {
80+
let target_type = match &t.target {
81+
Some(t) if !t.has_uids() => t.target_type,
82+
// We cannot use any tags bound to uids
83+
Some(_) => return TAG_RETAINED,
84+
None => TargetType::default(),
85+
};
86+
87+
t.simple_tags
88+
.retain_mut(|simple_tag| split_simple_tags(target_type, simple_tag, &mut tag));
89+
if t.simple_tags.is_empty() {
90+
return TAG_CONSUMED;
91+
}
92+
93+
return TAG_RETAINED;
94+
});
95+
96+
(ebml_tag, tag)
97+
}
98+
99+
fn split_simple_tags(
100+
target_type: TargetType,
101+
simple_tag: &mut SimpleTag<'_>,
102+
tag: &mut Tag,
103+
) -> bool {
104+
let lang: Lang;
105+
match &simple_tag.language {
106+
Some(Language::Iso639_2(l)) if l.len() == 3 => {
107+
lang = l.as_bytes().try_into().unwrap(); // Infallible
108+
},
109+
None => lang = *b"und",
110+
// `Lang` doesn't support anything outside of a 3 character ISO-639-2 code.
111+
_ => return TAG_RETAINED,
112+
}
113+
114+
let Some(item_key) = MAPPINGS.get(&(target_type, &*simple_tag.name)).cloned() else {
115+
return TAG_RETAINED;
116+
};
117+
118+
if simple_tag.value.is_none() {
119+
// Ignore empty items, `TagItem` is not made to handle them.
120+
return TAG_RETAINED;
121+
}
122+
123+
let simple_tag = std::mem::replace(simple_tag, TOMBSTONE_SIMPLE_TAG);
124+
tag.push(TagItem {
125+
lang,
126+
description: String::new(),
127+
item_key,
128+
item_value: simple_tag.value.unwrap().into(), // Infallible
129+
});
130+
131+
return TAG_CONSUMED;
132+
}

0 commit comments

Comments
 (0)