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 support for v1 timestamps file #10

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
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
168 changes: 102 additions & 66 deletions docs/Algorithm conversion explanation.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions tests/test_fps_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test__init__() -> None:
assert timestamps.rounding_method == rounding_method
assert timestamps.time_scale == time_scale
assert timestamps.fps == fps
assert timestamps.first_pts == 0
assert timestamps.first_timestamps == 0


def test_invalid_time_scale() -> None:
Expand All @@ -40,16 +40,16 @@ def test_invalid_fps() -> None:


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)
fps_1 = FPSTimestamps(RoundingMethod.ROUND, Fraction(1000), Fraction(24000, 1001), Fraction(0))
fps_2 = FPSTimestamps(RoundingMethod.ROUND, Fraction(1000), Fraction(24000, 1001), Fraction(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
Fraction(0)
)
assert fps_1 != fps_3
assert hash(fps_1) != hash(fps_3)
Expand All @@ -58,7 +58,7 @@ def test__eq__and__hash__() -> None:
RoundingMethod.ROUND,
Fraction(1001), # different
Fraction(24000, 1001),
0
Fraction(0)
)
assert fps_1 != fps_4
assert hash(fps_1) != hash(fps_4)
Expand All @@ -67,7 +67,7 @@ def test__eq__and__hash__() -> None:
RoundingMethod.ROUND,
Fraction(1000),
Fraction(1), # different
0
Fraction(0)
)
assert fps_1 != fps_5
assert hash(fps_1) != hash(fps_5)
Expand All @@ -76,7 +76,7 @@ def test__eq__and__hash__() -> None:
RoundingMethod.ROUND,
Fraction(1000),
Fraction(24000, 1001),
10 # different
Fraction(10) # different
)
assert fps_1 != fps_6
assert hash(fps_1) != hash(fps_6)
130 changes: 109 additions & 21 deletions tests/test_text_file_timestamps.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,116 @@
import os
from fractions import Fraction
from pathlib import Path
from video_timestamps import RoundingMethod, TextFileTimestamps
from video_timestamps import RoundingMethod, TextFileTimestamps, TimeType

dir_path = Path(os.path.dirname(os.path.realpath(__file__)))


def test_frame_to_time_v1() -> None:
timestamps_str = "# timecode format v1\n" "Assume 30\n" "5,10,15\n"
time_scale = Fraction(1000)
rounding_method = RoundingMethod.ROUND

timestamps = TextFileTimestamps(timestamps_str, time_scale, rounding_method)

assert timestamps.pts_list == [0, 33, 67, 100, 133, 167, 233, 300, 367, 433, 500, 567]
assert timestamps.fps == Fraction(30)

# Frame 0 to 5 - 30 fps
assert timestamps.frame_to_time(0, TimeType.EXACT) == Fraction(0)
assert timestamps.frame_to_time(1, TimeType.EXACT) == Fraction(33, 1000)
assert timestamps.frame_to_time(2, TimeType.EXACT) == Fraction(67, 1000)
assert timestamps.frame_to_time(3, TimeType.EXACT) == Fraction(100, 1000)
assert timestamps.frame_to_time(4, TimeType.EXACT) == Fraction(133, 1000)
assert timestamps.frame_to_time(5, TimeType.EXACT) == Fraction(167, 1000)
# Frame 6 to 11 - 15 fps
assert timestamps.frame_to_time(6, TimeType.EXACT) == Fraction(233, 1000)
assert timestamps.frame_to_time(7, TimeType.EXACT) == Fraction(300, 1000)
assert timestamps.frame_to_time(8, TimeType.EXACT) == Fraction(367, 1000)
assert timestamps.frame_to_time(9, TimeType.EXACT) == Fraction(433, 1000)
assert timestamps.frame_to_time(10, TimeType.EXACT) == Fraction(500, 1000)
assert timestamps.frame_to_time(11, TimeType.EXACT) == Fraction(567, 1000)
# From here, we guess the ms from the last frame timestamps and fps
# The last frame is equal to (5 * 1/30 * 1000 + 6 * 1/15 * 1000) = 1700/3 = 566.666.
assert timestamps.frame_to_time(12, TimeType.EXACT) == Fraction(600, 1000) # 1700/3 + 1/30 * 1000 = 600
assert timestamps.frame_to_time(13, TimeType.EXACT) == Fraction(633, 1000) # 1700/3 + 2/30 * 1000 = round(633.33) = 633
assert timestamps.frame_to_time(14, TimeType.EXACT) == Fraction(667, 1000) # 1700/3 + 3/30 * 1000 = round(666.66) = 667


def test_time_to_frame_round_v1() -> None:
timestamps_str = "# timecode format v1\n" "Assume 30\n" "5,10,15\n"
time_scale = Fraction(1000)
rounding_method = RoundingMethod.ROUND

timestamps = TextFileTimestamps(timestamps_str, time_scale, rounding_method)

# Frame 0 to 5 - 30 fps
# precision
assert timestamps.time_to_frame(Fraction(0), TimeType.EXACT) == 0
assert timestamps.time_to_frame(Fraction(33, 1000), TimeType.EXACT) == 1
assert timestamps.time_to_frame(Fraction(67, 1000), TimeType.EXACT) == 2
assert timestamps.time_to_frame(Fraction(100, 1000), TimeType.EXACT) == 3
assert timestamps.time_to_frame(Fraction(133, 1000), TimeType.EXACT) == 4
assert timestamps.time_to_frame(Fraction(167, 1000), TimeType.EXACT) == 5
# milliseconds
assert timestamps.time_to_frame(0, TimeType.EXACT, 3) == 0
assert timestamps.time_to_frame(32, TimeType.EXACT, 3) == 0
assert timestamps.time_to_frame(33, TimeType.EXACT, 3) == 1
assert timestamps.time_to_frame(66, TimeType.EXACT, 3) == 1
assert timestamps.time_to_frame(67, TimeType.EXACT, 3) == 2
assert timestamps.time_to_frame(99, TimeType.EXACT, 3) == 2
assert timestamps.time_to_frame(100, TimeType.EXACT, 3) == 3
assert timestamps.time_to_frame(132, TimeType.EXACT, 3) == 3
assert timestamps.time_to_frame(133, TimeType.EXACT, 3) == 4
assert timestamps.time_to_frame(166, TimeType.EXACT, 3) == 4
assert timestamps.time_to_frame(167, TimeType.EXACT, 3) == 5
assert timestamps.time_to_frame(232, TimeType.EXACT, 3) == 5
# Frame 6 to 11 - 15 fps
# precision
assert timestamps.time_to_frame(Fraction(233, 1000), TimeType.EXACT) == 6
assert timestamps.time_to_frame(Fraction(300, 1000), TimeType.EXACT) == 7
assert timestamps.time_to_frame(Fraction(367, 1000), TimeType.EXACT) == 8
assert timestamps.time_to_frame(Fraction(433, 1000), TimeType.EXACT) == 9
assert timestamps.time_to_frame(Fraction(500, 1000), TimeType.EXACT) == 10
assert timestamps.time_to_frame(Fraction(567, 1000), TimeType.EXACT) == 11
# milliseconds
assert timestamps.time_to_frame(233, TimeType.EXACT, 3) == 6
assert timestamps.time_to_frame(299, TimeType.EXACT, 3) == 6
assert timestamps.time_to_frame(300, TimeType.EXACT, 3) == 7
assert timestamps.time_to_frame(366, TimeType.EXACT, 3) == 7
assert timestamps.time_to_frame(367, TimeType.EXACT, 3) == 8
assert timestamps.time_to_frame(432, TimeType.EXACT, 3) == 8
assert timestamps.time_to_frame(433, TimeType.EXACT, 3) == 9
assert timestamps.time_to_frame(499, TimeType.EXACT, 3) == 9
assert timestamps.time_to_frame(500, TimeType.EXACT, 3) == 10
assert timestamps.time_to_frame(566, TimeType.EXACT, 3) == 10
assert timestamps.time_to_frame(567, TimeType.EXACT, 3) == 11
# From here, we guess the ms from the last frame timestamps and fps
# The last frame is equal to (5 * 1/30 * 1000 + 6 * 1/15 * 1000) = 1700/3 = 566.666
assert timestamps.time_to_frame(Fraction(600, 1000), TimeType.EXACT) == 12
assert timestamps.time_to_frame(Fraction(633, 1000), TimeType.EXACT) == 13
assert timestamps.time_to_frame(Fraction(667, 1000), TimeType.EXACT) == 14
assert timestamps.time_to_frame(599, TimeType.EXACT, 3) == 11
assert timestamps.time_to_frame(600, TimeType.EXACT, 3) == 12 # 1700/3 + 1/30 * 1000 = 600
assert timestamps.time_to_frame(632, TimeType.EXACT, 3) == 12
assert timestamps.time_to_frame(633, TimeType.EXACT, 3) == 13 # 1700/3 + 2/30 * 1000 = round(633.33) = 633
assert timestamps.time_to_frame(666, TimeType.EXACT, 3) == 13
assert timestamps.time_to_frame(667, TimeType.EXACT, 3) == 14 # 1700/3 + 3/30 * 1000 = round(666.66) = 667


def test_init_v1() -> None:
timestamps_str = "# timecode format v1\n" "Assume 30\n" "5,10,15\n" "12,15,40\n"
time_scale = Fraction(1000)
rounding_method = RoundingMethod.ROUND

timestamps = TextFileTimestamps(timestamps_str, time_scale, rounding_method)

assert timestamps.time_scale == Fraction(1000)
assert timestamps.rounding_method == RoundingMethod.ROUND
assert timestamps.fps == Fraction(30)
assert timestamps.pts_list == [0, 33, 67, 100, 133, 167, 233, 300, 367, 433, 500, 567, 600, 625, 650, 675, 700]


def test_init_v2() -> None:
timestamps_str = (
"# timecode format v2\n"
Expand All @@ -20,12 +125,11 @@ def test_init_v2() -> None:
time_scale = Fraction(1000)
rounding_method = RoundingMethod.ROUND

timestamps = TextFileTimestamps(timestamps_str, time_scale, rounding_method, approximate_pts_from_last_pts=True)
timestamps = TextFileTimestamps(timestamps_str, time_scale, rounding_method)

assert timestamps.time_scale == Fraction(1000)
assert timestamps.rounding_method == RoundingMethod.ROUND
assert timestamps.fps == Fraction(6, Fraction(2003, 1000))
assert timestamps.approximate_pts_from_last_pts is True
assert timestamps.pts_list == [0, 1000, 1500, 2000, 2001, 2002, 2003]


Expand All @@ -39,7 +143,6 @@ def test_init_from_file() -> None:
assert timestamps.time_scale == Fraction(1000)
assert timestamps.rounding_method == RoundingMethod.ROUND
assert timestamps.fps == Fraction(2, Fraction(100, 1000))
assert timestamps.approximate_pts_from_last_pts is False
assert timestamps.pts_list == [0, 50, 100]


Expand All @@ -54,8 +157,8 @@ def test__eq__and__hash__() -> None:
"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)
timestamps_1 = TextFileTimestamps(timestamps_str, Fraction(1000), RoundingMethod.ROUND, True, None)
timestamps_2 = TextFileTimestamps(timestamps_str, Fraction(1000), RoundingMethod.ROUND, True, None)
assert timestamps_1 == timestamps_2
assert hash(timestamps_1) == hash(timestamps_2)

Expand All @@ -71,7 +174,6 @@ def test__eq__and__hash__() -> None:
RoundingMethod.ROUND,
True,
None,
True
)
assert timestamps_1 != timestamps_3
assert hash(timestamps_1) != hash(timestamps_3)
Expand All @@ -82,7 +184,6 @@ def test__eq__and__hash__() -> None:
RoundingMethod.ROUND,
True,
None,
True
)
assert timestamps_1 != timestamps_4
assert hash(timestamps_1) != hash(timestamps_4)
Expand All @@ -93,7 +194,6 @@ def test__eq__and__hash__() -> None:
RoundingMethod.FLOOR, # different
True,
None,
True
)
assert timestamps_1 != timestamps_5
assert hash(timestamps_1) != hash(timestamps_5)
Expand All @@ -104,18 +204,6 @@ def test__eq__and__hash__() -> None:
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)
26 changes: 13 additions & 13 deletions tests/test_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ def test_time_to_frame_round(timestamp: ABCTimestamps) -> None:
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001)),
VideoTimestamps([0, 3753, 7507, 11261, 15015, 18768], Fraction(90000)),
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001), last_timestamps=2/Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
],
)
def test_frame_to_time_floor(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -454,7 +454,7 @@ def test_frame_to_time_floor(timestamp: ABCTimestamps) -> None:
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001)),
VideoTimestamps([0, 3753, 7507, 11261, 15015, 18768], Fraction(90000)),
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001), last_timestamps=2/Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
],
)
def test_time_to_frame_floor(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -590,9 +590,9 @@ def test_time_to_frame_floor(timestamp: ABCTimestamps) -> None:
@pytest.mark.parametrize(
"timestamp",
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), -10000),
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), Fraction(-10000, 90000)),
VideoTimestamps([-10000, -6247, -2493, 1261, 5015, 8768], Fraction(90000), False),
VideoTimestamps([-10000, -6247, -2493], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR), # Test VideoTimestamps over the video lenght
VideoTimestamps([-10000, -6247, -2493], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR, 2/Fraction(24000, 1001) - Fraction(10000, 90000)), # Test VideoTimestamps over the video lenght
],
)
def test_frame_to_time_floor_first_pts_under_0(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -692,9 +692,9 @@ def test_frame_to_time_floor_first_pts_under_0(timestamp: ABCTimestamps) -> None
@pytest.mark.parametrize(
"timestamp",
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), -10000),
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), Fraction(-10000, 90000)),
VideoTimestamps([-10000, -6247, -2493, 1261, 5015, 8768], Fraction(90000), False),
VideoTimestamps([-10000, -6247, -2493], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR), # Test VideoTimestamps over the video lenght
VideoTimestamps([-10000, -6247, -2493], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR, 2/Fraction(24000, 1001) - Fraction(10000, 90000)), # Test VideoTimestamps over the video lenght
],
)
def test_time_to_frame_floor_first_pts_under_0(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -835,9 +835,9 @@ def test_time_to_frame_floor_first_pts_under_0(timestamp: ABCTimestamps) -> None
@pytest.mark.parametrize(
"timestamp",
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), 3753),
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), Fraction(3753, 90000)),
VideoTimestamps([3753, 7506, 11260, 15014, 18768, 22521], Fraction(90000), False),
VideoTimestamps([3753, 7506, 11260], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR), # Test VideoTimestamps over the video lenght
VideoTimestamps([3753, 7506, 11260], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR, 2/Fraction(24000, 1001) + Fraction(3753, 90000)), # Test VideoTimestamps over the video lenght
],
)
def test_frame_to_time_floor_first_pts_over_0(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -938,9 +938,9 @@ def test_frame_to_time_floor_first_pts_over_0(timestamp: ABCTimestamps) -> None:
@pytest.mark.parametrize(
"timestamp",
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), 3753),
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001), Fraction(3753, 90000)),
VideoTimestamps([3753, 7506, 11260, 15014, 18768, 22521], Fraction(90000), False),
VideoTimestamps([3753, 7506, 11260], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR), # Test VideoTimestamps over the video lenght
VideoTimestamps([3753, 7506, 11260], Fraction(90000), False, Fraction(24000, 1001), RoundingMethod.FLOOR, 2/Fraction(24000, 1001) + Fraction(3753, 90000)), # Test VideoTimestamps over the video lenght
],
)
def test_time_to_frame_floor_first_pts_over_0(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -1040,7 +1040,7 @@ def test_time_to_frame_floor_first_pts_over_0(timestamp: ABCTimestamps) -> None:
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001)),
VideoTimestamps([0, 3753, 7507, 11261, 15015, 18768], Fraction(90000)),
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001), last_timestamps=2/Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
],
)
def test_frame_to_pts_floor(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -1072,7 +1072,7 @@ def test_frame_to_pts_floor(timestamp: ABCTimestamps) -> None:
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001)),
VideoTimestamps([0, 3753, 7507, 11261, 15015, 18768], Fraction(90000)),
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001), last_timestamps=2/Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
],
)
def test_frame_to_pts_floor_with_timescale(timestamp: ABCTimestamps) -> None:
Expand Down Expand Up @@ -1105,7 +1105,7 @@ def test_frame_to_pts_floor_with_timescale(timestamp: ABCTimestamps) -> None:
[
FPSTimestamps(RoundingMethod.FLOOR, Fraction(90000), Fraction(24000, 1001)),
VideoTimestamps([0, 3753, 7507, 11261, 15015, 18768], Fraction(90000)),
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
VideoTimestamps([0, 3753, 7507], Fraction(90000), fps=Fraction(24000, 1001), last_timestamps=2/Fraction(24000, 1001)), # Test VideoTimestamps over the video lenght
],
)
def test_pts_to_frame_floor(timestamp: ABCTimestamps) -> None:
Expand Down
14 changes: 7 additions & 7 deletions tests/test_video_timestamps.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ def test_guess_rounding_method_vfr() -> None:


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)
video_1 = VideoTimestamps([0, 42, 83], Fraction(1000), True, None, RoundingMethod.FLOOR, None)
video_2 = VideoTimestamps([0, 42, 83], Fraction(1000), True, None, RoundingMethod.FLOOR, None)
assert video_1 == video_2
assert hash(video_1) == hash(video_2)

Expand All @@ -121,7 +121,7 @@ def test__eq__and__hash__() -> None:
True,
None,
RoundingMethod.FLOOR,
False
None
)
assert video_1 != video_3
assert hash(video_1) != hash(video_3)
Expand All @@ -132,7 +132,7 @@ def test__eq__and__hash__() -> None:
True,
None,
RoundingMethod.FLOOR,
False
None
)
assert video_1 != video_4
assert hash(video_1) != hash(video_4)
Expand All @@ -143,7 +143,7 @@ def test__eq__and__hash__() -> None:
True,
Fraction(1), # different
RoundingMethod.FLOOR,
False
None
)
assert video_1 != video_5
assert hash(video_1) != hash(video_5)
Expand All @@ -154,7 +154,7 @@ def test__eq__and__hash__() -> None:
True,
None,
RoundingMethod.ROUND, # different
False
None
)
assert video_1 != video_6
assert hash(video_1) != hash(video_6)
Expand All @@ -165,7 +165,7 @@ def test__eq__and__hash__() -> None:
True,
None,
RoundingMethod.FLOOR,
True # different
Fraction(10) # different
)
assert video_1 != video_7
assert hash(video_1) != hash(video_7)
Loading