use crate::common::error::Result;
use crate::id3::frames::{Frame, TextFrame, CommentFrame};
use crate::id3::specs::{self, Encoding, GENRES};
pub fn find_id3v1(data: &[u8]) -> Option<usize> {
if data.len() < 128 {
return None;
}
let tag_offset = data.len() - 128;
if &data[tag_offset..tag_offset + 3] == b"TAG" {
Some(tag_offset)
} else {
None
}
}
pub fn parse_id3v1(data: &[u8]) -> Result<Vec<Frame>> {
if data.len() < 128 {
return Ok(vec![]);
}
let tag_data = if data.len() == 128 {
data
} else {
&data[data.len() - 128..]
};
if &tag_data[0..3] != b"TAG" {
return Ok(vec![]);
}
let mut frames = Vec::new();
let title = decode_v1_string(&tag_data[3..33]);
if !title.is_empty() {
frames.push(Frame::Text(TextFrame {
id: "TIT2".to_string(),
encoding: Encoding::Latin1,
text: vec![title],
}));
}
let artist = decode_v1_string(&tag_data[33..63]);
if !artist.is_empty() {
frames.push(Frame::Text(TextFrame {
id: "TPE1".to_string(),
encoding: Encoding::Latin1,
text: vec![artist],
}));
}
let album = decode_v1_string(&tag_data[63..93]);
if !album.is_empty() {
frames.push(Frame::Text(TextFrame {
id: "TALB".to_string(),
encoding: Encoding::Latin1,
text: vec![album],
}));
}
let year = decode_v1_string(&tag_data[93..97]);
if !year.is_empty() {
frames.push(Frame::Text(TextFrame {
id: "TDRC".to_string(),
encoding: Encoding::Latin1,
text: vec![year],
}));
}
if tag_data[125] == 0 && tag_data[126] != 0 {
let comment = decode_v1_string(&tag_data[97..125]);
if !comment.is_empty() {
frames.push(Frame::Comment(CommentFrame {
id: "COMM".to_string(),
encoding: Encoding::Latin1,
lang: "eng".to_string(),
desc: "ID3v1 Comment".to_string(),
text: comment,
}));
}
let track = tag_data[126];
frames.push(Frame::Text(TextFrame {
id: "TRCK".to_string(),
encoding: Encoding::Latin1,
text: vec![track.to_string()],
}));
} else {
let comment = decode_v1_string(&tag_data[97..127]);
if !comment.is_empty() {
frames.push(Frame::Comment(CommentFrame {
id: "COMM".to_string(),
encoding: Encoding::Latin1,
lang: "eng".to_string(),
desc: "ID3v1 Comment".to_string(),
text: comment,
}));
}
}
let genre_id = tag_data[127] as usize;
if genre_id < GENRES.len() {
frames.push(Frame::Text(TextFrame {
id: "TCON".to_string(),
encoding: Encoding::Latin1,
text: vec![GENRES[genre_id].to_string()],
}));
}
Ok(frames)
}
fn decode_v1_string(data: &[u8]) -> String {
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
let s = specs::decode_text(&data[..end], Encoding::Latin1).unwrap_or_default();
s.trim_end().to_string()
}
pub fn make_id3v1(frames: &[Frame]) -> Vec<u8> {
let mut tag = vec![0u8; 128];
tag[0] = b'T';
tag[1] = b'A';
tag[2] = b'G';
for frame in frames {
if let Frame::Text(f) = frame {
let text = f.text.first().map(|s| s.as_str()).unwrap_or("");
match f.id.as_str() {
"TIT2" => write_v1_string(&mut tag[3..33], text),
"TPE1" => write_v1_string(&mut tag[33..63], text),
"TALB" => write_v1_string(&mut tag[63..93], text),
"TDRC" | "TYER" => write_v1_string(&mut tag[93..97], text),
"TRCK" => {
if let Ok(n) = text.split('/').next().unwrap_or("").parse::<u8>() {
tag[125] = 0;
tag[126] = n;
}
}
"TCON" => {
let genres = specs::parse_genre(text);
if let Some(genre_name) = genres.first() {
if let Some(idx) = GENRES.iter().position(|&g| g == genre_name.as_str())
{
tag[127] = idx as u8;
} else {
tag[127] = 255; }
}
}
_ => {}
}
}
}
tag
}
fn write_v1_string(dest: &mut [u8], text: &str) {
let bytes = text.as_bytes();
let len = bytes.len().min(dest.len());
dest[..len].copy_from_slice(&bytes[..len]);
}