1use std::collections::HashMap;
2
3const FRAME_KEY: &str = "frame";
4const FPS_KEY: &str = "fps";
5const BITRATE_KEY: &str = "bitrate";
6const TOTAL_SIZE_KEY: &str = "total_size";
7const OUT_TIME_US_KEY: &str = "out_time_us";
8const OUT_TIME_MS_KEY: &str = "out_time_ms";
9const OUT_TIME_KEY: &str = "out_time";
10const DUP_FRAMES_KEY: &str = "dup_frames";
11const DROP_FRAMES_KEY: &str = "drop_frames";
12const SPEED_KEY: &str = "speed";
13const PROGRESS_KEY: &str = "progress";
14
15const N_A: &str = "N/A";
16
17#[derive(Debug, thiserror::Error)]
19pub enum LineBuilderError {
20 #[error("invalid key value pair")]
22 InvalidKeyValuePair,
23
24 #[error("invalid integer value for key \"{key}\" with value \"{value}\"")]
26 InvalidIntegerValue {
27 key: &'static str,
28 value: Box<str>,
29 #[source]
30 error: std::num::ParseIntError,
31 },
32
33 #[error("invalid float value for key \"{0}\"")]
35 InvalidFloatValue(&'static str, #[source] std::num::ParseFloatError),
36
37 #[error("duplicate key \"{key}\"")]
39 DuplicateKey {
40 key: Box<str>,
42 new_value: Box<str>,
44 old_value: Box<str>,
46 },
47
48 #[error("missing key value pair for key \"{0}\"")]
50 MissingKeyValuePair(&'static str),
51}
52
53#[derive(Debug)]
55pub struct ProgressEvent {
56 pub frame: u64,
58
59 pub fps: f64,
61
62 pub bitrate: Box<str>,
64
65 pub progress: Box<str>,
67
68 pub total_size: Option<u64>,
72
73 pub out_time_us: i64,
75
76 pub out_time_ms: i64,
78
79 pub out_time: Box<str>,
81
82 pub dup_frames: u64,
84
85 pub drop_frames: u64,
87
88 pub speed: Option<f64>,
92
93 pub extra: HashMap<Box<str>, Box<str>>,
95}
96
97impl ProgressEvent {
98 #[expect(clippy::too_many_arguments)]
100 pub(crate) fn try_from_optional_parts(
101 frame: Option<u64>,
102 fps: Option<f64>,
103 bitrate: Option<Box<str>>,
104 total_size: Option<Option<u64>>,
105 out_time_us: Option<i64>,
106 out_time_ms: Option<i64>,
107 out_time: Option<Box<str>>,
108 dup_frames: Option<u64>,
109 drop_frames: Option<u64>,
110 speed: Option<Option<f64>>,
111 progress: Option<Box<str>>,
112 extra: HashMap<Box<str>, Box<str>>,
113 ) -> Result<Self, LineBuilderError> {
114 Ok(ProgressEvent {
115 frame: frame.ok_or(LineBuilderError::MissingKeyValuePair(FRAME_KEY))?,
116 fps: fps.ok_or(LineBuilderError::MissingKeyValuePair(FPS_KEY))?,
117 bitrate: bitrate.ok_or(LineBuilderError::MissingKeyValuePair(BITRATE_KEY))?,
118 total_size: total_size.ok_or(LineBuilderError::MissingKeyValuePair(TOTAL_SIZE_KEY))?,
119 out_time_us: out_time_us
120 .ok_or(LineBuilderError::MissingKeyValuePair(OUT_TIME_US_KEY))?,
121 out_time_ms: out_time_ms
122 .ok_or(LineBuilderError::MissingKeyValuePair(OUT_TIME_MS_KEY))?,
123 out_time: out_time.ok_or(LineBuilderError::MissingKeyValuePair(OUT_TIME_KEY))?,
124 dup_frames: dup_frames.ok_or(LineBuilderError::MissingKeyValuePair(DUP_FRAMES_KEY))?,
125 drop_frames: drop_frames
126 .ok_or(LineBuilderError::MissingKeyValuePair(DROP_FRAMES_KEY))?,
127 speed: speed.ok_or(LineBuilderError::MissingKeyValuePair(SPEED_KEY))?,
128 progress: progress.ok_or(LineBuilderError::MissingKeyValuePair(PROGRESS_KEY))?,
129 extra,
130 })
131 }
132}
133
134#[derive(Debug)]
136pub(crate) struct ProgressEventLineBuilder {
137 maybe_frame: Option<u64>,
138 maybe_fps: Option<f64>,
139 maybe_bitrate: Option<Box<str>>,
140 maybe_total_size: Option<Option<u64>>,
141 maybe_out_time_us: Option<i64>,
142 maybe_out_time_ms: Option<i64>,
143 maybe_out_time: Option<Box<str>>,
144 maybe_dup_frames: Option<u64>,
145 maybe_drop_frames: Option<u64>,
146 maybe_speed: Option<Option<f64>>,
147
148 extra: HashMap<Box<str>, Box<str>>,
149}
150
151impl ProgressEventLineBuilder {
152 pub(crate) fn new() -> Self {
154 Self {
155 maybe_frame: None,
156 maybe_fps: None,
157 maybe_bitrate: None,
158 maybe_total_size: None,
159 maybe_out_time_us: None,
160 maybe_out_time_ms: None,
161 maybe_out_time: None,
162 maybe_dup_frames: None,
163 maybe_drop_frames: None,
164 maybe_speed: None,
165
166 extra: HashMap::new(),
167 }
168 }
169
170 pub(crate) fn push(&mut self, line: &str) -> Result<Option<ProgressEvent>, LineBuilderError> {
174 let line = line.trim();
175
176 let (key, value) = line
177 .split_once('=')
178 .ok_or(LineBuilderError::InvalidKeyValuePair)?;
179
180 fn parse_kv_u64(
181 key: &'static str,
182 value: &str,
183 store: &mut Option<u64>,
184 ) -> Result<(), LineBuilderError> {
185 if let Some(store) = store {
186 return Err(LineBuilderError::DuplicateKey {
187 key: key.into(),
188 new_value: value.into(),
189 old_value: store.to_string().into(),
190 });
191 }
192 *store =
193 Some(
194 value
195 .parse()
196 .map_err(|error| LineBuilderError::InvalidIntegerValue {
197 key,
198 value: value.into(),
199 error,
200 })?,
201 );
202 Ok(())
203 }
204
205 fn parse_kv_i64(
206 key: &'static str,
207 value: &str,
208 store: &mut Option<i64>,
209 ) -> Result<(), LineBuilderError> {
210 if let Some(store) = store {
211 return Err(LineBuilderError::DuplicateKey {
212 key: key.into(),
213 new_value: value.into(),
214 old_value: store.to_string().into(),
215 });
216 }
217 *store =
218 Some(
219 value
220 .parse()
221 .map_err(|error| LineBuilderError::InvalidIntegerValue {
222 key,
223 value: value.into(),
224 error,
225 })?,
226 );
227 Ok(())
228 }
229
230 fn parse_kv_f64(
231 key: &'static str,
232 value: &str,
233 store: &mut Option<f64>,
234 ) -> Result<(), LineBuilderError> {
235 if let Some(store) = store {
236 return Err(LineBuilderError::DuplicateKey {
237 key: key.into(),
238 old_value: store.to_string().into(),
239 new_value: value.into(),
240 });
241 }
242 *store = Some(
243 value
244 .parse()
245 .map_err(|e| LineBuilderError::InvalidFloatValue(key, e))?,
246 );
247 Ok(())
248 }
249
250 match key {
251 FRAME_KEY => {
252 parse_kv_u64(FRAME_KEY, value, &mut self.maybe_frame)?;
253 Ok(None)
254 }
255 FPS_KEY => {
256 parse_kv_f64(FPS_KEY, value, &mut self.maybe_fps)?;
257 Ok(None)
258 }
259 BITRATE_KEY => {
260 let value = value.trim();
261
262 if let Some(old_value) = self.maybe_bitrate.take() {
263 Err(LineBuilderError::DuplicateKey {
264 key: BITRATE_KEY.into(),
265 old_value,
266 new_value: value.into(),
267 })
268 } else {
269 self.maybe_bitrate = Some(value.into());
270 Ok(None)
271 }
272 }
273 TOTAL_SIZE_KEY => {
274 if value == N_A {
275 self.maybe_total_size = Some(None);
276 } else {
277 if let Some(maybe_total_size) = self.maybe_total_size {
278 return Err(LineBuilderError::DuplicateKey {
279 key: TOTAL_SIZE_KEY.into(),
280 old_value: maybe_total_size
281 .map(|v| Box::<str>::from(v.to_string()))
282 .unwrap_or_else(|| N_A.into()),
283 new_value: value.into(),
284 });
285 }
286 self.maybe_total_size = Some(Some(value.parse().map_err(|error| {
287 LineBuilderError::InvalidIntegerValue {
288 key: TOTAL_SIZE_KEY,
289 value: value.into(),
290 error,
291 }
292 })?));
293 }
294
295 Ok(None)
296 }
297 OUT_TIME_US_KEY => {
298 parse_kv_i64(OUT_TIME_US_KEY, value, &mut self.maybe_out_time_us)?;
299 Ok(None)
300 }
301 OUT_TIME_MS_KEY => {
302 parse_kv_i64(OUT_TIME_MS_KEY, value, &mut self.maybe_out_time_ms)?;
303 Ok(None)
304 }
305 OUT_TIME_KEY => {
306 if let Some(old_value) = self.maybe_out_time.take() {
307 Err(LineBuilderError::DuplicateKey {
308 key: OUT_TIME_KEY.into(),
309 old_value,
310 new_value: value.into(),
311 })
312 } else {
313 self.maybe_out_time = Some(value.into());
314 Ok(None)
315 }
316 }
317 DUP_FRAMES_KEY => {
318 parse_kv_u64(DUP_FRAMES_KEY, value, &mut self.maybe_dup_frames)?;
319 Ok(None)
320 }
321 DROP_FRAMES_KEY => {
322 parse_kv_u64(DROP_FRAMES_KEY, value, &mut self.maybe_drop_frames)?;
323 Ok(None)
324 }
325 SPEED_KEY => {
326 let value = value.trim_end_matches('x').trim_end_matches('X').trim();
327
328 if value == N_A {
329 self.maybe_speed = Some(None);
330 } else {
331 if let Some(maybe_speed) = self.maybe_speed {
332 return Err(LineBuilderError::DuplicateKey {
333 key: SPEED_KEY.into(),
334 old_value: maybe_speed
335 .map(|v| Box::<str>::from(v.to_string()))
336 .unwrap_or_else(|| N_A.into()),
337 new_value: value.into(),
338 });
339 }
340 self.maybe_speed =
341 Some(Some(value.parse().map_err(|e| {
342 LineBuilderError::InvalidFloatValue(SPEED_KEY, e)
343 })?));
344 }
345
346 Ok(None)
347 }
348 PROGRESS_KEY => {
349 let event = ProgressEvent::try_from_optional_parts(
350 self.maybe_frame.take(),
351 self.maybe_fps.take(),
352 self.maybe_bitrate.take(),
353 self.maybe_total_size.take(),
354 self.maybe_out_time_us.take(),
355 self.maybe_out_time_ms.take(),
356 self.maybe_out_time.take(),
357 self.maybe_dup_frames.take(),
358 self.maybe_drop_frames.take(),
359 self.maybe_speed.take(),
360 Some(value.into()),
361 std::mem::take(&mut self.extra),
362 )?;
363
364 Ok(Some(event))
365 }
366 key => {
367 if let Some(old_value) = self.extra.insert(key.into(), value.into()) {
368 Err(LineBuilderError::DuplicateKey {
369 key: key.into(),
370 old_value,
371 new_value: value.into(),
372 })
373 } else {
374 Ok(None)
375 }
376 }
377 }
378 }
379}