use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{
audio::{ChannelLayout, SampleRate},
codec::{Codec, CodecLevel, CodecProfile},
format::Format,
registry::Registry,
spatial::{FrameRate, Resolution},
};
use rskit_errors::{AppError, AppResult, ErrorCode};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Quality {
Lossless,
UltraHigh,
High,
Medium,
Low,
VeryLow,
Custom(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Bitrate {
Constant(u64),
Variable(u64),
Constrained {
target: u64,
max: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EncodingSpeed {
UltraFast,
SuperFast,
VeryFast,
Fast,
Medium,
Slow,
VerySlow,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoSettings {
pub codec: Codec,
pub resolution: Option<Resolution>,
pub frame_rate: Option<FrameRate>,
pub quality: Option<Quality>,
pub bitrate: Option<Bitrate>,
pub speed: Option<EncodingSpeed>,
pub profile: Option<CodecProfile>,
pub level: Option<CodecLevel>,
}
impl VideoSettings {
pub fn new(codec: Codec) -> Self {
Self {
codec,
resolution: None,
frame_rate: None,
quality: None,
bitrate: None,
speed: None,
profile: None,
level: None,
}
}
#[must_use]
pub fn with_resolution(mut self, res: Resolution) -> Self {
self.resolution = Some(res);
self
}
#[must_use]
pub fn with_frame_rate(mut self, fps: FrameRate) -> Self {
self.frame_rate = Some(fps);
self
}
#[must_use]
pub fn with_quality(mut self, q: Quality) -> Self {
self.quality = Some(q);
self
}
#[must_use]
pub fn with_bitrate(mut self, br: Bitrate) -> Self {
self.bitrate = Some(br);
self
}
#[must_use]
pub fn with_speed(mut self, speed: EncodingSpeed) -> Self {
self.speed = Some(speed);
self
}
#[must_use]
pub fn with_profile(mut self, profile: CodecProfile) -> Self {
self.profile = Some(profile);
self
}
#[must_use]
pub fn with_level(mut self, level: CodecLevel) -> Self {
self.level = Some(level);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioSettings {
pub codec: Codec,
pub sample_rate: Option<SampleRate>,
pub channels: Option<ChannelLayout>,
pub bitrate: Option<Bitrate>,
}
impl AudioSettings {
pub fn new(codec: Codec) -> Self {
Self {
codec,
sample_rate: None,
channels: None,
bitrate: None,
}
}
#[must_use]
pub fn with_sample_rate(mut self, sr: SampleRate) -> Self {
self.sample_rate = Some(sr);
self
}
#[must_use]
pub fn with_channels(mut self, ch: ChannelLayout) -> Self {
self.channels = Some(ch);
self
}
#[must_use]
pub fn with_bitrate(mut self, br: Bitrate) -> Self {
self.bitrate = Some(br);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
pub format: Format,
pub video: Option<VideoSettings>,
pub audio: Option<AudioSettings>,
pub streaming: Option<StreamingConfig>,
pub strip_metadata: bool,
pub extra: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StreamingConfig {
Hls(HlsConfig),
Dash(DashConfig),
Rtmp(RtmpConfig),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HlsConfig {
pub segment_duration: u32,
pub playlist_size: u32,
pub playlist_type: HlsPlaylistType,
pub segment_filename: Option<String>,
}
impl Default for HlsConfig {
fn default() -> Self {
Self {
segment_duration: 6,
playlist_size: 0,
playlist_type: HlsPlaylistType::Vod,
segment_filename: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HlsPlaylistType {
Vod,
Event,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DashConfig {
pub segment_duration: u32,
pub use_template: bool,
pub use_timeline: bool,
}
impl Default for DashConfig {
fn default() -> Self {
Self {
segment_duration: 4,
use_template: true,
use_timeline: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RtmpConfig {
pub url: String,
}
impl OutputConfig {
pub fn new(format: Format) -> Self {
Self {
format,
video: None,
audio: None,
streaming: None,
strip_metadata: false,
extra: HashMap::new(),
}
}
#[must_use]
pub fn with_video(mut self, video: VideoSettings) -> Self {
self.video = Some(video);
self
}
#[must_use]
pub fn with_audio(mut self, audio: AudioSettings) -> Self {
self.audio = Some(audio);
self
}
#[must_use]
pub fn with_streaming(mut self, streaming: StreamingConfig) -> Self {
self.streaming = Some(streaming);
self
}
#[must_use]
pub fn with_strip_metadata(mut self) -> Self {
self.strip_metadata = true;
self
}
#[must_use]
pub fn with_param(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
self.extra.insert(key.into(), val.into());
self
}
pub fn validate(&self, registry: &Registry) -> AppResult<()> {
if let Some(video) = &self.video
&& !registry.is_compatible(&video.codec, &self.format)
{
return Err(AppError::new(
ErrorCode::InvalidInput,
format!(
"video codec {} is not compatible with format {}",
video.codec, self.format,
),
));
}
if let Some(audio) = &self.audio
&& !registry.is_compatible(&audio.codec, &self.format)
{
return Err(AppError::new(
ErrorCode::InvalidInput,
format!(
"audio codec {} is not compatible with format {}",
audio.codec, self.format,
),
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
audio::{ChannelLayout, SampleRate},
codec::{self, Codec, CodecLevel, CodecProfile},
format,
spatial::{FrameRate, Resolution},
};
use super::*;
#[test]
fn video_audio_streaming_and_output_builders_set_all_fields() {
let video = VideoSettings::new(Codec::new(codec::video::H264))
.with_resolution(Resolution::p720())
.with_frame_rate(FrameRate::fps(30))
.with_quality(Quality::High)
.with_bitrate(Bitrate::Constrained {
target: 1_000,
max: 2_000,
})
.with_speed(EncodingSpeed::Fast)
.with_profile(CodecProfile::H264High)
.with_level(CodecLevel::new("4.1"));
let audio = AudioSettings::new(Codec::new(codec::audio::AAC))
.with_sample_rate(SampleRate::dvd())
.with_channels(ChannelLayout::Stereo)
.with_bitrate(Bitrate::Variable(128_000));
let output = OutputConfig::new(Format::new(format::MP4))
.with_video(video)
.with_audio(audio)
.with_streaming(StreamingConfig::Hls(HlsConfig::default()))
.with_strip_metadata()
.with_param("movflags", "faststart");
assert!(output.video.as_ref().unwrap().profile.is_some());
assert!(output.video.as_ref().unwrap().level.is_some());
assert!(output.audio.as_ref().unwrap().sample_rate.is_some());
assert!(matches!(output.streaming, Some(StreamingConfig::Hls(_))));
assert!(output.strip_metadata);
assert_eq!(
output.extra.get("movflags").map(String::as_str),
Some("faststart")
);
}
#[test]
fn streaming_defaults_are_stable() {
let hls = HlsConfig::default();
assert_eq!(hls.segment_duration, 6);
assert_eq!(hls.playlist_size, 0);
assert_eq!(hls.playlist_type, HlsPlaylistType::Vod);
assert!(hls.segment_filename.is_none());
let dash = DashConfig::default();
assert_eq!(dash.segment_duration, 4);
assert!(dash.use_template);
assert!(dash.use_timeline);
}
}