From c4fb0ed8d8b5621d8f46ff54a33cf918f245569c Mon Sep 17 00:00:00 2001 From: moi15moi Date: Thu, 2 Jan 2025 21:58:13 -0500 Subject: [PATCH 1/2] Add __eq__ __hash__ to ABCTimestamps --- tests/test_fps_timestamps.py | 43 +++++++++++++ tests/test_text_file_timestamps.py | 78 ++++++++++++++++++++++++ tests/test_video_timestamps.py | 62 +++++++++++++++++++ video_timestamps/abc_timestamps.py | 10 +++ video_timestamps/fps_timestamps.py | 20 ++++++ video_timestamps/text_file_timestamps.py | 23 +++++++ video_timestamps/video_timestamps.py | 23 +++++++ 7 files changed, 259 insertions(+) diff --git a/tests/test_fps_timestamps.py b/tests/test_fps_timestamps.py index 0fed083..8117b5c 100644 --- a/tests/test_fps_timestamps.py +++ b/tests/test_fps_timestamps.py @@ -37,3 +37,46 @@ def test_invalid_fps() -> None: with pytest.raises(ValueError) as exc_info: FPSTimestamps(rounding_method, time_scale, fps) assert str(exc_info.value) == "Parameter ``fps`` must be higher than 0." + + +def test__eq__and__hash__() -> None: + fps_1 = FPSTimestamps(RoundingMethod.ROUND, Fraction(1000), Fraction(24000, 1001), 0) + fps_2 = FPSTimestamps(RoundingMethod.ROUND, Fraction(1000), Fraction(24000, 1001), 0) + assert fps_1 == fps_2 + assert hash(fps_1) == hash(fps_2) + + fps_3 = FPSTimestamps( + RoundingMethod.FLOOR, # different + Fraction(1000), + Fraction(24000, 1001), + 0 + ) + assert fps_1 != fps_3 + assert hash(fps_1) != hash(fps_3) + + fps_4 = FPSTimestamps( + RoundingMethod.ROUND, + Fraction(1001), # different + Fraction(24000, 1001), + 0 + ) + assert fps_1 != fps_4 + assert hash(fps_1) != hash(fps_4) + + fps_5 = FPSTimestamps( + RoundingMethod.ROUND, + Fraction(1000), + Fraction(1), # different + 0 + ) + assert fps_1 != fps_5 + assert hash(fps_1) != hash(fps_5) + + fps_6 = FPSTimestamps( + RoundingMethod.ROUND, + Fraction(1000), + Fraction(24000, 1001), + 10 # different + ) + assert fps_1 != fps_6 + assert hash(fps_1) != hash(fps_6) diff --git a/tests/test_text_file_timestamps.py b/tests/test_text_file_timestamps.py index 5b27035..177b339 100644 --- a/tests/test_text_file_timestamps.py +++ b/tests/test_text_file_timestamps.py @@ -41,3 +41,81 @@ def test_init_from_file() -> None: assert timestamps.fps == Fraction(2, Fraction(100, 1000)) assert timestamps.approximate_pts_from_last_pts is False assert timestamps.pts_list == [0, 50, 100] + + +def test__eq__and__hash__() -> None: + timestamps_str = ( + "# timecode format v2\n" + "0\n" + "1000\n" + "1500\n" + "2000\n" + "2001\n" + "2002\n" + "2003\n" + ) + timestamps_1 = TextFileTimestamps(timestamps_str, Fraction(1000), RoundingMethod.ROUND, True, None, True) + timestamps_2 = TextFileTimestamps(timestamps_str, Fraction(1000), RoundingMethod.ROUND, True, None, True) + assert timestamps_1 == timestamps_2 + assert hash(timestamps_1) == hash(timestamps_2) + + timestamps_3_str = ( + "# timecode format v2\n" + "0\n" + "1000\n" + "1500\n" + ) + timestamps_3 = TextFileTimestamps( + timestamps_3_str, # different + Fraction(1000), + RoundingMethod.ROUND, + True, + None, + True + ) + assert timestamps_1 != timestamps_3 + assert hash(timestamps_1) != hash(timestamps_3) + + timestamps_4 = TextFileTimestamps( + timestamps_str, + Fraction(1001), # different + RoundingMethod.ROUND, + True, + None, + True + ) + assert timestamps_1 != timestamps_4 + assert hash(timestamps_1) != hash(timestamps_4) + + timestamps_5 = TextFileTimestamps( + timestamps_str, + Fraction(1000), + RoundingMethod.FLOOR, # different + True, + None, + True + ) + assert timestamps_1 != timestamps_5 + assert hash(timestamps_1) != hash(timestamps_5) + + timestamps_6 = TextFileTimestamps( + timestamps_str, + Fraction(1000), + RoundingMethod.ROUND, + True, + Fraction(1), # different + True + ) + assert timestamps_1 != timestamps_6 + assert hash(timestamps_1) != hash(timestamps_6) + + timestamps_7 = TextFileTimestamps( + timestamps_str, + Fraction(1000), + RoundingMethod.ROUND, + True, + None, + False # different + ) + assert timestamps_1 != timestamps_7 + assert hash(timestamps_1) != hash(timestamps_7) diff --git a/tests/test_video_timestamps.py b/tests/test_video_timestamps.py index 14d55e8..489de57 100644 --- a/tests/test_video_timestamps.py +++ b/tests/test_video_timestamps.py @@ -107,3 +107,65 @@ def test_guess_rounding_method_vfr() -> None: fps = Fraction(24000, 1001) assert VideoTimestamps.guess_rounding_method(pts_list, time_scale, fps) == RoundingMethod.FLOOR + + +def test__eq__and__hash__() -> None: + video_1 = VideoTimestamps([0, 42, 83], Fraction(1000), True, None, RoundingMethod.FLOOR, False) + video_2 = VideoTimestamps([0, 42, 83], Fraction(1000), True, None, RoundingMethod.FLOOR, False) + assert video_1 == video_2 + assert hash(video_1) == hash(video_2) + + video_3 = VideoTimestamps( + [0, 42, 100], # different + Fraction(1000), + True, + None, + RoundingMethod.FLOOR, + False + ) + assert video_1 != video_3 + assert hash(video_1) != hash(video_3) + + video_4 = VideoTimestamps( + [0, 42, 83], + Fraction(1001), # different + True, + None, + RoundingMethod.FLOOR, + False + ) + assert video_1 != video_4 + assert hash(video_1) != hash(video_4) + + video_5 = VideoTimestamps( + [0, 42, 83], + Fraction(1000), + True, + Fraction(1), # different + RoundingMethod.FLOOR, + False + ) + assert video_1 != video_5 + assert hash(video_1) != hash(video_5) + + video_6 = VideoTimestamps( + [0, 42, 83], + Fraction(1000), + True, + None, + RoundingMethod.ROUND, # different + False + ) + assert video_1 != video_6 + assert hash(video_1) != hash(video_6) + + video_7 = VideoTimestamps( + [0, 42, 83], + Fraction(1000), + True, + None, + RoundingMethod.FLOOR, + True # different + ) + assert video_1 != video_7 + assert hash(video_1) != hash(video_7) diff --git a/video_timestamps/abc_timestamps.py b/video_timestamps/abc_timestamps.py index 8b40be0..ed4e66c 100644 --- a/video_timestamps/abc_timestamps.py +++ b/video_timestamps/abc_timestamps.py @@ -319,3 +319,13 @@ def move_time_to_frame( """ return self.frame_to_time(self.time_to_frame(time, time_type, input_unit), time_type, output_unit, center_time) + + + @abstractmethod + def __eq__(self, other: object) -> bool: + pass + + + @abstractmethod + def __hash__(self) -> int: + pass diff --git a/video_timestamps/fps_timestamps.py b/video_timestamps/fps_timestamps.py index 6a0d285..2fe7977 100644 --- a/video_timestamps/fps_timestamps.py +++ b/video_timestamps/fps_timestamps.py @@ -125,3 +125,23 @@ def _frame_to_time( raise ValueError(f'The TimeType "{time_type}" isn\'t supported.') return time + + + def __eq__(self, other: object) -> bool: + if not isinstance(other, FPSTimestamps): + return False + return (self.rounding_method, self.fps, self.time_scale, self.first_pts, self.first_timestamps) == ( + other.rounding_method, other.fps, other.time_scale, other.first_pts, other.first_timestamps + ) + + + def __hash__(self) -> int: + return hash( + ( + self.rounding_method, + self.fps, + self.time_scale, + self.first_pts, + self.first_timestamps, + ) + ) diff --git a/video_timestamps/text_file_timestamps.py b/video_timestamps/text_file_timestamps.py index 36d0705..17297f2 100644 --- a/video_timestamps/text_file_timestamps.py +++ b/video_timestamps/text_file_timestamps.py @@ -52,3 +52,26 @@ def __init__( pts_list = [rounding_method(Fraction(time, pow(10, 3)) * time_scale) for time in timestamps] super().__init__(pts_list, time_scale, normalize, fps, rounding_method, approximate_pts_from_last_pts) + + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TextFileTimestamps): + return False + return (self.rounding_method, self.fps, self.time_scale, self.first_pts, self.first_timestamps, self.pts_list, self.timestamps, self.approximate_pts_from_last_pts) == ( + other.rounding_method, other.fps, other.time_scale, other.first_pts, other.first_timestamps, other.pts_list, other.timestamps, other.approximate_pts_from_last_pts + ) + + + def __hash__(self) -> int: + return hash( + ( + self.rounding_method, + self.fps, + self.time_scale, + self.first_pts, + self.first_timestamps, + tuple(self.pts_list), + tuple(self.timestamps), + self.approximate_pts_from_last_pts, + ) + ) diff --git a/video_timestamps/video_timestamps.py b/video_timestamps/video_timestamps.py index 87a0b5f..bbd1b8f 100644 --- a/video_timestamps/video_timestamps.py +++ b/video_timestamps/video_timestamps.py @@ -293,3 +293,26 @@ def get_time_at_frame(requested_frame: int) -> Fraction: raise ValueError(f'The TimeType "{time_type}" isn\'t supported.') return time + + + def __eq__(self, other: object) -> bool: + if not isinstance(other, VideoTimestamps): + return False + return (self.rounding_method, self.fps, self.time_scale, self.first_pts, self.first_timestamps, self.pts_list, self.timestamps, self.approximate_pts_from_last_pts) == ( + other.rounding_method, other.fps, other.time_scale, other.first_pts, other.first_timestamps, other.pts_list, other.timestamps, other.approximate_pts_from_last_pts + ) + + + def __hash__(self) -> int: + return hash( + ( + self.rounding_method, + self.fps, + self.time_scale, + self.first_pts, + self.first_timestamps, + tuple(self.pts_list), + tuple(self.timestamps), + self.approximate_pts_from_last_pts, + ) + ) From 3cedf992c3c289c87a5b6311e60d2423a7449bc5 Mon Sep 17 00:00:00 2001 From: moi15moi Date: Thu, 2 Jan 2025 22:05:41 -0500 Subject: [PATCH 2/2] Fix mypy error --- video_timestamps/rounding_method.py | 4 ++-- video_timestamps/text_file_timestamps.py | 4 ++-- video_timestamps/timestamps_file_parser.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/video_timestamps/rounding_method.py b/video_timestamps/rounding_method.py index 19e88f7..ada9efb 100644 --- a/video_timestamps/rounding_method.py +++ b/video_timestamps/rounding_method.py @@ -18,8 +18,8 @@ def round_method(number: Fraction) -> int: return ceil(number - Fraction(1, 2)) class RoundingMethod(Enum): - FLOOR: CallType = floor_method - ROUND: CallType = round_method + FLOOR = floor_method + ROUND = round_method def __call__(self, number: Fraction) -> int: method: CallType = self.value diff --git a/video_timestamps/text_file_timestamps.py b/video_timestamps/text_file_timestamps.py index 17297f2..97b5d77 100644 --- a/video_timestamps/text_file_timestamps.py +++ b/video_timestamps/text_file_timestamps.py @@ -46,8 +46,8 @@ def __init__( with open(path_to_timestamps_file_or_content, "r", encoding="utf-8") as f: timestamps = TimestampsFileParser.parse_file(f) else: - f = StringIO(path_to_timestamps_file_or_content) - timestamps = TimestampsFileParser.parse_file(f) + file = StringIO(path_to_timestamps_file_or_content) + timestamps = TimestampsFileParser.parse_file(file) pts_list = [rounding_method(Fraction(time, pow(10, 3)) * time_scale) for time in timestamps] diff --git a/video_timestamps/timestamps_file_parser.py b/video_timestamps/timestamps_file_parser.py index 3e2d4be..348b632 100644 --- a/video_timestamps/timestamps_file_parser.py +++ b/video_timestamps/timestamps_file_parser.py @@ -1,18 +1,18 @@ from fractions import Fraction -from io import TextIOWrapper +from io import TextIOBase from re import compile from typing import Optional class TimestampsFileParser: @staticmethod - def parse_file(file_content: TextIOWrapper) -> list[Fraction]: + def parse_file(file_content: TextIOBase) -> list[Fraction]: """Parse timestamps from a [timestamps file](https://mkvtoolnix.download/doc/mkvmerge.html#mkvmerge.external_timestamp_files) and return them. Inspired by: https://gitlab.com/mbunkus/mkvtoolnix/-/blob/72dfe260effcbd0e7d7cf6998c12bb35308c004f/src/merge/timestamp_factory.cpp#L27-74 Parameters: - file_content (TextIOWrapper): The timestamps content. + file_content (TextIOBase): The timestamps content. Returns: A list of each frame timestamps (in milliseconds). @@ -38,14 +38,14 @@ def parse_file(file_content: TextIOWrapper) -> list[Fraction]: @staticmethod def _parse_v2_and_v4_file( - file_content: TextIOWrapper, version: int + file_content: TextIOBase, version: int ) -> list[Fraction]: """Create timestamps based on the timestamps v2 or v4 file provided. Inspired by: https://gitlab.com/mbunkus/mkvtoolnix/-/blob/72dfe260effcbd0e7d7cf6998c12bb35308c004f/src/merge/timestamp_factory.cpp#L201-267 Parameters: - file_content (TextIOWrapper): The timestamps content + file_content (TextIOBase): The timestamps content version (int): The version of the timestamps (only 2 or 4 is allowed) Returns: