Skip to content

Commit ef1f39b

Browse files
committed
[common] Allow PTS mode to pass scanning loop
Most commands still fail due to missing arithmetic operators, but a quick spot check by overriding or skipping a few let me run `split-video` successfully. That will be fixed in a follow up however.
1 parent 727f94b commit ef1f39b

File tree

1 file changed

+59
-8
lines changed

1 file changed

+59
-8
lines changed

scenedetect/common.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
135135
class 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

Comments
 (0)