diff --git a/Cargo.lock b/Cargo.lock index 6bee7ff..1beb0ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,9 +560,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -689,6 +689,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mp3-duration" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348bdc7300502f0801e5b57c448815713cd843b744ef9bda252a2698fdf90a0f" +dependencies = [ + "thiserror", +] + [[package]] name = "ndk" version = "0.7.0" @@ -1145,6 +1154,7 @@ dependencies = [ "log", "minijinja", "minimp3", + "mp3-duration", "regex", "rodio", "serde", diff --git a/Cargo.toml b/Cargo.toml index b0d0f36..8e0d822 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,10 @@ axum = "0.6.18" axum-template = { version = "0.19.0", features = ["minijinja"] } cpal = "0.15.2" log = "0.4.20" +# metadata = "0.1.8" minijinja = { version = "1.0.3", features = ["loader"] } minimp3 = "0.5.1" +mp3-duration = "0.1.10" regex = "1.9.0" rodio = "0.17.1" serde = { version = "1.0.171", features = ["derive"] } diff --git a/src/soundclips.rs b/src/soundclips.rs index 91dfbd3..4023caa 100644 --- a/src/soundclips.rs +++ b/src/soundclips.rs @@ -6,9 +6,11 @@ use std::fs; #[derive(Debug, Clone, Serialize)] pub struct SoundClip { + title: String, hash: String, file_name: String, directory: String, + length: Option, } impl fmt::Display for SoundClip { @@ -23,8 +25,11 @@ impl fmt::Display for SoundClip { impl SoundClip { pub fn new(file_name: String, directory: String) -> Self { + let full_file_name = format!("{}/{}", directory, file_name); Self { + title: normalize_filename(&file_name), hash: encode_filename(&file_name), + length: get_sound_clip_length(&full_file_name), file_name: file_name, directory: directory, } @@ -49,6 +54,44 @@ fn encode_filename(file_name: &str) -> String { hash_hex } +/* // needs metadata to install ffmpeg. +fn get_sound_clip_length(file_path: &str) -> Option { + if let Ok(meta) = metadata::media_file::MediaFileMetadata::new(&std::path::Path::new(file_path)) + { + return Some(meta._duration); + } + return None; +} +*/ + +fn get_sound_clip_length(file_path: &str) -> Option { + let path = std::path::Path::new(file_path); + if let Ok(duration) = mp3_duration::from_path(&path) { + if let Ok(millis) = duration.as_millis().try_into() { + return Some(millis); + } + return Some(duration.as_secs() * 1000 + 1); + } + return None; +} + +fn normalize_filename(input: &str) -> String { + // List of extensions to be removed + let extensions = [".mp3", ".wav", ".flac", ".ogg"]; + + // Remove the extension if it matches any in the list + let without_extension = extensions.iter().fold(input.to_string(), |acc, &ext| { + if acc.ends_with(ext) { + acc.trim_end_matches(ext).to_string() + } else { + acc + } + }); + + // Replace all underscores with spaces + without_extension.replace("_", " ") +} + pub fn scan_directory_for_clips(directory: &str, extensions: &[&str]) -> Option> { let mut sound_clips = Vec::new(); diff --git a/templates/index.jinja b/templates/index.jinja index 235fde5..0419c6f 100644 --- a/templates/index.jinja +++ b/templates/index.jinja @@ -47,12 +47,42 @@ } } - function play(hash) { + var disableTimeout; + var none = null; + + function play(hash, timeout) { + + if (timeout && timeout != "none") { + // Add the "disabled" class to all clip buttons + $('.clip').addClass('disabled'); + + // Add "primary" and "loading" classes to the clicked button + var clickedButton = $('#clip-' + hash); + clickedButton.addClass('double loading positive'); + + // Clear any existing timeout to avoid conflicts + clearTimeout(disableTimeout); + + // Set a new timeout + disableTimeout = setTimeout(function () { + $('.clip').removeClass('disabled'); + clickedButton.removeClass('double loading positive'); + }, timeout); + } + fetch("/play/" + hash); } function stop() { fetch("/stop"); + + // Clear the timeout + clearTimeout(disableTimeout); + + // Remove the "disabled" class from all clip buttons + // and the "primary" and "loading" classes from any button that might have them + $('.clip').removeClass('disabled'); + $('.clip').removeClass('double loading positive'); }