youtube_dl_parser/state/parsed_state/
download_state.rs

1use std::num::ParseFloatError;
2
3/// Occurs when a download is in progress
4pub enum DownloadState {
5    Destination(String),
6    Resuming(u64),
7    Downloading(f32, u64, u64, u64),
8    Downloaded(f32, u64, u64),
9    ParseError(String),
10}
11
12impl DownloadState {
13    pub fn parse<'a>(mut split: impl DoubleEndedIterator<Item = &'a str> + Send) -> DownloadState {
14        let Some(download_indicator) = split.next() else { return DownloadState::ParseError("No download indicator detected".to_owned()); };
15        match download_indicator {
16            "Resuming" => match Self::parse_resuming(split) {
17                Ok(state) => state,
18                Err(err) => DownloadState::ParseError(err),
19            },
20            "Destination:" => Self::parse_destination(split),
21            _ => match Self::parse_progress(download_indicator, split) {
22                Ok(state) => state,
23                Err(err) => DownloadState::ParseError(err),
24            },
25        }
26    }
27
28    fn parse_resuming<'a>(
29        mut split: impl DoubleEndedIterator<Item = &'a str> + Send,
30    ) -> Result<DownloadState, String> {
31        split.next();
32        split.next();
33        split.next();
34        let Some(byte) = split.next() else { return Err("No resume byte detected".to_owned()) };
35        let byte = Self::parse_resume_byte(byte)?;
36        Ok(DownloadState::Resuming(byte))
37    }
38
39    fn parse_destination<'a>(
40        split: impl DoubleEndedIterator<Item = &'a str> + Send,
41    ) -> DownloadState {
42        let destination = split.collect::<Vec<&str>>().join(" ");
43        DownloadState::Destination(destination)
44    }
45
46    fn parse_progress<'a>(
47        download_indicator: &str,
48        mut split: impl DoubleEndedIterator<Item = &'a str> + Send,
49    ) -> Result<DownloadState, String> {
50        // Parse progress
51        let progress = Self::parse_percentage(download_indicator).map_err(|err| {
52            format!("Unable to parse progress \'{download_indicator}\' with error: {err}")
53        })?;
54
55        // Skip "of" text
56        split.next();
57
58        // Parse total size
59        let Some(total_size) = split.next() else { return Err("No total size detected".to_owned()); };
60        let total_size = Self::parse_total_size(total_size).map_err(|err| {
61            format!("Unable to parse total size \'{total_size}\' with error: {err}")
62        })?;
63
64        // Parse if still downloading
65        let Some(in_or_at)=  split.next() else {return Err("Unable to get if still downloading".to_owned())};
66
67        match in_or_at.trim() {
68            "at" => {
69                // Parse download speed
70                let Some(download_speed) = split.next() else { return Err("No download speed detected".to_owned()); };
71                let download_speed = Self::parse_download_speed(download_speed).map_err(|err| {
72                    format!("Unable to parse download speed \'{download_speed}\' with error: {err}")
73                })?;
74                // Skip "ETA" text
75                split.next();
76                // Parse ETA
77                let Some(eta) = split.next() else { return Err("No ETA detected".to_owned()); };
78                let eta = Self::parse_time(eta)
79                    .map_err(|err| format!("Unable to parse eta \'{eta}\' with error: {err}"))?;
80
81                Ok(DownloadState::Downloading(
82                    progress,
83                    total_size,
84                    download_speed,
85                    eta,
86                ))
87            }
88            "in" => {
89                // Parse completion time
90                let Some(completion_time) = split.next() else { return Err("No completion time detected".to_owned()); };
91                let completion_time = Self::parse_time(completion_time).map_err(|err| {
92                    format!("Unable to parse completion \'{completion_time}\' with error: {err}")
93                })?;
94
95                Ok(DownloadState::Downloaded(
96                    progress,
97                    total_size,
98                    completion_time,
99                ))
100            }
101            _ => Err("Unable to get if still downloading".to_owned()),
102        }
103    }
104
105    fn parse_resume_byte(byte: &str) -> Result<u64, String> {
106        byte.trim().parse::<u64>().map_err(|err| err.to_string())
107    }
108
109    fn parse_percentage(percentage: &str) -> Result<f32, String> {
110        let mut chars = percentage.chars();
111        chars.next_back();
112        chars
113            .as_str()
114            .trim()
115            .parse::<f32>()
116            .map_err(|err| err.to_string())
117    }
118
119    fn parse_total_size(size_str: &str) -> Result<u64, String> {
120        // Split the string into a value and unit
121
122        let Some(last_digit_index) = size_str.rfind(|char: char| char.is_ascii_digit()) else{
123            return Err("Incorrectly formatted size string".to_owned());
124        };
125        let (value_str, unit) = size_str.split_at(last_digit_index + 1);
126
127        // Parse the value as a float
128        let value: f64 = value_str
129            .trim()
130            .parse()
131            .map_err(|err: ParseFloatError| err.to_string())?;
132
133        // Convert the value to bytes based on the unit
134        let bytes = match unit {
135            "B" => value,
136            "KB" => value * 1_000.0,
137            "MB" => value * 1_000_000.0,
138            "GB" => value * 1_000_000_000.0,
139            "TB" => value * 1_000_000_000_000.0,
140            "KiB" => value * 1_024.0,
141            "MiB" => value * 1_048_576.0,
142            "GiB" => value * 1_073_741_824.0,
143            "TiB" => value * 1_099_511_627_776.0,
144            _ => return Err(format!("Unrecognized unit: {unit}")),
145        } as u64;
146
147        Ok(bytes)
148    }
149
150    fn parse_download_speed(size_str: &str) -> Result<u64, String> {
151        // Split the string into a value and unit
152
153        let Some(last_digit_index) = size_str.rfind(|char: char| char.is_ascii_digit()) else{
154            return Err("Incorrectly formatted size".to_owned());
155        };
156
157        let (value_str, mut unit) = size_str.split_at(last_digit_index + 1);
158
159        // Parse the value as a float
160        let value: f64 = value_str
161            .trim()
162            .parse()
163            .map_err(|err: ParseFloatError| err.to_string())?;
164
165        unit = &unit[..unit.len() - 2];
166
167        // Convert the value to bytes based on the unit
168        let bytes = match unit {
169            "B" => value,
170            "KB" => value * 1_000.0,
171            "MB" => value * 1_000_000.0,
172            "GB" => value * 1_000_000_000.0,
173            "TB" => value * 1_000_000_000_000.0,
174            "KiB" => value * 1_024.0,
175            "MiB" => value * 1_048_576.0,
176            "GiB" => value * 1_073_741_824.0,
177            "TiB" => value * 1_099_511_627_776.0,
178            _ => return Err(format!("Unrecognized unit: {unit}")),
179        } as u64;
180
181        Ok(bytes)
182    }
183
184    fn parse_time(time: &str) -> Result<u64, String> {
185        let parts: Vec<&str> = time.split(':').collect();
186        match parts.len() {
187            1 => {
188                // Time is in the format "SS"
189                let seconds = parts[0].parse::<u64>().map_err(|err| err.to_string())?;
190                Ok(seconds)
191            }
192            2 => {
193                // Time is in the format "MM:SS"
194                let minutes = parts[0].parse::<u64>().map_err(|err| err.to_string())?;
195                let seconds = parts[1].parse::<u64>().map_err(|err| err.to_string())?;
196                Ok((minutes * 60) + seconds)
197            }
198            3 => {
199                // Time is in the format "HH:MM:SS"
200                let hours = parts[0].parse::<u64>().map_err(|err| err.to_string())?;
201                let minutes = parts[1].parse::<u64>().map_err(|err| err.to_string())?;
202                let seconds = parts[2].parse::<u64>().map_err(|err| err.to_string())?;
203                Ok((hours * 3600) + (minutes * 60) + seconds)
204            }
205            4 => {
206                // Time is in the format "DD:HH:MM:SS"
207                let days = parts[0].parse::<u64>().map_err(|err| err.to_string())?;
208                let hours = parts[1].parse::<u64>().map_err(|err| err.to_string())?;
209                let minutes = parts[2].parse::<u64>().map_err(|err| err.to_string())?;
210                let seconds = parts[3].parse::<u64>().map_err(|err| err.to_string())?;
211                Ok((days * 86400) + (hours * 3600) + (minutes * 60) + seconds)
212            }
213            _ => {
214                // Time is in an invalid format
215                Err("Invalid time format".to_owned())
216            }
217        }
218    }
219}