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).unwrap()
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.interpolate(25).unwrap().unwrap();
329 assert_eq!(result, tc);
330 }
331
332 #[test]
333 fn test_interpolate_linear_between() {
334 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
335 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
336 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 2, 0), 50));
337 let result = interp.interpolate(10).unwrap().unwrap();
339 assert_eq!(result.hours, 0);
340 assert_eq!(result.minutes, 0);
341 assert_eq!(result.seconds, 0);
342 assert_eq!(result.frames, 10);
343 }
344
345 #[test]
346 fn test_interpolate_forward_extrapolation() {
347 let mut interp =
348 TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear).with_max_gap(500);
349 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
350 let result = interp.interpolate(30).unwrap().unwrap();
352 assert_eq!(result.seconds, 1);
353 assert_eq!(result.frames, 5);
354 }
355
356 #[test]
357 fn test_interpolate_empty() {
358 let interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
359 assert!(interp.interpolate(10).is_none());
360 }
361
362 #[test]
363 fn test_interpolate_nearest() {
364 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Nearest);
365 let tc0 = make_tc(0, 0, 0, 0);
366 let tc1 = make_tc(0, 0, 2, 0);
367 interp.add_ref(TcRefPoint::new(tc0, 0));
368 interp.add_ref(TcRefPoint::new(tc1, 50));
369 let result = interp.interpolate(20).unwrap().unwrap();
371 assert_eq!(result, tc0);
372 let result = interp.interpolate(30).unwrap().unwrap();
374 assert_eq!(result, tc1);
375 }
376
377 #[test]
378 fn test_interpolate_forward_only() {
379 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::ForwardOnly);
380 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 10));
381 assert!(interp.interpolate(5).is_none());
383 let result = interp.interpolate(15).unwrap().unwrap();
385 assert_eq!(result.frames, 5);
386 }
387
388 #[test]
389 fn test_max_gap_exceeded() {
390 let mut interp =
391 TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear).with_max_gap(10);
392 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
393 assert!(interp.interpolate(20).is_none());
395 }
396
397 #[test]
398 fn test_validate_consistency_ok() {
399 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
400 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
401 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 1, 0), 25));
402 let issues = interp.validate_consistency();
403 assert!(issues.is_empty());
404 }
405
406 #[test]
407 fn test_validate_consistency_mismatch() {
408 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
409 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
410 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 1, 0), 30));
412 let issues = interp.validate_consistency();
413 assert_eq!(issues.len(), 1);
414 assert_eq!(issues[0].expected_delta, 30);
415 assert_eq!(issues[0].actual_tc_delta, 25);
416 }
417
418 #[test]
419 fn test_batch_interpolate() {
420 let mut interp =
421 TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear).with_max_gap(500);
422 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
423 let positions = vec![0, 5, 10, 25];
424 let results = batch_interpolate(&interp, &positions);
425 assert_eq!(results.len(), 4);
426 assert!(results[0].reliable);
427 assert_eq!(results[0].timecode.unwrap().frames, 0);
428 assert_eq!(results[2].timecode.unwrap().frames, 10);
429 }
430
431 #[test]
432 fn test_clear() {
433 let mut interp = TcInterpolator::new(FrameRate::Fps25, InterpolationMode::Linear);
434 interp.add_ref(TcRefPoint::new(make_tc(0, 0, 0, 0), 0));
435 assert_eq!(interp.ref_count(), 1);
436 interp.clear();
437 assert_eq!(interp.ref_count(), 0);
438 }
439}