1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::path::PathBuf;
4use std::str::FromStr;
5use std::time::Duration as StdDuration;
6
7use crate::error::{Error, Result};
8
9#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
11pub struct Duration(StdDuration);
12
13impl Duration {
14 pub fn from_secs(secs: u64) -> Self {
16 Self(StdDuration::from_secs(secs))
17 }
18
19 pub fn from_millis(millis: u64) -> Self {
21 Self(StdDuration::from_millis(millis))
22 }
23
24 pub fn as_secs(&self) -> u64 {
26 self.0.as_secs()
27 }
28
29 pub fn as_millis(&self) -> u128 {
31 self.0.as_millis()
32 }
33
34 pub fn to_ffmpeg_format(&self) -> String {
36 let total_secs = self.0.as_secs();
37 let hours = total_secs / 3600;
38 let minutes = (total_secs % 3600) / 60;
39 let seconds = total_secs % 60;
40 let millis = self.0.subsec_millis();
41
42 if millis > 0 {
43 format!("{:02}:{:02}:{:02}.{:03}", hours, minutes, seconds, millis)
44 } else {
45 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
46 }
47 }
48
49 pub fn from_ffmpeg_format(s: &str) -> Result<Self> {
51 if let Ok(secs) = s.parse::<f64>() {
53 return Ok(Self(StdDuration::from_secs_f64(secs)));
54 }
55
56 let parts: Vec<&str> = s.split(':').collect();
58 if parts.len() != 3 {
59 return Err(Error::ParseError(format!("Invalid time format: {}", s)));
60 }
61
62 let hours: u64 = parts[0].parse()
63 .map_err(|_| Error::ParseError(format!("Invalid hours: {}", parts[0])))?;
64 let minutes: u64 = parts[1].parse()
65 .map_err(|_| Error::ParseError(format!("Invalid minutes: {}", parts[1])))?;
66
67 let (seconds, millis) = if parts[2].contains('.') {
68 let sec_parts: Vec<&str> = parts[2].split('.').collect();
69 let secs: u64 = sec_parts[0].parse()
70 .map_err(|_| Error::ParseError(format!("Invalid seconds: {}", sec_parts[0])))?;
71 let ms: u64 = sec_parts[1].parse()
72 .map_err(|_| Error::ParseError(format!("Invalid milliseconds: {}", sec_parts[1])))?;
73 (secs, ms)
74 } else {
75 let secs: u64 = parts[2].parse()
76 .map_err(|_| Error::ParseError(format!("Invalid seconds: {}", parts[2])))?;
77 (secs, 0)
78 };
79
80 let total_millis = (hours * 3600 + minutes * 60 + seconds) * 1000 + millis;
81 Ok(Self(StdDuration::from_millis(total_millis)))
82 }
83}
84
85impl From<StdDuration> for Duration {
86 fn from(d: StdDuration) -> Self {
87 Self(d)
88 }
89}
90
91impl From<Duration> for StdDuration {
92 fn from(d: Duration) -> Self {
93 d.0
94 }
95}
96
97impl fmt::Display for Duration {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(f, "{}", self.to_ffmpeg_format())
100 }
101}
102
103impl FromStr for Duration {
104 type Err = Error;
105
106 fn from_str(s: &str) -> Result<Self> {
107 Self::from_ffmpeg_format(s)
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
113pub struct Size(u64);
114
115impl Size {
116 pub fn from_bytes(bytes: u64) -> Self {
118 Self(bytes)
119 }
120
121 pub fn from_kb(kb: u64) -> Self {
123 Self(kb * 1000)
124 }
125
126 pub fn from_mb(mb: u64) -> Self {
128 Self(mb * 1_000_000)
129 }
130
131 pub fn from_gb(gb: u64) -> Self {
133 Self(gb * 1_000_000_000)
134 }
135
136 pub fn from_kib(kib: u64) -> Self {
138 Self(kib * 1024)
139 }
140
141 pub fn from_mib(mib: u64) -> Self {
143 Self(mib * 1024 * 1024)
144 }
145
146 pub fn from_gib(gib: u64) -> Self {
148 Self(gib * 1024 * 1024 * 1024)
149 }
150
151 pub fn as_bytes(&self) -> u64 {
153 self.0
154 }
155
156 pub fn parse(s: &str) -> Result<Self> {
158 let s = s.trim();
159
160 let (num_str, suffix) = s
162 .find(|c: char| c.is_alphabetic())
163 .map(|i| s.split_at(i))
164 .unwrap_or((s, ""));
165
166 let number: f64 = num_str.parse()
167 .map_err(|_| Error::ParseError(format!("Invalid number: {}", num_str)))?;
168
169 let multiplier = match suffix.to_uppercase().as_str() {
170 "" | "B" => 1.0,
171 "K" | "KB" => 1_000.0,
172 "M" | "MB" => 1_000_000.0,
173 "G" | "GB" => 1_000_000_000.0,
174 "KI" | "KIB" => 1_024.0,
175 "MI" | "MIB" => 1_048_576.0,
176 "GI" | "GIB" => 1_073_741_824.0,
177 _ => return Err(Error::ParseError(format!("Invalid size suffix: {}", suffix))),
178 };
179
180 Ok(Self((number * multiplier) as u64))
181 }
182}
183
184impl fmt::Display for Size {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(f, "{}", self.0)
187 }
188}
189
190impl FromStr for Size {
191 type Err = Error;
192
193 fn from_str(s: &str) -> Result<Self> {
194 Self::parse(s)
195 }
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
200pub enum StreamSpecifier {
201 Index(usize),
203 Type(StreamType),
205 TypeIndex(StreamType, usize),
207 All,
209 Program(usize),
211 StreamId(String),
213 Metadata { key: String, value: Option<String> },
215 Usable,
217}
218
219impl StreamSpecifier {
220 pub fn to_string(&self) -> String {
222 match self {
223 Self::Index(i) => i.to_string(),
224 Self::Type(t) => t.to_string(),
225 Self::TypeIndex(t, i) => format!("{}:{}", t, i),
226 Self::All => String::new(),
227 Self::Program(id) => format!("p:{}", id),
228 Self::StreamId(id) => format!("#{}", id),
229 Self::Metadata { key, value } => {
230 if let Some(val) = value {
231 format!("m:{}:{}", key, val)
232 } else {
233 format!("m:{}", key)
234 }
235 }
236 Self::Usable => "u".to_string(),
237 }
238 }
239}
240
241impl fmt::Display for StreamSpecifier {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 write!(f, "{}", self.to_string())
244 }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum StreamType {
250 Video,
251 VideoNoAttached,
252 Audio,
253 Subtitle,
254 Data,
255 Attachment,
256}
257
258impl StreamType {
259 pub fn as_str(&self) -> &'static str {
260 match self {
261 Self::Video => "v",
262 Self::VideoNoAttached => "V",
263 Self::Audio => "a",
264 Self::Subtitle => "s",
265 Self::Data => "d",
266 Self::Attachment => "t",
267 }
268 }
269}
270
271impl fmt::Display for StreamType {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 write!(f, "{}", self.as_str())
274 }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub enum LogLevel {
280 Quiet,
281 Panic,
282 Fatal,
283 Error,
284 Warning,
285 Info,
286 Verbose,
287 Debug,
288 Trace,
289}
290
291impl LogLevel {
292 pub fn as_str(&self) -> &'static str {
293 match self {
294 Self::Quiet => "quiet",
295 Self::Panic => "panic",
296 Self::Fatal => "fatal",
297 Self::Error => "error",
298 Self::Warning => "warning",
299 Self::Info => "info",
300 Self::Verbose => "verbose",
301 Self::Debug => "debug",
302 Self::Trace => "trace",
303 }
304 }
305
306 pub fn as_number(&self) -> i32 {
307 match self {
308 Self::Quiet => -8,
309 Self::Panic => 0,
310 Self::Fatal => 8,
311 Self::Error => 16,
312 Self::Warning => 24,
313 Self::Info => 32,
314 Self::Verbose => 40,
315 Self::Debug => 48,
316 Self::Trace => 56,
317 }
318 }
319}
320
321impl fmt::Display for LogLevel {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 write!(f, "{}", self.as_str())
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329pub struct PixelFormat(String);
330
331impl PixelFormat {
332 pub fn new(format: impl Into<String>) -> Self {
333 Self(format.into())
334 }
335
336 pub fn as_str(&self) -> &str {
337 &self.0
338 }
339
340 pub fn yuv420p() -> Self {
342 Self("yuv420p".to_string())
343 }
344
345 pub fn yuv422p() -> Self {
346 Self("yuv422p".to_string())
347 }
348
349 pub fn yuv444p() -> Self {
350 Self("yuv444p".to_string())
351 }
352
353 pub fn rgb24() -> Self {
354 Self("rgb24".to_string())
355 }
356
357 pub fn bgr24() -> Self {
358 Self("bgr24".to_string())
359 }
360
361 pub fn rgba() -> Self {
362 Self("rgba".to_string())
363 }
364
365 pub fn bgra() -> Self {
366 Self("bgra".to_string())
367 }
368
369 pub fn gray() -> Self {
370 Self("gray".to_string())
371 }
372
373 pub fn nv12() -> Self {
374 Self("nv12".to_string())
375 }
376}
377
378impl fmt::Display for PixelFormat {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 write!(f, "{}", self.0)
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
386pub struct SampleFormat(String);
387
388impl SampleFormat {
389 pub fn new(format: impl Into<String>) -> Self {
390 Self(format.into())
391 }
392
393 pub fn as_str(&self) -> &str {
394 &self.0
395 }
396
397 pub fn u8() -> Self {
399 Self("u8".to_string())
400 }
401
402 pub fn s16() -> Self {
403 Self("s16".to_string())
404 }
405
406 pub fn s32() -> Self {
407 Self("s32".to_string())
408 }
409
410 pub fn flt() -> Self {
411 Self("flt".to_string())
412 }
413
414 pub fn dbl() -> Self {
415 Self("dbl".to_string())
416 }
417
418 pub fn u8p() -> Self {
419 Self("u8p".to_string())
420 }
421
422 pub fn s16p() -> Self {
423 Self("s16p".to_string())
424 }
425
426 pub fn s32p() -> Self {
427 Self("s32p".to_string())
428 }
429
430 pub fn fltp() -> Self {
431 Self("fltp".to_string())
432 }
433
434 pub fn dblp() -> Self {
435 Self("dblp".to_string())
436 }
437}
438
439impl fmt::Display for SampleFormat {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 write!(f, "{}", self.0)
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
447pub struct Codec(String);
448
449impl Codec {
450 pub fn new(codec: impl Into<String>) -> Self {
451 Self(codec.into())
452 }
453
454 pub fn as_str(&self) -> &str {
455 &self.0
456 }
457
458 pub fn h264() -> Self {
460 Self("h264".to_string())
461 }
462
463 pub fn h265() -> Self {
464 Self("h265".to_string())
465 }
466
467 pub fn vp9() -> Self {
468 Self("vp9".to_string())
469 }
470
471 pub fn av1() -> Self {
472 Self("av1".to_string())
473 }
474
475 pub fn mpeg2video() -> Self {
476 Self("mpeg2video".to_string())
477 }
478
479 pub fn mpeg4() -> Self {
480 Self("mpeg4".to_string())
481 }
482
483 pub fn aac() -> Self {
485 Self("aac".to_string())
486 }
487
488 pub fn mp3() -> Self {
489 Self("mp3".to_string())
490 }
491
492 pub fn opus() -> Self {
493 Self("opus".to_string())
494 }
495
496 pub fn flac() -> Self {
497 Self("flac".to_string())
498 }
499
500 pub fn ac3() -> Self {
501 Self("ac3".to_string())
502 }
503
504 pub fn pcm_s16le() -> Self {
505 Self("pcm_s16le".to_string())
506 }
507
508 pub fn copy() -> Self {
510 Self("copy".to_string())
511 }
512}
513
514impl fmt::Display for Codec {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516 write!(f, "{}", self.0)
517 }
518}
519
520#[derive(Debug, Clone, PartialEq, Eq)]
522pub struct MediaPath {
523 path: PathBuf,
524 is_url: bool,
525}
526
527impl MediaPath {
528 pub fn from_path(path: impl Into<PathBuf>) -> Self {
530 Self {
531 path: path.into(),
532 is_url: false,
533 }
534 }
535
536 pub fn from_url(url: impl Into<String>) -> Self {
538 Self {
539 path: PathBuf::from(url.into()),
540 is_url: true,
541 }
542 }
543
544 pub fn parse(s: impl AsRef<str>) -> Self {
546 let s = s.as_ref();
547 if s.contains("://") || s.starts_with("rtmp") || s.starts_with("rtsp") {
548 Self::from_url(s)
549 } else {
550 Self::from_path(s)
551 }
552 }
553
554 pub fn as_str(&self) -> &str {
556 self.path.to_str().unwrap_or("")
557 }
558
559 pub fn is_url(&self) -> bool {
561 self.is_url
562 }
563
564 pub fn is_file(&self) -> bool {
566 !self.is_url
567 }
568
569 pub fn path(&self) -> &PathBuf {
571 &self.path
572 }
573}
574
575impl fmt::Display for MediaPath {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 write!(f, "{}", self.as_str())
578 }
579}
580
581impl From<PathBuf> for MediaPath {
582 fn from(path: PathBuf) -> Self {
583 Self::from_path(path)
584 }
585}
586
587impl From<&str> for MediaPath {
588 fn from(s: &str) -> Self {
589 Self::parse(s)
590 }
591}
592
593impl From<String> for MediaPath {
594 fn from(s: String) -> Self {
595 Self::parse(s)
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn test_duration_parsing() {
605 assert_eq!(Duration::from_ffmpeg_format("10").unwrap().as_secs(), 10);
606 assert_eq!(Duration::from_ffmpeg_format("01:30:00").unwrap().as_secs(), 5400);
607 assert_eq!(Duration::from_ffmpeg_format("00:00:30.500").unwrap().as_millis(), 30500);
608 }
609
610 #[test]
611 fn test_duration_formatting() {
612 assert_eq!(Duration::from_secs(90).to_ffmpeg_format(), "00:01:30");
613 assert_eq!(Duration::from_millis(30500).to_ffmpeg_format(), "00:00:30.500");
614 }
615
616 #[test]
617 fn test_size_parsing() {
618 assert_eq!(Size::parse("1024").unwrap().as_bytes(), 1024);
619 assert_eq!(Size::parse("10K").unwrap().as_bytes(), 10_000);
620 assert_eq!(Size::parse("10KB").unwrap().as_bytes(), 10_000);
621 assert_eq!(Size::parse("10KiB").unwrap().as_bytes(), 10_240);
622 assert_eq!(Size::parse("1.5M").unwrap().as_bytes(), 1_500_000);
623 }
624
625 #[test]
626 fn test_stream_specifier() {
627 assert_eq!(StreamSpecifier::Index(1).to_string(), "1");
628 assert_eq!(StreamSpecifier::Type(StreamType::Audio).to_string(), "a");
629 assert_eq!(StreamSpecifier::TypeIndex(StreamType::Video, 0).to_string(), "v:0");
630 assert_eq!(StreamSpecifier::Program(1).to_string(), "p:1");
631 }
632
633 #[test]
634 fn test_media_path() {
635 let file = MediaPath::parse("/path/to/file.mp4");
636 assert!(file.is_file());
637 assert!(!file.is_url());
638
639 let url = MediaPath::parse("https://example.com/video.mp4");
640 assert!(url.is_url());
641 assert!(!url.is_file());
642
643 let rtmp = MediaPath::parse("rtmp://server/live/stream");
644 assert!(rtmp.is_url());
645 }
646}