use std::io::{Cursor, Read, Seek, SeekFrom};
use std::fmt;
#[derive(Debug, Default)]
pub struct AtomMeta {
pub title: Option<String>,
pub year: Option<String>,
pub copyright: Option<String>,
pub artist: Option<String>,
pub album_artist: Option<String>,
pub author: Option<String>,
pub composer: Option<String>,
pub album: Option<String>,
pub description: Option<String>,
pub synopsis: Option<String>,
pub genre: Option<String>,
pub make: Option<String>,
pub model: Option<String>,
pub location: Option<String>,
pub grouping: Option<String>,
pub show: Option<String>,
pub episode_id: Option<String>,
pub episode_sort: Option<u8>,
pub season_number: Option<u8>,
pub lyrics: Option<String>,
pub compilation: Option<u8>,
pub network: Option<String>,
pub media_type: Option<u8>,
pub hd_video: Option<u8>,
pub gapless_playback: Option<u8>,
pub encoder: Option<String>,
pub encoding_tool: Option<String>,
}
impl fmt::Display for AtomMeta {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(title) = &self.title {
writeln!(f, "Title: {}", title)?;
}
if let Some(year) = &self.year {
writeln!(f, "Year: {}", year)?;
}
if let Some(copyright) = &self.copyright {
writeln!(f, "Copyright: {}", copyright)?;
}
if let Some(artist) = &self.artist {
writeln!(f, "Artist: {}", artist)?;
}
if let Some(album_artist) = &self.album_artist {
writeln!(f, "Album Artist: {}", album_artist)?;
}
if let Some(author) = &self.author {
writeln!(f, "Author: {}", author)?;
}
if let Some(composer) = &self.composer {
writeln!(f, "Composer: {}", composer)?;
}
if let Some(album) = &self.album {
writeln!(f, "Album: {}", album)?;
}
if let Some(description) = &self.description {
writeln!(f, "Description: {}", description)?;
}
if let Some(synopsis) = &self.synopsis {
writeln!(f, "Synopsis: {}", synopsis)?;
}
if let Some(genre) = &self.genre {
writeln!(f, "Genre: {}", genre)?;
}
if let Some(make) = &self.make {
writeln!(f, "Make: {}", make)?;
}
if let Some(model) = &self.model {
writeln!(f, "Model: {}", model)?;
}
if let Some(location) = &self.location {
writeln!(f, "Location: {}", location)?;
}
if let Some(grouping) = &self.grouping {
writeln!(f, "Grouping: {}", grouping)?;
}
if let Some(show) = &self.show {
writeln!(f, "Show: {}", show)?;
}
if let Some(episode_id) = &self.episode_id {
writeln!(f, "Episode ID: {}", episode_id)?;
}
if let Some(episode_sort) = self.episode_sort {
writeln!(f, "Episode Sort: {}", episode_sort)?;
}
if let Some(season_number) = self.season_number {
writeln!(f, "Season Number: {}", season_number)?;
}
if let Some(lyrics) = &self.lyrics {
writeln!(f, "Lyrics: {}", lyrics)?;
}
if let Some(compilation) = self.compilation {
writeln!(f, "Compilation: {}", compilation)?;
}
if let Some(network) = &self.network {
writeln!(f, "Network: {}", network)?;
}
if let Some(media_type) = self.media_type {
writeln!(f, "Media Type: {}", media_type)?;
}
if let Some(hd_video) = self.hd_video {
writeln!(f, "HD Video: {}", hd_video)?;
}
if let Some(gapless_playback) = self.gapless_playback {
writeln!(f, "Gapless Playback: {}", gapless_playback)?;
}
if let Some(encoder) = &self.encoder {
writeln!(f, "Encoder: {}", encoder)?;
}
if let Some(encoding_tool) = &self.encoding_tool {
writeln!(f, "Encoding Tool: {}", encoding_tool)?;
}
Ok(())
}
}
impl AtomMeta {
pub fn parse(data: &[u8]) -> Result<Self, AtomError> {
let mut cursor = Cursor::new(data);
let mut meta = AtomMeta::default();
while (cursor.position() as usize + 8) <= data.len() {
let size = read_u32(&mut cursor)?;
let atom_type = read_type(&mut cursor)?;
if size < 8 {
break;
}
let payload_size = size - 8;
let start = cursor.position();
match &atom_type {
b"moov" | b"udta" | b"ilst" => {
let mut buf = vec![0; payload_size as usize];
cursor.read_exact(&mut buf)?;
let sub = AtomMeta::parse(&buf)?;
meta.merge(sub);
}
b"meta" => {
cursor.seek(SeekFrom::Current(4))?;
let mut buf = vec![0; (payload_size - 4) as usize];
cursor.read_exact(&mut buf)?;
let sub = AtomMeta::parse(&buf)?;
meta.merge(sub);
}
b"\xa9nam" => meta.title = read_meta_entry(&mut cursor, payload_size)?,
b"\xa9ART" => meta.artist = read_meta_entry(&mut cursor, payload_size)?,
b"aART" => meta.album_artist = read_meta_entry(&mut cursor, payload_size)?,
b"\xa9alb" => meta.album = read_meta_entry(&mut cursor, payload_size)?,
b"\xa9gen" => meta.genre = read_meta_entry(&mut cursor, payload_size)?,
b"\xa9day" => meta.year = read_meta_entry(&mut cursor, payload_size)?,
b"\xa9too" => meta.encoding_tool = read_meta_entry(&mut cursor, payload_size)?,
_ => {
cursor.seek(SeekFrom::Start(start + payload_size as u64))?;
}
}
}
Ok(meta)
}
fn merge(&mut self, other: AtomMeta) {
if self.title.is_none() { self.title = other.title; }
if self.artist.is_none() { self.artist = other.artist; }
if self.album.is_none() { self.album = other.album; }
if self.genre.is_none() { self.genre = other.genre; }
if self.year.is_none() { self.year = other.year; }
}
}
fn read_meta_entry(
cursor: &mut Cursor<&[u8]>,
size: u32
) -> Result<Option<String>, AtomError> {
let start = cursor.position();
while (cursor.position() - start) < size as u64 {
let atom_size = read_u32(cursor)?;
let atom_type = read_type(cursor)?;
if atom_size < 8 {
break;
}
if &atom_type == b"data" {
cursor.seek(SeekFrom::Current(8))?;
let data_len = atom_size - 16;
let mut buf = vec![0u8; data_len as usize];
cursor.read_exact(&mut buf)?;
return Ok(Some(
String::from_utf8_lossy(&buf)
.trim_matches('\0')
.to_string()
));
} else {
cursor.seek(SeekFrom::Current((atom_size - 8) as i64))?;
}
}
Ok(None)
}
fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result<u32, AtomError> {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf)
.map_err(|e| AtomError { message: e.to_string() })?;
Ok(u32::from_be_bytes(buf))
}
fn read_type(cursor: &mut Cursor<&[u8]>) -> Result<[u8; 4], AtomError> {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf)
.map_err(|e| AtomError { message: e.to_string() })?;
Ok(buf)
}
#[derive(Debug)]
pub struct AtomError {
pub message: String,
}
impl From<std::io::Error> for AtomError {
fn from(e: std::io::Error) -> Self {
AtomError { message: e.to_string() }
}
}