use crate::models::{CurrentMetadata, MetadataSource};
use anyhow::{Result, Context};
use std::path::Path;
pub fn extract_current_metadata(file_path: &Path) -> Result<CurrentMetadata> {
let embedded = extract_from_embedded_tags(file_path)?;
let from_filename = extract_from_filename(file_path)?;
Ok(embedded.merge_with(from_filename))
}
fn extract_from_embedded_tags(file_path: &Path) -> Result<CurrentMetadata> {
let tag = mp4ameta::Tag::read_from_path(file_path)
.context("Failed to read M4B metadata")?;
Ok(CurrentMetadata {
title: tag.title().map(|s| s.to_string()),
author: tag.artist().map(|s| s.to_string())
.or_else(|| tag.album_artist().map(|s| s.to_string())),
year: tag.year().and_then(|s| s.parse::<u32>().ok()),
duration: None, source: MetadataSource::Embedded,
})
}
fn extract_from_filename(file_path: &Path) -> Result<CurrentMetadata> {
let filename = file_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
if let Some((author, title)) = parse_author_title_pattern(filename) {
return Ok(CurrentMetadata {
title: Some(title),
author: Some(author),
year: None,
duration: None,
source: MetadataSource::Filename,
});
}
Ok(CurrentMetadata {
title: Some(filename.to_string()),
author: None,
year: None,
duration: None,
source: MetadataSource::Filename,
})
}
fn parse_author_title_pattern(filename: &str) -> Option<(String, String)> {
let separators = [" - ", "_-_", " -_ ", "_ -_", "_- "];
for separator in separators {
let parts: Vec<&str> = filename.split(separator).collect();
if parts.len() >= 2 {
let author = parts[0].replace('_', " ").trim().to_string();
let title = parts[1..].join(separator).replace('_', " ").trim().to_string();
if !author.is_empty() && !title.is_empty() {
return Some((author, title));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_author_title_pattern() {
let (author, title) = parse_author_title_pattern("Andy Weir - Project Hail Mary").unwrap();
assert_eq!(author, "Andy Weir");
assert_eq!(title, "Project Hail Mary");
let (author, title) = parse_author_title_pattern("Isaac Asimov - I, Robot - Complete Edition").unwrap();
assert_eq!(author, "Isaac Asimov");
assert_eq!(title, "I, Robot - Complete Edition");
let (author, title) = parse_author_title_pattern("Adam_Phillips_-_On_Giving_Up").unwrap();
assert_eq!(author, "Adam Phillips");
assert_eq!(title, "On Giving Up");
let (author, title) = parse_author_title_pattern("Morgan_Housel_-_The_Art_of_Spending_Money").unwrap();
assert_eq!(author, "Morgan Housel");
assert_eq!(title, "The Art of Spending Money");
let (author, title) = parse_author_title_pattern("Neil_deGrasse_Tyson - Just Visiting This Planet").unwrap();
assert_eq!(author, "Neil deGrasse Tyson");
assert_eq!(title, "Just Visiting This Planet");
assert_eq!(parse_author_title_pattern("JustATitle"), None);
}
}