Skip to content

Commit f65cd84

Browse files
committed
EBML: Start parsing \Ebml\Segment\Attachments
1 parent fc23525 commit f65cd84

File tree

6 files changed

+218
-10
lines changed

6 files changed

+218
-10
lines changed

lofty/src/ebml/element_reader.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,17 @@ where
581581
todo!()
582582
}
583583

584-
pub(crate) fn read_binary(&mut self) -> Result<Vec<u8>> {
585-
todo!()
584+
pub(crate) fn read_binary(&mut self, element_length: u64) -> Result<Vec<u8>> {
585+
// https://www.rfc-editor.org/rfc/rfc8794.html#section-7.8
586+
// A Binary Element MUST declare a length in octets from zero to VINTMAX.
587+
588+
if element_length > VInt::MAX {
589+
decode_err!(@BAIL Ebml, "Binary element length is too large")
590+
}
591+
592+
let mut content = try_vec![0; element_length as usize];
593+
self.read_exact(&mut content)?;
594+
Ok(content)
586595
}
587596
}
588597

lofty/src/ebml/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use lofty_attr::LoftyFile;
1010
// Exports
1111

1212
pub use properties::EbmlProperties;
13-
pub use tag::EbmlTag;
13+
pub use tag::*;
1414
pub use vint::VInt;
1515

1616
/// An EBML file
Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,118 @@
11
use crate::config::ParseOptions;
2-
use crate::ebml::element_reader::ElementReader;
3-
use crate::ebml::EbmlTag;
2+
use crate::ebml::element_reader::{ElementIdent, ElementReader, ElementReaderYield};
3+
use crate::ebml::{AttachedFile, EbmlTag};
44
use crate::error::Result;
5+
use crate::macros::decode_err;
6+
use crate::picture::MimeType;
57

68
use std::io::{Read, Seek};
79

810
pub(super) fn read_from<R>(
9-
_element_reader: &mut ElementReader<R>,
11+
element_reader: &mut ElementReader<R>,
1012
_parse_options: ParseOptions,
11-
_tag: &mut EbmlTag,
13+
tag: &mut EbmlTag,
1214
) -> Result<()>
1315
where
1416
R: Read + Seek,
1517
{
16-
unimplemented!("\\Ebml\\Segment\\Attachments")
18+
let mut children_reader = element_reader.children();
19+
20+
while let Some(child) = children_reader.next()? {
21+
match child {
22+
ElementReaderYield::Master((ElementIdent::AttachedFile, size)) => {
23+
let attached_file = read_attachment(&mut children_reader)?;
24+
tag.attached_files.push(attached_file);
25+
},
26+
ElementReaderYield::Eof => break,
27+
_ => unreachable!("Unhandled child element in \\Ebml\\Segment\\Attachments: {child:?}"),
28+
}
29+
}
30+
31+
Ok(())
32+
}
33+
34+
fn read_attachment<R>(element_reader: &mut ElementReader<R>) -> Result<AttachedFile>
35+
where
36+
R: Read + Seek,
37+
{
38+
let mut description = None;
39+
let mut file_name = None;
40+
let mut mime_type = None;
41+
let mut file_data = None;
42+
let mut uid = None;
43+
let mut referral = None;
44+
let mut used_start_time = None;
45+
let mut used_end_time = None;
46+
47+
let mut children_reader = element_reader.children();
48+
while let Some(child) = children_reader.next()? {
49+
let ElementReaderYield::Child((child, size)) = child else {
50+
match child {
51+
ElementReaderYield::Eof => break,
52+
_ => unreachable!(
53+
"Unhandled child element in \\Ebml\\Segment\\Attachments\\AttachedFile: \
54+
{child:?}"
55+
),
56+
}
57+
};
58+
59+
let size = size.value();
60+
match child.ident {
61+
ElementIdent::FileDescription => {
62+
description = Some(children_reader.read_string(size)?);
63+
},
64+
ElementIdent::FileName => {
65+
file_name = Some(children_reader.read_string(size)?);
66+
},
67+
ElementIdent::FileMimeType => {
68+
let mime_str = children_reader.read_string(size)?;
69+
mime_type = Some(MimeType::from_str(&mime_str));
70+
},
71+
ElementIdent::FileData => {
72+
file_data = Some(children_reader.read_binary(size)?);
73+
},
74+
ElementIdent::FileUID => {
75+
uid = Some(children_reader.read_unsigned_int(size)?);
76+
},
77+
ElementIdent::FileReferral => {
78+
referral = Some(children_reader.read_string(size)?);
79+
},
80+
ElementIdent::FileUsedStartTime => {
81+
used_start_time = Some(children_reader.read_unsigned_int(size)?);
82+
},
83+
ElementIdent::FileUsedEndTime => {
84+
used_end_time = Some(children_reader.read_unsigned_int(size)?);
85+
},
86+
_ => unreachable!(
87+
"Unhandled child element in \\Ebml\\Segment\\Attachments\\AttachedFile: {child:?}"
88+
),
89+
}
90+
}
91+
92+
let Some(file_name) = file_name else {
93+
decode_err!(@BAIL Ebml, "File name is required for an attached file");
94+
};
95+
96+
let Some(mime_type) = mime_type else {
97+
decode_err!(@BAIL Ebml, "MIME type is required for an attached file");
98+
};
99+
100+
let Some(file_data) = file_data else {
101+
decode_err!(@BAIL Ebml, "File data is required for an attached file");
102+
};
103+
104+
let Some(uid) = uid else {
105+
decode_err!(@BAIL Ebml, "UID is required for an attached file");
106+
};
107+
108+
Ok(AttachedFile {
109+
description,
110+
file_name,
111+
mime_type,
112+
file_data,
113+
uid,
114+
referral,
115+
used_start_time,
116+
used_end_time,
117+
})
17118
}

lofty/src/ebml/tag/attached_file.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use crate::error::Result;
2+
use crate::macros::encode_err;
3+
use crate::picture::MimeType;
4+
5+
use std::fmt::Debug;
6+
7+
/// Some attached file
8+
///
9+
/// This element contains any attached files, similar to the [GEOB]
10+
/// frame in ID3v2. The difference is, this is *also* used for images.
11+
///
12+
/// [GEOB]: crate::id3::v2::GeneralEncapsulatedObject
13+
#[derive(Clone, Eq, PartialEq)]
14+
pub struct AttachedFile {
15+
/// A human-friendly name for the attached file.
16+
pub description: Option<String>,
17+
/// The actual file name of the attached file.
18+
pub file_name: String,
19+
/// Media type of the file following the [RFC6838] format.
20+
///
21+
/// [RFC6838]: https://tools.ietf.org/html/rfc6838
22+
pub mime_type: MimeType,
23+
/// The data of the file.
24+
pub file_data: Vec<u8>,
25+
/// Unique ID representing the file, as random as possible.
26+
pub uid: u64,
27+
/// A binary value that a track/codec can refer to when the attachment is needed.
28+
pub referral: Option<String>,
29+
/// The timestamp at which this optimized font attachment comes into context.
30+
///
31+
/// This is expressed in Segment Ticks which is based on `TimestampScale`. This element is
32+
/// reserved for future use and if written **MUST** be the segment start timestamp.
33+
pub used_start_time: Option<u64>,
34+
/// The timestamp at which this optimized font attachment goes out of context.
35+
///
36+
/// This is expressed in Segment Ticks which is based on `TimestampScale`. This element is
37+
/// reserved for future use and if written **MUST** be the segment end timestamp.
38+
pub used_end_time: Option<u64>,
39+
}
40+
41+
impl Debug for AttachedFile {
42+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43+
f.debug_struct("AttachedFile")
44+
.field("description", &self.description)
45+
.field("file_name", &self.file_name)
46+
.field("mime_type", &self.mime_type)
47+
.field("file_data", &format!("<{} bytes>", self.file_data.len()))
48+
.field("uid", &self.uid)
49+
.field("referral", &self.referral)
50+
.field("used_start_time", &self.used_start_time)
51+
.field("used_end_time", &self.used_end_time)
52+
.finish()
53+
}
54+
}
55+
56+
impl AttachedFile {
57+
pub(crate) fn validate(&self) -> Result<()> {
58+
if self.uid == 0 {
59+
encode_err!(@BAIL Ebml, "The UID of an attachment cannot be 0");
60+
}
61+
62+
Ok(())
63+
}
64+
}

lofty/src/ebml/tag/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
pub(crate) mod attached_file;
2+
pub use attached_file::*;
3+
14
use crate::config::WriteOptions;
25
use crate::error::LoftyError;
36
use crate::io::{FileLike, Length, Truncate};
@@ -12,7 +15,9 @@ use lofty_attr::tag;
1215
/// TODO
1316
#[derive(Default, Debug, PartialEq, Eq, Clone)]
1417
#[tag(description = "An `EBML` tag", supported_formats(Ebml))]
15-
pub struct EbmlTag {}
18+
pub struct EbmlTag {
19+
pub(crate) attached_files: Vec<AttachedFile>,
20+
}
1621

1722
impl Accessor for EbmlTag {}
1823

@@ -76,6 +81,7 @@ impl TagExt for EbmlTag {
7681
}
7782
}
7883

84+
#[doc(hidden)]
7985
#[derive(Debug, Clone, Default)]
8086
pub struct SplitTagRemainder(EbmlTag);
8187

lofty/src/macros.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ macro_rules! decode_err {
5050
};
5151
}
5252

53+
// Shorthand for FileEncodingError::new(FileType::Foo, "Message")
54+
//
55+
// Usage:
56+
//
57+
// - encode_err!(Variant, Message)
58+
// - encode_err!(Message)
59+
//
60+
// or bail:
61+
//
62+
// - encode_err!(@BAIL Variant, Message)
63+
// - encode_err!(@BAIL Message)
64+
macro_rules! encode_err {
65+
($file_ty:ident, $reason:literal) => {
66+
Into::<crate::error::LoftyError>::into(crate::error::FileEncodingError::new(
67+
crate::file::FileType::$file_ty,
68+
$reason,
69+
))
70+
};
71+
($reason:literal) => {
72+
Into::<crate::error::LoftyError>::into(crate::error::FileEncodingError::from_description(
73+
$reason,
74+
))
75+
};
76+
(@BAIL $($file_ty:ident,)? $reason:literal) => {
77+
return Err(encode_err!($($file_ty,)? $reason))
78+
};
79+
}
80+
5381
// A macro for handling the different `ParsingMode`s
5482
//
5583
// NOTE: All fields are optional, if `STRICT` or `RELAXED` are missing, it will
@@ -95,4 +123,4 @@ macro_rules! parse_mode_choice {
95123
};
96124
}
97125

98-
pub(crate) use {decode_err, err, parse_mode_choice, try_vec};
126+
pub(crate) use {decode_err, encode_err, err, parse_mode_choice, try_vec};

0 commit comments

Comments
 (0)