@@ -131,7 +131,7 @@ class Interpolation(Enum):
131131#
132132# We might be able to avoid changing the detector interface if we just have them work directly with
133133# PTS and convert them back to FrameTimecodes with the same time base.
134- @dataclass
134+ @dataclass ( frozen = True )
135135class Timecode :
136136 """Timing information associated with a given frame."""
137137
@@ -181,6 +181,7 @@ def __init__(
181181 if isinstance (timecode , FrameTimecode ):
182182 self ._framerate = timecode ._framerate if fps is None else fps
183183 self ._frame_num = timecode ._frame_num
184+ self ._timecode = timecode ._timecode
184185 return
185186
186187 # Timecode.
@@ -411,35 +412,60 @@ def _get_other_as_frames(self, other: ty.Union[int, float, str, "FrameTimecode"]
411412 if isinstance (other , str ):
412413 return self ._parse_timecode_string (other )
413414 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 :
415421 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 )
417424 raise TypeError ("Unsupported type for performing arithmetic with FrameTimecode." )
418425
419426 def __eq__ (self , other : ty .Union [int , float , str , "FrameTimecode" ]) -> bool :
420427 if other is None :
421428 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 )
424431 return self .frame_num == self ._get_other_as_frames (other )
425432
426433 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 )
428439
429440 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 )
430443 return self .frame_num < self ._get_other_as_frames (other )
431444
432445 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 )
433448 return self .frame_num <= self ._get_other_as_frames (other )
434449
435450 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 )
436453 return self .frame_num > self ._get_other_as_frames (other )
437454
438455 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 )
439458 return self .frame_num >= self ._get_other_as_frames (other )
440459
441460 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 )
443469 if self ._frame_num < 0 : # Required to allow adding negative seconds/frames.
444470 self ._frame_num = 0
445471 return self
@@ -450,7 +476,14 @@ def __add__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> "FrameTi
450476 return to_return
451477
452478 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 )
454487 if self ._frame_num < 0 :
455488 self ._frame_num = 0
456489 return self
@@ -473,7 +506,25 @@ def __str__(self) -> str:
473506 return self .get_timecode ()
474507
475508 def __repr__ (self ) -> str :
509+ if self ._timecode :
510+ return f"{ self .get_timecode ()} [pts={ self ._timecode .pts } , time_base={ self ._timecode .time_base } ]"
476511 return "%s [frame=%d, fps=%.3f]" % (self .get_timecode (), self ._frame_num , self ._framerate )
477512
478513 def __hash__ (self ) -> int :
514+ if self ._timecode :
515+ return hash (self ._timecode )
479516 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