use std::{path::PathBuf, str::FromStr};
use chrono::{DateTime, Utc};
use lunar_lib::error;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{
library::{
album::AlbumId,
artist::ArtistId,
collection::rules::{EqOp, OrdOp, Rule, eq_many, eq_single, ord_single},
track::{Track, TrackId, lyric_data::LyricData},
},
media_container::ContainerFormat,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TrackRule {
Id { id: Vec<TrackId>, op: EqOp },
SourceDir { source_dir: Vec<PathBuf>, op: EqOp },
LibraryDir { library_dir: Vec<PathBuf>, op: EqOp },
Format {
format: Vec<ContainerFormat>,
op: EqOp,
},
Duration { duration: f64, op: OrdOp },
ChannelCount { count: usize, op: OrdOp },
Title { regex: Vec<String>, op: EqOp },
Single,
Album { albums: Vec<AlbumId>, op: EqOp },
Artist { artists: Vec<ArtistId>, op: EqOp },
Date { date: DateTime<Utc>, op: OrdOp },
Genre { genres: Vec<String>, op: EqOp },
HasLyrics,
HasSyncedLyrics,
HasPlainLyrics,
Instrumental,
MetadataOther {
key: String,
value: Vec<String>,
op: EqOp,
},
}
impl Rule for TrackRule {
type Item = Track;
fn matches(&self, item: &Self::Item) -> bool {
match self {
TrackRule::Id { id, op } => eq_single(&item.id(), id, *op),
TrackRule::SourceDir { source_dir, op } => {
eq_single(&item.container().path().to_path_buf(), source_dir, *op)
}
TrackRule::LibraryDir { library_dir, op } => {
eq_single(&item.container().path().to_path_buf(), library_dir, *op)
}
TrackRule::Format { format, op } => eq_single(item.container().format(), format, *op),
TrackRule::Duration { duration, op } => {
ord_single(item.container().stream().duration(), *duration, *op)
}
TrackRule::ChannelCount { count, op } => {
ord_single(&item.container().stream().codec_params.channels, count, *op)
}
TrackRule::Title { regex, op } => {
let regex: Vec<_> = match regex
.iter()
.map(|r| Regex::from_str(r))
.collect::<Result<Vec<_>, _>>()
{
Ok(v) => v,
Err(err) => {
error!("Failed to create regex: {err}");
return false;
}
};
item.metadata.title.as_ref().is_some_and(|t| match op {
EqOp::EqAny => regex.iter().any(|r| r.is_match(t)),
EqOp::EqAll => regex.iter().all(|r| r.is_match(t)),
EqOp::NeqAny => !regex.iter().any(|r| r.is_match(t)),
EqOp::NeqAll => !regex.iter().all(|r| r.is_match(t)),
})
}
TrackRule::Single => item.metadata.album.is_none(),
TrackRule::Album { albums: album, op } => item
.metadata
.album
.as_ref()
.is_some_and(|a| eq_single(a, album, *op)),
TrackRule::Artist {
artists: artist,
op,
} => eq_many(item.metadata.artists.artist_ids(), artist, *op),
TrackRule::Date { date, op } => item
.metadata
.date
.is_some_and(|d| ord_single(&d, date, *op)),
TrackRule::Genre { genres: genre, op } => eq_many(&item.metadata.genre, genre, *op),
TrackRule::HasLyrics => item
.metadata
.lyric_data
.as_ref()
.is_some_and(LyricData::has_lyrics),
TrackRule::HasSyncedLyrics => item
.metadata
.lyric_data
.as_ref()
.is_some_and(|l| matches!(l, LyricData::Synced(_))),
TrackRule::HasPlainLyrics => item
.metadata
.lyric_data
.as_ref()
.is_some_and(|l| matches!(l, LyricData::Plain(_))),
TrackRule::Instrumental => item
.metadata
.lyric_data
.as_ref()
.is_some_and(LyricData::instrumental),
TrackRule::MetadataOther { key, value, op } => item
.metadata
.other
.iter()
.any(|(k, v)| k == key && eq_single(v, value, *op)),
}
}
}