Skip to content
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
2 changes: 2 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
"sphinx-autobuild",
"docs/",
"docs/_build/html",
"--watch",
"compuglobal/",
"--open-browser"
],
"type": "shell",
Expand Down
6 changes: 3 additions & 3 deletions compuglobal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from compuglobal.errors import APIPageStatusError, NoSearchResultsFoundError
from compuglobal.models.comic import ComicLayout, ComicOverlay, ComicPanel, ComicStrip
from compuglobal.models.episode import Episode, EpisodeMetadata, EpisodeSummary
from compuglobal.models.font import FontAlignment, FontColorRGB, FontFamily
from compuglobal.models.font import FontAlignment, FontColor, FontFamily
from compuglobal.models.frame import Frame, FrameResult
from compuglobal.models.overlay import OverlayFormat
from compuglobal.models.screencap import Screencap, ScreencapMoment
Expand All @@ -22,7 +22,7 @@
__title__ = "compuglobal"
__author__ = "MitchellAW"
__license__ = "MIT"
__version__ = "0.3.8"
__version__ = "0.4.0"

__all__ = [
"APIPageStatusError",
Expand All @@ -36,7 +36,7 @@
"EpisodeMetadata",
"EpisodeSummary",
"FontAlignment",
"FontColorRGB",
"FontColor",
"FontFamily",
"Frame",
"FrameResult",
Expand Down
38 changes: 10 additions & 28 deletions compuglobal/aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,44 +67,26 @@ def __init__(self, session: aiohttp.ClientSession) -> None:

async def get_screencap(
self,
*,
episode: str | None = None,
timestamp: int | None = None,
frame: Frame | None = None,
) -> Screencap:
"""Get the screencap for the given episode & timestamp, or a screencap of the Frame object.
"""Get the screencap for the given episode & timestamp.

Parameters
----------
episode : str | None, optional
An episode key
timestamp : int | None, optional
A timestamp of the screencap
frame : Frame | None, optional
A Frame object

Returns
-------
Screencap
The screencap for the given episode key and timestamp.

Raises
------
TypeError
Must give only episode + timestamp, or a Frame object.

"""
if isinstance(episode, str) and isinstance(timestamp, int):
params = {"e": episode, "t": timestamp, "nearby": 1}

elif isinstance(frame, Frame):
params = {"e": frame.key, "t": frame.timestamp, "nearby": 1}

else:
invalid_args_error = (
f"Expected str and int or compuglobal.Frame, but received {type(episode)},"
f" {type(timestamp)} and {type(frame)} instead"
)
raise TypeError(invalid_args_error)
params = {"e": episode, "t": timestamp, "nearby": 1}

request = self.discovery.CAPTION.build_request(self.client.base_url, query=params)
caption = await self.client.handle_request(request)
Expand Down Expand Up @@ -182,7 +164,7 @@ async def search_for_screencap(
"""
search_results = await self.search(search_text, season_minimum=season_minimum, season_maximum=season_maximum)
result = search_results[0]
return await self.get_screencap(result.key, result.timestamp)
return await self.get_screencap(episode=result.key, timestamp=result.timestamp)

async def get_random_screencap(
self,
Expand Down Expand Up @@ -348,7 +330,7 @@ async def get_comic_panel_url(

panel = ComicPanel.from_screencap(screencap=screencap, overlay_format=overlay_format)

params = {"b64": panel.get_encoded()}
params = {"b64": panel.encoded}
return self.media.COMIC_PANEL.build_encoded_url(self.client.base_url, query=params)

async def get_comic_strip_url(
Expand Down Expand Up @@ -378,7 +360,7 @@ async def get_comic_strip_url(
screencap, subtitles, overlay_format = self._resolve_overlay_inputs(screencap, subtitles, overlay_format)

comic_strip = ComicStrip.from_screencap(screencap=screencap, overlay_format=overlay_format)
params = {"b64": comic_strip.get_encoded(), "layout": comic_strip.layout}
params = {"b64": comic_strip.encoded, "layout": comic_strip.layout}
return self.media.COMIC_STRIP.build_encoded_url(self.client.base_url, query=params)

async def get_comic_maker_url(
Expand Down Expand Up @@ -412,7 +394,7 @@ async def get_comic_maker_url(
return self.media.COMIC_MAKER.build_encoded_url(
base_url=self.BASE_URL,
path_params=path_params,
query={"b64": strip.get_encoded(), "layout": strip.layout},
query={"b64": strip.encoded, "layout": strip.layout},
)

async def get_gif_url(
Expand Down Expand Up @@ -482,16 +464,16 @@ async def get_gif_maker_url(

path_params = {
"key": screencap.frame.key,
"start_timestamp": screencap.get_start(),
"end_timestamp": screencap.get_end(),
"start_timestamp": screencap.start,
"end_timestamp": screencap.end,
}

stream = Stream.from_screencap(screencap=screencap, overlay_format=overlay_format)

return self.media.GIF_MAKER.build_encoded_url(
base_url=self.BASE_URL,
path_params=path_params,
query={"b64": stream.get_encoded()},
query={"b64": stream.encoded},
)

@overload
Expand Down
6 changes: 4 additions & 2 deletions compuglobal/models/comic.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ def from_screencap(

return cls(e=screencap.frame.key, ts=screencap.frame.timestamp, o=overlays)

def get_encoded(self) -> str:
@property
def encoded(self) -> str:
"""Get the base 64 encoded representation of this panel.

Returns
Expand Down Expand Up @@ -281,7 +282,8 @@ def build_comic_overlays(
for subtitle, overlay_format in zip(subtitles, overlay_formats, strict=True)
]

def get_encoded(self) -> str:
@property
def encoded(self) -> str:
"""Get the base 64 encoded representation of this comic strip.

Returns
Expand Down
88 changes: 80 additions & 8 deletions compuglobal/models/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class FontAlignment(StrEnum):
ALIGN_CENTER = "c"


class FontColorRGB(BaseCompuGlobalModel):
class FontColor(BaseCompuGlobalModel):
"""A color for a font.

Attributes
Expand All @@ -53,18 +53,19 @@ class FontColorRGB(BaseCompuGlobalModel):
The amount of green in the color (0-255)
blue : int
The amount of blue in the color (0-255)
alpha : int
alpha : int, optional
The amount of alpha transparency in the color (0-255)

"""

red: int = Field(alias="r", ge=0, le=255)
green: int = Field(alias="g", ge=0, le=255)
blue: int = Field(alias="b", ge=0, le=255)
alpha: int = Field(alias="a", ge=0, le=255)
red: int = Field(alias="r", ge=0, le=255, default=255)
green: int = Field(alias="g", ge=0, le=255, default=255)
blue: int = Field(alias="b", ge=0, le=255, default=255)
alpha: int = Field(alias="a", ge=0, le=255, default=255)

def get_rgba(self) -> list[int]:
"""Get a list of the rgba values.
@property
def rgba(self) -> list[int]:
"""The font color as a list of the rgba values.

Returns
-------
Expand All @@ -73,3 +74,74 @@ def get_rgba(self) -> list[int]:

"""
return [self.red, self.green, self.blue, self.alpha]

@property
def hex(self) -> str:
"""The font color as a hex string.

Returns
-------
str
The color hex code

"""
return f"{self.red:02x}{self.green:02x}{self.blue:02x}{self.alpha:02x}"

@classmethod
def from_rgba(cls, r: int, g: int, b: int, a: int) -> "FontColor":
"""Create a FontColor from rgba.

Parameters
----------
r : int
Red (0-255)
g : int
Green (0-255)
b : int
Blue (0-255)
a : int
Alpha (0-255)

Returns
-------
FontColor
The font color

"""
return cls(red=r, green=g, blue=b, alpha=a)

@classmethod
def from_hex(cls, hex_str: str) -> "FontColor":
"""Create a FontColor from a hex string.

Parameters
----------
hex_str : str
A hex color string, with or without a leading ``#``.
Supports 6-character (RRGGBB) or 8-character (RRGGBBAA) formats.
Alpha is 255 if not given.

Returns
-------
FontColor
The color represented by the hex string

Raises
------
ValueError
If the hex string is not 6 or 8 characters (excluding ``#``)

"""
hex_str = hex_str.lstrip("#")

rgb_size = 6
rgba_size = 8

if len(hex_str) not in {rgb_size, rgba_size}:
msg = f"Hex string must be 6 or 8 characters, got {len(hex_str)}"
raise ValueError(msg)

r, g, b = (int(hex_str[i : i + 2], 16) for i in (0, 2, 4))
a = int(hex_str[rgb_size:rgba_size], 16) if len(hex_str) == rgba_size else 255

return cls(red=r, green=g, blue=b, alpha=a)
11 changes: 6 additions & 5 deletions compuglobal/models/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ class Frame(BaseCompuGlobalModel):
key: str = Field(alias="Episode")
timestamp: int = Field(alias="Timestamp", ge=0)

def get_real_timestamp(self) -> str:
"""Get a readable timestamp for the frame in format "mm:ss".
@property
def timecode(self) -> str:
"""A readable timecode for the frame's timestamp in format ``mm:ss``.

Returns
-------
str
A readable timestamp for the frame in format `mm:ss`.
A readable timecode in format ``mm:ss``

"""
return Timestamp.get_real_timestamp(timestamp=self.timestamp)
return Timestamp.get_timecode(timestamp=self.timestamp)

def __str__(self) -> str:
"""Get the string representation of the Frame.
Expand All @@ -44,7 +45,7 @@ def __str__(self) -> str:
The frame as a string e.g. S01E01 - 00000001 (00:01)

"""
return f"{self.key} - {self.timestamp} ({self.get_real_timestamp()})"
return f"{self.key} - {self.timestamp} ({self.timecode})"


class FrameResult(Frame):
Expand Down
42 changes: 17 additions & 25 deletions compuglobal/models/overlay.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Helper class for formatting of StreamOverlays and ComicOverlays."""

import dataclasses
from dataclasses import dataclass
from dataclasses import dataclass, field

from compuglobal.models.font import FontAlignment, FontFamily
from compuglobal.models.font import FontAlignment, FontColor, FontFamily


@dataclass(frozen=True)
Expand All @@ -16,7 +16,7 @@ class OverlayFormat:
The font to use for the text in the overlay
font_size : int
The size of the font in the overlay
font_color : tuple[int, int, int, int]
font_color : FontColor
The color of the font as an RGBA tuple (0-255, 0-255, 0-255, 0-255)
text_position_x : int
The position of the text on the X-axis
Expand All @@ -31,31 +31,12 @@ class OverlayFormat:

font_family: FontFamily = FontFamily.IMPACT
font_size: int = 0
font_color: tuple[int, int, int, int] = (255, 255, 255, 255)
font_color: FontColor = field(default_factory=FontColor)
text_position_x: int = 50
text_position_y: int = 97
text_alignment: FontAlignment = FontAlignment.ALIGN_CENTER
all_caps: bool = True

def __post_init__(self) -> None:
"""Validate font_color is correct.

Raises
------
ValueError
If font_colour does not contain 4 values, or any values are not between 0 and 255.

"""
required_rgba_values = 4
if len(self.font_color) != required_rgba_values:
msg = f"font_color must have exactly 4 values (RGBA), got {len(self.font_color)}"
raise ValueError(msg)

min_color, max_color = 0, 255
if not all(min_color <= color <= max_color for color in self.font_color):
msg = f"font_color values must be between 0 and 255, got {self.font_color}"
raise ValueError(msg)

def _changed_fields(self) -> dict:
return {f.name: getattr(self, f.name) for f in dataclasses.fields(self) if getattr(self, f.name) != f.default}

Expand All @@ -72,8 +53,19 @@ def font_color_hex(self) -> str:
The color hex code

"""
r, g, b, a = self.font_color
return f"{r:02x}{g:02x}{b:02x}{a:02x}"
return self.font_color.hex

@property
def font_color_rgba(self) -> list[int]:
"""The font color as a a list of rgba values.

Returns
-------
list[int]
The color in rgba

"""
return self.font_color.rgba

@classmethod
def normalise(
Expand Down
Loading