use std::path::PathBuf;
use anyhow::Context;
use clap::Parser;
use mtag_cli::{
executor::{execute_plan, ConflictStrategy, ExecutionMode, ExecutionOptions},
metadata::read_track_metadata,
planner::{build_copy_plan, OrganizationOptions, DEFAULT_TEMPLATE},
scanner::scan_audio_files,
};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(
value_name = "MUSIC_FOLDER",
help = "Folder that contains source music files"
)]
music_folder_path: PathBuf,
#[arg(
value_name = "TARGET_FOLDER",
default_value = "Music",
help = "Folder where organized files will be written"
)]
target_folder_path: PathBuf,
#[arg(long, help = "Print planned file operations without writing files")]
dry_run: bool,
#[arg(long = "move", help = "Move files instead of copying them")]
move_files: bool,
#[arg(
long,
value_enum,
default_value_t = ConflictStrategy::Fail,
help = "How to handle existing destination files"
)]
on_conflict: ConflictStrategy,
#[arg(long, default_value = DEFAULT_TEMPLATE, help = "Organization template")]
template: String,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let audio_file_list = scan_audio_files(&cli.music_folder_path)?;
let mut skipped_metadata = 0usize;
let mut tracks = Vec::new();
for audio_path in &audio_file_list {
match read_track_metadata(audio_path) {
Ok(track) => tracks.push(track),
Err(error) => {
skipped_metadata += 1;
eprintln!("skip: {error}");
}
}
}
let plan = build_copy_plan(
&tracks,
&cli.target_folder_path,
&OrganizationOptions {
template: cli.template,
},
)
.context("failed to build organization plan")?;
if cli.dry_run {
for task in &plan.tasks {
println!("{} -> {}", task.from.display(), task.to.display());
}
}
let summary = execute_plan(
&plan,
&ExecutionOptions {
conflict_strategy: cli.on_conflict,
mode: if cli.move_files {
ExecutionMode::Move
} else {
ExecutionMode::Copy
},
dry_run: cli.dry_run,
},
)?;
println!(
"planned: {}, copied: {}, moved: {}, skipped: {}, renamed: {}, metadata skipped: {}",
summary.planned,
summary.copied,
summary.moved,
summary.skipped,
summary.renamed,
skipped_metadata
);
Ok(())
}