Skip to content

Commit 0464ccf

Browse files
committed
[timecode] Fix some failing timecode tests after updated arithmetic
1 parent 684e4b4 commit 0464ccf

File tree

3 files changed

+34
-28
lines changed

3 files changed

+34
-28
lines changed

scenedetect/common.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,10 @@ def __init__(
210210
# Process the timecode value, storing it as an exact number of frames.
211211
if isinstance(timecode, str):
212212
self._seconds = self._timecode_to_seconds(timecode)
213-
self._frame_num = self._seconds_to_frames(self._timecode_to_seconds(timecode))
213+
self._frame_num = self._seconds_to_frames(self._seconds)
214214
else:
215215
self._frame_num = self._parse_timecode_number(timecode)
216+
self._seconds = timecode if isinstance(timecode, float) else None
216217

217218
@property
218219
def frame_num(self) -> ty.Optional[int]:
@@ -379,14 +380,14 @@ def _timecode_to_seconds(self, input: str) -> float:
379380
Raises:
380381
ValueError: Value could not be parsed correctly.
381382
"""
382-
assert self._framerate is not None
383+
assert self._framerate is not None and self._framerate > MAX_FPS_DELTA
383384
input = input.strip()
384385
# Exact number of frames N
385386
if input.isdigit():
386387
timecode = int(input)
387388
if timecode < 0:
388389
raise ValueError("Timecode frame number must be positive.")
389-
return timecode * self.framerate
390+
return timecode / self.framerate
390391
# Timecode in string format 'HH:MM:SS[.nnn]' or 'MM:SS[.nnn]'
391392
elif input.find(":") >= 0:
392393
values = input.split(":")
@@ -438,33 +439,45 @@ def _get_other_as_frames(self, other: ty.Union[int, float, str, "FrameTimecode"]
438439
def __eq__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> bool:
439440
if other is None:
440441
return False
442+
if _compare_as_fixed(self, other):
443+
return self._frame_num == other._frame_num
441444
if self._timecode or self._seconds is not None:
442445
return self.seconds == self._get_other_as_seconds(other)
443446
return self.frame_num == self._get_other_as_frames(other)
444447

445448
def __ne__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> bool:
446449
if other is None:
447450
return True
451+
if _compare_as_fixed(self, other):
452+
return self._frame_num != other._frame_num
448453
if self._timecode or self._seconds is not None:
449454
return self.seconds != self._get_other_as_seconds(other)
450455
return self.frame_num != self._get_other_as_frames(other)
451456

452457
def __lt__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> bool:
458+
if _compare_as_fixed(self, other):
459+
return self._frame_num < other._frame_num
453460
if self._timecode or self._seconds is not None:
454461
return self.seconds < self._get_other_as_seconds(other)
455462
return self.frame_num < self._get_other_as_frames(other)
456463

457464
def __le__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> bool:
465+
if _compare_as_fixed(self, other):
466+
return self._frame_num <= other._frame_num
458467
if self._timecode or self._seconds is not None:
459468
return self.seconds <= self._get_other_as_seconds(other)
460469
return self.frame_num <= self._get_other_as_frames(other)
461470

462471
def __gt__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> bool:
472+
if _compare_as_fixed(self, other):
473+
return self._frame_num > other._frame_num
463474
if self._timecode or self._seconds is not None:
464475
return self.seconds > self._get_other_as_seconds(other)
465476
return self.frame_num > self._get_other_as_frames(other)
466477

467478
def __ge__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> bool:
479+
if _compare_as_fixed(self, other):
480+
return self._frame_num >= other._frame_num
468481
if self._timecode or self._seconds is not None:
469482
return self.seconds >= self._get_other_as_seconds(other)
470483
return self.frame_num >= self._get_other_as_frames(other)
@@ -495,11 +508,14 @@ def __iadd__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> "FrameT
495508
self._frame_num = None
496509
return self
497510

498-
if self._seconds and other._seconds:
511+
other_has_seconds = isinstance(other, FrameTimecode) and other._seconds
512+
if self._seconds is not None and other_has_seconds:
499513
self._seconds = max(0, self._seconds + other._seconds)
500514
return self
501515

502516
self._frame_num = max(0, self._frame_num + self._get_other_as_frames(other))
517+
if self._seconds is not None:
518+
self._seconds = max(0.0, self._seconds + self._get_other_as_seconds(other))
503519
return self
504520

505521
def __add__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> "FrameTimecode":
@@ -533,11 +549,14 @@ def __isub__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> "FrameT
533549
self._frame_num = None
534550
return self
535551

536-
if self._seconds and other._seconds:
552+
other_has_seconds = isinstance(other, FrameTimecode) and other._seconds
553+
if self._seconds is not None and other_has_seconds:
537554
self._seconds = max(0, self._seconds - other._seconds)
538555
return self
539556

540557
self._frame_num = max(0, self._frame_num - self._get_other_as_frames(other))
558+
if self._seconds is not None:
559+
self._seconds = max(0.0, self._seconds - self._get_other_as_seconds(other))
541560
return self
542561

543562
def __sub__(self, other: ty.Union[int, float, str, "FrameTimecode"]) -> "FrameTimecode":
@@ -585,3 +604,7 @@ def _get_other_as_seconds(self, other: ty.Union[int, float, str, "FrameTimecode"
585604
if isinstance(other, FrameTimecode):
586605
return other.seconds
587606
raise TypeError("Unsupported type for performing arithmetic with FrameTimecode.")
607+
608+
609+
def _compare_as_fixed(a: FrameTimecode, b: ty.Any) -> bool:
610+
return a._framerate is not None and isinstance(b, FrameTimecode) and b._framerate is not None

tests/test_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ def test_cli_load_scenes_with_time_frames():
691691

692692

693693
def test_cli_load_scenes_round_trip():
694-
"""Verify we can use `load-scenes` with the `time` command and get the desired output."""
694+
"""Verify we can use `load-scenes` and get the same scenes as output with `list-scenes`."""
695695
scenes_csv = """
696696
Scene Number,Start Frame
697697
1,49

tests/test_timecode.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ def test_get_timecode():
187187

188188
assert FrameTimecode(timecode="00:00:02.0000", fps=1).get_timecode() == "00:00:02.000"
189189
assert FrameTimecode(timecode="00:00:00.5", fps=10).get_timecode() == "00:00:00.500"
190-
assert FrameTimecode(timecode="00:00:01.501", fps=10).get_timecode() == "00:00:01.500"
190+
# If a value is provided in seconds, we store that value internally now.
191+
assert FrameTimecode(timecode="00:00:01.501", fps=10).get_timecode() == "00:00:01.501"
191192
assert FrameTimecode(timecode="00:01:00.000", fps=1).get_timecode() == "00:01:00.000"
192193

193194

@@ -199,12 +200,6 @@ def test_equality():
199200
assert x == FrameTimecode(timecode=1.0, fps=10.0)
200201
assert x != FrameTimecode(timecode=10.0, fps=10.0)
201202
assert x != FrameTimecode(timecode=10.0, fps=10.0)
202-
# Comparing FrameTimecodes with different framerates raises a TypeError.
203-
with pytest.raises(ValueError):
204-
assert x == FrameTimecode(timecode=1.0, fps=100.0)
205-
with pytest.raises(ValueError):
206-
assert x == FrameTimecode(timecode=1.0, fps=10.1)
207-
208203
assert x == FrameTimecode(x)
209204
assert x == FrameTimecode(1.0, x)
210205
assert x == FrameTimecode(10, x)
@@ -232,44 +227,32 @@ def test_equality():
232227

233228
assert FrameTimecode(timecode="00:00:00.5", fps=10) == "00:00:00.500"
234229
assert FrameTimecode(timecode="00:00:01.500", fps=10) == "00:00:01.500"
235-
assert FrameTimecode(timecode="00:00:01.500", fps=10) == "00:00:01.501"
236-
assert FrameTimecode(timecode="00:00:01.500", fps=10) == "00:00:01.502"
237-
assert FrameTimecode(timecode="00:00:01.500", fps=10) == "00:00:01.508"
238-
assert FrameTimecode(timecode="00:00:01.500", fps=10) == "00:00:01.509"
239-
assert FrameTimecode(timecode="00:00:01.519", fps=10) == "00:00:01.510"
240230

241231

242232
def test_addition():
243233
"""Test FrameTimecode addition (+/+=, __add__/__iadd__) operator."""
244234
x = FrameTimecode(timecode=1.0, fps=10.0)
245235
assert x + 1 == FrameTimecode(timecode=1.1, fps=10.0)
246236
assert x + 1 == FrameTimecode(1.1, x)
237+
assert x + 10 == "00:00:02.000", str(x + 10)
247238
assert x + 10 == 20
248239
assert x + 10 == 2.0
249-
250240
assert x + 10 == "00:00:02.000"
251241

252-
with pytest.raises(ValueError):
253-
assert FrameTimecode("00:00:02.000", fps=20.0) == x + 10
254-
255242

256243
def test_subtraction():
257244
"""Test FrameTimecode subtraction (-/-=, __sub__) operator."""
258245
x = FrameTimecode(timecode=1.0, fps=10.0)
259246
assert (x - 1) == FrameTimecode(timecode=0.9, fps=10.0)
260247
assert x - 2 == FrameTimecode(0.8, x)
261248
assert x - 10 == FrameTimecode(0.0, x)
262-
# TODO(v1.0): Allow negative values
249+
# TODO(v1.0): Allow negative values. For now we clamp.
263250
assert x - 11 == FrameTimecode(0.0, x)
264251
assert x - 100 == FrameTimecode(0.0, x)
265-
266252
assert x - 1.0 == FrameTimecode(0.0, x)
267253
assert x - 100.0 == FrameTimecode(0.0, x)
268-
269254
assert x - 1 == FrameTimecode(timecode=0.9, fps=10.0)
270-
271-
with pytest.raises(ValueError):
272-
assert FrameTimecode("00:00:02.000", fps=20.0) == x - 10
255+
assert FrameTimecode("00:00:00.000", fps=20.0) == x - 10
273256

274257

275258
@pytest.mark.parametrize("frame_num,fps", [(1, 1), (61, 14), (29, 25), (126, 24000 / 1001.0)])

0 commit comments

Comments
 (0)