1#![allow(dead_code)]
2use crate::config::SegmentFormat;
9use crate::error::{PackagerError, PackagerResult};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum StreamCodec {
14 Av1,
16 Vp9,
18 Vp8,
20 Opus,
22 Vorbis,
24 Flac,
26 WebVtt,
28}
29
30impl StreamCodec {
31 #[must_use]
33 pub const fn codecs_string(&self) -> &'static str {
34 match self {
35 Self::Av1 => "av01.0.08M.08",
36 Self::Vp9 => "vp09.00.31.08",
37 Self::Vp8 => "vp8",
38 Self::Opus => "opus",
39 Self::Vorbis => "vorbis",
40 Self::Flac => "flac",
41 Self::WebVtt => "wvtt",
42 }
43 }
44
45 #[must_use]
47 pub const fn is_video(&self) -> bool {
48 matches!(self, Self::Av1 | Self::Vp9 | Self::Vp8)
49 }
50
51 #[must_use]
53 pub const fn is_audio(&self) -> bool {
54 matches!(self, Self::Opus | Self::Vorbis | Self::Flac)
55 }
56
57 #[must_use]
59 pub const fn is_subtitle(&self) -> bool {
60 matches!(self, Self::WebVtt)
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct VariantStream {
67 pub id: String,
69 pub video_codec: Option<StreamCodec>,
71 pub audio_codec: Option<StreamCodec>,
73 pub width: Option<u32>,
75 pub height: Option<u32>,
77 pub frame_rate: Option<f64>,
79 pub video_bitrate: u64,
81 pub audio_bitrate: u64,
83 pub segment_format: SegmentFormat,
85 pub language: Option<String>,
87 pub is_default: bool,
89}
90
91impl VariantStream {
92 #[must_use]
94 pub fn video(id: &str, codec: StreamCodec, width: u32, height: u32, bitrate: u64) -> Self {
95 Self {
96 id: id.to_string(),
97 video_codec: Some(codec),
98 audio_codec: None,
99 width: Some(width),
100 height: Some(height),
101 frame_rate: None,
102 video_bitrate: bitrate,
103 audio_bitrate: 0,
104 segment_format: SegmentFormat::Fmp4,
105 language: None,
106 is_default: false,
107 }
108 }
109
110 #[must_use]
112 pub fn audio(id: &str, codec: StreamCodec, bitrate: u64, language: &str) -> Self {
113 Self {
114 id: id.to_string(),
115 video_codec: None,
116 audio_codec: Some(codec),
117 width: None,
118 height: None,
119 frame_rate: None,
120 video_bitrate: 0,
121 audio_bitrate: bitrate,
122 segment_format: SegmentFormat::Fmp4,
123 language: Some(language.to_string()),
124 is_default: false,
125 }
126 }
127
128 #[must_use]
130 pub fn as_default(mut self) -> Self {
131 self.is_default = true;
132 self
133 }
134
135 #[must_use]
137 pub fn with_frame_rate(mut self, fps: f64) -> Self {
138 self.frame_rate = Some(fps);
139 self
140 }
141
142 #[must_use]
144 pub fn total_bandwidth(&self) -> u64 {
145 self.video_bitrate + self.audio_bitrate
146 }
147
148 #[must_use]
150 pub fn combined_codecs(&self) -> String {
151 let mut parts = Vec::new();
152 if let Some(vc) = &self.video_codec {
153 parts.push(vc.codecs_string().to_string());
154 }
155 if let Some(ac) = &self.audio_codec {
156 parts.push(ac.codecs_string().to_string());
157 }
158 parts.join(",")
159 }
160
161 #[must_use]
163 pub fn resolution_string(&self) -> Option<String> {
164 match (self.width, self.height) {
165 (Some(w), Some(h)) => Some(format!("{w}x{h}")),
166 _ => None,
167 }
168 }
169
170 pub fn validate(&self) -> PackagerResult<()> {
176 if self.id.is_empty() {
177 return Err(PackagerError::InvalidConfig(
178 "Variant stream ID must not be empty".into(),
179 ));
180 }
181 if self.video_codec.is_none() && self.audio_codec.is_none() {
182 return Err(PackagerError::InvalidConfig(
183 "Variant must have at least one codec".into(),
184 ));
185 }
186 if self.video_codec.is_some() && (self.width.is_none() || self.height.is_none()) {
187 return Err(PackagerError::InvalidConfig(
188 "Video variant must specify width and height".into(),
189 ));
190 }
191 Ok(())
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct VariantSet {
198 pub variants: Vec<VariantStream>,
200}
201
202impl VariantSet {
203 #[must_use]
205 pub fn new() -> Self {
206 Self {
207 variants: Vec::new(),
208 }
209 }
210
211 pub fn add(&mut self, variant: VariantStream) {
213 self.variants.push(variant);
214 }
215
216 #[must_use]
218 pub fn len(&self) -> usize {
219 self.variants.len()
220 }
221
222 #[must_use]
224 pub fn is_empty(&self) -> bool {
225 self.variants.is_empty()
226 }
227
228 #[must_use]
230 pub fn video_variants(&self) -> Vec<&VariantStream> {
231 let mut vids: Vec<&VariantStream> = self
232 .variants
233 .iter()
234 .filter(|v| v.video_codec.is_some())
235 .collect();
236 vids.sort_by_key(|v| v.video_bitrate);
237 vids
238 }
239
240 #[must_use]
242 pub fn audio_variants(&self) -> Vec<&VariantStream> {
243 self.variants
244 .iter()
245 .filter(|v| v.video_codec.is_none() && v.audio_codec.is_some())
246 .collect()
247 }
248
249 pub fn validate(&self) -> PackagerResult<()> {
255 if self.variants.is_empty() {
256 return Err(PackagerError::InvalidConfig(
257 "Variant set must have at least one variant".into(),
258 ));
259 }
260 for v in &self.variants {
261 v.validate()?;
262 }
263 Ok(())
264 }
265}
266
267impl Default for VariantSet {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_stream_codec_properties() {
279 assert!(StreamCodec::Av1.is_video());
280 assert!(!StreamCodec::Av1.is_audio());
281 assert!(StreamCodec::Opus.is_audio());
282 assert!(StreamCodec::WebVtt.is_subtitle());
283 }
284
285 #[test]
286 fn test_codecs_string() {
287 assert_eq!(StreamCodec::Av1.codecs_string(), "av01.0.08M.08");
288 assert_eq!(StreamCodec::Opus.codecs_string(), "opus");
289 }
290
291 #[test]
292 fn test_video_variant_creation() {
293 let v = VariantStream::video("v1", StreamCodec::Av1, 1920, 1080, 5_000_000);
294 assert_eq!(v.width, Some(1920));
295 assert_eq!(v.height, Some(1080));
296 assert!(v.validate().is_ok());
297 }
298
299 #[test]
300 fn test_audio_variant_creation() {
301 let v = VariantStream::audio("a1", StreamCodec::Opus, 128_000, "en");
302 assert_eq!(v.language, Some("en".to_string()));
303 assert!(v.validate().is_ok());
304 }
305
306 #[test]
307 fn test_variant_total_bandwidth() {
308 let mut v = VariantStream::video("v1", StreamCodec::Vp9, 1280, 720, 3_000_000);
309 v.audio_bitrate = 128_000;
310 assert_eq!(v.total_bandwidth(), 3_128_000);
311 }
312
313 #[test]
314 fn test_combined_codecs() {
315 let mut v = VariantStream::video("v1", StreamCodec::Av1, 1920, 1080, 5_000_000);
316 v.audio_codec = Some(StreamCodec::Opus);
317 let codecs = v.combined_codecs();
318 assert!(codecs.contains("av01"));
319 assert!(codecs.contains("opus"));
320 }
321
322 #[test]
323 fn test_resolution_string() {
324 let v = VariantStream::video("v1", StreamCodec::Av1, 1920, 1080, 5_000_000);
325 assert_eq!(v.resolution_string(), Some("1920x1080".to_string()));
326 }
327
328 #[test]
329 fn test_audio_variant_no_resolution() {
330 let v = VariantStream::audio("a1", StreamCodec::Opus, 128_000, "en");
331 assert_eq!(v.resolution_string(), None);
332 }
333
334 #[test]
335 fn test_variant_validate_empty_id() {
336 let v = VariantStream::video("", StreamCodec::Av1, 1920, 1080, 5_000_000);
337 assert!(v.validate().is_err());
338 }
339
340 #[test]
341 fn test_variant_validate_no_codec() {
342 let v = VariantStream {
343 id: "x".to_string(),
344 video_codec: None,
345 audio_codec: None,
346 width: None,
347 height: None,
348 frame_rate: None,
349 video_bitrate: 0,
350 audio_bitrate: 0,
351 segment_format: SegmentFormat::Fmp4,
352 language: None,
353 is_default: false,
354 };
355 assert!(v.validate().is_err());
356 }
357
358 #[test]
359 fn test_variant_set() {
360 let mut set = VariantSet::new();
361 set.add(VariantStream::video(
362 "v1",
363 StreamCodec::Av1,
364 1920,
365 1080,
366 5_000_000,
367 ));
368 set.add(VariantStream::video(
369 "v2",
370 StreamCodec::Av1,
371 1280,
372 720,
373 3_000_000,
374 ));
375 assert_eq!(set.len(), 2);
376 assert!(set.validate().is_ok());
377 }
378
379 #[test]
380 fn test_variant_set_video_sorted() {
381 let mut set = VariantSet::new();
382 set.add(VariantStream::video(
383 "hi",
384 StreamCodec::Av1,
385 1920,
386 1080,
387 5_000_000,
388 ));
389 set.add(VariantStream::video(
390 "lo",
391 StreamCodec::Av1,
392 640,
393 360,
394 500_000,
395 ));
396 let vids = set.video_variants();
397 assert!(vids[0].video_bitrate < vids[1].video_bitrate);
398 }
399
400 #[test]
401 fn test_variant_set_empty_validation() {
402 let set = VariantSet::new();
403 assert!(set.validate().is_err());
404 }
405
406 #[test]
407 fn test_default_variant() {
408 let v = VariantStream::video("v1", StreamCodec::Av1, 1920, 1080, 5_000_000).as_default();
409 assert!(v.is_default);
410 }
411}