selene_core/library/collection/rules/
track_rules.rs1use std::{path::PathBuf, str::FromStr};
2
3use chrono::{DateTime, Utc};
4use lunar_lib::{error, iterator_ext::IteratorExtensions};
5use regex::Regex;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 library::{
10 album::AlbumId,
11 artist::ArtistId,
12 collection::rules::{EqOp, OrdOp, Rule, eq_many, eq_single, ord_single},
13 track::{Track, TrackId, lyric_data::LyricData},
14 },
15 media_container::ContainerFormat,
16};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum TrackRule {
20 Id { id: Vec<TrackId>, op: EqOp },
22
23 SourceDir { source_dir: Vec<PathBuf>, op: EqOp },
25
26 LibraryDir { library_dir: Vec<PathBuf>, op: EqOp },
28
29 Format {
31 format: Vec<ContainerFormat>,
32 op: EqOp,
33 },
34
35 Duration { duration: f64, op: OrdOp },
37
38 ChannelCount { count: usize, op: OrdOp },
40
41 Title { regex: Vec<String>, op: EqOp },
43
44 Single,
46
47 Album { albums: Vec<AlbumId>, op: EqOp },
49
50 Artist { artists: Vec<ArtistId>, op: EqOp },
52
53 Date { date: DateTime<Utc>, op: OrdOp },
55
56 Genre { genres: Vec<String>, op: EqOp },
58
59 HasLyrics,
61
62 HasSyncedLyrics,
64
65 HasPlainLyrics,
67
68 Instrumental,
70
71 MetadataOther {
73 key: String,
74 value: Vec<String>,
75 op: EqOp,
76 },
77}
78
79impl Rule for TrackRule {
80 type Item = Track;
81
82 fn matches(&self, item: &Self::Item) -> bool {
83 match self {
84 TrackRule::Id { id, op } => eq_single(&item.id(), id, *op),
85 TrackRule::SourceDir { source_dir, op } => {
86 eq_single(&item.container().path().to_path_buf(), source_dir, *op)
87 }
88 TrackRule::LibraryDir { library_dir, op } => {
89 eq_single(&item.container().path().to_path_buf(), library_dir, *op)
90 }
91 TrackRule::Format { format, op } => eq_single(item.container().format(), format, *op),
92 TrackRule::Duration { duration, op } => {
93 ord_single(item.container().stream().duration(), *duration, *op)
94 }
95 TrackRule::ChannelCount { count, op } => {
96 ord_single(&item.container().stream().codec_params.channels, count, *op)
97 }
98 TrackRule::Title { regex, op } => {
99 let regex: Vec<_> = match regex.iter().try_map(|r| Regex::from_str(r)) {
100 Ok(v) => v.collect(),
101 Err(err) => {
102 error!("Failed to create regex: {err}");
103 return false;
104 }
105 };
106
107 item.metadata.title.as_ref().is_some_and(|t| match op {
108 EqOp::EqAny => regex.iter().any(|r| r.is_match(t)),
109 EqOp::EqAll => regex.iter().all(|r| r.is_match(t)),
110 EqOp::NeqAny => !regex.iter().any(|r| r.is_match(t)),
111 EqOp::NeqAll => !regex.iter().all(|r| r.is_match(t)),
112 })
113 }
114 TrackRule::Single => item.metadata.album.is_none(),
115 TrackRule::Album { albums: album, op } => item
116 .metadata
117 .album
118 .as_ref()
119 .is_some_and(|a| eq_single(a, album, *op)),
120 TrackRule::Artist {
121 artists: artist,
122 op,
123 } => eq_many(item.metadata.artists_raw(), artist, *op),
124 TrackRule::Date { date, op } => item
125 .metadata
126 .date
127 .is_some_and(|d| ord_single(&d, date, *op)),
128 TrackRule::Genre { genres: genre, op } => eq_many(&item.metadata.genre, genre, *op),
129 TrackRule::HasLyrics => item
130 .metadata
131 .lyric_data
132 .as_ref()
133 .is_some_and(LyricData::has_lyrics),
134 TrackRule::HasSyncedLyrics => item
135 .metadata
136 .lyric_data
137 .as_ref()
138 .is_some_and(|l| matches!(l, LyricData::Synced(_))),
139 TrackRule::HasPlainLyrics => item
140 .metadata
141 .lyric_data
142 .as_ref()
143 .is_some_and(|l| matches!(l, LyricData::Plain(_))),
144 TrackRule::Instrumental => item
145 .metadata
146 .lyric_data
147 .as_ref()
148 .is_some_and(LyricData::instrumental),
149 TrackRule::MetadataOther { key, value, op } => item
150 .metadata
151 .other
152 .iter()
153 .any(|(k, v)| k == key && eq_single(v, value, *op)),
154 }
155 }
156}