Add ID3v1 writing

This commit is contained in:
Serial 2021-09-05 18:41:53 -04:00
parent 92475b74cf
commit 4e625f7e80

View file

@ -1,9 +1,12 @@
use super::constants::GENRES;
use crate::{ItemKey, ItemValue, Result, Tag, TagItem, TagType};
use crate::error::Result;
use crate::types::item::ItemKey;
use crate::types::tag::{ItemValue, Tag, TagItem, TagType};
use std::io::{Read, Seek, SeekFrom};
use byteorder::WriteBytesExt;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
pub(crate) fn find_id3v1<R>(data: &mut R, read: bool) -> Result<(bool, Option<Tag>)>
pub(in crate::logic) fn find_id3v1<R>(data: &mut R, read: bool) -> Result<(bool, Option<Tag>)>
where
R: Read + Seek,
{
@ -36,41 +39,44 @@ where
Ok((exists, id3v1))
}
pub(crate) fn parse_id3v1(reader: [u8; 128]) -> Tag {
pub(in crate::logic) fn parse_id3v1(reader: [u8; 128]) -> Tag {
let mut tag = Tag::new(TagType::Id3v1);
if let Some(title) = decode_text(ItemKey::TrackTitle, &reader[3..33]) {
tag.insert_item(title);
let reader = &reader[3..];
if let Some(title) = decode_text(ItemKey::TrackTitle, &reader[..30]) {
tag.insert_item_unchecked(title);
}
if let Some(artist) = decode_text(ItemKey::TrackArtist, &reader[33..63]) {
tag.insert_item(artist);
if let Some(artist) = decode_text(ItemKey::TrackArtist, &reader[30..60]) {
tag.insert_item_unchecked(artist);
}
if let Some(album) = decode_text(ItemKey::AlbumTitle, &reader[63..93]) {
tag.insert_item(album);
if let Some(album) = decode_text(ItemKey::AlbumTitle, &reader[60..90]) {
tag.insert_item_unchecked(album);
}
if let Some(year) = decode_text(ItemKey::Year, &reader[93..97]) {
tag.insert_item(year);
if let Some(year) = decode_text(ItemKey::Year, &reader[90..94]) {
tag.insert_item_unchecked(year);
}
let range = if reader[122] == 0 {
if let Ok(track) = String::from_utf8(vec![reader[123]]) {
tag.insert_item(TagItem::new(ItemKey::TrackNumber, ItemValue::Text(track)));
}
let range = if reader[119] == 0 && reader[122] != 0 {
tag.insert_item_unchecked(TagItem::new(
ItemKey::TrackNumber,
ItemValue::UInt(u32::from(reader[122])),
));
97_usize..122
94_usize..123
} else {
97..124
94..124
};
if let Some(comment) = decode_text(ItemKey::Comment, &reader[range]) {
tag.insert_item(comment);
tag.insert_item_unchecked(comment);
}
if reader[125] < GENRES.len() as u8 {
tag.insert_item(TagItem::new(
if reader[124] < GENRES.len() as u8 {
tag.insert_item_unchecked(TagItem::new(
ItemKey::Genre,
ItemValue::Text(GENRES[reader[125] as usize].to_string()),
));
@ -82,7 +88,7 @@ pub(crate) fn parse_id3v1(reader: [u8; 128]) -> Tag {
fn decode_text(key: ItemKey, data: &[u8]) -> Option<TagItem> {
let read = data
.iter()
.filter(|c| **c > 0x1f)
.filter(|c| **c != 0)
.map(|c| *c as char)
.collect::<String>();
@ -92,3 +98,107 @@ fn decode_text(key: ItemKey, data: &[u8]) -> Option<TagItem> {
Some(TagItem::new(key, ItemValue::Text(read)))
}
}
pub(in crate::logic) fn write_id3v1<W>(writer: &mut W, tag: &Tag) -> Result<()>
where
W: Write + Read + Seek,
{
let tag = encode(tag)?;
// This will seek us to the writing position
find_id3v1(writer, false)?;
writer.write_all(&tag)?;
Ok(())
}
fn encode(tag: &Tag) -> Result<Vec<u8>> {
fn resize_string(item: Option<&TagItem>, size: usize) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(vec![0; size]);
cursor.seek(SeekFrom::Start(0))?;
if let Some(ItemValue::Text(text)) = item.map(TagItem::value) {
if text.len() > size {
cursor.write_all(text.split_at(size).0.as_bytes())?;
} else {
cursor.write_all(text.as_bytes())?;
}
}
Ok(cursor.into_inner())
}
let mut writer = Vec::with_capacity(128);
writer.write_all(&[b'T', b'A', b'G'])?;
let title = resize_string(tag.get_item_ref(&ItemKey::TrackTitle), 30)?;
writer.write_all(&*title)?;
let artist = resize_string(tag.get_item_ref(&ItemKey::TrackArtist), 30)?;
writer.write_all(&*artist)?;
let album = resize_string(tag.get_item_ref(&ItemKey::AlbumTitle), 30)?;
writer.write_all(&*album)?;
let year = resize_string(tag.get_item_ref(&ItemKey::Year), 4)?;
writer.write_all(&*year)?;
let comment = resize_string(tag.get_item_ref(&ItemKey::Comment), 28)?;
writer.write_all(&*comment)?;
writer.write_u8(0)?;
let item_to_byte = |key: &ItemKey, max: u8, empty: u8| {
if let Some(track_number) = tag.get_item_ref(key) {
match track_number.value() {
ItemValue::Text(text) => {
if let Ok(parsed) = text.parse::<u8>() {
if parsed <= max {
return parsed;
}
}
empty
},
ItemValue::UInt(i) => {
if *i <= u32::from(max) {
*i as u8
} else {
empty
}
},
ItemValue::UInt64(i) => {
if *i <= u64::from(max) {
*i as u8
} else {
empty
}
},
ItemValue::Int(i) => {
if i.is_positive() && *i <= i32::from(max) {
*i as u8
} else {
empty
}
},
ItemValue::Int64(i) => {
if i.is_positive() && *i <= i64::from(max) {
*i as u8
} else {
empty
}
},
_ => empty,
}
} else {
empty
}
};
writer.write_u8(item_to_byte(&ItemKey::TrackNumber, 255, 0))?;
writer.write_u8(item_to_byte(&ItemKey::Genre, 191, 255))?;
Ok(writer)
}