use ffprobe::{FfProbe, Stream};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum BitrateMode {
Cbr,
Vbr,
Abr,
Lossless,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CompressionMode {
Lossy,
Lossless,
}
#[derive(Debug, Clone)]
pub struct AudioStreamMetadata {
pub codec: Option<String>,
pub channels: Option<u32>,
pub channel_layout: Option<String>,
pub sample_rate: Option<u32>,
pub bitrate: Option<u64>,
}
pub fn extract_format_metadata(
probe_result: &FfProbe,
) -> (Option<String>, Option<f64>, Option<u64>) {
let format = &probe_result.format;
let container_format = Some(format.format_name.clone());
let duration_seconds = format.duration.as_ref().and_then(|d| d.parse::<f64>().ok());
let bitrate = format.bit_rate.as_ref().and_then(|b| b.parse::<u64>().ok());
(container_format, duration_seconds, bitrate)
}
pub fn find_stream_by_type<'a>(probe_result: &'a FfProbe, codec_type: &str) -> Option<&'a Stream> {
probe_result
.streams
.iter()
.find(|s| s.codec_type.as_deref() == Some(codec_type))
}
pub fn extract_audio_stream_metadata(audio_stream: Option<&Stream>) -> AudioStreamMetadata {
let codec = audio_stream.and_then(|s| s.codec_name.clone());
let channels = audio_stream
.and_then(|s| s.channels)
.and_then(|c| u32::try_from(c).ok());
let channel_layout = audio_stream.and_then(|s| s.channel_layout.clone());
let sample_rate = audio_stream
.and_then(|s| s.sample_rate.as_ref())
.and_then(|sr| sr.parse::<u32>().ok());
let bitrate = audio_stream
.and_then(|s| s.bit_rate.as_ref())
.and_then(|b| b.parse::<u64>().ok());
AudioStreamMetadata {
codec,
channels,
channel_layout,
sample_rate,
bitrate,
}
}
pub fn calculate_stream_size(bitrate: Option<u64>, duration_seconds: Option<f64>) -> Option<u64> {
bitrate.zip(duration_seconds).map(|(br, dur)| {
let bytes = br as f64 * dur / 8.0;
if !bytes.is_finite() || bytes <= 0.0 {
0u64
} else {
let cap = u64::MAX as f64;
if bytes >= cap {
u64::MAX
} else {
#[allow(clippy::cast_possible_truncation)]
{
bytes as u64
}
}
}
})
}
pub fn extract_encoded_library(stream: Option<&Stream>, probe_result: &FfProbe) -> Option<String> {
stream
.and_then(|s| s.tags.as_ref())
.and_then(|tags| tags.encoder.clone())
.or_else(|| {
probe_result
.format
.tags
.as_ref()
.and_then(|tags| tags.encoder.clone())
})
}
pub fn extract_language(stream: Option<&Stream>) -> Option<String> {
stream
.and_then(|s| s.tags.as_ref())
.and_then(|tags| tags.language.clone())
}
pub fn extract_creation_time(stream: Option<&Stream>, probe_result: &FfProbe) -> Option<String> {
stream
.and_then(|s| s.tags.as_ref())
.and_then(|tags| tags.creation_time.clone())
.or_else(|| {
probe_result
.format
.tags
.as_ref()
.and_then(|tags| tags.creation_time.clone())
})
}
pub fn extract_codec_profile(stream: Option<&Stream>) -> Option<String> {
stream.and_then(|s| s.profile.clone())
}
pub fn extract_stream_bitrate(stream: Option<&Stream>) -> Option<u64> {
stream
.and_then(|s| s.bit_rate.as_ref())
.and_then(|b| b.parse::<u64>().ok())
}
pub fn extract_stream_bit_depth(stream: Option<&Stream>) -> Option<u32> {
stream
.and_then(|s| s.bits_per_sample)
.and_then(|b| u32::try_from(b).ok())
}
pub fn extract_bitrate_mode(
codec: Option<&String>,
encoder: Option<&String>,
_file_path: Option<&str>, ) -> Option<BitrateMode> {
extract_bitrate_mode_from_metadata(codec, encoder)
}
pub(crate) fn extract_bitrate_mode_from_metadata(
codec: Option<&String>,
encoder: Option<&String>,
) -> Option<BitrateMode> {
codec
.and_then(|codec_str| {
use crate::parsers::media::audio;
if audio::is_lossless_codec(codec_str) {
Some(BitrateMode::Lossless)
} else {
None
}
})
.or_else(|| {
encoder.and_then(|enc| {
let enc_lower = enc.to_lowercase();
if enc_lower.contains("cbr") {
Some(BitrateMode::Cbr)
} else if enc_lower.contains("abr") {
Some(BitrateMode::Abr)
} else if enc_lower.contains("vbr") {
Some(BitrateMode::Vbr)
} else {
use crate::parsers::media::audio;
if audio::is_opus_codec(enc) || codec.is_some_and(|c| audio::is_opus_codec(c)) {
Some(BitrateMode::Vbr)
} else {
None
}
}
})
})
.or_else(|| {
codec.and_then(|codec_str| {
use crate::parsers::media::audio;
if audio::is_opus_codec(codec_str) {
Some(BitrateMode::Vbr)
} else {
None
}
})
})
}