@@ -131,7 +131,7 @@ class Interpolation(Enum):
131
131
#
132
132
# We might be able to avoid changing the detector interface if we just have them work directly with
133
133
# PTS and convert them back to FrameTimecodes with the same time base.
134
- @dataclass
134
+ @dataclass ( frozen = True )
135
135
class Timecode :
136
136
"""Timing information associated with a given frame."""
137
137
@@ -181,6 +181,7 @@ def __init__(
181
181
if isinstance (timecode , FrameTimecode ):
182
182
self ._framerate = timecode ._framerate if fps is None else fps
183
183
self ._frame_num = timecode ._frame_num
184
+ self ._timecode = timecode ._timecode
184
185
return
185
186
186
187
# Timecode.
@@ -411,35 +412,60 @@ def _get_other_as_frames(self, other: ty.Union[int, float, str, "FrameTimecode"]
411
412
if isinstance (other , str ):
412
413
return self ._parse_timecode_string (other )
413
414
if isinstance (other , FrameTimecode ):
414
- if self .equal_framerate (other ._framerate ):
415
+ # If comparing two FrameTimecodes, they must have the same framerate for frame-based operations.
416
+ if self ._framerate and other ._framerate and not self .equal_framerate (other ._framerate ):
417
+ raise ValueError (
418
+ "FrameTimecode instances require equal framerate for frame-based arithmetic."
419
+ )
420
+ if other ._frame_num is not None :
415
421
return other ._frame_num
416
- raise ValueError ("FrameTimecode instances require equal framerate for arithmetic." )
422
+ # If other has no frame_num, it must have a timecode. Convert to frames.
423
+ return self ._seconds_to_frames (other .seconds )
417
424
raise TypeError ("Unsupported type for performing arithmetic with FrameTimecode." )
418
425
419
426
def __eq__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
420
427
if other is None :
421
428
return False
422
- # Allow comparison with other types by converting them to frames.
423
- # If the framerate is not equal, a TypeError will be raised.
429
+ if self . _timecode :
430
+ return self . seconds == self . _get_other_as_seconds ( other )
424
431
return self .frame_num == self ._get_other_as_frames (other )
425
432
426
433
def __ne__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
427
- return not self == other
434
+ if other is None :
435
+ return True
436
+ if self ._timecode :
437
+ return self .seconds != self ._get_other_as_seconds (other )
438
+ return self .frame_num != self ._get_other_as_frames (other )
428
439
429
440
def __lt__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
441
+ if self ._timecode :
442
+ return self .seconds < self ._get_other_as_seconds (other )
430
443
return self .frame_num < self ._get_other_as_frames (other )
431
444
432
445
def __le__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
446
+ if self ._timecode :
447
+ return self .seconds <= self ._get_other_as_seconds (other )
433
448
return self .frame_num <= self ._get_other_as_frames (other )
434
449
435
450
def __gt__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
451
+ if self ._timecode :
452
+ return self .seconds > self ._get_other_as_seconds (other )
436
453
return self .frame_num > self ._get_other_as_frames (other )
437
454
438
455
def __ge__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
456
+ if self ._timecode :
457
+ return self .seconds >= self ._get_other_as_seconds (other )
439
458
return self .frame_num >= self ._get_other_as_frames (other )
440
459
441
460
def __iadd__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> "FrameTimecode" :
442
- self ._frame_num += self ._get_other_as_frames (other )
461
+ if self ._timecode :
462
+ new_seconds = self .seconds + self ._get_other_as_seconds (other )
463
+ # TODO: This is incorrect for VFR, need a better way to handle this.
464
+ # For now, we convert back to a frame number.
465
+ self ._frame_num = self ._seconds_to_frames (new_seconds )
466
+ self ._timecode = None
467
+ else :
468
+ self ._frame_num += self ._get_other_as_frames (other )
443
469
if self ._frame_num < 0 : # Required to allow adding negative seconds/frames.
444
470
self ._frame_num = 0
445
471
return self
@@ -450,7 +476,14 @@ def __add__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> "FrameTi
450
476
return to_return
451
477
452
478
def __isub__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> "FrameTimecode" :
453
- self ._frame_num -= self ._get_other_as_frames (other )
479
+ if self ._timecode :
480
+ new_seconds = self .seconds - self ._get_other_as_seconds (other )
481
+ # TODO: This is incorrect for VFR, need a better way to handle this.
482
+ # For now, we convert back to a frame number.
483
+ self ._frame_num = self ._seconds_to_frames (new_seconds )
484
+ self ._timecode = None
485
+ else :
486
+ self ._frame_num -= self ._get_other_as_frames (other )
454
487
if self ._frame_num < 0 :
455
488
self ._frame_num = 0
456
489
return self
@@ -473,7 +506,25 @@ def __str__(self) -> str:
473
506
return self .get_timecode ()
474
507
475
508
def __repr__ (self ) -> str :
509
+ if self ._timecode :
510
+ return f"{ self .get_timecode ()} [pts={ self ._timecode .pts } , time_base={ self ._timecode .time_base } ]"
476
511
return "%s [frame=%d, fps=%.3f]" % (self .get_timecode (), self ._frame_num , self ._framerate )
477
512
478
513
def __hash__ (self ) -> int :
514
+ if self ._timecode :
515
+ return hash (self ._timecode )
479
516
return self ._frame_num
517
+
518
+ def _get_other_as_seconds (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> float :
519
+ """Get the time in seconds from `other` for arithmetic operations."""
520
+ if isinstance (other , int ):
521
+ return float (other ) / self ._framerate
522
+ if isinstance (other , float ):
523
+ return other
524
+ if isinstance (other , str ):
525
+ # This is not ideal, but we need a framerate to parse strings.
526
+ # We create a temporary FrameTimecode to do this.
527
+ return FrameTimecode (timecode = other , fps = self ._framerate ).seconds
528
+ if isinstance (other , FrameTimecode ):
529
+ return other .seconds
530
+ raise TypeError ("Unsupported type for performing arithmetic with FrameTimecode." )
0 commit comments