Skip to content

Commit

Permalink
EBML: Finish parsing \Segment\Tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Serial-ATA committed Sep 15, 2024
1 parent c784502 commit 78430fc
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 109 deletions.
2 changes: 1 addition & 1 deletion lofty/src/ebml/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion lofty/src/ebml/read/segment_attachments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
Expand Down
87 changes: 44 additions & 43 deletions lofty/src/ebml/read/segment_tags.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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:?}"),
Expand All @@ -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 {
Expand All @@ -43,39 +47,39 @@ 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:?}");
},
}
}

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 {
Expand All @@ -89,46 +93,43 @@ 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:?}")
},
}
}

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>
Expand Down
23 changes: 13 additions & 10 deletions lofty/src/ebml/tag/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>,
}

Expand Down Expand Up @@ -107,27 +110,27 @@ 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!()
}
}

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)
}
}
3 changes: 3 additions & 0 deletions lofty/src/ebml/tag/simple_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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
///
Expand Down
16 changes: 16 additions & 0 deletions lofty/src/ebml/tag/tag.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 95 additions & 0 deletions lofty/src/ebml/tag/target.rs
Original file line number Diff line number Diff line change
@@ -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>>,
}
Loading

0 comments on commit 78430fc

Please sign in to comment.