From d19a4a410f26fab98b946de997ddd0f33113659f Mon Sep 17 00:00:00 2001 From: Mitch Woollatt <21175347+MitchellAW@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:14:15 +0930 Subject: [PATCH 1/3] Add Timestamp class with timestamp helper methods --- compuglobal/__init__.py | 2 + compuglobal/models/frame.py | 6 +-- compuglobal/models/screencap.py | 29 +++++++++++-- compuglobal/models/subtitle.py | 5 ++- compuglobal/models/timestamp.py | 76 +++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 compuglobal/models/timestamp.py diff --git a/compuglobal/__init__.py b/compuglobal/__init__.py index 2aefd38..092a1c4 100644 --- a/compuglobal/__init__.py +++ b/compuglobal/__init__.py @@ -16,6 +16,7 @@ from compuglobal.models.screencap import Screencap, ScreencapMoment from compuglobal.models.stream import Stream, StreamOverlay from compuglobal.models.subtitle import Subtitle +from compuglobal.models.timestamp import Timestamp __title__ = "compuglobal" __author__ = "MitchellAW" @@ -47,4 +48,5 @@ "Stream", "StreamOverlay", "Subtitle", + "Timestamp", ] diff --git a/compuglobal/models/frame.py b/compuglobal/models/frame.py index 7008a55..b6083d0 100644 --- a/compuglobal/models/frame.py +++ b/compuglobal/models/frame.py @@ -3,6 +3,7 @@ from pydantic import Field from compuglobal.models.base import BaseCompuGlobalModel +from compuglobal.models.timestamp import Timestamp class Frame(BaseCompuGlobalModel): @@ -32,10 +33,7 @@ def get_real_timestamp(self) -> str: A readable timestamp for the frame in format `mm:ss`. """ - seconds = int(self.timestamp / 1000) - minutes = int(seconds / 60) - seconds -= minutes * 60 - return f"{minutes}:{seconds:02d}" + return Timestamp.get_real_timestamp(timestamp=self.timestamp) def __str__(self) -> str: """Get the string representation of the Frame. diff --git a/compuglobal/models/screencap.py b/compuglobal/models/screencap.py index 6eab778..6a70d87 100644 --- a/compuglobal/models/screencap.py +++ b/compuglobal/models/screencap.py @@ -6,6 +6,7 @@ from compuglobal.models.episode import EpisodeMetadata from compuglobal.models.frame import Frame from compuglobal.models.subtitle import Subtitle +from compuglobal.models.timestamp import Timestamp class ScreencapMoment(BaseCompuGlobalModel): @@ -29,6 +30,17 @@ class ScreencapMoment(BaseCompuGlobalModel): content: str = Field(alias="Content") title: str = Field(alias="Title") + def get_real_timestamp(self) -> str: + """Get a readable timestamp for the moments timestamp in format `mm:ss`. + + Returns + ------- + str + A readable timestamp in format `mm:ss`. + + """ + return Timestamp.get_real_timestamp(self.timestamp) + class Screencap(BaseCompuGlobalModel): """A Screencap of an episode at a point in time of a TV Show. @@ -58,7 +70,7 @@ class Screencap(BaseCompuGlobalModel): max_timestamp: int = Field(alias="MaxTimestamp", ge=0) def get_real_timestamp(self) -> str: - """Get a readable timestamp for the frame in format "mm:ss". + """Get a readable timestamp for the frame in format `mm:ss`. Returns ------- @@ -66,10 +78,21 @@ def get_real_timestamp(self) -> str: A readable timestamp for the frame in format `mm:ss`. """ - return self.frame.get_real_timestamp() + return Timestamp.get_real_timestamp(timestamp=self.frame.timestamp) + + def get_duration(self) -> int: + """Get duration of screencap subtitles in milliseconds. + + Returns + ------- + int + Duration in milliseconds + + """ + return Timestamp.get_subtitles_duration(self.subtitles) def captions(self) -> list[str]: - """Get a list of captions for the screencap from all subttiles. + """Get a list of captions for the screencap from all subtitles. Returns ------- diff --git a/compuglobal/models/subtitle.py b/compuglobal/models/subtitle.py index 2075f19..4dd2013 100644 --- a/compuglobal/models/subtitle.py +++ b/compuglobal/models/subtitle.py @@ -3,6 +3,7 @@ from pydantic import Field from compuglobal.models.base import BaseCompuGlobalModel +from compuglobal.models.timestamp import Timestamp class Subtitle(BaseCompuGlobalModel): @@ -41,7 +42,7 @@ def get_duration(self) -> int: Returns ------- int - The duration in milliseonds. + The duration in milliseconds. """ - return self.end_timestamp - self.start_timestamp + return Timestamp.get_duration(start_timestamp=self.start_timestamp, end_timestamp=self.end_timestamp) diff --git a/compuglobal/models/timestamp.py b/compuglobal/models/timestamp.py new file mode 100644 index 0000000..4465f91 --- /dev/null +++ b/compuglobal/models/timestamp.py @@ -0,0 +1,76 @@ +"""Timestamp module with helper class for working with timestamps.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from compuglobal.models.subtitle import Subtitle + + +class Timestamp: + """Helper class for working with timestamps.""" + + @staticmethod + def get_minutes_seconds(milliseconds: int) -> tuple[int, int]: + """Get minutes and seconds from milliseconds. + + Parameters + ---------- + milliseconds : int + The length of time in milliseconds + + Returns + ------- + tuple[int, int] + The minutes, and seconds as a tuple + + """ + seconds = int(milliseconds / 1000) + minutes = int(seconds / 60) + seconds -= minutes * 60 + return minutes, seconds + + @staticmethod + def get_real_timestamp(timestamp: int) -> str: + """Get a readable timestamp for the frame in format `mm:ss`. + + Returns + ------- + str + A readable timestamp for the frame in format `mm:ss`. + + """ + minutes, seconds = Timestamp.get_minutes_seconds(timestamp) + return f"{minutes}:{seconds:02d}" + + @staticmethod + def get_duration(start_timestamp: int, end_timestamp: int) -> int: + """Get the duration of the subtitle in milliseconds. + + Returns + ------- + int + The duration in milliseconds. + + """ + return end_timestamp - start_timestamp + + @staticmethod + def get_subtitles_duration(subtitles: list[Subtitle]) -> int: + """Get the duration between the start of the earliest subtitle, and the end of the latest subtitle. + + Parameters + ---------- + subtitles : list[Subtitle] + The subtitles + + Returns + ------- + int + The duration in milliseconds + + """ + start_timestamp = min(subtitle.start_timestamp for subtitle in subtitles) + end_timestamp = max(subtitle.end_timestamp for subtitle in subtitles) + return Timestamp.get_duration(start_timestamp, end_timestamp) From 316b81d7c46cfe893dc674bbabbbdb5bf1a18122 Mon Sep 17 00:00:00 2001 From: Mitch Woollatt <21175347+MitchellAW@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:27:30 +0930 Subject: [PATCH 2/3] Add tests for new screencap/moment methods --- tests/models/test_screencap.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/models/test_screencap.py b/tests/models/test_screencap.py index e47f06c..32e49e0 100644 --- a/tests/models/test_screencap.py +++ b/tests/models/test_screencap.py @@ -48,6 +48,11 @@ def test_screencap_moment_validate_invalid_timestamp(bad_timestamp: int) -> None ScreencapMoment.model_validate(payload) +def test_screencap_moment_get_real_timestamp(screencap_moment: dict[str, Any]) -> None: + moment = ScreencapMoment.model_validate(screencap_moment) + assert moment.get_real_timestamp() == snapshot("18:38") + + def test_screencap_validate_dump(screencap: Screencap) -> None: dump = screencap.model_dump() expected = Screencap.model_validate(dump) @@ -72,6 +77,10 @@ def test_screencap_get_caption(screencap: Screencap) -> None: ) +def test_screencap_get_subtitles_duration(screencap: Screencap) -> None: + assert screencap.get_duration() == snapshot(7799) + + def test_screencap_get_start(screencap: Screencap) -> None: assert screencap.get_start() == 347055 From bac9cde11ce6f48758464474c4b8c269702f927d Mon Sep 17 00:00:00 2001 From: Mitch Woollatt <21175347+MitchellAW@users.noreply.github.com> Date: Fri, 5 Jun 2026 13:41:32 +0930 Subject: [PATCH 3/3] Bump package version to 0.3.8 --- compuglobal/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compuglobal/__init__.py b/compuglobal/__init__.py index 092a1c4..62a0fb6 100644 --- a/compuglobal/__init__.py +++ b/compuglobal/__init__.py @@ -21,7 +21,7 @@ __title__ = "compuglobal" __author__ = "MitchellAW" __license__ = "MIT" -__version__ = "0.3.7" +__version__ = "0.3.8" __all__ = [ "APIPageStatusError",