EBML: Start parsing \Ebml\Segment\Attachments

This commit is contained in:
Serial 2024-08-30 23:25:29 -04:00
parent e92a2cd0c4
commit 009defc797
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
6 changed files with 218 additions and 10 deletions

View file

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

View file

@ -10,7 +10,7 @@ use lofty_attr::LoftyFile;
// Exports // Exports
pub use properties::EbmlProperties; pub use properties::EbmlProperties;
pub use tag::EbmlTag; pub use tag::*;
pub use vint::VInt; pub use vint::VInt;
/// An EBML file /// An EBML file

View file

@ -1,17 +1,118 @@
use crate::config::ParseOptions; use crate::config::ParseOptions;
use crate::ebml::element_reader::ElementReader; use crate::ebml::element_reader::{ElementIdent, ElementReader, ElementReaderYield};
use crate::ebml::EbmlTag; use crate::ebml::{AttachedFile, EbmlTag};
use crate::error::Result; use crate::error::Result;
use crate::macros::decode_err;
use crate::picture::MimeType;
use std::io::{Read, Seek}; use std::io::{Read, Seek};
pub(super) fn read_from<R>( pub(super) fn read_from<R>(
_element_reader: &mut ElementReader<R>, element_reader: &mut ElementReader<R>,
_parse_options: ParseOptions, _parse_options: ParseOptions,
_tag: &mut EbmlTag, tag: &mut EbmlTag,
) -> Result<()> ) -> Result<()>
where where
R: Read + Seek, R: Read + Seek,
{ {
unimplemented!("\\Ebml\\Segment\\Attachments") let mut children_reader = element_reader.children();
while let Some(child) = children_reader.next()? {
match child {
ElementReaderYield::Master((ElementIdent::AttachedFile, size)) => {
let attached_file = read_attachment(&mut children_reader)?;
tag.attached_files.push(attached_file);
},
ElementReaderYield::Eof => break,
_ => unreachable!("Unhandled child element in \\Ebml\\Segment\\Attachments: {child:?}"),
}
}
Ok(())
}
fn read_attachment<R>(element_reader: &mut ElementReader<R>) -> Result<AttachedFile>
where
R: Read + Seek,
{
let mut description = None;
let mut file_name = None;
let mut mime_type = None;
let mut file_data = None;
let mut uid = None;
let mut referral = None;
let mut used_start_time = None;
let mut used_end_time = None;
let mut children_reader = element_reader.children();
while let Some(child) = children_reader.next()? {
let ElementReaderYield::Child((child, size)) = child else {
match child {
ElementReaderYield::Eof => break,
_ => unreachable!(
"Unhandled child element in \\Ebml\\Segment\\Attachments\\AttachedFile: \
{child:?}"
),
}
};
let size = size.value();
match child.ident {
ElementIdent::FileDescription => {
description = Some(children_reader.read_string(size)?);
},
ElementIdent::FileName => {
file_name = Some(children_reader.read_string(size)?);
},
ElementIdent::FileMimeType => {
let mime_str = children_reader.read_string(size)?;
mime_type = Some(MimeType::from_str(&mime_str));
},
ElementIdent::FileData => {
file_data = Some(children_reader.read_binary(size)?);
},
ElementIdent::FileUID => {
uid = Some(children_reader.read_unsigned_int(size)?);
},
ElementIdent::FileReferral => {
referral = Some(children_reader.read_string(size)?);
},
ElementIdent::FileUsedStartTime => {
used_start_time = Some(children_reader.read_unsigned_int(size)?);
},
ElementIdent::FileUsedEndTime => {
used_end_time = Some(children_reader.read_unsigned_int(size)?);
},
_ => unreachable!(
"Unhandled child element in \\Ebml\\Segment\\Attachments\\AttachedFile: {child:?}"
),
}
}
let Some(file_name) = file_name else {
decode_err!(@BAIL Ebml, "File name is required for an attached file");
};
let Some(mime_type) = mime_type else {
decode_err!(@BAIL Ebml, "MIME type is required for an attached file");
};
let Some(file_data) = file_data else {
decode_err!(@BAIL Ebml, "File data is required for an attached file");
};
let Some(uid) = uid else {
decode_err!(@BAIL Ebml, "UID is required for an attached file");
};
Ok(AttachedFile {
description,
file_name,
mime_type,
file_data,
uid,
referral,
used_start_time,
used_end_time,
})
} }

View file

@ -0,0 +1,64 @@
use crate::error::Result;
use crate::macros::encode_err;
use crate::picture::MimeType;
use std::fmt::Debug;
/// Some attached file
///
/// This element contains any attached files, similar to the [GEOB]
/// frame in ID3v2. The difference is, this is *also* used for images.
///
/// [GEOB]: crate::id3::v2::GeneralEncapsulatedObject
#[derive(Clone, Eq, PartialEq)]
pub struct AttachedFile {
/// A human-friendly name for the attached file.
pub description: Option<String>,
/// The actual file name of the attached file.
pub file_name: String,
/// Media type of the file following the [RFC6838] format.
///
/// [RFC6838]: https://tools.ietf.org/html/rfc6838
pub mime_type: MimeType,
/// The data of the file.
pub file_data: Vec<u8>,
/// Unique ID representing the file, as random as possible.
pub uid: u64,
/// A binary value that a track/codec can refer to when the attachment is needed.
pub referral: Option<String>,
/// The timestamp at which this optimized font attachment comes into context.
///
/// This is expressed in Segment Ticks which is based on `TimestampScale`. This element is
/// reserved for future use and if written **MUST** be the segment start timestamp.
pub used_start_time: Option<u64>,
/// The timestamp at which this optimized font attachment goes out of context.
///
/// This is expressed in Segment Ticks which is based on `TimestampScale`. This element is
/// reserved for future use and if written **MUST** be the segment end timestamp.
pub used_end_time: Option<u64>,
}
impl Debug for AttachedFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AttachedFile")
.field("description", &self.description)
.field("file_name", &self.file_name)
.field("mime_type", &self.mime_type)
.field("file_data", &format!("<{} bytes>", self.file_data.len()))
.field("uid", &self.uid)
.field("referral", &self.referral)
.field("used_start_time", &self.used_start_time)
.field("used_end_time", &self.used_end_time)
.finish()
}
}
impl AttachedFile {
pub(crate) fn validate(&self) -> Result<()> {
if self.uid == 0 {
encode_err!(@BAIL Ebml, "The UID of an attachment cannot be 0");
}
Ok(())
}
}

View file

@ -1,3 +1,6 @@
pub(crate) mod attached_file;
pub use attached_file::*;
use crate::config::WriteOptions; use crate::config::WriteOptions;
use crate::error::LoftyError; use crate::error::LoftyError;
use crate::io::{FileLike, Length, Truncate}; use crate::io::{FileLike, Length, Truncate};
@ -12,7 +15,9 @@ use lofty_attr::tag;
/// TODO /// TODO
#[derive(Default, Debug, PartialEq, Eq, Clone)] #[derive(Default, Debug, PartialEq, Eq, Clone)]
#[tag(description = "An `EBML` tag", supported_formats(Ebml))] #[tag(description = "An `EBML` tag", supported_formats(Ebml))]
pub struct EbmlTag {} pub struct EbmlTag {
pub(crate) attached_files: Vec<AttachedFile>,
}
impl Accessor for EbmlTag {} impl Accessor for EbmlTag {}
@ -76,6 +81,7 @@ impl TagExt for EbmlTag {
} }
} }
#[doc(hidden)]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct SplitTagRemainder(EbmlTag); pub struct SplitTagRemainder(EbmlTag);

View file

@ -50,6 +50,34 @@ macro_rules! decode_err {
}; };
} }
// Shorthand for FileEncodingError::new(FileType::Foo, "Message")
//
// Usage:
//
// - encode_err!(Variant, Message)
// - encode_err!(Message)
//
// or bail:
//
// - encode_err!(@BAIL Variant, Message)
// - encode_err!(@BAIL Message)
macro_rules! encode_err {
($file_ty:ident, $reason:literal) => {
Into::<crate::error::LoftyError>::into(crate::error::FileEncodingError::new(
crate::file::FileType::$file_ty,
$reason,
))
};
($reason:literal) => {
Into::<crate::error::LoftyError>::into(crate::error::FileEncodingError::from_description(
$reason,
))
};
(@BAIL $($file_ty:ident,)? $reason:literal) => {
return Err(encode_err!($($file_ty,)? $reason))
};
}
// A macro for handling the different `ParsingMode`s // A macro for handling the different `ParsingMode`s
// //
// NOTE: All fields are optional, if `STRICT` or `RELAXED` are missing, it will // NOTE: All fields are optional, if `STRICT` or `RELAXED` are missing, it will
@ -95,4 +123,4 @@ macro_rules! parse_mode_choice {
}; };
} }
pub(crate) use {decode_err, err, parse_mode_choice, try_vec}; pub(crate) use {decode_err, encode_err, err, parse_mode_choice, try_vec};