use std::fmt::Debug;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::Path;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::{Atom, atom, Content, Data};
pub const GENRES: [(u16, &str); 80] = [
(1, "Blues"),
(2, "Classic rock"),
(3, "Country"),
(4, "Dance"),
(5, "Disco"),
(6, "Funk"),
(7, "Grunge"),
(8, "Hip,-Hop"),
(9, "Jazz"),
(10, "Metal"),
(11, "New Age"),
(12, "Oldies"),
(13, "Other"),
(14, "Pop"),
(15, "Rhythm and Blues"),
(16, "Rap"),
(17, "Reggae"),
(18, "Rock"),
(19, "Techno"),
(20, "Industrial"),
(21, "Alternative"),
(22, "Ska"),
(23, "Death metal"),
(24, "Pranks"),
(25, "Soundtrack"),
(26, "Euro-Techno"),
(27, "Ambient"),
(28, "Trip-Hop"),
(29, "Vocal"),
(30, "Jazz & Funk"),
(31, "Fusion"),
(32, "Trance"),
(33, "Classical"),
(34, "Instrumental"),
(35, "Acid"),
(36, "House"),
(37, "Game"),
(38, "Sound clip"),
(39, "Gospel"),
(40, "Noise"),
(41, "Alternative Rock"),
(42, "Bass"),
(43, "Soul"),
(44, "Punk"),
(45, "Space"),
(46, "Meditative"),
(47, "Instrumental Pop"),
(48, "Instrumental Rock"),
(49, "Ethnic"),
(50, "Gothic"),
(51, "Darkwave"),
(52, "Techno-Industrial"),
(53, "Electronic"),
(54, "Pop-Folk"),
(55, "Eurodance"),
(56, "Dream"),
(57, "Southern Rock"),
(58, "Comedy"),
(59, "Cult"),
(60, "Gangsta"),
(61, "Top 41"),
(62, "Christian Rap"),
(63, "Pop/Funk"),
(64, "Jungle"),
(65, "Native US"),
(66, "Cabaret"),
(67, "New Wave"),
(68, "Psychedelic"),
(69, "Rave"),
(70, "Show tunes"),
(71, "Trailer"),
(72, "Lo,-Fi"),
(73, "Tribal"),
(74, "Acid Punk"),
(75, "Acid Jazz"),
(76, "Polka"),
(77, "Retro"),
(78, "Musical"),
(79, "Rock ’n’ Roll"),
(80, "Hard Rock"),
];
#[derive(Debug)]
pub struct Tag {
pub atoms: Vec<Atom>,
pub readonly_atoms: Vec<Atom>,
}
impl Tag {
pub fn new() -> Tag {
Tag { atoms: Vec::new(), readonly_atoms: Vec::new() }
}
pub fn with(atoms: Vec<Atom>, readonly_atoms: Vec<Atom>) -> Tag {
let mut tag = Tag { atoms, readonly_atoms };
let mut i = 0;
while i < tag.atoms.len() {
if let Some(a) = tag.atoms[i].first_child() {
if let Content::TypedData(Data::Unparsed(_)) = a.content {
tag.atoms.remove(i);
continue;
}
}
i += 1;
}
tag
}
pub fn read_from(reader: &mut (impl Read + Seek)) -> crate::Result<Tag> {
Atom::read_from(reader)
}
pub fn read_from_path(path: impl AsRef<Path>) -> crate::Result<Tag> {
let mut file = BufReader::new(File::open(path)?);
Tag::read_from(&mut file)
}
pub fn write_to(&self, file: &File) -> crate::Result<()> {
let mut reader = BufReader::new(file);
let mut writer = BufWriter::new(file);
let atom_pos_and_len = Atom::locate_metadata_item_list(&mut reader)?;
let old_file_length = reader.seek(SeekFrom::End(0))?;
let metadata_position = atom_pos_and_len[atom_pos_and_len.len() - 1].0 + 8;
let old_metadata_length = atom_pos_and_len[atom_pos_and_len.len() - 1].1 - 8;
let new_metadata_length = self.atoms.iter().map(|a| a.len()).sum::<usize>();
let metadata_length_difference = new_metadata_length as i32 - old_metadata_length as i32;
let mut additional_data = Vec::new();
reader.seek(SeekFrom::Start((metadata_position + old_metadata_length) as u64))?;
reader.read_to_end(&mut additional_data)?;
file.set_len((old_file_length as i64 + metadata_length_difference as i64) as u64)?;
for (pos, len) in atom_pos_and_len {
writer.seek(SeekFrom::Start(pos as u64))?;
writer.write_u32::<BigEndian>((len as i32 + metadata_length_difference) as u32)?;
}
writer.seek(SeekFrom::Current(4))?;
for a in &self.atoms {
a.write_to(&mut writer)?;
}
writer.write(&additional_data)?;
writer.flush()?;
Ok(())
}
pub fn write_to_path(&self, path: impl AsRef<Path>) -> crate::Result<()> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
self.write_to(&file)?;
Ok(())
}
pub fn album(&self) -> Option<&str> {
self.string(atom::ALBUM)
}
pub fn set_album(&mut self, album: impl Into<String>) {
self.set_data(atom::ALBUM, Data::Utf8(album.into()));
}
pub fn remove_album(&mut self) {
self.remove_data(atom::ALBUM);
}
pub fn album_artist(&self) -> Option<&str> {
self.string(atom::ALBUM_ARTIST)
}
pub fn set_album_artist(&mut self, album_artist: impl Into<String>) {
self.set_data(atom::ALBUM_ARTIST, Data::Utf8(album_artist.into()));
}
pub fn remove_album_artist(&mut self) {
self.remove_data(atom::ALBUM_ARTIST);
}
pub fn artist(&self) -> Option<&str> {
self.string(atom::ARTIST)
}
pub fn set_artist(&mut self, artist: impl Into<String>) {
self.set_data(atom::ARTIST, Data::Utf8(artist.into()));
}
pub fn remove_artist(&mut self) {
self.remove_data(atom::ARTIST);
}
pub fn category(&self) -> Option<&str> {
self.string(atom::CATEGORY)
}
pub fn set_category(&mut self, category: impl Into<String>) {
self.set_data(atom::CATEGORY, Data::Utf8(category.into()));
}
pub fn remove_category(&mut self) {
self.remove_data(atom::CATEGORY);
}
pub fn comment(&self) -> Option<&str> {
self.string(atom::COMMENT)
}
pub fn set_comment(&mut self, comment: impl Into<String>) {
self.set_data(atom::COMMENT, Data::Utf8(comment.into()));
}
pub fn remove_comment(&mut self) {
self.remove_data(atom::COMMENT);
}
pub fn composer(&self) -> Option<&str> {
self.string(atom::COMPOSER)
}
pub fn set_composer(&mut self, composer: impl Into<String>) {
self.set_data(atom::COMPOSER, Data::Utf8(composer.into()));
}
pub fn remove_composer(&mut self) {
self.remove_data(atom::COMMENT);
}
pub fn copyright(&self) -> Option<&str> {
self.string(atom::COPYRIGHT)
}
pub fn set_copyright(&mut self, copyright: impl Into<String>) {
self.set_data(atom::COPYRIGHT, Data::Utf8(copyright.into()));
}
pub fn remove_copyright(&mut self) {
self.remove_data(atom::COPYRIGHT);
}
pub fn description(&self) -> Option<&str> {
self.string(atom::DESCRIPTION)
}
pub fn set_description(&mut self, description: impl Into<String>) {
self.set_data(atom::DESCRIPTION, Data::Utf8(description.into()));
}
pub fn remove_description(&mut self) {
self.remove_data(atom::DESCRIPTION);
}
pub fn encoder(&self) -> Option<&str> {
self.string(atom::ENCODER)
}
pub fn set_encoder(&mut self, encoder: impl Into<String>) {
self.set_data(atom::ENCODER, Data::Utf8(encoder.into()));
}
pub fn remove_encoder(&mut self) {
self.remove_data(atom::ENCODER);
}
pub fn grouping(&self) -> Option<&str> {
self.string(atom::GROUPING)
}
pub fn set_grouping(&mut self, grouping: impl Into<String>) {
self.set_data(atom::GROUPING, Data::Utf8(grouping.into()));
}
pub fn remove_grouping(&mut self) {
self.remove_data(atom::GROUPING);
}
pub fn keyword(&self) -> Option<&str> {
self.string(atom::KEYWORD)
}
pub fn set_keyword(&mut self, keyword: impl Into<String>) {
self.set_data(atom::KEYWORD, Data::Utf8(keyword.into()));
}
pub fn remove_keyword(&mut self) {
self.remove_data(atom::KEYWORD);
}
pub fn lyrics(&self) -> Option<&str> {
self.string(atom::LYRICS)
}
pub fn set_lyrics(&mut self, lyrics: impl Into<String>) {
self.set_data(atom::LYRICS, Data::Utf8(lyrics.into()));
}
pub fn remove_lyrics(&mut self) {
self.remove_data(atom::LYRICS);
}
pub fn title(&self) -> Option<&str> {
self.string(atom::TITLE)
}
pub fn set_title(&mut self, title: impl Into<String>) {
self.set_data(atom::TITLE, Data::Utf8(title.into()));
}
pub fn remove_title(&mut self) {
self.remove_data(atom::TITLE);
}
pub fn tv_episode_number(&self) -> Option<&str> {
self.string(atom::TV_EPISODE_NUMBER)
}
pub fn set_tv_episode_number(&mut self, tv_episode_number: impl Into<String>) {
self.set_data(atom::TV_EPISODE_NUMBER, Data::Utf8(tv_episode_number.into()));
}
pub fn remove_tv_episode_number(&mut self) {
self.remove_data(atom::TV_EPISODE_NUMBER);
}
pub fn tv_network_name(&self) -> Option<&str> {
self.string(atom::TV_NETWORK_NAME)
}
pub fn set_tv_network_name(&mut self, tv_network_name: impl Into<String>) {
self.set_data(atom::TV_NETWORK_NAME, Data::Utf8(tv_network_name.into()));
}
pub fn remove_tv_network_name(&mut self) {
self.remove_data(atom::TV_NETWORK_NAME);
}
pub fn tv_show_name(&self) -> Option<&str> {
self.string(atom::TV_SHOW_NAME)
}
pub fn set_tv_show_name(&mut self, tv_show_name: impl Into<String>) {
self.set_data(atom::TV_SHOW_NAME, Data::Utf8(tv_show_name.into()));
}
pub fn remove_tv_show_name(&mut self) {
self.remove_data(atom::TV_SHOW_NAME);
}
pub fn year(&self) -> Option<&str> {
self.string(atom::YEAR)
}
pub fn set_year(&mut self, year: impl Into<String>) {
self.set_data(atom::YEAR, Data::Utf8(year.into()));
}
pub fn remove_year(&mut self) {
self.remove_data(atom::YEAR);
}
pub fn genre(&self) -> Option<&str> {
if let Some(s) = self.custom_genre() {
return Some(s);
}
if let Some(genre_code) = self.standard_genre() {
for g in GENRES.iter() {
if g.0 == genre_code {
return Some(g.1);
}
}
}
None
}
pub fn set_genre(&mut self, genre: impl Into<String>) {
let gen = genre.into();
for g in GENRES.iter() {
if g.1 == gen {
self.remove_custom_genre();
self.set_standard_genre(g.0);
return;
}
}
self.remove_standard_genre();
self.set_custom_genre(gen)
}
pub fn remove_genre(&mut self) {
self.remove_standard_genre();
self.remove_custom_genre();
}
pub fn standard_genre(&self) -> Option<u16> {
if let Some(v) = self.reserved(atom::STANDARD_GENRE) {
let mut chunks = v.chunks(2);
if let Ok(genre_code) = chunks.next()?.read_u16::<BigEndian>() {
return Some(genre_code);
}
}
None
}
pub fn set_standard_genre(&mut self, genre_code: u16) {
if genre_code > 0 && genre_code <= 80 {
let mut vec: Vec<u8> = Vec::new();
let _ = vec.write_u16::<BigEndian>(genre_code).is_ok();
self.set_data(atom::STANDARD_GENRE, Data::Reserved(vec));
}
}
pub fn remove_standard_genre(&mut self) {
self.remove_data(atom::STANDARD_GENRE);
}
pub fn custom_genre(&self) -> Option<&str> {
self.string(atom::CUSTOM_GENRE)
}
pub fn set_custom_genre(&mut self, custom_genre: impl Into<String>) {
self.set_data(atom::CUSTOM_GENRE, Data::Utf8(custom_genre.into()));
}
pub fn remove_custom_genre(&mut self) {
self.remove_data(atom::CUSTOM_GENRE);
}
pub fn track_number(&self) -> (Option<u16>, Option<u16>) {
let vec = match self.reserved(atom::TRACK_NUMBER) {
Some(v) => v,
None => return (None, None),
};
let mut buffs = Vec::new();
for chunk in vec.chunks(2) {
buffs.push(chunk);
}
let track_number = match buffs[1].read_u16::<BigEndian>() {
Ok(tnr) => Some(tnr),
Err(_) => None,
};
let total_tracks = match buffs[2].read_u16::<BigEndian>() {
Ok(atr) => Some(atr),
Err(_) => None,
};
(track_number, total_tracks)
}
pub fn set_track_number(&mut self, track_number: u16, total_tracks: u16) {
let vec16 = vec![0u16, track_number, total_tracks, 0u16];
let mut vec = Vec::new();
for i in vec16 {
let _ = vec.write_u16::<BigEndian>(i).is_ok();
}
self.set_data(atom::TRACK_NUMBER, Data::Reserved(vec));
}
pub fn remove_track_number(&mut self) {
self.remove_data(atom::TRACK_NUMBER);
}
pub fn disk_number(&self) -> (Option<u16>, Option<u16>) {
let vec = match self.reserved(atom::DISK_NUMBER) {
Some(v) => v,
None => return (None, None),
};
let mut buffs = Vec::new();
for chunk in vec.chunks(2) {
buffs.push(chunk);
}
let disk_number = match buffs[1].read_u16::<BigEndian>() {
Ok(tnr) => Some(tnr),
Err(_) => None,
};
let total_disks = match buffs[2].read_u16::<BigEndian>() {
Ok(atr) => Some(atr),
Err(_) => None,
};
(disk_number, total_disks)
}
pub fn set_disk_number(&mut self, disk_number: u16, total_disks: u16) {
let vec16 = vec![0u16, disk_number, total_disks];
let mut vec = Vec::new();
for i in vec16 {
let _ = vec.write_u16::<BigEndian>(i).is_ok();
}
self.set_data(atom::DISK_NUMBER, Data::Reserved(vec));
}
pub fn remove_disk_number(&mut self) {
self.remove_data(atom::DISK_NUMBER);
}
pub fn artwork(&self) -> Option<Data> {
self.image(atom::ARTWORK)
}
pub fn set_artwork(&mut self, image: Data) {
match &image {
Data::Jpeg(_) => (),
Data::Png(_) => (),
_ => return,
}
self.set_data(atom::ARTWORK, image);
}
pub fn remove_artwork(&mut self) {
self.remove_data(atom::ARTWORK);
}
pub fn duration(&self) -> Option<f64> {
let mut vec = &Vec::new();
for a in &self.readonly_atoms {
if a.identifier == atom::MEDIA_HEADER {
if let Content::RawData(Data::Reserved(v)) = &a.content {
vec = v;
}
}
}
if vec.len() < 24 {
return None;
}
let mut buffs = Vec::new();
for chunk in vec.chunks(4) {
buffs.push(chunk);
}
let timescale_unit = match buffs[3].read_u32::<BigEndian>() {
Ok(t) => t,
Err(_) => return None,
};
let unit_duration = match buffs[4].read_u32::<BigEndian>() {
Ok(d) => d,
Err(_) => return None,
};
let duration = unit_duration as f64 / timescale_unit as f64;
Some(duration)
}
pub fn reserved(&self, identifier: [u8; 4]) -> Option<&Vec<u8>> {
match self.data(identifier) {
Some(Data::Reserved(v)) => Some(v),
_ => None,
}
}
pub fn string(&self, identifier: [u8; 4]) -> Option<&str> {
let d = self.data(identifier)?;
match d {
Data::Utf8(s) => Some(s),
Data::Utf16(s) => Some(s),
_ => None,
}
}
pub fn mut_string(&mut self, identifier: [u8; 4]) -> Option<&mut String> {
let d = self.mut_data(identifier)?;
match d {
Data::Utf8(s) => Some(s),
Data::Utf16(s) => Some(s),
_ => None,
}
}
pub fn image(&self, identifier: [u8; 4]) -> Option<Data> {
let d = self.data(identifier)?;
match d {
Data::Jpeg(d) => Some(Data::Jpeg(d.to_vec())),
Data::Png(d) => Some(Data::Png(d.to_vec())),
_ => None,
}
}
pub fn data(&self, identifier: [u8; 4]) -> Option<&Data> {
for a in &self.atoms {
if a.identifier == identifier {
if let Content::TypedData(data) = &a.first_child()?.content {
return Some(data);
}
}
}
None
}
pub fn mut_data(&mut self, identifier: [u8; 4]) -> Option<&mut Data> {
for a in &mut self.atoms {
if a.identifier == identifier {
if let Content::TypedData(data) = &mut a.mut_first_child()?.content {
return Some(data);
}
}
}
None
}
pub fn set_data(&mut self, identifier: [u8; 4], data: Data) {
for a in &mut self.atoms {
if a.identifier == identifier {
if let Some(p) = a.mut_first_child() {
if let Content::TypedData(d) = &mut p.content {
*d = data;
return;
}
}
}
}
self.atoms.push(Atom::with(identifier, 0, Content::data_atom_with(data)));
}
pub fn remove_data(&mut self, identifier: [u8; 4]) {
for i in 0..self.atoms.len() {
if self.atoms[i].identifier == identifier {
self.atoms.remove(i);
return;
}
}
}
pub fn readonly_data(&self, identifier: [u8; 4]) -> Option<&Data> {
for a in &self.readonly_atoms {
if a.identifier == identifier {
if let Content::RawData(data) = &a.content {
return Some(data);
}
}
}
None
}
}