selene_core/library/collection/rules/
track_rules.rs1use std::{path::PathBuf, str::FromStr};
2
3use chrono::{DateTime, Utc};
4use lunar_lib::error;
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::{ChannelLayout, Codec, ContainerFormat},
16};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum TrackRule {
20 Id { ids: Vec<TrackId>, op: EqOp },
22
23 SourceDir { source_dirs: Vec<PathBuf>, op: EqOp },
25
26 LibraryDir {
28 library_dirs: Vec<PathBuf>,
29 op: EqOp,
30 },
31
32 Format {
34 formats: Vec<ContainerFormat>,
35 op: EqOp,
36 },
37
38 Codec { codecs: Vec<Codec>, op: EqOp },
40
41 Duration { duration: f64, op: OrdOp },
43
44 ChannelCount { count: usize, op: OrdOp },
46
47 ChannelLayout {
49 layouts: Vec<ChannelLayout>,
50 op: EqOp,
51 },
52
53 Title { regex: Vec<String>, op: EqOp },
55
56 Single,
58
59 Album { albums: Vec<AlbumId>, op: EqOp },
61
62 Artist { artists: Vec<ArtistId>, op: EqOp },
64
65 Date { date: DateTime<Utc>, op: OrdOp },
67
68 Genre { genres: Vec<String>, op: EqOp },
70
71 HasLyrics,
73
74 HasSyncedLyrics,
76
77 HasPlainLyrics,
79
80 Instrumental,
82
83 MetadataOther {
85 key: String,
86 value: Vec<String>,
87 op: EqOp,
88 },
89}
90
91impl Rule for TrackRule {
92 type Item = Track;
93
94 fn matches(&self, item: &Self::Item) -> bool {
95 match self {
96 TrackRule::Id { ids: id, op } => eq_single(&item.id(), id, *op),
97 TrackRule::SourceDir {
98 source_dirs: source_dir,
99 op,
100 } => eq_single(&item.src_container().path().to_path_buf(), source_dir, *op),
101 TrackRule::LibraryDir {
102 library_dirs: library_dir,
103 op,
104 } => item.lib_container().is_some_and(|container| {
105 eq_single(&container.path().to_path_buf(), library_dir, *op)
106 }),
107 TrackRule::Format {
108 formats: format,
109 op,
110 } => item
111 .lib_container()
112 .is_some_and(|container| eq_single(container.container(), format, *op)),
113 TrackRule::Codec { codecs: codec, op } => item
114 .lib_container()
115 .is_some_and(|container| eq_single(container.codec(), codec, *op)),
116 TrackRule::Duration { duration, op } => item
117 .lib_container()
118 .and_then(|c| c.stream().duration())
119 .is_some_and(|c| ord_single(c, *duration, *op)),
120 TrackRule::ChannelCount { count, op } => item
121 .lib_container()
122 .map(|c| c.stream().channel_count())
123 .is_some_and(|c| ord_single(c, *count, *op)),
124 TrackRule::ChannelLayout {
125 layouts: layout,
126 op,
127 } => item.lib_container().is_some_and(|container| {
128 container
129 .stream()
130 .channel_layout()
131 .is_some_and(|channel_layout| eq_single(channel_layout, layout, *op))
132 }),
133 TrackRule::Title { regex, op } => {
134 let regex: Vec<_> = match regex
135 .iter()
136 .map(|r| Regex::from_str(r))
137 .collect::<Result<Vec<_>, _>>()
138 {
139 Ok(v) => v,
140 Err(err) => {
141 error!("Failed to create regex: {err}");
142 return false;
143 }
144 };
145
146 item.metadata.title.as_ref().is_some_and(|t| match op {
147 EqOp::EqAny => regex.iter().any(|r| r.is_match(t)),
148 EqOp::EqAll => regex.iter().all(|r| r.is_match(t)),
149 EqOp::NeqAny => !regex.iter().any(|r| r.is_match(t)),
150 EqOp::NeqAll => !regex.iter().all(|r| r.is_match(t)),
151 })
152 }
153 TrackRule::Single => item.metadata.album.is_none(),
154 TrackRule::Album { albums: album, op } => item
155 .metadata
156 .album
157 .as_ref()
158 .is_some_and(|a| eq_single(a, album, *op)),
159 TrackRule::Artist {
160 artists: artist,
161 op,
162 } => eq_many(item.metadata.artists.artist_ids(), artist, *op),
163 TrackRule::Date { date, op } => item
164 .metadata
165 .date
166 .is_some_and(|d| ord_single(&d, date, *op)),
167 TrackRule::Genre { genres: genre, op } => eq_many(&item.metadata.genre, genre, *op),
168 TrackRule::HasLyrics => item
169 .metadata
170 .lyric_data
171 .as_ref()
172 .is_some_and(LyricData::has_lyrics),
173 TrackRule::HasSyncedLyrics => item
174 .metadata
175 .lyric_data
176 .as_ref()
177 .is_some_and(|l| matches!(l, LyricData::Synced(_))),
178 TrackRule::HasPlainLyrics => item
179 .metadata
180 .lyric_data
181 .as_ref()
182 .is_some_and(|l| matches!(l, LyricData::Plain(_))),
183 TrackRule::Instrumental => item
184 .metadata
185 .lyric_data
186 .as_ref()
187 .is_some_and(LyricData::instrumental),
188 TrackRule::MetadataOther { key, value, op } => item
189 .metadata
190 .other
191 .iter()
192 .any(|(k, v)| k == key && eq_single(v, value, *op)),
193 }
194 }
195}