mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-03-04 06:47:12 +00:00
EBML: Finish parsing \Segment\Tags
This commit is contained in:
parent
92bca37a46
commit
6f1404ae61
8 changed files with 173 additions and 109 deletions
|
@ -26,7 +26,7 @@ where
|
|||
// new ones all scattered throughout the file
|
||||
let mut properties = EbmlProperties::default();
|
||||
|
||||
let mut ebml_tag = None;
|
||||
let ebml_tag;
|
||||
|
||||
let mut element_reader = ElementReader::new(reader);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ where
|
|||
{
|
||||
while let Some(child) = children_reader.next()? {
|
||||
match child {
|
||||
ElementReaderYield::Master((ElementIdent::AttachedFile, size)) => {
|
||||
ElementReaderYield::Master((ElementIdent::AttachedFile, _size)) => {
|
||||
let attached_file = read_attachment(children_reader)?;
|
||||
tag.attached_files.push(attached_file);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::config::ParseOptions;
|
||||
use crate::ebml::element_reader::{ElementChildIterator, ElementIdent, ElementReaderYield};
|
||||
use crate::ebml::{EbmlTag, Language, SimpleTag, TagValue, TargetType};
|
||||
use crate::ebml::{EbmlTag, Language, SimpleTag, Tag, TagValue, Target, TargetType};
|
||||
use crate::error::Result;
|
||||
|
||||
use crate::macros::decode_err;
|
||||
|
@ -17,7 +17,8 @@ where
|
|||
while let Some(child) = children_reader.next()? {
|
||||
match child {
|
||||
ElementReaderYield::Master((ElementIdent::Tag, _size)) => {
|
||||
read_tag(&mut children_reader.children(), tag)?
|
||||
let tag_element = read_tag(&mut children_reader.children())?;
|
||||
tag.tags.push(tag_element);
|
||||
},
|
||||
ElementReaderYield::Eof => break,
|
||||
_ => unimplemented!("Unhandled child element in \\Segment\\Tags: {child:?}"),
|
||||
|
@ -27,10 +28,13 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_tag<R>(children_reader: &mut ElementChildIterator<'_, R>, _tag: &mut EbmlTag) -> Result<()>
|
||||
fn read_tag<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<Tag>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut target = None;
|
||||
let mut simple_tags = Vec::new();
|
||||
|
||||
while let Some(child) = children_reader.next()? {
|
||||
let ElementReaderYield::Master((master, _size)) = child else {
|
||||
match child {
|
||||
|
@ -43,10 +47,17 @@ where
|
|||
|
||||
match master {
|
||||
ElementIdent::Targets => {
|
||||
let _ = read_targets(&mut children_reader.children())?;
|
||||
if target.is_some() {
|
||||
decode_err!(
|
||||
@BAIL Ebml,
|
||||
"Duplicate Targets element found in \\Segment\\Tags\\Tag"
|
||||
);
|
||||
}
|
||||
|
||||
target = Some(read_targets(&mut children_reader.children())?);
|
||||
},
|
||||
ElementIdent::SimpleTag => {
|
||||
let _ = read_simple_tag(&mut children_reader.children())?;
|
||||
simple_tags.push(read_simple_tag(&mut children_reader.children())?);
|
||||
},
|
||||
_ => {
|
||||
unimplemented!("Unhandled child element in \\Segment\\Tags\\Tag: {master:?}");
|
||||
|
@ -54,28 +65,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
let Some(target) = target else {
|
||||
decode_err!(@BAIL Ebml, "\\Segment\\Tags\\Tag is missing the required `Targets` element");
|
||||
};
|
||||
|
||||
struct Target {
|
||||
target_type_value: TargetType,
|
||||
target_type: Option<String>,
|
||||
track_uid: Vec<u64>,
|
||||
edition_uid: Vec<u64>,
|
||||
chapter_uid: Vec<u64>,
|
||||
attachment_uid: Vec<u64>,
|
||||
Ok(Tag {
|
||||
target,
|
||||
simple_tags,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_targets<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<Target>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut target_type_value = None;
|
||||
let mut target_type = None;
|
||||
let mut track_uid = Vec::new();
|
||||
let mut edition_uid = Vec::new();
|
||||
let mut chapter_uid = Vec::new();
|
||||
let mut attachment_uid = Vec::new();
|
||||
let mut target = Target::default();
|
||||
|
||||
while let Some(child) = children_reader.next()? {
|
||||
let ElementReaderYield::Child((child, size)) = child else {
|
||||
|
@ -89,22 +93,35 @@ where
|
|||
|
||||
match child.ident {
|
||||
ElementIdent::TargetTypeValue => {
|
||||
target_type_value = Some(children_reader.read_unsigned_int(size.value())?);
|
||||
let value = children_reader.read_unsigned_int(size.value())?;
|
||||
|
||||
// Casting the `u64` to `u8` is safe because the value is checked to be within
|
||||
// the range of `TargetType` anyway.
|
||||
let target_type = TargetType::try_from(value as u8)?;
|
||||
target.target_type = target_type;
|
||||
},
|
||||
ElementIdent::TargetType => {
|
||||
target_type = Some(children_reader.read_string(size.value())?);
|
||||
target.name = Some(children_reader.read_string(size.value())?);
|
||||
},
|
||||
ElementIdent::TagTrackUID => {
|
||||
track_uid.push(children_reader.read_unsigned_int(size.value())?);
|
||||
let mut track_uids = target.track_uids.unwrap_or_default();
|
||||
track_uids.push(children_reader.read_unsigned_int(size.value())?);
|
||||
target.track_uids = Some(track_uids);
|
||||
},
|
||||
ElementIdent::TagEditionUID => {
|
||||
edition_uid.push(children_reader.read_unsigned_int(size.value())?);
|
||||
let mut edition_uids = target.edition_uids.unwrap_or_default();
|
||||
edition_uids.push(children_reader.read_unsigned_int(size.value())?);
|
||||
target.edition_uids = Some(edition_uids);
|
||||
},
|
||||
ElementIdent::TagChapterUID => {
|
||||
chapter_uid.push(children_reader.read_unsigned_int(size.value())?);
|
||||
let mut chapter_uids = target.chapter_uids.unwrap_or_default();
|
||||
chapter_uids.push(children_reader.read_unsigned_int(size.value())?);
|
||||
target.chapter_uids = Some(chapter_uids);
|
||||
},
|
||||
ElementIdent::TagAttachmentUID => {
|
||||
attachment_uid.push(children_reader.read_unsigned_int(size.value())?);
|
||||
let mut attachment_uids = target.attachment_uids.unwrap_or_default();
|
||||
attachment_uids.push(children_reader.read_unsigned_int(size.value())?);
|
||||
target.attachment_uids = Some(attachment_uids);
|
||||
},
|
||||
_ => {
|
||||
unreachable!("Unhandled child element in \\Segment\\Tags\\Tag\\Targets: {child:?}")
|
||||
|
@ -112,23 +129,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
let target_type_value = match target_type_value {
|
||||
// Casting the `u64` to `u8` is safe because the value is checked to be within
|
||||
// the range of `TargetType` anyway.
|
||||
Some(value) => TargetType::try_from(value as u8)?,
|
||||
// The spec defines TargetType 50 (Album) as the default value, as it is the most
|
||||
// common grouping level.
|
||||
None => TargetType::Album,
|
||||
};
|
||||
|
||||
Ok(Target {
|
||||
target_type_value,
|
||||
target_type,
|
||||
track_uid,
|
||||
edition_uid,
|
||||
chapter_uid,
|
||||
attachment_uid,
|
||||
})
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
fn read_simple_tag<R>(children_reader: &mut ElementChildIterator<'_, R>) -> Result<SimpleTag>
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
pub(crate) mod attached_file;
|
||||
pub(crate) mod simple_tag;
|
||||
pub(crate) mod target_type;
|
||||
mod attached_file;
|
||||
mod simple_tag;
|
||||
mod tag;
|
||||
mod target;
|
||||
|
||||
pub use attached_file::*;
|
||||
pub use simple_tag::*;
|
||||
pub use target_type::*;
|
||||
pub use tag::*;
|
||||
pub use target::*;
|
||||
|
||||
use crate::config::WriteOptions;
|
||||
use crate::error::LoftyError;
|
||||
use crate::io::{FileLike, Length, Truncate};
|
||||
use crate::tag::{Accessor, MergeTag, SplitTag, Tag, TagExt, TagType};
|
||||
use crate::tag::{Accessor, MergeTag, SplitTag, TagExt, TagType};
|
||||
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
|
@ -21,6 +23,7 @@ use lofty_attr::tag;
|
|||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
#[tag(description = "An `EBML` tag", supported_formats(Ebml))]
|
||||
pub struct EbmlTag {
|
||||
pub(crate) tags: Vec<Tag>,
|
||||
pub(crate) attached_files: Vec<AttachedFile>,
|
||||
}
|
||||
|
||||
|
@ -107,7 +110,7 @@ impl Deref for SplitTagRemainder {
|
|||
impl SplitTag for EbmlTag {
|
||||
type Remainder = SplitTagRemainder;
|
||||
|
||||
fn split_tag(mut self) -> (Self::Remainder, Tag) {
|
||||
fn split_tag(mut self) -> (Self::Remainder, crate::tag::Tag) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -115,19 +118,19 @@ impl SplitTag for EbmlTag {
|
|||
impl MergeTag for SplitTagRemainder {
|
||||
type Merged = EbmlTag;
|
||||
|
||||
fn merge_tag(self, _tag: Tag) -> Self::Merged {
|
||||
fn merge_tag(self, _tag: crate::tag::Tag) -> Self::Merged {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EbmlTag> for Tag {
|
||||
impl From<EbmlTag> for crate::tag::Tag {
|
||||
fn from(input: EbmlTag) -> Self {
|
||||
input.split_tag().1
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tag> for EbmlTag {
|
||||
fn from(input: Tag) -> Self {
|
||||
impl From<crate::tag::Tag> for EbmlTag {
|
||||
fn from(input: crate::tag::Tag) -> Self {
|
||||
SplitTagRemainder::default().merge_tag(input)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::tag::ItemValue;
|
|||
/// - The ISO-639-2 language code allows for an optional country code, so the [Lang] type cannot be used.
|
||||
///
|
||||
/// [Lang]: crate::tag::items::Lang
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Language {
|
||||
/// An ISO-639-2 language code
|
||||
Iso639_2(String),
|
||||
|
@ -33,6 +34,7 @@ pub enum Language {
|
|||
///
|
||||
/// - [`ItemValue::Text`] | [`ItemValue::Locator`] -> [`TagValue::String`]
|
||||
/// - [`ItemValue::Binary`] -> [`TagValue::Binary`]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum TagValue {
|
||||
/// A UTF-8 string tag value
|
||||
String(String),
|
||||
|
@ -66,6 +68,7 @@ impl From<ItemValue> for TagValue {
|
|||
/// - They each describe a single [`Target`].
|
||||
/// - This also means that multiple tags can describe the same target.
|
||||
/// - They **do not** need to have a value.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SimpleTag {
|
||||
/// The name of the tag as it is stored
|
||||
///
|
||||
|
|
16
lofty/src/ebml/tag/tag.rs
Normal file
16
lofty/src/ebml/tag/tag.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use super::{SimpleTag, Target};
|
||||
|
||||
/// A single metadata descriptor.
|
||||
///
|
||||
/// This represents a `\Segment\Tags\Tag` element in the EBML tree. It contains a single [`Target`] and
|
||||
/// its associated [`SimpleTag`]s.
|
||||
///
|
||||
/// This structure is very different from other formats. See [`Target`] and [`SimpleTag`] for more
|
||||
/// information on how these work.
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Tag {
|
||||
/// The target for which the tags are applied.
|
||||
pub target: Target,
|
||||
/// General information about the target
|
||||
pub simple_tags: Vec<SimpleTag>,
|
||||
}
|
95
lofty/src/ebml/tag/target.rs
Normal file
95
lofty/src/ebml/tag/target.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::error::{LoftyError, Result};
|
||||
use crate::macros::decode_err;
|
||||
|
||||
/// The type of the target.
|
||||
///
|
||||
/// This is used to determine the type of the target that the tag is applied to.
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub enum TargetType {
|
||||
/// For video, this represents: SHOT
|
||||
Shot = 10,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: SUBTRACK / PART / MOVEMENT
|
||||
/// - Video: SCENE
|
||||
Scene = 20,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: TRACK / SONG
|
||||
/// - Video: CHAPTER
|
||||
Track = 30,
|
||||
/// For both audio and video, this represents: PART / SESSION
|
||||
Part = 40,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: ALBUM / OPERA / CONCERT
|
||||
/// - Video: MOVIE / EPISODE / CONCERT
|
||||
// The spec defines TargetType 50 (Album) as the default value, as it is the most
|
||||
// common grouping level.
|
||||
#[default]
|
||||
Album = 50,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: EDITION / ISSUE / VOLUME / OPUS
|
||||
/// - Video: SEASON / SEQUEL / VOLUME
|
||||
Edition = 60,
|
||||
/// For both audio and video, this represents: COLLECTION
|
||||
Collection = 70,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for TargetType {
|
||||
type Error = LoftyError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
10 => Ok(Self::Shot),
|
||||
20 => Ok(Self::Scene),
|
||||
30 => Ok(Self::Track),
|
||||
40 => Ok(Self::Part),
|
||||
50 => Ok(Self::Album),
|
||||
60 => Ok(Self::Edition),
|
||||
70 => Ok(Self::Collection),
|
||||
_ => decode_err!(@BAIL Ebml, "TargetType value out of range"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The target for which a [`SimpleTag`] is applied.
|
||||
///
|
||||
/// In Matroska, tags are specified on the level of targets. For example, there is no "TRACK TITLE"
|
||||
/// tag, but rather a "TITLE" tag that is applied to a [`TargetType::Track`] target.
|
||||
///
|
||||
/// See [`TargetType`] for more information on the types of targets.
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Target {
|
||||
/// The type of the target.
|
||||
pub target_type: TargetType,
|
||||
/// An informational string that can be used to display the logical level of the target.
|
||||
pub name: Option<String>,
|
||||
/// A unique ID to identify the [Track](s) the tags belong to.
|
||||
///
|
||||
/// If the value is 0 at this level, the tags apply to all tracks in the Segment. If set to any
|
||||
/// other value, it **MUST** match the [`TrackUID`] value of a track found in this Segment.
|
||||
pub track_uids: Option<Vec<u64>>,
|
||||
/// A unique ID to identify the [EditionEntry](s) the tags belong to.
|
||||
///
|
||||
/// If the value is 0 at this level, the tags apply to all editions in the Segment. If set to
|
||||
/// any other value, it **MUST** match the [`EditionUID`] value of an edition found in this Segment.
|
||||
pub edition_uids: Option<Vec<u64>>,
|
||||
/// A unique ID to identify the [Chapter](s) the tags belong to.
|
||||
///
|
||||
/// If the value is 0 at this level, the tags apply to all chapters in the Segment. If set to
|
||||
/// any other value, it **MUST** match the [`ChapterUID`] value of a chapter found in this Segment.
|
||||
pub chapter_uids: Option<Vec<u64>>,
|
||||
/// A unique ID to identify the [`AttachedFile`]\(s) the tags belong to.
|
||||
///
|
||||
/// If the value is 0 at this level, the tags apply to all the attachments in the Segment. If
|
||||
/// set to any other value, it **MUST** match the [`AttachedFile::uid`]) value of an attachment
|
||||
/// found in this Segment.
|
||||
///
|
||||
/// [`AttachedFile`]: crate::ebml::AttachedFile
|
||||
/// [`AttachedFile::uid`]: crate::ebml::AttachedFile::uid
|
||||
pub attachment_uids: Option<Vec<u64>>,
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
use crate::error::{LoftyError, Result};
|
||||
use crate::macros::decode_err;
|
||||
|
||||
/// The type of the target.
|
||||
///
|
||||
/// This is used to determine the type of the target that the tag is applied to.
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum TargetType {
|
||||
/// For video, this represents: SHOT
|
||||
Shot = 10,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: SUBTRACK / PART / MOVEMENT
|
||||
/// - Video: SCENE
|
||||
Scene = 20,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: TRACK / SONG
|
||||
/// - Video: CHAPTER
|
||||
Track = 30,
|
||||
/// For both audio and video, this represents: PART / SESSION
|
||||
Part = 40,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: ALBUM / OPERA / CONCERT
|
||||
/// - Video: MOVIE / EPISODE / CONCERT
|
||||
Album = 50,
|
||||
/// This is used to represent the following:
|
||||
///
|
||||
/// - Audio: EDITION / ISSUE / VOLUME / OPUS
|
||||
/// - Video: SEASON / SEQUEL / VOLUME
|
||||
Edition = 60,
|
||||
/// For both audio and video, this represents: COLLECTION
|
||||
Collection = 70,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for TargetType {
|
||||
type Error = LoftyError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
10 => Ok(Self::Shot),
|
||||
20 => Ok(Self::Scene),
|
||||
30 => Ok(Self::Track),
|
||||
40 => Ok(Self::Part),
|
||||
50 => Ok(Self::Album),
|
||||
60 => Ok(Self::Edition),
|
||||
70 => Ok(Self::Collection),
|
||||
_ => decode_err!(@BAIL Ebml, "TargetType value out of range"),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue