audio tagging utilities
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

add only-imperfect option

Stella 76e04bc8 150b2527

+112
+112
src/main.rs
··· 19 19 /// Pretty-print output. With `--output json` this prints pretty JSON. With `--output debug` this prints human-friendly text. 20 20 #[arg(long)] 21 21 pretty: bool, 22 + 23 + /// Only output files that fail one or more validations (imperfect data) 24 + #[arg(long)] 25 + only_imperfect: bool, 22 26 /// Directory to scan 23 27 directory: String, 24 28 } ··· 119 123 120 124 type ResultType = Result<Vec<AudioFileEntry>, Box<dyn std::error::Error>>; 121 125 126 + /// A validation result: None == ok, Some(reason) == failed with reason 127 + type ValidationResult = Option<String>; 128 + 129 + /// Validator trait for AudioFileEntry 130 + trait Validator { 131 + fn validate(&self, entry: &AudioFileEntry) -> ValidationResult; 132 + } 133 + 134 + struct TitleValidator; 135 + impl Validator for TitleValidator { 136 + fn validate(&self, entry: &AudioFileEntry) -> ValidationResult { 137 + let title = entry.tags.title(); 138 + if title.is_none() || title == Some("") { 139 + Some("missing title".to_string()) 140 + } else { 141 + None 142 + } 143 + } 144 + } 145 + 146 + struct ArtistValidator; 147 + impl Validator for ArtistValidator { 148 + fn validate(&self, entry: &AudioFileEntry) -> ValidationResult { 149 + let artist = entry.tags.artist(); 150 + if artist.is_none() || artist == Some("") { 151 + Some("missing artist".to_string()) 152 + } else { 153 + None 154 + } 155 + } 156 + } 157 + 158 + struct AlbumValidator; 159 + impl Validator for AlbumValidator { 160 + fn validate(&self, entry: &AudioFileEntry) -> ValidationResult { 161 + match entry.tags.album() { 162 + Some(a) => { 163 + if a.title.trim().is_empty() { 164 + Some("missing album title".to_string()) 165 + } else { 166 + None 167 + } 168 + } 169 + None => Some("missing album".to_string()), 170 + } 171 + } 172 + } 173 + 174 + struct CoverValidator; 175 + impl Validator for CoverValidator { 176 + fn validate(&self, entry: &AudioFileEntry) -> ValidationResult { 177 + match entry.tags.album() { 178 + Some(a) => match a.cover.as_ref() { 179 + Some(c) => { 180 + if c.data.is_empty() { 181 + Some("empty cover".to_string()) 182 + } else { 183 + None 184 + } 185 + } 186 + None => Some("missing cover".to_string()), 187 + }, 188 + None => Some("missing cover".to_string()), 189 + } 190 + } 191 + } 192 + 193 + fn default_validators() -> Vec<Box<dyn Validator>> { 194 + vec![ 195 + Box::new(TitleValidator), 196 + Box::new(ArtistValidator), 197 + Box::new(AlbumValidator), 198 + Box::new(CoverValidator), 199 + ] 200 + } 201 + 122 202 fn is_audio_file(path: &Path) -> bool { 123 203 if let Some(extension) = path.extension().and_then(|s| s.to_str()) { 124 204 matches!( ··· 207 287 match read_folder(&args.directory, args.recursive) { 208 288 Ok(audio_files) => { 209 289 println!("Found {} audio files in '{}':", audio_files.len(), args.directory); 290 + 291 + // total files before filtering 292 + let total_files = audio_files.len(); 293 + 294 + // build validators (future: could be dynamic from CLI) 295 + let validators = default_validators(); 296 + 297 + // if user requested only imperfect, filter entries that fail any validator 298 + let audio_files: Vec<AudioFileEntry> = if args.only_imperfect { 299 + audio_files 300 + .into_iter() 301 + .filter(|e| validators.iter().any(|v| v.validate(e).is_some())) 302 + .collect() 303 + } else { 304 + audio_files 305 + }; 306 + 307 + // how many imperfect files we have after filtering 308 + let imperfect_count = audio_files.len(); 210 309 // Decide behavior based on `--pretty` and `--output` 211 310 if args.pretty { 212 311 match args.output { ··· 239 338 } 240 339 } 241 340 } 341 + } 342 + 343 + // If requested, print an imperfect summary in yellow at the end 344 + if args.only_imperfect { 345 + let percent = if total_files > 0 { 346 + (imperfect_count as f64 / total_files as f64) * 100.0 347 + } else { 348 + 0.0 349 + }; 350 + // ANSI yellow 351 + let yellow = "\x1b[33m"; 352 + let reset = "\x1b[0m"; 353 + eprintln!("{}Imperfect files: {}/{} ({:.2}%){}", yellow, imperfect_count, total_files, percent, reset); 242 354 } 243 355 } 244 356 Err(e) => {