Skip to main content

rskit_media/encoding/
output.rs

1//! Output configuration and encoding settings.
2
3use 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/// Encoding quality preset.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum Quality {
19    /// Lossless encoding.
20    Lossless,
21    /// Ultra-high quality.
22    UltraHigh,
23    /// High quality.
24    High,
25    /// Medium quality (default).
26    Medium,
27    /// Low quality.
28    Low,
29    /// Very low quality.
30    VeryLow,
31    /// Custom CRF/quality value (0–51 for x264).
32    Custom(u8),
33}
34
35/// Bitrate specification.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum Bitrate {
38    /// Constant bitrate (bits/sec).
39    Constant(u64),
40    /// Variable bitrate target (bits/sec).
41    Variable(u64),
42    /// Constrained variable bitrate.
43    Constrained {
44        /// Target bitrate.
45        target: u64,
46        /// Maximum bitrate.
47        max: u64,
48    },
49}
50
51/// Encoding speed/effort tradeoff.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub enum EncodingSpeed {
54    /// Fastest encoding, lowest quality.
55    UltraFast,
56    /// Very fast encoding.
57    SuperFast,
58    /// Fast encoding.
59    VeryFast,
60    /// Faster than medium.
61    Fast,
62    /// Balanced speed/quality.
63    Medium,
64    /// Slower encoding, better quality.
65    Slow,
66    /// Slowest encoding, best quality.
67    VerySlow,
68}
69
70/// Video-specific encoding settings.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct VideoSettings {
73    /// Video codec.
74    pub codec: Codec,
75    /// Output resolution.
76    pub resolution: Option<Resolution>,
77    /// Output frame rate.
78    pub frame_rate: Option<FrameRate>,
79    /// Quality preset.
80    pub quality: Option<Quality>,
81    /// Bitrate setting.
82    pub bitrate: Option<Bitrate>,
83    /// Encoding speed.
84    pub speed: Option<EncodingSpeed>,
85    /// Codec profile (e.g., H264High, HevcMain10).
86    pub profile: Option<CodecProfile>,
87    /// Codec level (e.g., "4.1").
88    pub level: Option<CodecLevel>,
89}
90
91impl VideoSettings {
92    /// Create new video settings with the given codec.
93    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    /// Set the output resolution.
107    #[must_use]
108    pub fn with_resolution(mut self, res: Resolution) -> Self {
109        self.resolution = Some(res);
110        self
111    }
112
113    /// Set the output frame rate.
114    #[must_use]
115    pub fn with_frame_rate(mut self, fps: FrameRate) -> Self {
116        self.frame_rate = Some(fps);
117        self
118    }
119
120    /// Set the quality preset.
121    #[must_use]
122    pub fn with_quality(mut self, q: Quality) -> Self {
123        self.quality = Some(q);
124        self
125    }
126
127    /// Set the bitrate.
128    #[must_use]
129    pub fn with_bitrate(mut self, br: Bitrate) -> Self {
130        self.bitrate = Some(br);
131        self
132    }
133
134    /// Set the encoding speed.
135    #[must_use]
136    pub fn with_speed(mut self, speed: EncodingSpeed) -> Self {
137        self.speed = Some(speed);
138        self
139    }
140
141    /// Set the codec profile.
142    #[must_use]
143    pub fn with_profile(mut self, profile: CodecProfile) -> Self {
144        self.profile = Some(profile);
145        self
146    }
147
148    /// Set the codec level.
149    #[must_use]
150    pub fn with_level(mut self, level: CodecLevel) -> Self {
151        self.level = Some(level);
152        self
153    }
154}
155
156/// Audio-specific encoding settings.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AudioSettings {
159    /// Audio codec.
160    pub codec: Codec,
161    /// Sample rate.
162    pub sample_rate: Option<SampleRate>,
163    /// Channel layout.
164    pub channels: Option<ChannelLayout>,
165    /// Bitrate.
166    pub bitrate: Option<Bitrate>,
167}
168
169impl AudioSettings {
170    /// Create new audio settings with the given codec.
171    pub fn new(codec: Codec) -> Self {
172        Self {
173            codec,
174            sample_rate: None,
175            channels: None,
176            bitrate: None,
177        }
178    }
179
180    /// Set the sample rate.
181    #[must_use]
182    pub fn with_sample_rate(mut self, sr: SampleRate) -> Self {
183        self.sample_rate = Some(sr);
184        self
185    }
186
187    /// Set the channel layout.
188    #[must_use]
189    pub fn with_channels(mut self, ch: ChannelLayout) -> Self {
190        self.channels = Some(ch);
191        self
192    }
193
194    /// Set the bitrate.
195    #[must_use]
196    pub fn with_bitrate(mut self, br: Bitrate) -> Self {
197        self.bitrate = Some(br);
198        self
199    }
200}
201
202/// Complete output configuration.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct OutputConfig {
205    /// Output container format.
206    pub format: Format,
207    /// Video encoding settings (None for audio-only).
208    pub video: Option<VideoSettings>,
209    /// Audio encoding settings (None for video-only).
210    pub audio: Option<AudioSettings>,
211    /// Streaming output settings (HLS, DASH, RTMP).
212    pub streaming: Option<StreamingConfig>,
213    /// Whether to strip metadata from output.
214    pub strip_metadata: bool,
215    /// Extra backend-specific parameters.
216    pub extra: HashMap<String, String>,
217}
218
219/// Streaming output configuration.
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub enum StreamingConfig {
222    /// HTTP Live Streaming (HLS) output.
223    Hls(HlsConfig),
224    /// MPEG-DASH output.
225    Dash(DashConfig),
226    /// RTMP push output.
227    Rtmp(RtmpConfig),
228}
229
230/// HLS output configuration.
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct HlsConfig {
233    /// Segment duration in seconds (default: 6).
234    pub segment_duration: u32,
235    /// Number of segments in playlist (0 = all).
236    pub playlist_size: u32,
237    /// Playlist type.
238    pub playlist_type: HlsPlaylistType,
239    /// Segment filename pattern (default: "segment_%03d.ts").
240    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/// HLS playlist type.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256pub enum HlsPlaylistType {
257    /// Video on demand — all segments in playlist.
258    Vod,
259    /// Live/event — sliding window.
260    Event,
261}
262
263/// DASH output configuration.
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct DashConfig {
266    /// Segment duration in seconds (default: 4).
267    pub segment_duration: u32,
268    /// Use segment template mode.
269    pub use_template: bool,
270    /// Use segment timeline.
271    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/// RTMP push configuration.
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct RtmpConfig {
287    /// RTMP server URL (e.g., "rtmp://live.example.com/app/stream_key").
288    pub url: String,
289}
290
291impl OutputConfig {
292    /// Create a new output config with the given format.
293    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    /// Set video encoding settings.
305    #[must_use]
306    pub fn with_video(mut self, video: VideoSettings) -> Self {
307        self.video = Some(video);
308        self
309    }
310
311    /// Set audio encoding settings.
312    #[must_use]
313    pub fn with_audio(mut self, audio: AudioSettings) -> Self {
314        self.audio = Some(audio);
315        self
316    }
317
318    /// Set streaming output configuration.
319    #[must_use]
320    pub fn with_streaming(mut self, streaming: StreamingConfig) -> Self {
321        self.streaming = Some(streaming);
322        self
323    }
324
325    /// Strip metadata from output.
326    #[must_use]
327    pub fn with_strip_metadata(mut self) -> Self {
328        self.strip_metadata = true;
329        self
330    }
331
332    /// Add an extra backend-specific parameter.
333    #[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    /// Validate codec/format compatibility against a registry.
340    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}