1#![allow(dead_code)]
9
10use std::path::{Path, PathBuf};
11use file_format::FileFormat;
12use tracing::warn;
13use crate::DashMpdError;
14use crate::fetch::DashDownloader;
15
16
17#[derive(Debug, Clone)]
18pub struct AudioTrack {
19 pub language: String,
20 pub path: PathBuf,
21}
22
23
24#[tracing::instrument(level="trace")]
27pub(crate) fn audio_container_type(container: &Path) -> Result<String, DashMpdError> {
28 let format = FileFormat::from_file(container)
29 .map_err(|e| DashMpdError::Io(e, String::from("determining audio container type")))?;
30 Ok(format.extension().to_string())
31}
32
33#[tracing::instrument(level="trace")]
34pub(crate) fn video_container_type(container: &Path) -> Result<String, DashMpdError> {
35 let format = FileFormat::from_file(container)
36 .map_err(|e| DashMpdError::Io(e, String::from("determining video container type")))?;
37 Ok(format.extension().to_string())
38}
39
40
41#[derive(Debug, Clone)]
44struct VideoMetainfo {
45 width: i64,
46 height: i64,
47 frame_rate: f64,
48 sar: Option<f64>,
49}
50
51impl PartialEq for VideoMetainfo {
52 fn eq(&self, other: &Self) -> bool {
53 if self.width != other.width {
54 return false;
55 }
56 if self.height != other.height {
57 return false;
58 }
59 if (self.frame_rate - other.frame_rate).abs() / self.frame_rate > 0.01 {
60 return false;
61 }
62 if let Some(sar1) = self.sar {
65 if let Some(sar2) = other.sar {
66 if (sar1 - sar2).abs() / sar1 > 0.01 {
67 return false;
68 }
69 }
70 }
71 true
72 }
73}
74
75fn parse_frame_rate(s: &str) -> Option<f64> {
77 if let Some((num, den)) = s.split_once('/') {
78 if let Ok(numerator) = num.parse::<u64>() {
79 if let Ok(denominator) = den.parse::<u64>() {
80 return Some(numerator as f64 / denominator as f64);
81 }
82 }
83 }
84 None
85}
86
87fn parse_aspect_ratio(s: &str) -> Option<f64> {
89 if let Some((num, den)) = s.split_once(':') {
90 if let Ok(numerator) = num.parse::<u64>() {
91 if let Ok(denominator) = den.parse::<u64>() {
92 return Some(numerator as f64 / denominator as f64);
93 }
94 }
95 }
96 None
97}
98
99#[tracing::instrument(level="trace")]
102fn video_container_metainfo(path: &PathBuf) -> Result<VideoMetainfo, DashMpdError> {
103 match ffprobe::ffprobe(path) {
104 Ok(meta) => {
105 if meta.streams.is_empty() {
106 return Err(DashMpdError::Muxing(String::from("reading video resolution")));
107 }
108 if let Some(s) = &meta.streams.iter().find(|s| s.width.is_some() && s.height.is_some()) {
109 if let Some(frame_rate) = parse_frame_rate(&s.avg_frame_rate) {
110 let sar = s.sample_aspect_ratio.as_ref()
111 .and_then(|sr| parse_aspect_ratio(sr));
112 if let Some(width) = s.width {
113 if let Some(height) = s.height {
114 return Ok(VideoMetainfo { width, height, frame_rate, sar });
115 }
116 }
117 }
118 }
119 },
120 Err(e) => warn!("Error running ffprobe: {e}"),
121 }
122 Err(DashMpdError::Muxing(String::from("reading video metainformation")))
123}
124
125#[tracing::instrument(level="trace")]
126pub(crate) fn container_only_audio(path: &PathBuf) -> bool {
127 if let Ok(meta) = ffprobe::ffprobe(path) {
128 return meta.streams.iter().all(|s| s.codec_type.as_ref().is_some_and(|typ| typ.eq("audio")));
129 }
130 false
131}
132
133
134#[tracing::instrument(level="trace")]
136pub(crate) fn container_has_audio(path: &PathBuf) -> bool {
137 if let Ok(meta) = ffprobe::ffprobe(path) {
138 return meta.streams.iter().any(|s| s.codec_type.as_ref().is_some_and(|typ| typ.eq("audio")));
139 }
140 false
141}
142
143#[tracing::instrument(level="trace")]
145pub(crate) fn container_has_video(path: &PathBuf) -> bool {
146 if let Ok(meta) = ffprobe::ffprobe(path) {
147 return meta.streams.iter().any(|s| s.codec_type.as_ref().is_some_and(|typ| typ.eq("video")));
148 }
149 false
150}
151
152#[tracing::instrument(level="trace", skip(_downloader))]
157pub(crate) fn video_containers_concatable(_downloader: &DashDownloader, paths: &[PathBuf]) -> bool {
158 if paths.is_empty() {
159 return false;
160 }
161 if let Some(p0) = &paths.first() {
162 if let Ok(p0m) = video_container_metainfo(p0) {
163 return paths.iter().all(
164 |p| video_container_metainfo(p).is_ok_and(|m| m == p0m));
165 }
166 }
167 paths.iter().all(container_only_audio)
168}
169
170#[cfg(target_os = "windows")]
173pub fn temporary_outpath(suffix: &str) -> Result<String, DashMpdError> {
174 Ok(format!("dashmpdrs-tmp{suffix}"))
175}
176
177#[cfg(not(target_os = "windows"))]
178pub fn temporary_outpath(suffix: &str) -> Result<String, DashMpdError> {
179 let tmpout = tempfile::Builder::new()
180 .prefix("dashmpdrs")
181 .suffix(suffix)
182 .rand_bytes(5)
183 .tempfile()
184 .map_err(|e| DashMpdError::Io(e, String::from("creating temporary output file")))?;
185 match tmpout.path().to_str() {
186 Some(s) => Ok(s.to_string()),
187 None => Ok(format!("/tmp/dashmpdrs-tmp{suffix}")),
188 }
189}
190