use super::AudibleMetadata;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MatchConfidence {
Strong,
Medium,
Low,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataDistance {
penalties: HashMap<String, f64>,
total: f64,
}
impl MetadataDistance {
pub fn new() -> Self {
Self {
penalties: HashMap::new(),
total: 0.0,
}
}
pub fn add_penalty(&mut self, field: &str, distance: f64, weight: f64) {
let weighted = distance * weight;
self.penalties.insert(field.to_string(), distance);
self.total += weighted;
}
pub fn total_distance(&self) -> f64 {
self.total
}
pub fn get_penalty(&self, field: &str) -> Option<f64> {
self.penalties.get(field).copied()
}
pub fn penalties(&self) -> &HashMap<String, f64> {
&self.penalties
}
}
impl Default for MetadataDistance {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchCandidate {
pub distance: MetadataDistance,
pub metadata: AudibleMetadata,
pub confidence: MatchConfidence,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MetadataSource {
Embedded,
Filename,
Manual,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrentMetadata {
pub title: Option<String>,
pub author: Option<String>,
pub year: Option<u32>,
pub duration: Option<f64>,
pub source: MetadataSource,
}
impl CurrentMetadata {
pub fn is_sufficient(&self) -> bool {
self.title.is_some() || self.author.is_some()
}
pub fn merge_with(self, other: CurrentMetadata) -> CurrentMetadata {
CurrentMetadata {
title: self.title.or(other.title),
author: self.author.or(other.author),
year: self.year.or(other.year),
duration: self.duration.or(other.duration),
source: self.source, }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metadata_distance() {
let mut distance = MetadataDistance::new();
distance.add_penalty("title", 0.1, 0.4); distance.add_penalty("author", 0.2, 0.3);
assert!((distance.total_distance() - 0.10).abs() < 0.001);
assert_eq!(distance.get_penalty("title"), Some(0.1));
assert_eq!(distance.get_penalty("author"), Some(0.2));
}
#[test]
fn test_current_metadata_is_sufficient() {
let metadata = CurrentMetadata {
title: Some("Test".to_string()),
author: None,
year: None,
duration: None,
source: MetadataSource::Embedded,
};
assert!(metadata.is_sufficient());
let empty = CurrentMetadata {
title: None,
author: None,
year: None,
duration: None,
source: MetadataSource::Embedded,
};
assert!(!empty.is_sufficient());
}
#[test]
fn test_current_metadata_merge() {
let embedded = CurrentMetadata {
title: Some("Title from tags".to_string()),
author: None,
year: Some(2020),
duration: None,
source: MetadataSource::Embedded,
};
let filename = CurrentMetadata {
title: Some("Title from filename".to_string()),
author: Some("Author from filename".to_string()),
year: None,
duration: None,
source: MetadataSource::Filename,
};
let merged = embedded.merge_with(filename);
assert_eq!(merged.title, Some("Title from tags".to_string()));
assert_eq!(merged.author, Some("Author from filename".to_string()));
assert_eq!(merged.year, Some(2020));
assert_eq!(merged.source, MetadataSource::Embedded);
}
}