Skip to content

Commit

Permalink
treewide: Reorganise code so types are colocated
Browse files Browse the repository at this point in the history
  • Loading branch information
cdown committed Feb 14, 2025
1 parent a968803 commit adeaf32
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 38 deletions.
54 changes: 54 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use clap::Parser;
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Config {
#[arg(
long,
short = 'n',
help = "Don't actually rename or tag files, only display what would happen"
)]
pub dry_run: bool,

#[arg(
long,
short,
help = "Ignore .lastmack timestamp, run on all files present regardless"
)]
pub force: bool,

#[arg(
long,
short,
help = "Use a different output directory (by default, it's the same as the input dir)"
)]
pub output_dir: Option<PathBuf>,

/// The format to apply to files, excluding the extension.
///
/// Substitutions can be applied inside curly brackets, for example with {artist} to get the
/// track artist. Any formats returning data with "/" will have it transformed to "_".
///
/// Available formats:
///
/// TAG:
///
/// artist
/// album
/// track (width: 2)
/// title
///
/// LITERAL:
///
/// {{ and }} indicate literal brackets.
#[arg(
long,
verbatim_doc_comment,
default_value = "{artist}/{album}/{track} {title}"
)]
pub fmt: String,

#[arg(help = "Directories to find music files in.")]
pub paths: Option<Vec<PathBuf>>,
}
52 changes: 19 additions & 33 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
mod extract;
mod fixers;
mod config;
mod mtime;
mod rename;
mod track;
mod types;

use anyhow::Result;
use clap::Parser;
use funcfmt::{fm, FormatMap, FormatPieces, ToFormatPieces};
use funcfmt::{fm, FormatPieces, ToFormatPieces};
use id3::TagLike;
use jwalk::WalkDir;
use rayon::prelude::*;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use config::Config;
use track::{get_track, Track};

const ALLOWED_EXTS: &[&str] = &["mp3", "flac", "m4a"];

fn fix_track(track: &mut types::Track, dry_run: bool) {
let fix_results = fixers::run_fixers(track, dry_run);
fn fix_track(track: &mut Track, dry_run: bool) {
let fix_results = track::fixers::run_fixers(track, dry_run);
match fix_results {
Ok(applied_fixers) => {
if applied_fixers {
Expand All @@ -29,7 +29,7 @@ fn fix_track(track: &mut types::Track, dry_run: bool) {
}
}

fn print_updated_tags(track: &types::Track) {
fn print_updated_tags(track: &Track) {
println!(
"{}: updated tags: artist: '{}', album: '{}', title: '{}'",
track.path.display(),
Expand All @@ -39,13 +39,8 @@ fn print_updated_tags(track: &types::Track) {
);
}

fn rename_track(
track: &types::Track,
fp: &FormatPieces<types::Track>,
output_path: &Path,
dry_run: bool,
) {
let new_path = rename::rename_track(track, fp, output_path, dry_run);
fn rename_track(track: &Track, fp: &FormatPieces<Track>, output_path: &Path, dry_run: bool) {
let new_path = track::rename::rename_track(track, fp, output_path, dry_run);

match new_path {
Ok(Some(new_path)) => println!(
Expand Down Expand Up @@ -73,21 +68,12 @@ fn clean_part(path_part: &str) -> String {
.collect()
}

fn get_format_pieces(tmpl: &str) -> Result<funcfmt::FormatPieces<types::Track>> {
let formatters: FormatMap<types::Track> = fm!(
"artist" => |t: &types::Track| Some(clean_part(
t.tag.artist().unwrap_or("Unknown Artist")
)),
"album" => |t: &types::Track| Some(clean_part(
t.tag.album().unwrap_or("Unknown Album")
)),
"title" => |t: &types::Track| Some(clean_part(
t.tag.title().unwrap_or("Unknown Title")
)),
"track" => |t: &types::Track| Some(format!(
"{:02}",
t.tag.track().unwrap_or_default()
)),
fn get_format_pieces(tmpl: &str) -> Result<funcfmt::FormatPieces<Track>> {
let formatters = fm!(
"artist" => |t: &Track| Some(clean_part(t.tag.artist().unwrap_or("Unknown Artist"))),
"album" => |t: &Track| Some(clean_part(t.tag.album().unwrap_or("Unknown Album"))),
"title" => |t: &Track| Some(clean_part(t.tag.title().unwrap_or("Unknown Title"))),
"track" => |t: &Track| Some(format!("{:02}", t.tag.track().unwrap_or_default())),
);

Ok(formatters.to_format_pieces(tmpl)?)
Expand All @@ -97,7 +83,7 @@ fn is_updated_since_last_run(path: &PathBuf, last_run_time: SystemTime) -> bool
mtime::mtime_def_now(path) > last_run_time
}

fn fix_all_tracks(cfg: &types::Config, base_path: &PathBuf, output_path: &Path) {
fn fix_all_tracks(cfg: &Config, base_path: &PathBuf, output_path: &Path) {
// If the output path is different, we don't know if we should run or not, so just do them all
let last_run_time = if output_path == base_path {
mtime::get_last_run_time(base_path).unwrap_or(SystemTime::UNIX_EPOCH)
Expand Down Expand Up @@ -130,7 +116,7 @@ fn fix_all_tracks(cfg: &types::Config, base_path: &PathBuf, output_path: &Path)
.filter(|e| cfg.force || is_updated_since_last_run(e, last_run_time))
.collect::<Vec<_>>()
.into_par_iter()
.for_each(|path| match track::get_track(path.clone()) {
.for_each(|path| match get_track(path.clone()) {
Ok(mut track) => {
fix_track(&mut track, cfg.dry_run);
rename_track(&track, &fp, output_path, cfg.dry_run);
Expand All @@ -150,7 +136,7 @@ fn fix_all_tracks(cfg: &types::Config, base_path: &PathBuf, output_path: &Path)
}

fn main() {
let mut cfg = types::Config::parse();
let mut cfg = Config::parse();

let paths = cfg.paths.take().unwrap_or_else(|| vec![PathBuf::from(".")]);

Expand Down
10 changes: 9 additions & 1 deletion src/extract.rs → src/track/feat.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::types::TrackFeat;
use once_cell::sync::Lazy;
use regex::{Regex, RegexBuilder};

Expand All @@ -14,6 +13,15 @@ static FEAT_RE: Lazy<Regex> = Lazy::new(|| {
static FEAT_ARTIST_SPLIT: Lazy<Regex> =
Lazy::new(|| Regex::new(r#", (?:and |& )?"#).expect("BUG: Invalid regex"));

/// Represents a track's title after extracting featured artists.
#[derive(Debug, PartialEq, Eq)]
pub struct TrackFeat {
pub title: String,
pub featured_artists: Vec<String>,
pub original_title: String,
}

/// Extracts featured artist information from a track title.
pub fn extract_feat(title: &str) -> TrackFeat {
if let Some(caps) = FEAT_RE.captures(title) {
let trimmed = caps["feat_artists"].trim();
Expand Down
4 changes: 2 additions & 2 deletions src/fixers.rs → src/track/fixers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::extract::extract_feat;
use crate::types::{Track, TrackFeat};
use crate::track::feat::{extract_feat, TrackFeat};
use crate::track::Track;
use anyhow::{bail, Result};
use cow_utils::CowUtils;
use id3::{Tag, TagLike, Version};
Expand Down
2 changes: 1 addition & 1 deletion src/track.rs → src/track/loader.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::types::Track;
use crate::track::Track;
use anyhow::Result;
use id3::Tag;
use std::path::PathBuf;
Expand Down
15 changes: 15 additions & 0 deletions src/track/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub mod feat;
pub mod fixers;
pub mod loader;
pub mod rename;

pub use loader::get_track;

use id3::Tag;
use std::path::PathBuf;

/// Represents a music track with its file path and associated ID3 tag.
pub struct Track {
pub path: PathBuf,
pub tag: Tag,
}
2 changes: 1 addition & 1 deletion src/rename.rs → src/track/rename.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::types::Track;
use crate::track::Track;
use anyhow::{Context, Result};
use funcfmt::{FormatPieces, Render};
use once_cell::sync::Lazy;
Expand Down

0 comments on commit adeaf32

Please sign in to comment.