From d79ec1fda7133187e6ad9e2803ae502463ba83bc Mon Sep 17 00:00:00 2001 From: LOSSES Don <1384036+Losses@users.noreply.github.com> Date: Sun, 5 Jan 2025 09:38:51 +0800 Subject: [PATCH] feat(music-brainz): Enhance audio fingerprinting to return duration and add CLI for AcoustID identification --- tag-editor/examples/music_brainz.rs | 48 ++++++++++++++++++++++ tag-editor/src/music_brainz/fingerprint.rs | 20 +++++---- 2 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tag-editor/examples/music_brainz.rs diff --git a/tag-editor/examples/music_brainz.rs b/tag-editor/examples/music_brainz.rs new file mode 100644 index 000000000..b022fc01f --- /dev/null +++ b/tag-editor/examples/music_brainz.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use clap::{Arg, Command}; +use rusty_chromaprint::Configuration; +use tag_editor::music_brainz::{api::identify, fingerprint::calc_fingerprint}; + +#[tokio::main] +async fn main() -> Result<()> { + // Set up CLI arguments + let matches = Command::new("MusicBrainz Audio Identifier") + .version("1.0") + .author("Rune Developers") + .about("Identifies audio files using AcoustID") + .arg( + Arg::new("API_KEY") + .help("Sets the AcoustID API key") + .required(true) + .index(1), + ) + .arg( + Arg::new("FILE_PATH") + .help("Sets the input audio file path") + .required(true) + .index(2), + ) + .get_matches(); + + // Get the API key and file path from arguments + let api_key = matches.get_one::("API_KEY").unwrap(); + let file_path = matches.get_one::("FILE_PATH").unwrap(); + + // Create Chromaprint configuration + let config = Configuration::default(); + + // Calculate fingerprint + let (fingerprint, duration) = calc_fingerprint(file_path, &config)?; + + // Call the identify function + match identify(api_key, fingerprint, duration.as_secs().try_into()?).await { + Ok(response) => { + println!("Identification successful: {:?}", response); + } + Err(e) => { + eprintln!("Identification failed: {:?}", e); + } + } + + Ok(()) +} diff --git a/tag-editor/src/music_brainz/fingerprint.rs b/tag-editor/src/music_brainz/fingerprint.rs index ca4a9f8ee..0f6636745 100644 --- a/tag-editor/src/music_brainz/fingerprint.rs +++ b/tag-editor/src/music_brainz/fingerprint.rs @@ -1,8 +1,5 @@ -/** - * Source: https://github.com/darksv/rusty-chromaprint/blob/main/chromaprint/Cargo.toml - * Original License: MIT - */ use std::path::Path; +use std::time::Duration; use anyhow::Context; use symphonia::core::audio::SampleBuffer; @@ -18,7 +15,7 @@ use rusty_chromaprint::{Configuration, Fingerprinter}; pub fn calc_fingerprint( path: impl AsRef, config: &Configuration, -) -> anyhow::Result> { +) -> anyhow::Result<(Vec, Duration)> { let path = path.as_ref(); let src = std::fs::File::open(path).context("failed to open file")?; let mss = MediaSourceStream::new(Box::new(src), Default::default()); @@ -66,15 +63,17 @@ pub fn calc_fingerprint( .context("initializing fingerprinter")?; let mut sample_buf = None; + let mut total_frames = 0u64; while let Ok(packet) = format.next_packet() { - if packet.track_id() != track_id { continue; } match decoder.decode(&packet) { Ok(audio_buf) => { + total_frames += audio_buf.frames() as u64; + if sample_buf.is_none() { let spec = *audio_buf.spec(); let duration = audio_buf.capacity() as u64; @@ -92,5 +91,12 @@ pub fn calc_fingerprint( } printer.finish(); - Ok(printer.fingerprint().to_vec()) + + // Calculate the total duration in seconds. + // Calculate the total duration as a Duration object. + let total_duration_secs = total_frames / sample_rate as u64; + let total_duration_nanos = (total_frames % sample_rate as u64) * 1_000_000_000 / sample_rate as u64; + let total_duration = Duration::new(total_duration_secs, total_duration_nanos as u32); + + Ok((printer.fingerprint().to_vec(), total_duration)) }