commit 8a07a3edc20b8226528e228c72da788576e2ecc6 Author: Connor Johnstone Date: Mon Mar 2 21:09:47 2026 -0500 Walk through filesystem and print metadata diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..963987f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,195 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "lofty" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "179408be6ddda3771589a4e940b1b5718613fa9986d78f420890d20e2b6fc278" +dependencies = [ + "byteorder", + "data-encoding", + "flate2", + "lofty_attr", + "log", + "ogg_pager", + "paste", +] + +[[package]] +name = "lofty_attr" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458ace39169e4b83c4f77ae3d42d5d1d11c422feef590219a97c973d3b524557" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ogg_pager" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6d1ca8364b84e0cf725eed06b1460c44671e6c0fb28765f5262de3ece07fdc" +dependencies = [ + "byteorder", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "playlists" +version = "0.1.0" +dependencies = [ + "lofty", + "walkdir", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b759a6e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "playlists" +version = "0.1.0" +edition = "2024" + +[dependencies] +lofty = "0.23" +walkdir = "2.5" diff --git a/src/filesystem.rs b/src/filesystem.rs new file mode 100644 index 0000000..305a016 --- /dev/null +++ b/src/filesystem.rs @@ -0,0 +1,22 @@ +use std::path::Path; + +use walkdir::WalkDir; + +const MUSIC_EXTENSIONS: &[&str] = &[ + "mp3", "flac", "ogg", "opus", "m4a", "mp4", "wav", "aif", "aiff", "wv", "ape", "mpc", +]; + +fn is_music_file(path: &Path) -> bool { + path.extension() + .and_then(|ext| ext.to_str()) + .is_some_and(|ext| MUSIC_EXTENSIONS.contains(&ext.to_ascii_lowercase().as_str())) +} + +/// Returns an iterator over music file paths found recursively under `dir`. +pub fn walk_music_files(dir: &Path) -> impl Iterator + '_ { + WalkDir::new(dir) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| is_music_file(e.path())) + .map(|e| e.into_path()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bb64f70 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,30 @@ +mod filesystem; +mod metadata; + +use std::env; +use std::path::Path; + +fn main() { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let dir = Path::new(&args[1]); + + for path in filesystem::walk_music_files(dir) { + println!("{}", path.display()); + + match metadata::read_track_metadata(&path) { + Ok(Some(meta)) => { + let unknown = "(unknown)"; + println!(" Title: {}", meta.title.as_deref().unwrap_or(unknown)); + println!(" Artist: {}", meta.artist.as_deref().unwrap_or(unknown)); + println!(" Album: {}", meta.album.as_deref().unwrap_or(unknown)); + } + Ok(None) => println!(" (no metadata tags found)"), + Err(e) => eprintln!(" warning: could not read metadata: {e}"), + } + } +} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..bd78f20 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,25 @@ +use std::path::Path; + +use lofty::file::TaggedFileExt; +use lofty::tag::Accessor; + +pub struct TrackMetadata { + pub title: Option, + pub artist: Option, + pub album: Option, +} + +/// Read metadata from a music file, returning `None` if no tags are present. +pub fn read_track_metadata(path: &Path) -> Result, lofty::error::LoftyError> { + let tagged_file = lofty::read_from_path(path)?; + + let Some(tag) = tagged_file.primary_tag().or_else(|| tagged_file.first_tag()) else { + return Ok(None); + }; + + Ok(Some(TrackMetadata { + title: tag.title().map(|s| s.into_owned()), + artist: tag.artist().map(|s| s.into_owned()), + album: tag.album().map(|s| s.into_owned()), + })) +}