1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 audio::{ChannelLayout, SampleRate},
9 codec::{Codec, CodecLevel, CodecProfile},
10 format::Format,
11 registry::Registry,
12 spatial::{FrameRate, Resolution},
13};
14use rskit_errors::{AppError, AppResult, ErrorCode};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum Quality {
19 Lossless,
21 UltraHigh,
23 High,
25 Medium,
27 Low,
29 VeryLow,
31 Custom(u8),
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum Bitrate {
38 Constant(u64),
40 Variable(u64),
42 Constrained {
44 target: u64,
46 max: u64,
48 },
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub enum EncodingSpeed {
54 UltraFast,
56 SuperFast,
58 VeryFast,
60 Fast,
62 Medium,
64 Slow,
66 VerySlow,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct VideoSettings {
73 pub codec: Codec,
75 pub resolution: Option<Resolution>,
77 pub frame_rate: Option<FrameRate>,
79 pub quality: Option<Quality>,
81 pub bitrate: Option<Bitrate>,
83 pub speed: Option<EncodingSpeed>,
85 pub profile: Option<CodecProfile>,
87 pub level: Option<CodecLevel>,
89}
90
91impl VideoSettings {
92 pub fn new(codec: Codec) -> Self {
94 Self {
95 codec,
96 resolution: None,
97 frame_rate: None,
98 quality: None,
99 bitrate: None,
100 speed: None,
101 profile: None,
102 level: None,
103 }
104 }
105
106 #[must_use]
108 pub fn with_resolution(mut self, res: Resolution) -> Self {
109 self.resolution = Some(res);
110 self
111 }
112
113 #[must_use]
115 pub fn with_frame_rate(mut self, fps: FrameRate) -> Self {
116 self.frame_rate = Some(fps);
117 self
118 }
119
120 #[must_use]
122 pub fn with_quality(mut self, q: Quality) -> Self {
123 self.quality = Some(q);
124 self
125 }
126
127 #[must_use]
129 pub fn with_bitrate(mut self, br: Bitrate) -> Self {
130 self.bitrate = Some(br);
131 self
132 }
133
134 #[must_use]
136 pub fn with_speed(mut self, speed: EncodingSpeed) -> Self {
137 self.speed = Some(speed);
138 self
139 }
140
141 #[must_use]
143 pub fn with_profile(mut self, profile: CodecProfile) -> Self {
144 self.profile = Some(profile);
145 self
146 }
147
148 #[must_use]
150 pub fn with_level(mut self, level: CodecLevel) -> Self {
151 self.level = Some(level);
152 self
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AudioSettings {
159 pub codec: Codec,
161 pub sample_rate: Option<SampleRate>,
163 pub channels: Option<ChannelLayout>,
165 pub bitrate: Option<Bitrate>,
167}
168
169impl AudioSettings {
170 pub fn new(codec: Codec) -> Self {
172 Self {
173 codec,
174 sample_rate: None,
175 channels: None,
176 bitrate: None,
177 }
178 }
179
180 #[must_use]
182 pub fn with_sample_rate(mut self, sr: SampleRate) -> Self {
183 self.sample_rate = Some(sr);
184 self
185 }
186
187 #[must_use]
189 pub fn with_channels(mut self, ch: ChannelLayout) -> Self {
190 self.channels = Some(ch);
191 self
192 }
193
194 #[must_use]
196 pub fn with_bitrate(mut self, br: Bitrate) -> Self {
197 self.bitrate = Some(br);
198 self
199 }
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct OutputConfig {
205 pub format: Format,
207 pub video: Option<VideoSettings>,
209 pub audio: Option<AudioSettings>,
211 pub streaming: Option<StreamingConfig>,
213 pub strip_metadata: bool,
215 pub extra: HashMap<String, String>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub enum StreamingConfig {
222 Hls(HlsConfig),
224 Dash(DashConfig),
226 Rtmp(RtmpConfig),
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct HlsConfig {
233 pub segment_duration: u32,
235 pub playlist_size: u32,
237 pub playlist_type: HlsPlaylistType,
239 pub segment_filename: Option<String>,
241}
242
243impl Default for HlsConfig {
244 fn default() -> Self {
245 Self {
246 segment_duration: 6,
247 playlist_size: 0,
248 playlist_type: HlsPlaylistType::Vod,
249 segment_filename: None,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256pub enum HlsPlaylistType {
257 Vod,
259 Event,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct DashConfig {
266 pub segment_duration: u32,
268 pub use_template: bool,
270 pub use_timeline: bool,
272}
273
274impl Default for DashConfig {
275 fn default() -> Self {
276 Self {
277 segment_duration: 4,
278 use_template: true,
279 use_timeline: true,
280 }
281 }
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct RtmpConfig {
287 pub url: String,
289}
290
291impl OutputConfig {
292 pub fn new(format: Format) -> Self {
294 Self {
295 format,
296 video: None,
297 audio: None,
298 streaming: None,
299 strip_metadata: false,
300 extra: HashMap::new(),
301 }
302 }
303
304 #[must_use]
306 pub fn with_video(mut self, video: VideoSettings) -> Self {
307 self.video = Some(video);
308 self
309 }
310
311 #[must_use]
313 pub fn with_audio(mut self, audio: AudioSettings) -> Self {
314 self.audio = Some(audio);
315 self
316 }
317
318 #[must_use]
320 pub fn with_streaming(mut self, streaming: StreamingConfig) -> Self {
321 self.streaming = Some(streaming);
322 self
323 }
324
325 #[must_use]
327 pub fn with_strip_metadata(mut self) -> Self {
328 self.strip_metadata = true;
329 self
330 }
331
332 #[must_use]
334 pub fn with_param(mut self, key: impl Into<String>, val: impl Into<String>) -> Self {
335 self.extra.insert(key.into(), val.into());
336 self
337 }
338
339 pub fn validate(&self, registry: &Registry) -> AppResult<()> {
341 if let Some(video) = &self.video
342 && !registry.is_compatible(&video.codec, &self.format)
343 {
344 return Err(AppError::new(
345 ErrorCode::InvalidInput,
346 format!(
347 "video codec {} is not compatible with format {}",
348 video.codec, self.format,
349 ),
350 ));
351 }
352 if let Some(audio) = &self.audio
353 && !registry.is_compatible(&audio.codec, &self.format)
354 {
355 return Err(AppError::new(
356 ErrorCode::InvalidInput,
357 format!(
358 "audio codec {} is not compatible with format {}",
359 audio.codec, self.format,
360 ),
361 ));
362 }
363 Ok(())
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use crate::{
370 audio::{ChannelLayout, SampleRate},
371 codec::{self, Codec, CodecLevel, CodecProfile},
372 format,
373 spatial::{FrameRate, Resolution},
374 };
375
376 use super::*;
377
378 #[test]
379 fn video_audio_streaming_and_output_builders_set_all_fields() {
380 let video = VideoSettings::new(Codec::new(codec::video::H264))
381 .with_resolution(Resolution::p720())
382 .with_frame_rate(FrameRate::fps(30))
383 .with_quality(Quality::High)
384 .with_bitrate(Bitrate::Constrained {
385 target: 1_000,
386 max: 2_000,
387 })
388 .with_speed(EncodingSpeed::Fast)
389 .with_profile(CodecProfile::H264High)
390 .with_level(CodecLevel::new("4.1"));
391 let audio = AudioSettings::new(Codec::new(codec::audio::AAC))
392 .with_sample_rate(SampleRate::dvd())
393 .with_channels(ChannelLayout::Stereo)
394 .with_bitrate(Bitrate::Variable(128_000));
395 let output = OutputConfig::new(Format::new(format::MP4))
396 .with_video(video)
397 .with_audio(audio)
398 .with_streaming(StreamingConfig::Hls(HlsConfig::default()))
399 .with_strip_metadata()
400 .with_param("movflags", "faststart");
401
402 assert!(output.video.as_ref().unwrap().profile.is_some());
403 assert!(output.video.as_ref().unwrap().level.is_some());
404 assert!(output.audio.as_ref().unwrap().sample_rate.is_some());
405 assert!(matches!(output.streaming, Some(StreamingConfig::Hls(_))));
406 assert!(output.strip_metadata);
407 assert_eq!(
408 output.extra.get("movflags").map(String::as_str),
409 Some("faststart")
410 );
411 }
412
413 #[test]
414 fn streaming_defaults_are_stable() {
415 let hls = HlsConfig::default();
416 assert_eq!(hls.segment_duration, 6);
417 assert_eq!(hls.playlist_size, 0);
418 assert_eq!(hls.playlist_type, HlsPlaylistType::Vod);
419 assert!(hls.segment_filename.is_none());
420
421 let dash = DashConfig::default();
422 assert_eq!(dash.segment_duration, 4);
423 assert!(dash.use_template);
424 assert!(dash.use_timeline);
425 }
426}