use crate::{Result, TranscodeError};
use oximedia_codec::traits::VideoEncoder;
#[cfg(feature = "mjpeg")]
use oximedia_codec::CodecError;
use oximedia_core::CodecId;
#[derive(Debug, Clone)]
pub struct VideoEncoderParams {
pub width: u32,
pub height: u32,
pub quality: u8,
}
impl VideoEncoderParams {
pub fn new(width: u32, height: u32, quality: u8) -> Result<Self> {
if width == 0 || height == 0 {
return Err(TranscodeError::InvalidInput(
"width and height must be non-zero".into(),
));
}
Ok(Self {
width,
height,
quality,
})
}
}
pub fn make_video_encoder(
codec_id: CodecId,
params: &VideoEncoderParams,
) -> Result<Box<dyn VideoEncoder>> {
match codec_id {
CodecId::Mjpeg => make_mjpeg_encoder(params),
CodecId::Apv => make_apv_encoder(params),
other => Err(TranscodeError::Unsupported(format!(
"codec {other:?} is not handled by codec_dispatch; \
use the stream-copy pipeline for this codec"
))),
}
}
#[cfg(feature = "mjpeg")]
fn make_mjpeg_encoder(params: &VideoEncoderParams) -> Result<Box<dyn VideoEncoder>> {
use oximedia_codec::{MjpegConfig, MjpegEncoder};
let config = MjpegConfig::new(params.width, params.height)
.map_err(|e| TranscodeError::CodecError(e.to_string()))?
.with_quality(params.quality);
let encoder = MjpegEncoder::new(config)
.map_err(|e: CodecError| TranscodeError::CodecError(e.to_string()))?;
Ok(Box::new(encoder))
}
#[cfg(not(feature = "mjpeg"))]
fn make_mjpeg_encoder(_params: &VideoEncoderParams) -> Result<Box<dyn VideoEncoder>> {
Err(TranscodeError::Unsupported(
"MJPEG support requires the `mjpeg` feature of oximedia-codec".into(),
))
}
#[cfg(feature = "apv")]
fn make_apv_encoder(params: &VideoEncoderParams) -> Result<Box<dyn VideoEncoder>> {
use oximedia_codec::{ApvConfig, ApvEncoder};
let config = ApvConfig::new(params.width, params.height)
.map_err(|e| TranscodeError::CodecError(e.to_string()))?
.with_qp(params.quality);
let encoder = ApvEncoder::new(config).map_err(|e| TranscodeError::CodecError(e.to_string()))?;
Ok(Box::new(encoder))
}
#[cfg(not(feature = "apv"))]
fn make_apv_encoder(_params: &VideoEncoderParams) -> Result<Box<dyn VideoEncoder>> {
Err(TranscodeError::Unsupported(
"APV support requires the `apv` feature of oximedia-codec".into(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_params_new_valid() {
let p = VideoEncoderParams::new(1920, 1080, 85);
assert!(p.is_ok());
let p = p.expect("valid params");
assert_eq!(p.width, 1920);
assert_eq!(p.height, 1080);
assert_eq!(p.quality, 85);
}
#[test]
fn test_params_zero_width() {
assert!(VideoEncoderParams::new(0, 1080, 85).is_err());
}
#[test]
fn test_params_zero_height() {
assert!(VideoEncoderParams::new(1920, 0, 85).is_err());
}
#[test]
fn test_unsupported_codec() {
let p = VideoEncoderParams::new(320, 240, 30).expect("valid");
let result = make_video_encoder(CodecId::Vp9, &p);
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, TranscodeError::Unsupported(_)));
}
}
#[cfg(feature = "mjpeg")]
#[test]
fn test_make_mjpeg_encoder() {
let p = VideoEncoderParams::new(320, 240, 85).expect("valid");
let enc = make_video_encoder(CodecId::Mjpeg, &p);
assert!(enc.is_ok(), "MJPEG encoder should build");
let enc = enc.expect("ok");
assert_eq!(enc.codec(), CodecId::Mjpeg);
}
#[cfg(feature = "apv")]
#[test]
fn test_make_apv_encoder() {
let p = VideoEncoderParams::new(320, 240, 22).expect("valid");
let enc = make_video_encoder(CodecId::Apv, &p);
assert!(enc.is_ok(), "APV encoder should build");
let enc = enc.expect("ok");
assert_eq!(enc.codec(), CodecId::Apv);
}
#[cfg(not(feature = "mjpeg"))]
#[test]
fn test_mjpeg_disabled() {
let p = VideoEncoderParams::new(320, 240, 85).expect("valid");
let result = make_video_encoder(CodecId::Mjpeg, &p);
assert!(matches!(result, Err(TranscodeError::Unsupported(_))));
}
#[cfg(not(feature = "apv"))]
#[test]
fn test_apv_disabled() {
let p = VideoEncoderParams::new(320, 240, 22).expect("valid");
let result = make_video_encoder(CodecId::Apv, &p);
assert!(matches!(result, Err(TranscodeError::Unsupported(_))));
}
}