Skip to main content

mtag_cli/
scanner.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4};
5
6use crate::error::{MtagError, MtagResult};
7
8/// Recursively scans a folder and returns files detected as audio.
9///
10/// The returned paths are sorted for deterministic planning and test output.
11///
12/// # Errors
13///
14/// Returns [`MtagError::ReadDirectory`] when a directory cannot be read and
15/// [`MtagError::InspectFileType`] when MIME detection cannot inspect a file.
16pub fn scan_audio_files(root: &Path) -> MtagResult<Vec<PathBuf>> {
17    let mut files = Vec::new();
18    visit_folder(root, &mut files)?;
19    files.sort();
20    Ok(files)
21}
22
23fn visit_folder(folder: &Path, files: &mut Vec<PathBuf>) -> MtagResult<()> {
24    let entries = fs::read_dir(folder).map_err(|source| MtagError::ReadDirectory {
25        path: folder.to_path_buf(),
26        source,
27    })?;
28
29    for entry in entries {
30        let entry = entry.map_err(|source| MtagError::ReadDirectory {
31            path: folder.to_path_buf(),
32            source,
33        })?;
34        let path = entry.path();
35        if path.is_dir() {
36            visit_folder(&path, files)?;
37        } else if path.is_file() && is_audio_file(&path)? {
38            files.push(path);
39        }
40    }
41
42    Ok(())
43}
44
45fn is_audio_file(path: &Path) -> MtagResult<bool> {
46    let kind = infer::get_from_path(path).map_err(|source| MtagError::InspectFileType {
47        path: path.to_path_buf(),
48        source,
49    })?;
50
51    Ok(kind.is_some_and(|kind| kind.mime_type().starts_with("audio/")))
52}