Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add __eq__ __hash__ to ABCTimestamps #8

Merged
merged 2 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions tests/test_fps_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
78 changes: 78 additions & 0 deletions tests/test_text_file_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
62 changes: 62 additions & 0 deletions tests/test_video_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
10 changes: 10 additions & 0 deletions video_timestamps/abc_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 20 additions & 0 deletions video_timestamps/fps_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
)
4 changes: 2 additions & 2 deletions video_timestamps/rounding_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 25 additions & 2 deletions video_timestamps/text_file_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,32 @@ 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]

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,
)
)
10 changes: 5 additions & 5 deletions video_timestamps/timestamps_file_parser.py
Original file line number Diff line number Diff line change
@@ -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).
Expand All @@ -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:
Expand Down
23 changes: 23 additions & 0 deletions video_timestamps/video_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
)
Loading