mqa-identify 0.3.0

A minimal library, to check if a flac file has been encoded by/with MQA
Documentation
#![deny(clippy::pedantic)]

use std::path::{Path, PathBuf};
use std::process::ExitCode;

use clap::Parser;
use indicatif::{ParallelProgressIterator, ProgressStyle};
use itertools::{Either, Itertools};
use mqa_identify::identify_mqa;
use rayon::prelude::*;
use walkdir::WalkDir;

const PROGRESS_TEMPLATE: &str = "{spinner} [{elapsed_precise}] [{wide_bar}] {pos}/{len} ({eta})";

#[derive(Parser)]
#[command(version, about)]
struct Cli {
    /// Files and directories to scan for MQA encoded FLAC files
    #[arg(required = true)]
    paths: Vec<PathBuf>,
}

fn main() -> ExitCode {
    let cli = Cli::parse();

    let mut errors = Vec::new();
    let flac_files = collect_flac_files(&cli.paths, &mut errors);

    let style = ProgressStyle::with_template(PROGRESS_TEMPLATE)
        .unwrap_or_else(|_| ProgressStyle::default_bar());

    let (mqa_files, scan_errors): (Vec<PathBuf>, Vec<String>) = flac_files
        .into_par_iter()
        .progress_with_style(style)
        .filter_map(|path| match identify_mqa(&path) {
            Ok(true) => Some(Either::Left(path)),
            Ok(false) => None,
            Err(error) => Some(Either::Right(format!("{}: {error}", path.display()))),
        })
        .collect::<Vec<_>>()
        .into_iter()
        .partition_map(|result| result);

    errors.extend(scan_errors);

    match mqa_files.len() {
        0 => println!("No MQA files found."),
        1 => println!("Found 1 MQA file:\n{}", mqa_files[0].display()),
        n => println!(
            "Found {} MQA files:\n{}",
            n,
            mqa_files.iter().map(|path| path.display()).join("\n")
        ),
    }

    if errors.is_empty() {
        return ExitCode::SUCCESS;
    }

    eprintln!("\n{} path(s) could not be checked:", errors.len());
    for error in &errors {
        eprintln!("  {error}");
    }

    ExitCode::FAILURE
}

fn collect_flac_files(paths: &[PathBuf], errors: &mut Vec<String>) -> Vec<PathBuf> {
    let mut flac_files = Vec::new();

    for path in paths {
        for entry in WalkDir::new(path) {
            match entry {
                Ok(entry) if entry.file_type().is_file() && is_flac(entry.path()) => {
                    flac_files.push(entry.into_path());
                }
                Ok(_) => {}
                Err(error) => errors.push(error.to_string()),
            }
        }
    }

    flac_files
}

fn is_flac(path: &Path) -> bool {
    path.extension()
        .is_some_and(|extension| extension.eq_ignore_ascii_case("flac"))
}