use crate::api::{AudioCodec, VideoCodec};
use crate::codec::av1::is_av1_keyframe;
use crate::codec::h264::is_h264_keyframe;
use crate::codec::h265::is_hevc_keyframe;
use crate::codec::vp9::is_vp9_keyframe;
use crate::codec::opus::is_valid_opus_packet;
#[derive(Debug, Clone, PartialEq)]
pub struct ValidationResult {
pub is_valid: bool,
pub messages: Vec<String>,
pub errors: Vec<String>,
}
impl ValidationResult {
pub fn valid() -> Self {
Self {
is_valid: true,
messages: Vec::new(),
errors: Vec::new(),
}
}
pub fn invalid(errors: Vec<String>) -> Self {
Self {
is_valid: false,
messages: Vec::new(),
errors,
}
}
pub fn with_message(mut self, message: String) -> Self {
self.messages.push(message);
self
}
pub fn with_error(mut self, error: String) -> Self {
self.errors.push(error);
self.is_valid = false;
self
}
}
pub fn validate_video_config(
codec: VideoCodec,
width: u32,
height: u32,
framerate: f64,
) -> ValidationResult {
let mut result = ValidationResult::valid();
match codec {
VideoCodec::H264 | VideoCodec::H265 | VideoCodec::Av1 | VideoCodec::Vp9 => {
result = result.with_message(format!("✓ Video codec {} is supported", codec));
}
}
if width == 0 || height == 0 {
result = result.with_error("Video width and height must be positive".to_string());
} else if width > 4096 || height > 2160 {
result = result.with_error(format!(
"Video dimensions {}x{} exceed maximum supported size (4096x2160)",
width, height
));
} else if width < 320 || height < 240 {
result = result.with_error(format!(
"Video dimensions {}x{} below minimum supported size (320x240)",
width, height
));
} else {
result = result.with_message(format!("✓ Video dimensions {}x{} are valid", width, height));
}
if framerate <= 0.0 {
result = result.with_error("Video framerate must be positive".to_string());
} else if framerate > 120.0 {
result = result.with_error(format!(
"Video framerate {:.1} exceeds maximum supported rate (120 fps)",
framerate
));
} else {
result = result.with_message(format!("✓ Video framerate {:.1} fps is valid", framerate));
}
result
}
pub fn validate_audio_config(
codec: AudioCodec,
sample_rate: u32,
channels: u8,
) -> ValidationResult {
let mut result = ValidationResult::valid();
match codec {
AudioCodec::Aac(_) | AudioCodec::Opus => {
result = result.with_message(format!("✓ Audio codec {} is supported", codec));
}
AudioCodec::None => {
result = result.with_message("✓ No audio configured".to_string());
return result;
}
}
if sample_rate == 0 {
result = result.with_error("Audio sample rate must be positive".to_string());
} else if sample_rate > 192000 {
result = result.with_error(format!(
"Audio sample rate {} exceeds maximum supported rate (192000 Hz)",
sample_rate
));
} else {
result = result.with_message(format!("✓ Audio sample rate {} Hz is valid", sample_rate));
}
if channels == 0 {
result = result.with_error("Audio channels must be positive".to_string());
} else if channels > 8 {
result = result.with_error(format!(
"Audio channels {} exceeds maximum supported count (8)",
channels
));
} else {
result = result.with_message(format!("✓ Audio channels {} are valid", channels));
}
result
}
pub fn validate_video_frame(codec: VideoCodec, frame_data: &[u8], is_keyframe: bool) -> ValidationResult {
let mut result = ValidationResult::valid();
if frame_data.is_empty() {
return result.with_error("Video frame data cannot be empty".to_string());
}
let detected_keyframe = match codec {
VideoCodec::H264 => is_h264_keyframe(frame_data),
VideoCodec::H265 => is_hevc_keyframe(frame_data),
VideoCodec::Av1 => is_av1_keyframe(frame_data),
VideoCodec::Vp9 => is_vp9_keyframe(frame_data).unwrap_or(false),
};
if is_keyframe && !detected_keyframe {
result = result.with_error(format!(
"Frame marked as keyframe but {} codec detection indicates it's not a keyframe",
codec
));
} else if !is_keyframe && detected_keyframe {
result = result.with_message(format!(
"⚠ Frame not marked as keyframe but {} codec detection indicates it is a keyframe",
codec
));
} else {
result = result.with_message(format!(
"✓ Frame keyframe flag matches {} codec detection",
codec
));
}
result
}
pub fn validate_audio_frame(codec: AudioCodec, frame_data: &[u8]) -> ValidationResult {
let mut result = ValidationResult::valid();
if frame_data.is_empty() {
return result.with_error("Audio frame data cannot be empty".to_string());
}
match codec {
AudioCodec::Aac(_) => {
if frame_data.len() < 7 {
result = result.with_error("AAC frame too short for ADTS header".to_string());
} else if frame_data[0] != 0xFF || (frame_data[1] & 0xF0) != 0xF0 {
result = result.with_error("Invalid AAC ADTS syncword".to_string());
} else {
result = result.with_message("✓ AAC frame has valid ADTS header".to_string());
}
}
AudioCodec::Opus => {
if !is_valid_opus_packet(frame_data) {
result = result.with_error("Invalid Opus packet structure".to_string());
} else {
result = result.with_message("✓ Opus packet has valid structure".to_string());
}
}
AudioCodec::None => {
result = result.with_error("Cannot validate audio frame for None codec".to_string());
}
}
result
}
pub fn validate_muxing_config(
video_codec: Option<VideoCodec>,
width: Option<u32>,
height: Option<u32>,
framerate: Option<f64>,
audio_codec: Option<AudioCodec>,
sample_rate: Option<u32>,
channels: Option<u8>,
sample_video_frame: Option<(&[u8], bool)>,
sample_audio_frame: Option<&[u8]>,
) -> ValidationResult {
let mut result = ValidationResult::valid();
if let (Some(vc), Some(w), Some(h), Some(fps)) = (video_codec, width, height, framerate) {
let video_result = validate_video_config(vc, w, h, fps);
result.is_valid &= video_result.is_valid;
result.messages.extend(video_result.messages);
result.errors.extend(video_result.errors);
} else if video_codec.is_some() {
result = result.with_error("Video codec specified but missing width, height, or framerate".to_string());
}
if let (Some(ac), Some(sr), Some(ch)) = (audio_codec, sample_rate, channels) {
let audio_result = validate_audio_config(ac, sr, ch);
result.is_valid &= audio_result.is_valid;
result.messages.extend(audio_result.messages);
result.errors.extend(audio_result.errors);
} else if audio_codec.is_some() && audio_codec != Some(AudioCodec::None) {
result = result.with_error("Audio codec specified but missing sample rate or channels".to_string());
}
if video_codec.is_none() && (audio_codec.is_none() || audio_codec == Some(AudioCodec::None)) {
result = result.with_error("At least one of video or audio must be configured".to_string());
}
if let (Some(vc), Some((frame_data, is_keyframe))) = (video_codec, sample_video_frame) {
let frame_result = validate_video_frame(vc, frame_data, is_keyframe);
result.is_valid &= frame_result.is_valid;
result.messages.extend(frame_result.messages);
result.errors.extend(frame_result.errors);
}
if let (Some(ac), Some(frame_data)) = (audio_codec, sample_audio_frame) {
let frame_result = validate_audio_frame(ac, frame_data);
result.is_valid &= frame_result.is_valid;
result.messages.extend(frame_result.messages);
result.errors.extend(frame_result.errors);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::{AacProfile, AudioCodec, VideoCodec};
#[test]
fn test_validate_video_config_valid() {
let result = validate_video_config(VideoCodec::H264, 1920, 1080, 30.0);
assert!(result.is_valid);
assert!(result.errors.is_empty());
assert!(result.messages.len() >= 3); }
#[test]
fn test_validate_video_config_invalid_dimensions() {
let result = validate_video_config(VideoCodec::H264, 0, 1080, 30.0);
assert!(!result.is_valid);
assert!(result.errors.len() > 0);
}
#[test]
fn test_validate_audio_config_valid() {
let result = validate_audio_config(AudioCodec::Aac(AacProfile::Lc), 44100, 2);
assert!(result.is_valid);
assert!(result.errors.is_empty());
}
#[test]
fn test_validate_muxing_config_minimal() {
let result = validate_muxing_config(
Some(VideoCodec::H264),
Some(1920),
Some(1080),
Some(30.0),
None,
None,
None,
None,
None,
);
assert!(result.is_valid);
}
}