mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-13 14:12:31 +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!()
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
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::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);
|
||||||
|
|
||||||
|
|
|
@ -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};
|
||||||
|
|
Loading…
Reference in a new issue