From aeee5122ad83c8e8f937d1f2072390b259fa7b55 Mon Sep 17 00:00:00 2001 From: Lucy Date: Thu, 26 Dec 2024 16:26:53 -0500 Subject: [PATCH] Reimplement `audio_length` based off of Kapu's more thorough implementation from https://github.com/tgstation/rust-g/pull/192 --- Cargo.lock | 80 +++++++++++++++++++++++++++-------------- crates/audio/Cargo.toml | 2 +- crates/audio/src/lib.rs | 77 ++++++++++++++++++++++++++------------- 3 files changed, 107 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a6cebd..4756f62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -951,12 +951,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "extended" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" - [[package]] name = "faster-hex" version = "0.9.0" @@ -2971,9 +2965,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -3271,14 +3265,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" dependencies = [ "lazy_static", + "symphonia-bundle-flac", "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", "symphonia-codec-vorbis", "symphonia-core", - "symphonia-format-ogg", - "symphonia-format-riff", "symphonia-metadata", ] +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + [[package]] name = "symphonia-bundle-mp3" version = "0.5.4" @@ -3292,51 +3301,68 @@ dependencies = [ ] [[package]] -name = "symphonia-codec-vorbis" +name = "symphonia-codec-aac" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70" dependencies = [ + "lazy_static", "log", "symphonia-core", - "symphonia-utils-xiph", ] [[package]] -name = "symphonia-core" +name = "symphonia-codec-adpcm" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f" dependencies = [ - "arrayvec", - "bitflags 1.3.2", - "bytemuck", - "lazy_static", "log", + "symphonia-core", ] [[package]] -name = "symphonia-format-ogg" +name = "symphonia-codec-alac" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada3505789516bcf00fc1157c67729eded428b455c27ca370e41f4d785bfa931" +checksum = "2d8a6666649a08412906476a8b0efd9b9733e241180189e9f92b09c08d0e38f3" dependencies = [ "log", "symphonia-core", - "symphonia-metadata", - "symphonia-utils-xiph", ] [[package]] -name = "symphonia-format-riff" +name = "symphonia-codec-pcm" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" dependencies = [ - "extended", "log", "symphonia-core", - "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", ] [[package]] diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index e2d1fd9..4f31eb5 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -10,4 +10,4 @@ publish.workspace = true [dependencies] meowtonin = { workspace = true } -symphonia = { version = "0.5", default-features = false, features = ["mp3", "vorbis", "ogg", "wav"] } +symphonia = { version = "0.5", default-features = false, features = ["all-codecs"] } diff --git a/crates/audio/src/lib.rs b/crates/audio/src/lib.rs index 1a97eea..37f2833 100644 --- a/crates/audio/src/lib.rs +++ b/crates/audio/src/lib.rs @@ -1,47 +1,76 @@ // SPDX-License-Identifier: MPL-2.0 - use meowtonin::byond_fn; use std::{ffi::OsStr, fs::File, path::PathBuf, time::Duration}; -use symphonia::core::{ - formats::FormatOptions, - io::{MediaSourceStream, MediaSourceStreamOptions}, - meta::MetadataOptions, - probe::Hint, +use symphonia::{ + core::{ + codecs::DecoderOptions, + formats::FormatOptions, + io::{MediaSourceStream, MediaSourceStreamOptions}, + meta::MetadataOptions, + probe::{Hint, ProbeResult}, + }, + default::{get_codecs, get_probe}, }; #[byond_fn] -pub fn audio_length(path: PathBuf) -> Option { +pub fn audio_length(path: PathBuf) -> f32 { // If the path has an extension, include it as a hint. let mut hint = Hint::new(); if let Some(extension) = path.extension().and_then(OsStr::to_str) { hint.with_extension(extension); } - // Open the file - let file = File::open(path).ok()?; + let file = File::open(&path) + .map(Box::new) + .expect("failed to open audio file"); - // Create a media source stream - let mss = MediaSourceStream::new(Box::new(file), MediaSourceStreamOptions::default()); + let mss = MediaSourceStream::new(file, MediaSourceStreamOptions::default()); - // Use default options for format and metadata let format_opts = FormatOptions::default(); let metadata_opts = MetadataOptions::default(); - // Probe the media source - let probed = symphonia::default::get_probe() + let probed = get_probe() .format(&hint, mss, &format_opts, &metadata_opts) - .ok()?; + .expect("failed to probe audio"); - // Get the default track - let track = probed.format.default_track()?; + sound_length_simple(&probed).unwrap_or_else(|| sound_length_decode(probed)) as f32 +} - // Calculate the duration +fn sound_length_simple(probed: &ProbeResult) -> Option { + let track = probed.format.default_track()?; let time_base = track.codec_params.time_base?; - let duration = track.codec_params.n_frames.map(|frames| { - let time = time_base.calc_time(frames); - Duration::from_secs(time.seconds) + Duration::from_secs_f64(time.frac) - })?; + let n_frames = track.codec_params.n_frames?; + + let time = time_base.calc_time(n_frames); + let duration = Duration::from_secs(time.seconds) + Duration::from_secs_f64(time.frac); + + Some(duration.as_secs_f64() * 10.0) +} + +fn sound_length_decode(probed: ProbeResult) -> f64 { + let mut format = probed.format; + + let track = format.default_track().expect("could not get default track"); + + // Grab the number of frames of the track + let samples_capacity = track.codec_params.n_frames.unwrap_or(0) as f64; + + // Create a decoder using the provided codec parameters in the track. + let decoder_opts = DecoderOptions::default(); + let mut decoder = get_codecs() + .make(&track.codec_params, &decoder_opts) + .expect("decoder creation error"); + + // Try to grab a data packet from the container + let encoded_packet = format.next_packet().expect("next_packet error"); + + // Try to decode the data packet + let decoded_packet = decoder + .decode(&encoded_packet) + .expect("failed to decode packet"); - // Convert to deciseconds - Some((duration.as_secs_f64() * 10.0).ceil() as u32) + // Grab the sample rate from the spec of the buffer. + let sample_rate = decoded_packet.spec().rate as f64; + // Math! + samples_capacity / sample_rate * 10.0 }