use std::time::Duration;
use ffmpeg_next::codec::context::Context as CodecContext;
use ffmpeg_next::format::{Sample, sample::Type as SampleType};
use ffmpeg_next::frame::Audio as AudioFrame;
use ffmpeg_next::software::resampling::Context as ResamplingContext;
use ffmpeg_next::{ChannelLayout, Rational};
use crate::error::UnbundleError;
use crate::unbundle::MediaFile;
#[derive(Debug, Clone)]
pub struct WaveformOptions {
pub bins: usize,
pub start: Option<Duration>,
pub end: Option<Duration>,
}
impl Default for WaveformOptions {
fn default() -> Self {
Self {
bins: 800,
start: None,
end: None,
}
}
}
impl WaveformOptions {
pub fn new() -> Self {
Self::default()
}
pub fn bins(mut self, bins: usize) -> Self {
self.bins = bins;
self
}
pub fn with_bins(self, bins: usize) -> Self {
self.bins(bins)
}
pub fn start(mut self, start: Duration) -> Self {
self.start = Some(start);
self
}
pub fn with_start(self, start: Duration) -> Self {
self.start(start)
}
pub fn end(mut self, end: Duration) -> Self {
self.end = Some(end);
self
}
pub fn with_end(self, end: Duration) -> Self {
self.end(end)
}
}
#[derive(Debug, Clone, Copy)]
pub struct WaveformBin {
pub min: f32,
pub max: f32,
pub rms: f32,
}
#[derive(Debug, Clone)]
pub struct WaveformData {
pub bins: Vec<WaveformBin>,
pub duration: Duration,
pub sample_rate: u32,
pub total_samples: u64,
}
pub(crate) fn generate_waveform_impl(
unbundler: &mut MediaFile,
audio_stream_index: usize,
config: &WaveformOptions,
) -> Result<WaveformData, UnbundleError> {
log::debug!(
"Generating waveform (stream={}, bins={})",
audio_stream_index,
config.bins
);
let stream = unbundler
.input_context
.stream(audio_stream_index)
.ok_or(UnbundleError::NoAudioStream)?;
let time_base: Rational = stream.time_base();
let codec_parameters = stream.parameters();
let decoder_context = CodecContext::from_parameters(codec_parameters)?;
let mut decoder = decoder_context.decoder().audio().map_err(|e| {
UnbundleError::WaveformDecodeError(format!("Failed to create audio decoder: {e}"))
})?;
let sample_rate = decoder.rate();
let mut resampler = ResamplingContext::get(
decoder.format(),
decoder.channel_layout(),
sample_rate,
Sample::F32(SampleType::Packed),
ChannelLayout::MONO,
sample_rate,
)
.map_err(|e| UnbundleError::WaveformDecodeError(format!("Failed to create resampler: {e}")))?;
let start_pts: Option<i64> = config.start.map(|duration| {
(duration.as_secs_f64() * time_base.denominator() as f64
/ time_base.numerator().max(1) as f64) as i64
});
let end_pts: Option<i64> = config.end.map(|duration| {
(duration.as_secs_f64() * time_base.denominator() as f64
/ time_base.numerator().max(1) as f64) as i64
});
let mut all_samples: Vec<f32> = Vec::new();
let mut decoded_frame = AudioFrame::empty();
let mut resampled_frame = AudioFrame::empty();
for (stream, packet) in unbundler.input_context.packets() {
if stream.index() != audio_stream_index {
continue;
}
if let Some(end) = end_pts {
if let Some(packet_pts) = packet.pts() {
if packet_pts > end {
break;
}
}
}
if let Some(start) = start_pts {
if let Some(packet_pts) = packet.pts() {
if let Some(packet_end_pts) = packet.duration().checked_add(packet_pts as i64) {
if packet_end_pts < start {
continue;
}
}
}
}
decoder
.send_packet(&packet)
.map_err(|e| UnbundleError::WaveformDecodeError(format!("Audio decode error: {e}")))?;
while decoder.receive_frame(&mut decoded_frame).is_ok() {
let delay = resampler
.run(&decoded_frame, &mut resampled_frame)
.map_err(|e| UnbundleError::WaveformDecodeError(format!("Resample error: {e}")))?;
let data = resampled_frame.data(0);
let sample_count = resampled_frame.samples();
let float_samples: &[f32] =
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, sample_count) };
all_samples.extend_from_slice(float_samples);
if delay.is_some() {
let flush_frame = AudioFrame::empty();
if resampler.run(&flush_frame, &mut resampled_frame).is_ok() {
let data = resampled_frame.data(0);
let flush_sample_count = resampled_frame.samples();
let flush_samples: &[f32] = unsafe {
std::slice::from_raw_parts(data.as_ptr() as *const f32, flush_sample_count)
};
all_samples.extend_from_slice(flush_samples);
}
}
}
}
let total_samples = all_samples.len() as u64;
let duration = Duration::from_secs_f64(total_samples as f64 / sample_rate as f64);
let bin_count = config.bins.max(1);
let samples_per_bin = (all_samples.len() as f64 / bin_count as f64).ceil() as usize;
let mut bins = Vec::with_capacity(bin_count);
for chunk in all_samples.chunks(samples_per_bin.max(1)) {
let mut min_value = f32::INFINITY;
let mut max_value = f32::NEG_INFINITY;
let mut sum_squared = 0.0_f64;
for &sample in chunk {
if sample < min_value {
min_value = sample;
}
if sample > max_value {
max_value = sample;
}
sum_squared += (sample as f64) * (sample as f64);
}
let rms = (sum_squared / chunk.len() as f64).sqrt() as f32;
bins.push(WaveformBin {
min: min_value,
max: max_value,
rms,
});
}
while bins.len() < bin_count {
bins.push(WaveformBin {
min: 0.0,
max: 0.0,
rms: 0.0,
});
}
Ok(WaveformData {
bins,
duration,
sample_rate,
total_samples,
})
}