From 536e117576e11f759dece1301344bbebec83be6d Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 25 Jun 2026 21:04:47 +0200 Subject: [PATCH] Parse music metadata --- Cargo.lock | 191 ++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 + devenv.nix | 3 + src/local.rs | 19 ++++- src/main.rs | 1 + src/music_metadata.rs | 56 +++++++++++++ 6 files changed, 264 insertions(+), 10 deletions(-) create mode 100644 src/music_metadata.rs diff --git a/Cargo.lock b/Cargo.lock index bf44a83..efa0c8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,12 +52,24 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" @@ -73,6 +85,12 @@ dependencies = [ "objc2", ] +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "cfg-if" version = "1.0.4" @@ -154,18 +172,33 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags", + "bitflags 2.13.0", "block2", "libc", "objc2", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -181,7 +214,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80a5eca878900c2e39e9e52fd797954b7fc39eeefc8558257114bfea6a698fcf" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", "log", "memchr", @@ -223,7 +256,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533e68a5842e734946fe159fb03fc9bbbb254f590dd0d8ad321ae5ff7beca2c1" dependencies = [ - "bitflags", + "bitflags 2.13.0", "inotify-sys", "libc", ] @@ -259,10 +292,16 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.186" @@ -320,6 +359,7 @@ dependencies = [ "fuser", "libc", "notify", + "symphonia", "time", ] @@ -329,7 +369,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -342,7 +382,7 @@ version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -354,7 +394,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags", + "bitflags 2.13.0", "fsevent-sys", "inotify", "kqueue", @@ -372,7 +412,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -502,7 +542,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -578,6 +618,139 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "symphonia" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-alac", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "2.0.118" diff --git a/Cargo.toml b/Cargo.toml index 939ab9a..b0bcdc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,7 @@ fuser = "0.17.0" libc = "0.2.186" notify = "8.2.0" time = "0.3.49" + +symphonia = { version = "0.5", default-features = false, features = [ + "aac", "alac", "flac", "mp3", "ogg", "vorbis", "wav" +] } diff --git a/devenv.nix b/devenv.nix index f836c61..47fd17b 100644 --- a/devenv.nix +++ b/devenv.nix @@ -27,6 +27,9 @@ packages = with pkgs; [ git just + + ffprobe + opencode ]; diff --git a/src/local.rs b/src/local.rs index 4758a8d..7eba964 100644 --- a/src/local.rs +++ b/src/local.rs @@ -8,10 +8,12 @@ use std::{ time::{Duration, SystemTime}, }; +use clap::builder::OsStr; use fuser::{Errno, FileAttr, Filesystem, Generation, INodeNo, Request}; use notify::{Event, EventKind, RecursiveMode, Watcher}; use crate::file_watcher::FileWatcher; +use crate::music_metadata::MusicMetadata; #[derive(PartialEq, Eq, Copy, Clone, Debug)] enum FileType { @@ -27,6 +29,7 @@ struct LocalItem { local_path: PathBuf, file_type: FileType, metadata: fs::Metadata, + music_metadata: Option, } #[derive(Debug)] @@ -49,6 +52,7 @@ impl LocalOrigin { local_path: PathBuf::from(&destination), file_type: FileType::Directory, metadata: fs::metadata(&source).unwrap(), + music_metadata: Option::None, }; map.lock().unwrap().insert(INodeNo::ROOT, local_root); @@ -79,6 +83,7 @@ impl LocalOrigin { local_path: destination.to_path_buf(), file_type: FileType::Directory, metadata: fs::metadata(&source).unwrap(), + music_metadata: Option::None, }; map.lock().unwrap().insert(INodeNo::ROOT, local_root); @@ -109,13 +114,25 @@ impl LocalOrigin { // local_path.push(destination); local_path.push(name.clone()); + let music_metadata: Option = if item_path + .extension() + .and_then(std::ffi::OsStr::to_str) + .unwrap() + == "flac" + { + MusicMetadata::parse_music_metadata(&item_path) + } else { + Option::None + }; + let local_item = LocalItem { inode: INodeNo(metadata.ino()), name: name, original_path: item_path.clone(), local_path: local_path, file_type: file_type, - metadata, + metadata: metadata, + music_metadata: music_metadata, }; let inode = local_item.inode; diff --git a/src/main.rs b/src/main.rs index a1ce5ab..8241cb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use std::time::Duration; mod file_watcher; mod local; +mod music_metadata; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] diff --git a/src/music_metadata.rs b/src/music_metadata.rs new file mode 100644 index 0000000..cd12a84 --- /dev/null +++ b/src/music_metadata.rs @@ -0,0 +1,56 @@ +use std::{fs, path::Path}; + +use symphonia::core::{ + formats::FormatOptions, + io::MediaSourceStream, + meta::{Metadata, MetadataOptions, MetadataRevision, StandardTagKey}, + probe::Hint, +}; + +#[derive(Debug, Default)] +pub struct MusicMetadata { + artist: Vec, + album: String, + track_number: i32, + track_title: String, +} + +impl MusicMetadata { + pub fn parse_music_metadata(path: &Path) -> Option { + let src = fs::File::open(path).expect("failed to open media"); + let mss = MediaSourceStream::new(Box::new(src), Default::default()); + let mut hint = Hint::new(); + hint.with_extension("flac"); + + let meta_opts: MetadataOptions = Default::default(); + let fmt_opts: FormatOptions = Default::default(); + + let mut format = symphonia::default::get_probe() + .format(&hint, mss, &fmt_opts, &meta_opts) + .expect("unsupported format"); + + let metadata: Metadata = format.format.metadata(); + let revision: &MetadataRevision = metadata.current().unwrap(); + + let mut music_metadata = MusicMetadata { + ..Default::default() + }; + + for tag in revision.tags() { + let tag_type = tag.std_key.expect("no std_key for tag found"); + let value = tag.value.to_string(); + + match tag_type { + StandardTagKey::Artist => music_metadata.artist.push(value), + StandardTagKey::Album => music_metadata.album = value, + StandardTagKey::TrackNumber => { + music_metadata.track_number = value.parse::().unwrap() + } + StandardTagKey::TrackTitle => music_metadata.track_title = value, + _ => {} + } + } + + return Some(music_metadata); + } +}