mtag-cli 0.2.0

Organize music for self-built media libraries like Plex, Emby, and Jellyfin
Documentation
use std::{
    fs,
    path::{Path, PathBuf},
};

use crate::error::{MtagError, MtagResult};

/// Recursively scans a folder and returns files detected as audio.
///
/// The returned paths are sorted for deterministic planning and test output.
///
/// # Errors
///
/// Returns [`MtagError::ReadDirectory`] when a directory cannot be read and
/// [`MtagError::InspectFileType`] when MIME detection cannot inspect a file.
pub fn scan_audio_files(root: &Path) -> MtagResult<Vec<PathBuf>> {
    let mut files = Vec::new();
    visit_folder(root, &mut files)?;
    files.sort();
    Ok(files)
}

fn visit_folder(folder: &Path, files: &mut Vec<PathBuf>) -> MtagResult<()> {
    let entries = fs::read_dir(folder).map_err(|source| MtagError::ReadDirectory {
        path: folder.to_path_buf(),
        source,
    })?;

    for entry in entries {
        let entry = entry.map_err(|source| MtagError::ReadDirectory {
            path: folder.to_path_buf(),
            source,
        })?;
        let path = entry.path();
        if path.is_dir() {
            visit_folder(&path, files)?;
        } else if path.is_file() && is_audio_file(&path)? {
            files.push(path);
        }
    }

    Ok(())
}

fn is_audio_file(path: &Path) -> MtagResult<bool> {
    let kind = infer::get_from_path(path).map_err(|source| MtagError::InspectFileType {
        path: path.to_path_buf(),
        source,
    })?;

    Ok(kind.is_some_and(|kind| kind.mime_type().starts_with("audio/")))
}