use std::path::PathBuf; use clap::Parser; use tracing_subscriber::EnvFilter; use shanty_db::Database; use shanty_tag::{MusicBrainzClient, TagConfig, run_tagging}; #[derive(Parser)] #[command( name = "shanty-tag", about = "Fill in missing metadata on music files via MusicBrainz" )] struct Cli { /// Database URL. Defaults to sqlite:///shanty/shanty.db?mode=rwc #[arg(long, env = "SHANTY_DATABASE_URL")] database: Option, /// Tag all untagged tracks in the database. #[arg(long)] all: bool, /// Tag a specific track by its database ID. #[arg(long)] track: Option, /// Preview matches without writing to DB or files. #[arg(long)] dry_run: bool, /// Write updated tags back to music files. #[arg(long)] write_tags: bool, /// Minimum match confidence (0.0 - 1.0). #[arg(long, default_value = "0.8")] confidence: f64, /// Increase verbosity (-v info, -vv debug, -vvv trace). #[arg(short, long, action = clap::ArgAction::Count)] verbose: u8, } fn default_database_url() -> String { let data_dir = dirs::data_dir() .unwrap_or_else(|| PathBuf::from(".")) .join("shanty"); std::fs::create_dir_all(&data_dir).ok(); let db_path = data_dir.join("shanty.db"); format!("sqlite://{}?mode=rwc", db_path.display()) } #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); // Set up tracing let filter = match cli.verbose { 0 => "warn", 1 => "info,shanty_tag=info", 2 => "info,shanty_tag=debug", _ => "debug,shanty_tag=trace", }; tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(filter)), ) .init(); // Validate args if !cli.all && cli.track.is_none() { anyhow::bail!("specify either --all or --track "); } // Connect to database let database_url = cli.database.unwrap_or_else(default_database_url); tracing::info!(url = %database_url, "connecting to database"); let db = Database::new(&database_url).await?; // Create MusicBrainz client let provider = MusicBrainzClient::new()?; // Run tagging let config = TagConfig { dry_run: cli.dry_run, write_tags: cli.write_tags, confidence: cli.confidence, }; if config.dry_run { println!("DRY RUN — no changes will be written"); } let stats = run_tagging(db.conn(), &provider, &config, cli.track).await?; println!("\nTagging complete: {stats}"); Ok(()) }