use crate::frame::content_cmp::ContentCmp::{Comparable, Incomparable, Same};
use crate::frame::Frame;
use crate::stream::encoding::Encoding;
use crate::tag::Version;
use crate::taglike::TagLike;
use std::borrow::Cow;
use std::fmt;
use std::io;
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum Content {
Text(String),
ExtendedText(ExtendedText),
Link(String),
ExtendedLink(ExtendedLink),
Comment(Comment),
Popularimeter(Popularimeter),
Lyrics(Lyrics),
SynchronisedLyrics(SynchronisedLyrics),
Picture(Picture),
EncapsulatedObject(EncapsulatedObject),
Chapter(Chapter),
MpegLocationLookupTable(MpegLocationLookupTable),
Private(Private),
TableOfContents(TableOfContents),
Unknown(Unknown),
}
impl Content {
pub(crate) fn unique(&self) -> impl PartialEq + '_ {
match self {
Self::Text(_) => Same,
Self::ExtendedText(extended_text) => {
Comparable(vec![Cow::Borrowed(extended_text.description.as_bytes())])
}
Self::Link(_) => Same,
Self::ExtendedLink(extended_link) => {
Comparable(vec![Cow::Borrowed(extended_link.description.as_bytes())])
}
Self::Popularimeter(popularimeter) => {
Comparable(vec![Cow::Borrowed(popularimeter.user.as_bytes())])
}
Self::Comment(comment) => Comparable(vec![
Cow::Borrowed(comment.lang.as_bytes()),
Cow::Borrowed(comment.description.as_bytes()),
]),
Self::Lyrics(lyrics) => Comparable(vec![
Cow::Borrowed(lyrics.lang.as_bytes()),
Cow::Borrowed(lyrics.description.as_bytes()),
]),
Self::SynchronisedLyrics(synchronised_lyrics) => Comparable(vec![
Cow::Borrowed(synchronised_lyrics.lang.as_bytes()),
Cow::Owned(
synchronised_lyrics
.content_type
.to_string()
.as_bytes()
.to_owned(),
),
]),
Self::Picture(picture) => Comparable(vec![Cow::Owned(
picture.picture_type.to_string().as_bytes().to_owned(),
)]),
Self::EncapsulatedObject(encapsulated_object) => Comparable(vec![Cow::Borrowed(
encapsulated_object.description.as_bytes(),
)]),
Self::Chapter(chapter) => {
Comparable(vec![Cow::Borrowed(chapter.element_id.as_bytes())])
}
Self::MpegLocationLookupTable(_) => Same,
Self::Private(private) => Comparable(vec![
Cow::Borrowed(private.owner_identifier.as_bytes()),
Cow::Borrowed(private.private_data.as_slice()),
]),
Self::TableOfContents(table_of_contents) => {
Comparable(vec![Cow::Borrowed(table_of_contents.element_id.as_bytes())])
}
Self::Unknown(_) => Incomparable,
}
}
pub fn new_text_values(texts: impl IntoIterator<Item = impl Into<String>>) -> Self {
let text = texts
.into_iter()
.map(|t| t.into())
.inspect(|s| assert!(!s.contains('\u{0}')))
.collect::<Vec<String>>()
.join("\u{0}");
Self::Text(text)
}
pub fn text(&self) -> Option<&str> {
match self {
Content::Text(content) => Some(content),
_ => None,
}
}
pub fn text_values(&self) -> Option<impl Iterator<Item = &str>> {
self.text().map(|content| content.split('\0'))
}
pub fn extended_text(&self) -> Option<&ExtendedText> {
match self {
Content::ExtendedText(content) => Some(content),
_ => None,
}
}
pub fn link(&self) -> Option<&str> {
match self {
Content::Link(content) => Some(content),
_ => None,
}
}
pub fn extended_link(&self) -> Option<&ExtendedLink> {
match self {
Content::ExtendedLink(content) => Some(content),
_ => None,
}
}
pub fn encapsulated_object(&self) -> Option<&EncapsulatedObject> {
match self {
Content::EncapsulatedObject(content) => Some(content),
_ => None,
}
}
pub fn comment(&self) -> Option<&Comment> {
match self {
Content::Comment(content) => Some(content),
_ => None,
}
}
pub fn lyrics(&self) -> Option<&Lyrics> {
match self {
Content::Lyrics(content) => Some(content),
_ => None,
}
}
pub fn synchronised_lyrics(&self) -> Option<&SynchronisedLyrics> {
match self {
Content::SynchronisedLyrics(content) => Some(content),
_ => None,
}
}
pub fn picture(&self) -> Option<&Picture> {
match self {
Content::Picture(picture) => Some(picture),
_ => None,
}
}
pub fn chapter(&self) -> Option<&Chapter> {
match self {
Content::Chapter(chapter) => Some(chapter),
_ => None,
}
}
pub fn mpeg_location_lookup_table(&self) -> Option<&MpegLocationLookupTable> {
match self {
Content::MpegLocationLookupTable(mpeg_table) => Some(mpeg_table),
_ => None,
}
}
pub fn popularimeter(&self) -> Option<&Popularimeter> {
match self {
Content::Popularimeter(popularimeter) => Some(popularimeter),
_ => None,
}
}
pub fn table_of_contents(&self) -> Option<&TableOfContents> {
match self {
Content::TableOfContents(table_of_contents) => Some(table_of_contents),
_ => None,
}
}
#[deprecated(note = "Use to_unknown")]
pub fn unknown(&self) -> Option<&[u8]> {
match self {
Content::Unknown(unknown) => Some(&unknown.data),
_ => None,
}
}
pub fn to_unknown(&self) -> crate::Result<Cow<'_, Unknown>> {
match self {
Content::Unknown(unknown) => Ok(Cow::Borrowed(unknown)),
content => {
let version = Version::default();
let mut data = Vec::new();
crate::stream::frame::content::encode(&mut data, content, version, Encoding::UTF8)?;
Ok(Cow::Owned(Unknown { data, version }))
}
}
}
}
impl fmt::Display for Content {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Content::Text(s) => write!(f, "{}", s),
Content::Link(s) => write!(f, "{}", s),
Content::EncapsulatedObject(enc_obj) => write!(f, "{}", enc_obj),
Content::ExtendedText(ext_text) => write!(f, "{}", ext_text),
Content::ExtendedLink(ext_link) => write!(f, "{}", ext_link),
Content::Comment(comment) => write!(f, "{}", comment),
Content::Popularimeter(popularimeter) => write!(f, "{}", popularimeter),
Content::Lyrics(lyrics) => write!(f, "{}", lyrics),
Content::SynchronisedLyrics(sync_lyrics) => write!(f, "{}", sync_lyrics.content_type),
Content::Picture(picture) => write!(f, "{}", picture),
Content::Chapter(chapter) => write!(f, "{}", chapter),
Content::MpegLocationLookupTable(mpeg_table) => write!(f, "{}", mpeg_table),
Content::Private(private) => write!(f, "{}", private),
Content::TableOfContents(table_of_contents) => write!(f, "{}", table_of_contents),
Content::Unknown(unknown) => write!(f, "{}", unknown),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct ExtendedText {
pub description: String,
pub value: String,
}
impl fmt::Display for ExtendedText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.description.is_empty() {
f.write_str(&self.value)
} else {
write!(f, "{}: {}", self.description, self.value)
}
}
}
impl From<ExtendedText> for Frame {
fn from(c: ExtendedText) -> Self {
Self::with_content("TXXX", Content::ExtendedText(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct ExtendedLink {
pub description: String,
pub link: String,
}
impl fmt::Display for ExtendedLink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.description.is_empty() {
f.write_str(&self.link)
} else {
write!(f, "{}: {}", self.description, self.link)
}
}
}
impl From<ExtendedLink> for Frame {
fn from(c: ExtendedLink) -> Self {
Self::with_content("WXXX", Content::ExtendedLink(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct EncapsulatedObject {
pub mime_type: String,
pub filename: String,
pub description: String,
pub data: Vec<u8>,
}
impl fmt::Display for EncapsulatedObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = if self.description.is_empty() {
"Unknown GEOB"
} else {
&self.description
};
write!(
f,
"{} (\"{}\", \"{}\"), {} bytes",
desc,
self.filename,
self.mime_type,
self.data.len()
)
}
}
impl From<EncapsulatedObject> for Frame {
fn from(c: EncapsulatedObject) -> Self {
Self::with_content("GEOB", Content::EncapsulatedObject(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct Comment {
pub lang: String,
pub description: String,
pub text: String,
}
impl fmt::Display for Comment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.description.is_empty() {
f.write_str(&self.text)
} else {
write!(f, "{}: {}", self.description, self.text)
}
}
}
impl From<Comment> for Frame {
fn from(c: Comment) -> Self {
Self::with_content("COMM", Content::Comment(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Popularimeter {
pub user: String,
pub rating: u8,
pub counter: u64,
}
impl fmt::Display for Popularimeter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: *{}* ({})", self.user, self.rating, self.counter)
}
}
impl From<Popularimeter> for Frame {
fn from(c: Popularimeter) -> Self {
Self::with_content("POPM", Content::Popularimeter(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct Lyrics {
pub lang: String,
pub description: String,
pub text: String,
}
impl fmt::Display for Lyrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.description.is_empty() {
f.write_str(&self.text)
} else {
write!(f, "{}: {}", self.description, self.text)
}
}
}
impl From<Lyrics> for Frame {
fn from(c: Lyrics) -> Self {
Self::with_content("USLT", Content::Lyrics(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct SynchronisedLyrics {
pub lang: String,
pub timestamp_format: TimestampFormat,
pub content_type: SynchronisedLyricsType,
pub description: String,
pub content: Vec<(u32, String)>,
}
const MILLISECONDS_PER_HOUR: u32 = 3600000;
const MILLISECONDS_PER_MINUTE: u32 = 60000;
const MILLISECONDS_PER_SECOND: u32 = 1000;
impl SynchronisedLyrics {
pub fn fmt_table(&self, mut writer: impl io::Write) -> io::Result<()> {
match self.timestamp_format {
TimestampFormat::Mpeg => {
writeln!(writer, "Frame\t{}", self.content_type)?;
for (frame, lyric) in self.content.iter() {
writeln!(writer, "{}\t{}", frame, lyric)?;
}
}
TimestampFormat::Ms => {
writeln!(writer, "Timecode\t{}", self.content_type)?;
for (total_ms, lyric) in self.content.iter() {
let hours = total_ms / MILLISECONDS_PER_HOUR;
let mins = (total_ms % MILLISECONDS_PER_HOUR) / MILLISECONDS_PER_MINUTE;
let secs = (total_ms % MILLISECONDS_PER_MINUTE) / MILLISECONDS_PER_SECOND;
let ms = total_ms % MILLISECONDS_PER_SECOND;
writeln!(
writer,
"{:02}:{:02}:{:02}.{:03}\t{}",
hours, mins, secs, ms, lyric
)?;
}
}
}
Ok(())
}
}
impl From<SynchronisedLyrics> for Frame {
fn from(c: SynchronisedLyrics) -> Self {
Self::with_content("SYLT", Content::SynchronisedLyrics(c))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub enum TimestampFormat {
Mpeg,
Ms,
}
impl fmt::Display for TimestampFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TimestampFormat::Mpeg => f.write_str("MPEG frames"),
TimestampFormat::Ms => f.write_str("Milliseconds"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub enum SynchronisedLyricsType {
Other,
Lyrics,
Transcription,
PartName,
Event,
Chord,
Trivia,
}
impl fmt::Display for SynchronisedLyricsType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SynchronisedLyricsType::Other => f.write_str("Other"),
SynchronisedLyricsType::Lyrics => f.write_str("Lyrics"),
SynchronisedLyricsType::Transcription => f.write_str("Transcription"),
SynchronisedLyricsType::PartName => f.write_str("Part name"),
SynchronisedLyricsType::Event => f.write_str("Event"),
SynchronisedLyricsType::Chord => f.write_str("Chord"),
SynchronisedLyricsType::Trivia => f.write_str("Trivia"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub enum PictureType {
Other,
Icon,
OtherIcon,
CoverFront,
CoverBack,
Leaflet,
Media,
LeadArtist,
Artist,
Conductor,
Band,
Composer,
Lyricist,
RecordingLocation,
DuringRecording,
DuringPerformance,
ScreenCapture,
BrightFish,
Illustration,
BandLogo,
PublisherLogo,
Undefined(u8),
}
impl From<PictureType> for u8 {
fn from(pt: PictureType) -> Self {
match pt {
PictureType::Other => 0,
PictureType::Icon => 1,
PictureType::OtherIcon => 2,
PictureType::CoverFront => 3,
PictureType::CoverBack => 4,
PictureType::Leaflet => 5,
PictureType::Media => 6,
PictureType::LeadArtist => 7,
PictureType::Artist => 8,
PictureType::Conductor => 9,
PictureType::Band => 10,
PictureType::Composer => 11,
PictureType::Lyricist => 12,
PictureType::RecordingLocation => 13,
PictureType::DuringRecording => 14,
PictureType::DuringPerformance => 15,
PictureType::ScreenCapture => 16,
PictureType::BrightFish => 17,
PictureType::Illustration => 18,
PictureType::BandLogo => 19,
PictureType::PublisherLogo => 20,
PictureType::Undefined(b) => b,
}
}
}
impl fmt::Display for PictureType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PictureType::Other => f.write_str("Other"),
PictureType::Icon => f.write_str("Icon"),
PictureType::OtherIcon => f.write_str("Other icon"),
PictureType::CoverFront => f.write_str("Front cover"),
PictureType::CoverBack => f.write_str("Back cover"),
PictureType::Leaflet => f.write_str("Leaflet"),
PictureType::Media => f.write_str("Media"),
PictureType::LeadArtist => f.write_str("Lead artist"),
PictureType::Artist => f.write_str("Artist"),
PictureType::Conductor => f.write_str("Conductor"),
PictureType::Band => f.write_str("Band"),
PictureType::Composer => f.write_str("Composer"),
PictureType::Lyricist => f.write_str("Lyricist"),
PictureType::RecordingLocation => f.write_str("Recording location"),
PictureType::DuringRecording => f.write_str("During recording"),
PictureType::DuringPerformance => f.write_str("During performance"),
PictureType::ScreenCapture => f.write_str("Screen capture"),
PictureType::BrightFish => f.write_str("Bright fish"),
PictureType::Illustration => f.write_str("Illustration"),
PictureType::BandLogo => f.write_str("Band logo"),
PictureType::PublisherLogo => f.write_str("Publisher logo"),
PictureType::Undefined(b) => write!(f, "Undefined type {}", b),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Picture {
pub mime_type: String,
pub picture_type: PictureType,
pub description: String,
pub data: Vec<u8>,
}
impl fmt::Display for Picture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.description.is_empty() {
write!(f, "{} ({})", self.picture_type, self.mime_type)
} else {
write!(
f,
"{}: {} ({}, {} bytes)",
self.description,
self.picture_type,
self.mime_type,
self.data.len()
)
}
}
}
impl From<Picture> for Frame {
fn from(c: Picture) -> Self {
Self::with_content("APIC", Content::Picture(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct Chapter {
pub element_id: String,
pub start_time: u32,
pub end_time: u32,
pub start_offset: u32,
pub end_offset: u32,
pub frames: Vec<Frame>,
}
impl fmt::Display for Chapter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (start, end, unit) = match (self.start_offset, self.end_offset) {
(0xffffffff, 0xffffffff) => (self.start_time, self.end_time, "ms"),
(_, _) => (self.start_offset, self.end_offset, "b"),
};
let frames: Vec<&str> = self.frames.iter().map(|f| f.id()).collect();
write!(
f,
"{start}{unit}-{end}{unit}: {frames}",
start = start,
end = end,
unit = unit,
frames = frames.join(", "),
)
}
}
impl Extend<Frame> for Chapter {
fn extend<I: IntoIterator<Item = Frame>>(&mut self, iter: I) {
self.frames.extend(iter)
}
}
impl TagLike for Chapter {
fn frames_vec(&self) -> &Vec<Frame> {
&self.frames
}
fn frames_vec_mut(&mut self) -> &mut Vec<Frame> {
&mut self.frames
}
}
impl From<Chapter> for Frame {
fn from(c: Chapter) -> Self {
Self::with_content("CHAP", Content::Chapter(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct MpegLocationLookupTable {
pub frames_between_reference: u16,
pub bytes_between_reference: u32,
pub millis_between_reference: u32,
pub bits_for_bytes: u8,
pub bits_for_millis: u8,
pub references: Vec<MpegLocationLookupTableReference>,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct MpegLocationLookupTableReference {
pub deviate_bytes: u32,
pub deviate_millis: u32,
}
impl fmt::Display for MpegLocationLookupTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Mpeg Lookup Table, {} references", self.references.len())
}
}
impl From<MpegLocationLookupTable> for Frame {
fn from(c: MpegLocationLookupTable) -> Self {
Self::with_content("MLLT", Content::MpegLocationLookupTable(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Private {
pub owner_identifier: String,
pub private_data: Vec<u8>,
}
impl fmt::Display for Private {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {:x?}", self.owner_identifier, self.private_data)
}
}
impl From<Private> for Frame {
fn from(c: Private) -> Self {
Self::with_content("PRIV", Content::Private(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct TableOfContents {
pub element_id: String,
pub top_level: bool,
pub ordered: bool,
pub elements: Vec<String>,
pub frames: Vec<Frame>,
}
impl fmt::Display for TableOfContents {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let frames: Vec<&str> = self.frames.iter().map(|f| f.id()).collect();
write!(
f,
"isTopLevel:{top_level}, isOrdered:{ordered}, childList: []: {elements}, frames:{frames}",
top_level = self.top_level,
ordered = self.ordered,
elements = self.elements.join(", "),
frames = frames.join(", "),
)
}
}
impl Extend<Frame> for TableOfContents {
fn extend<I: IntoIterator<Item = Frame>>(&mut self, iter: I) {
self.frames.extend(iter)
}
}
impl TagLike for TableOfContents {
fn frames_vec(&self) -> &Vec<Frame> {
&self.frames
}
fn frames_vec_mut(&mut self) -> &mut Vec<Frame> {
&mut self.frames
}
}
impl From<TableOfContents> for Frame {
fn from(c: TableOfContents) -> Self {
Self::with_content("CTOC", Content::TableOfContents(c))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Unknown {
pub data: Vec<u8>,
pub version: Version,
}
impl fmt::Display for Unknown {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {} bytes", self.version, self.data.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn content_text_display() {
let text = Content::Text(String::from("text value"));
assert_eq!(format!("{}", text), "text value");
}
#[test]
fn content_extended_text_display() {
let ext_text = Content::ExtendedText(ExtendedText {
description: String::from("description value"),
value: String::from("value value"),
});
assert_eq!(format!("{}", ext_text), "description value: value value");
}
#[test]
fn content_link_display() {
let link = Content::Link(String::from("link value"));
assert_eq!(format!("{}", link), "link value");
}
#[test]
fn content_extended_link_display() {
let ext_link = Content::ExtendedLink(ExtendedLink {
description: String::from("description value"),
link: String::from("link value"),
});
assert_eq!(format!("{}", ext_link), "description value: link value");
}
#[test]
fn content_comment_display() {
let comment = Content::Comment(Comment {
lang: String::from("lang value"),
description: String::from("description value"),
text: String::from("text value"),
});
assert_eq!(format!("{}", comment), "description value: text value");
}
#[test]
fn content_lyrics_display() {
let lyrics = Content::Lyrics(Lyrics {
lang: String::from("lang value"),
description: String::from("description value"),
text: String::from("text value"),
});
assert_eq!(format!("{}", lyrics), "description value: text value");
}
#[test]
fn content_synchronised_lyrics_display() {
let sync_lyrics = Content::SynchronisedLyrics(SynchronisedLyrics {
lang: String::from("lang value"),
timestamp_format: TimestampFormat::Mpeg,
content_type: SynchronisedLyricsType::Lyrics,
content: vec![
(1, String::from("first line")),
(2, String::from("second line")),
],
description: String::from("description"),
});
assert_eq!(format!("{}", sync_lyrics), "Lyrics");
}
#[test]
fn content_picture_display() {
let picture = Content::Picture(Picture {
mime_type: String::from("MIME type"),
picture_type: PictureType::Artist,
description: String::from("description value"),
data: vec![1, 2, 3],
});
assert_eq!(
format!("{}", picture),
"description value: Artist (MIME type, 3 bytes)"
);
}
#[test]
fn content_unknown_display() {
let unknown = Content::Unknown(Unknown {
version: Version::Id3v24,
data: vec![1, 2, 3],
});
assert_eq!(format!("{}", unknown), "ID3v2.4, 3 bytes");
}
#[test]
fn synchronised_lyrics_format_table() {
let sync_lyrics_mpeg_lyrics = SynchronisedLyrics {
lang: String::from("lang value"),
timestamp_format: TimestampFormat::Mpeg,
content_type: SynchronisedLyricsType::Lyrics,
content: vec![
(1, String::from("first line")),
(2, String::from("second line")),
],
description: String::from("description"),
};
let mut buffer: Vec<u8> = Vec::new();
assert!(sync_lyrics_mpeg_lyrics.fmt_table(&mut buffer).is_ok());
assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
"Frame\tLyrics\n1\tfirst line\n2\tsecond line\n"
);
let sync_lyrics_ms_chord = SynchronisedLyrics {
lang: String::from("lang value"),
timestamp_format: TimestampFormat::Ms,
content_type: SynchronisedLyricsType::Chord,
content: vec![
(1000, String::from("A")),
(2000, String::from("B")),
(12345678, String::from("C")),
],
description: String::from("description"),
};
let mut buffer: Vec<u8> = Vec::new();
assert!(sync_lyrics_ms_chord.fmt_table(&mut buffer).is_ok());
assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
"Timecode\tChord\n00:00:01.000\tA\n00:00:02.000\tB\n03:25:45.678\tC\n"
);
}
#[test]
fn unknown_to_unknown() {
let unknown = Unknown {
version: Version::Id3v22,
data: vec![1, 2, 3, 4],
};
let content = Content::Unknown(unknown.clone());
assert_eq!(*content.to_unknown().unwrap(), unknown);
}
#[test]
fn link_to_unknown() {
let content = Content::Text("https://polyfloyd.net".to_string());
let mut data = vec![3]; data.extend("https://polyfloyd.net".bytes());
let unknown = Unknown {
version: Version::Id3v24,
data,
};
assert_eq!(*content.to_unknown().unwrap(), unknown);
}
}