mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 13:42:34 +00:00
EBML: Start parsing \Ebml\Segment\Attachments
This commit is contained in:
parent
e92a2cd0c4
commit
009defc797
6 changed files with 218 additions and 10 deletions
|
@ -581,8 +581,17 @@ where
|
|||
todo!()
|
||||
}
|
||||
|
||||
pub(crate) fn read_binary(&mut self) -> Result<Vec<u8>> {
|
||||
todo!()
|
||||
pub(crate) fn read_binary(&mut self, element_length: u64) -> Result<Vec<u8>> {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use lofty_attr::LoftyFile;
|
|||
// Exports
|
||||
|
||||
pub use properties::EbmlProperties;
|
||||
pub use tag::EbmlTag;
|
||||
pub use tag::*;
|
||||
pub use vint::VInt;
|
||||
|
||||
/// An EBML file
|
||||
|
|
|
@ -1,17 +1,118 @@
|
|||
use crate::config::ParseOptions;
|
||||
use crate::ebml::element_reader::ElementReader;
|
||||
use crate::ebml::EbmlTag;
|
||||
use crate::ebml::element_reader::{ElementIdent, ElementReader, ElementReaderYield};
|
||||
use crate::ebml::{AttachedFile, EbmlTag};
|
||||
use crate::error::Result;
|
||||
use crate::macros::decode_err;
|
||||
use crate::picture::MimeType;
|
||||
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
pub(super) fn read_from<R>(
|
||||
_element_reader: &mut ElementReader<R>,
|
||||
element_reader: &mut ElementReader<R>,
|
||||
_parse_options: ParseOptions,
|
||||
_tag: &mut EbmlTag,
|
||||
tag: &mut EbmlTag,
|
||||
) -> Result<()>
|
||||
where
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
64
lofty/src/ebml/tag/attached_file.rs
Normal file
64
lofty/src/ebml/tag/attached_file.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
pub(crate) mod attached_file;
|
||||
pub use attached_file::*;
|
||||
|
||||
use crate::config::WriteOptions;
|
||||
use crate::error::LoftyError;
|
||||
use crate::io::{FileLike, Length, Truncate};
|
||||
|
@ -12,7 +15,9 @@ use lofty_attr::tag;
|
|||
/// TODO
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[tag(description = "An `EBML` tag", supported_formats(Ebml))]
|
||||
pub struct EbmlTag {}
|
||||
pub struct EbmlTag {
|
||||
pub(crate) attached_files: Vec<AttachedFile>,
|
||||
}
|
||||
|
||||
impl Accessor for EbmlTag {}
|
||||
|
||||
|
@ -76,6 +81,7 @@ impl TagExt for EbmlTag {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SplitTagRemainder(EbmlTag);
|
||||
|
||||
|
|
|
@ -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
|
||||
//
|
||||
// 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};
|
||||
|
|
Loading…
Reference in a new issue