oximedia_transcode/
encoding_log.rs1#![allow(dead_code)]
2use std::time::{Duration, SystemTime};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub enum EncodingEvent {
9 Warning(String),
11 Error(String),
13 Progress(u8),
15 Phase(String),
17 Info(String),
19}
20
21impl EncodingEvent {
22 #[must_use]
24 pub fn is_error(&self) -> bool {
25 matches!(self, Self::Error(_))
26 }
27
28 #[must_use]
30 pub fn is_warning(&self) -> bool {
31 matches!(self, Self::Warning(_))
32 }
33
34 #[must_use]
36 pub fn is_progress(&self) -> bool {
37 matches!(self, Self::Progress(_))
38 }
39
40 #[must_use]
42 pub fn message(&self) -> Option<&str> {
43 match self {
44 Self::Warning(m) | Self::Error(m) | Self::Phase(m) | Self::Info(m) => Some(m),
45 Self::Progress(_) => None,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct EncodingLogEntry {
53 pub event: EncodingEvent,
55 pub timestamp: SystemTime,
57 pub elapsed: Duration,
59}
60
61impl EncodingLogEntry {
62 #[must_use]
64 pub fn new(event: EncodingEvent, timestamp: SystemTime, elapsed: Duration) -> Self {
65 Self {
66 event,
67 timestamp,
68 elapsed,
69 }
70 }
71
72 #[must_use]
74 pub fn is_recent(&self, window: Duration) -> bool {
75 self.timestamp
76 .elapsed()
77 .map(|age| age < window)
78 .unwrap_or(false)
79 }
80}
81
82#[derive(Debug, Default)]
84pub struct EncodingLog {
85 entries: Vec<EncodingLogEntry>,
86 session_start: Option<SystemTime>,
87}
88
89impl EncodingLog {
90 #[must_use]
92 pub fn new() -> Self {
93 Self {
94 entries: Vec::new(),
95 session_start: Some(SystemTime::now()),
96 }
97 }
98
99 pub fn record(&mut self, event: EncodingEvent) {
101 let now = SystemTime::now();
102 let elapsed = self
103 .session_start
104 .and_then(|s| now.duration_since(s).ok())
105 .unwrap_or(Duration::ZERO);
106 self.entries
107 .push(EncodingLogEntry::new(event, now, elapsed));
108 }
109
110 #[must_use]
112 pub fn errors(&self) -> Vec<&EncodingLogEntry> {
113 self.entries.iter().filter(|e| e.event.is_error()).collect()
114 }
115
116 #[must_use]
118 pub fn warnings(&self) -> Vec<&EncodingLogEntry> {
119 self.entries
120 .iter()
121 .filter(|e| e.event.is_warning())
122 .collect()
123 }
124
125 #[must_use]
127 pub fn progress_events(&self) -> Vec<&EncodingLogEntry> {
128 self.entries
129 .iter()
130 .filter(|e| e.event.is_progress())
131 .collect()
132 }
133
134 #[must_use]
136 pub fn all_entries(&self) -> &[EncodingLogEntry] {
137 &self.entries
138 }
139
140 #[must_use]
142 pub fn len(&self) -> usize {
143 self.entries.len()
144 }
145
146 #[must_use]
148 pub fn is_empty(&self) -> bool {
149 self.entries.is_empty()
150 }
151
152 #[must_use]
154 pub fn has_errors(&self) -> bool {
155 self.entries.iter().any(|e| e.event.is_error())
156 }
157
158 #[must_use]
160 pub fn last_progress_pct(&self) -> Option<u8> {
161 self.entries.iter().rev().find_map(|e| {
162 if let EncodingEvent::Progress(p) = e.event {
163 Some(p)
164 } else {
165 None
166 }
167 })
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_encoding_event_is_error_true() {
177 let e = EncodingEvent::Error("oops".into());
178 assert!(e.is_error());
179 }
180
181 #[test]
182 fn test_encoding_event_is_error_false() {
183 assert!(!EncodingEvent::Warning("w".into()).is_error());
184 assert!(!EncodingEvent::Progress(50).is_error());
185 }
186
187 #[test]
188 fn test_encoding_event_is_warning() {
189 assert!(EncodingEvent::Warning("low bitrate".into()).is_warning());
190 assert!(!EncodingEvent::Info("ok".into()).is_warning());
191 }
192
193 #[test]
194 fn test_encoding_event_is_progress() {
195 assert!(EncodingEvent::Progress(75).is_progress());
196 assert!(!EncodingEvent::Error("x".into()).is_progress());
197 }
198
199 #[test]
200 fn test_encoding_event_message() {
201 let e = EncodingEvent::Error("disk full".into());
202 assert_eq!(e.message(), Some("disk full"));
203 let p = EncodingEvent::Progress(50);
204 assert!(p.message().is_none());
205 }
206
207 #[test]
208 fn test_log_record_and_len() {
209 let mut log = EncodingLog::new();
210 log.record(EncodingEvent::Info("start".into()));
211 log.record(EncodingEvent::Progress(25));
212 assert_eq!(log.len(), 2);
213 }
214
215 #[test]
216 fn test_log_errors() {
217 let mut log = EncodingLog::new();
218 log.record(EncodingEvent::Error("bad codec".into()));
219 log.record(EncodingEvent::Warning("slow".into()));
220 assert_eq!(log.errors().len(), 1);
221 }
222
223 #[test]
224 fn test_log_warnings() {
225 let mut log = EncodingLog::new();
226 log.record(EncodingEvent::Warning("W1".into()));
227 log.record(EncodingEvent::Warning("W2".into()));
228 log.record(EncodingEvent::Error("E".into()));
229 assert_eq!(log.warnings().len(), 2);
230 }
231
232 #[test]
233 fn test_log_progress_events() {
234 let mut log = EncodingLog::new();
235 log.record(EncodingEvent::Progress(10));
236 log.record(EncodingEvent::Progress(50));
237 log.record(EncodingEvent::Info("info".into()));
238 assert_eq!(log.progress_events().len(), 2);
239 }
240
241 #[test]
242 fn test_log_has_errors_false() {
243 let mut log = EncodingLog::new();
244 log.record(EncodingEvent::Warning("w".into()));
245 assert!(!log.has_errors());
246 }
247
248 #[test]
249 fn test_log_has_errors_true() {
250 let mut log = EncodingLog::new();
251 log.record(EncodingEvent::Error("fatal".into()));
252 assert!(log.has_errors());
253 }
254
255 #[test]
256 fn test_log_last_progress_pct_none() {
257 let log = EncodingLog::new();
258 assert!(log.last_progress_pct().is_none());
259 }
260
261 #[test]
262 fn test_log_last_progress_pct_some() {
263 let mut log = EncodingLog::new();
264 log.record(EncodingEvent::Progress(25));
265 log.record(EncodingEvent::Progress(75));
266 assert_eq!(log.last_progress_pct(), Some(75));
267 }
268
269 #[test]
270 fn test_log_is_empty() {
271 let log = EncodingLog::new();
272 assert!(log.is_empty());
273 }
274
275 #[test]
276 fn test_entry_elapsed_non_negative() {
277 let mut log = EncodingLog::new();
278 log.record(EncodingEvent::Info("hi".into()));
279 assert!(!log.is_empty());
280 let entry = &log.all_entries()[0];
281 assert!(entry.elapsed < Duration::from_secs(5));
283 }
284}