1#![allow(dead_code)]
2use crate::{FrameRate, Timecode, TimecodeError};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct TcRefPoint {
13 pub timecode: Timecode,
15 pub position: u64,
17}
18
19impl TcRefPoint {
20 pub fn new(timecode: Timecode, position: u64) -> Self {
22 Self { timecode, position }
23 }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum InterpolationMode {
29 Linear,
31 Nearest,
33 ForwardOnly,
35}
36
37#[derive(Debug, Clone)]
39pub struct TcInterpolator {
40 refs: Vec<TcRefPoint>,
42 frame_rate: FrameRate,
44 mode: InterpolationMode,
46 max_gap: u64,
48}
49
50impl TcInterpolator {
51 pub fn new(frame_rate: FrameRate, mode: InterpolationMode) -> Self {
53 Self {
54 refs: Vec::new(),
55 frame_rate,
56 mode,
57 max_gap: 300, }
59 }
60
61 pub fn with_max_gap(mut self, gap: u64) -> Self {
63 self.max_gap = gap;
64 self
65 }
66
67 pub fn add_ref(&mut self, point: TcRefPoint) {
69 let idx = self
70 .refs
71 .binary_search_by_key(&point.position, |r| r.position)
72 .unwrap_or_else(|i| i);
73 self.refs.insert(idx, point);
74 }
75
76 pub fn ref_count(&self) -> usize {
78 self.refs.len()
79 }
80
81 pub fn clear(&mut self) {
83 self.refs.clear();
84 }
85
86 pub fn frame_rate(&self) -> FrameRate {
88 self.frame_rate
89 }
90
91 pub fn mode(&self) -> InterpolationMode {
93 self.mode
94 }
95
96 pub fn max_gap(&self) -> u64 {
98 self.max_gap
99 }
100
101 pub fn interpolate(&self, position: u64) -> Option<Result<Timecode, TimecodeError>> {
106 if self.refs.is_empty() {
107 return None;
108 }
109
110 match self.mode {
111 InterpolationMode::Linear => self.interpolate_linear(position),
112 InterpolationMode::Nearest => self.interpolate_nearest(position),
113 InterpolationMode::ForwardOnly => self.interpolate_forward(position),
114 }
115 }
116
117 fn interpolate_linear(&self, position: u64) -> Option<Result<Timecode, TimecodeError>> {
119 if let Ok(idx) = self.refs.binary_search_by_key(&position, |r| r.position) {
121 return Some(Ok(self.refs[idx].timecode));
122 }
123
124 let idx = self
126 .refs
127 .binary_search_by_key(&position, |r| r.position)
128 .unwrap_or_else(|i| i);
129
130 if idx == 0 {
131 let first = &self.refs[0];
133 let delta = first.position.saturating_sub(position);
134 if delta > self.max_gap {
135 return None;
136 }
137 let base_frames = first.timecode.to_frames();
138 let target_frames = base_frames.saturating_sub(delta);
139 Some(Timecode::from_frames(target_frames, self.frame_rate))
140 } else if idx >= self.refs.len() {
141 let last = &self.refs[self.refs.len() - 1];
143 let delta = position.saturating_sub(last.position);
144 if delta > self.max_gap {
145 return None;
146 }
147 let base_frames = last.timecode.to_frames();
148 Some(Timecode::from_frames(base_frames + delta, self.frame_rate))
149 } else {
150 let prev = &self.refs[idx - 1];
152 let delta = position - prev.position;
153 if delta > self.max_gap {
154 return None;
155 }
156 let base_frames = prev.timecode.to_frames();
157 Some(Timecode::from_frames(base_frames + delta, self.frame_rate))
158 }
159 }
160
161 fn interpolate_nearest(&self, position: u64) -> Option<Result<Timecode, TimecodeError>> {
163 let idx = self
164 .refs
165 .binary_search_by_key(&position, |r| r.position)
166 .unwrap_or_else(|i| i);
167
168 let candidate = if idx == 0 {
169 &self.refs[0]
170 } else if idx >= self.refs.len() {
171 &self.refs[self.refs.len() - 1]
172 } else {
173 let dist_prev = position - self.refs[idx - 1].position;
174 let dist_next = self.refs[idx].position - position;
175 if dist_prev <= dist_next {
176 &self.refs[idx - 1]
177 } else {
178 &self.refs[idx]
179 }
180 };
181
182 let gap = position.abs_diff(candidate.position);
183
184 if gap > self.max_gap {
185 return None;
186 }
187
188 Some(Ok(candidate.timecode))
189 }
190
191 fn interpolate_forward(&self, position: u64) -> Option<Result<Timecode, TimecodeError>> {
193 if let Ok(idx) = self.refs.binary_search_by_key(&position, |r| r.position) {
194 return Some(Ok(self.refs[idx].timecode));
195 }
196
197 let idx = self
198 .refs
199 .binary_search_by_key(&position, |r| r.position)
200 .unwrap_or_else(|i| i);
201
202 if idx == 0 {
203 return None; }
205
206 let prev = &self.refs[idx - 1];
207 let delta = position - prev.position;
208 if delta > self.max_gap {
209 return None;
210 }
211 let base_frames = prev.timecode.to_frames();
212 Some(Timecode::from_frames(base_frames + delta, self.frame_rate))
213 }
214
215 pub fn validate_consistency(&self) -> Vec<ConsistencyIssue> {
218 let mut issues = Vec::new();
219 for pair in self.refs.windows(2) {
220 let (a, b) = (&pair[0], &pair[1]);
221 let pos_delta = b.position - a.position;
222 let tc_delta = b
223 .timecode
224 .to_frames()
225 .saturating_sub(a.timecode.to_frames());
226 if pos_delta != tc_delta {
227 issues.push(ConsistencyIssue {
228 position_a: a.position,
229 position_b: b.position,
230 expected_delta: pos_delta,
231 actual_tc_delta: tc_delta,
232 });
233 }
234 }
235 issues
236 }
237}
238
239#[derive(Debug, Clone, PartialEq)]
241pub struct ConsistencyIssue {
242 pub position_a: u64,
244 pub position_b: u64,
246 pub expected_delta: u64,
248 pub actual_tc_delta: u64,
250}
251
252#[derive(Debug, Clone)]
254pub struct BatchInterpolationResult {
255 pub position: u64,
257 pub timecode: Option<Timecode>,
259 pub reliable: bool,
261}
262
263pub fn batch_interpolate(
265 interp: &TcInterpolator,
266 positions: &[u64],
267) -> Vec<BatchInterpolationResult> {
268 positions
269 .iter()
270 .map(|&pos| {
271 let result = interp.interpolate(pos);
272 let (tc, reliable) = match result {
273 Some(Ok(tc)) => (Some(tc), true),
274 Some(Err(_)) => (None, false),
275 None => (None, false),
276 };
277 BatchInterpolationResult {
278 position: pos,
279 timecode: tc,
280 reliable,
281 }
282 })
283 .collect()
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 fn make_tc(h: u8, m: u8, s: u8, f: u8) -> Timecode {
291 Timecode::new(h, m, s, f, FrameRate::Fps25).expect("valid timecode")
292 }
293
294 #[test]
295 fn test_ref_point_creation() {
296 let tc = make_tc(1, 0, 0, 0);
297 let rp = TcRefPoint::new(tc, 90000);
298 assert_eq!(rp.position, 90000);
299 assert_eq!(rp.timecode, tc);
300 }
301
302 #[test]
303 fn test_interpolator_creation() {
304 let interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
305 assert_eq!(interp.ref_count(), 0);
306 assert_eq!(interp.mode(), InterpolationMode::Linear);
307 assert_eq!(interp.max_gap(), 300);
308 }
309
310 #[test]
311 fn test_add_ref_sorted() {
312 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
313 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 1, 0), 25));
314 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
315 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 2, 0), 50));
316 assert_eq!(interp.ref_count(), 3);
317 assert_eq!(interp.refs[0].position, 0);
319 assert_eq!(interp.refs[1].position, 25);
320 assert_eq!(interp.refs[2].position, 50);
321 }
322
323 #[test]
324 fn test_interpolate_exact_match() {
325 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
326 let tc = make_tc(0, 0, 1, 0);
327 interp.add_ref(TcRefPoint::new(tc, 25));
328 let result = interp
329 .interpolate(25)
330 .expect("interpolation should succeed")
331 .expect("interpolation should succeed");
332 assert_eq!(result, tc);
333 }
334
335 #[test]
336 fn test_interpolate_linear_between() {
337 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
338 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
339 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 2, 0), 50));
340 let result = interp
342 .interpolate(10)
343 .expect("interpolation should succeed")
344 .expect("interpolation should succeed");
345 assert_eq!(result.hours, 0);
346 assert_eq!(result.minutes, 0);
347 assert_eq!(result.seconds, 0);
348 assert_eq!(result.frames, 10);
349 }
350
351 #[test]
352 fn test_interpolate_forward_extrapolation() {
353 let mut interp =
354 TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear).with_max_gap(500);
355 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
356 let result = interp
358 .interpolate(30)
359 .expect("interpolation should succeed")
360 .expect("interpolation should succeed");
361 assert_eq!(result.seconds, 1);
362 assert_eq!(result.frames, 5);
363 }
364
365 #[test]
366 fn test_interpolate_empty() {
367 let interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
368 assert!(interp.interpolate(10).is_none());
369 }
370
371 #[test]
372 fn test_interpolate_nearest() {
373 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Nearest);
374 let tc0 = make_tc(0, 0, 0, 0);
375 let tc1 = make_tc(0, 0, 2, 0);
376 interp.add_ref(TcRefPoint::new(tc0, 0));
377 interp.add_ref(TcRefPoint::new(tc1, 50));
378 let result = interp
380 .interpolate(20)
381 .expect("interpolation should succeed")
382 .expect("interpolation should succeed");
383 assert_eq!(result, tc0);
384 let result = interp
386 .interpolate(30)
387 .expect("interpolation should succeed")
388 .expect("interpolation should succeed");
389 assert_eq!(result, tc1);
390 }
391
392 #[test]
393 fn test_interpolate_forward_only() {
394 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::ForwardOnly);
395 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 10));
396 assert!(interp.interpolate(5).is_none());
398 let result = interp
400 .interpolate(15)
401 .expect("interpolation should succeed")
402 .expect("interpolation should succeed");
403 assert_eq!(result.frames, 5);
404 }
405
406 #[test]
407 fn test_max_gap_exceeded() {
408 let mut interp =
409 TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear).with_max_gap(10);
410 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
411 assert!(interp.interpolate(20).is_none());
413 }
414
415 #[test]
416 fn test_validate_consistency_ok() {
417 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
418 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
419 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 1, 0), 25));
420 let issues = interp.validate_consistency();
421 assert!(issues.is_empty());
422 }
423
424 #[test]
425 fn test_validate_consistency_mismatch() {
426 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
427 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
428 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 1, 0), 30));
430 let issues = interp.validate_consistency();
431 assert_eq!(issues.len(), 1);
432 assert_eq!(issues[0].expected_delta, 30);
433 assert_eq!(issues[0].actual_tc_delta, 25);
434 }
435
436 #[test]
437 fn test_batch_interpolate() {
438 let mut interp =
439 TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear).with_max_gap(500);
440 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
441 let positions = vec![0, 5, 10, 25];
442 let results = batch_interpolate(&interp, &positions);
443 assert_eq!(results.len(), 4);
444 assert!(results[0].reliable);
445 assert_eq!(
446 results[0].timecode.expect("timecode should exist").frames,
447 0
448 );
449 assert_eq!(
450 results[2].timecode.expect("timecode should exist").frames,
451 10
452 );
453 }
454
455 #[test]
456 fn test_clear() {
457 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
458 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
459 assert_eq!(interp.ref_count(), 1);
460 interp.clear();
461 assert_eq!(interp.ref_count(), 0);
462 }
463}