compuglobal 0.3.7__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {compuglobal-0.3.7 → compuglobal-0.4.0}/PKG-INFO +5 -25
  2. {compuglobal-0.3.7 → compuglobal-0.4.0}/README.rst +3 -23
  3. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/__init__.py +7 -3
  4. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/aio.py +223 -83
  5. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/api/client.py +17 -14
  6. compuglobal-0.4.0/compuglobal/api/config.py +16 -0
  7. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/api/discovery.py +12 -12
  8. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/api/endpoint.py +29 -7
  9. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/api/media.py +29 -12
  10. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/api/metadata.py +4 -4
  11. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/errors.py +9 -11
  12. compuglobal-0.4.0/compuglobal/models/base.py +25 -0
  13. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/models/comic.py +102 -34
  14. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/models/episode.py +18 -18
  15. compuglobal-0.4.0/compuglobal/models/font.py +147 -0
  16. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/models/frame.py +10 -11
  17. compuglobal-0.4.0/compuglobal/models/overlay.py +106 -0
  18. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/models/screencap.py +90 -24
  19. compuglobal-0.4.0/compuglobal/models/stream.py +234 -0
  20. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/models/subtitle.py +12 -10
  21. compuglobal-0.4.0/compuglobal/models/timestamp.py +88 -0
  22. {compuglobal-0.3.7 → compuglobal-0.4.0}/pyproject.toml +15 -5
  23. compuglobal-0.3.7/compuglobal/api/config.py +0 -13
  24. compuglobal-0.3.7/compuglobal/models/base.py +0 -9
  25. compuglobal-0.3.7/compuglobal/models/font.py +0 -59
  26. compuglobal-0.3.7/compuglobal/models/stream.py +0 -135
  27. {compuglobal-0.3.7 → compuglobal-0.4.0}/LICENSE +0 -0
  28. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/api/__init__.py +0 -0
  29. {compuglobal-0.3.7 → compuglobal-0.4.0}/compuglobal/models/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: compuglobal
3
- Version: 0.3.7
3
+ Version: 0.4.0
4
4
  Summary: Python wrapper for the CGHMC API (Frinkiac, Morbotron Master Of All Science and more.
5
5
  Keywords: The Simpsons,Futurama,Rick and Morty,West Wing,Frinkiac,Morbotron,Master of all Science,Capital Beat,API,API Wrapper,CompuGlobal,CompuGlobalHyperMegaNet,CGHMC
6
6
  Author: MitchellAW
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.14
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Intended Audience :: Developers
14
14
  License-File: LICENSE
15
- Requires-Dist: aiohttp>=3.13.0
15
+ Requires-Dist: aiohttp>=3.14.0
16
16
  Requires-Dist: pydantic>=2.13.0
17
17
  Project-URL: Documentation, https://compuglobal.readthedocs.io/en/latest/
18
18
  Project-URL: Source, https://github.com/MitchellAW/CompuGlobal
@@ -29,6 +29,9 @@ CompuGlobal
29
29
  .. image:: https://img.shields.io/pypi/pyversions/compuglobal.svg
30
30
  :target: https://pypi.python.org/pypi/compuglobal
31
31
 
32
+ .. image:: https://app.readthedocs.org/projects/compuglobal/badge/?version=latest&style=flat
33
+ :target: https://compuglobal.readthedocs.io/en/latest/
34
+
32
35
  .. image:: https://img.shields.io/github/actions/workflow/status/MitchellAW/CompuGlobal/test.yml?label=tests
33
36
  :target: https://github.com/MitchellAW/CompuGlobal/actions/workflows/test.yml
34
37
 
@@ -86,29 +89,6 @@ Basic Usage
86
89
 
87
90
  For documented examples, check `here.`_
88
91
 
89
- What's New
90
- ------------
91
- **0.3.0 - Breaking changes**
92
-
93
- These changes are to accommodate the extensive update to the APIs with new features:
94
-
95
- - All synchronous implementations of the package have been removed
96
- - The async endpoints persist and are now accessible via **compuglobal** or **compuglobal.aio** as done previously
97
- - The `Master Of All Science`_ API appears to be unavailable at this point in time and redirects to Frinkiac, I have added a **deprecation warning** to this API and it will remain unless the API returns
98
- - The package now requires **Python 3.13+**
99
- - Image, comic, and gif generation are all now performed using the API rather than from a Screencap
100
-
101
- .. code-block:: py
102
-
103
- # This is now async
104
- simpsons = compuglobal.Frinkiac()
105
-
106
- # Previous usage
107
- await screencap.get_gif_url()
108
-
109
- # New usage
110
- await simpsons.get_gif_url(screencap)
111
-
112
92
  Preview
113
93
  ------------
114
94
  .. image:: https://raw.githubusercontent.com/MitchellAW/MitchellAW.github.io/refs/heads/master/images/nothing-at-all.gif
@@ -10,6 +10,9 @@ CompuGlobal
10
10
  .. image:: https://img.shields.io/pypi/pyversions/compuglobal.svg
11
11
  :target: https://pypi.python.org/pypi/compuglobal
12
12
 
13
+ .. image:: https://app.readthedocs.org/projects/compuglobal/badge/?version=latest&style=flat
14
+ :target: https://compuglobal.readthedocs.io/en/latest/
15
+
13
16
  .. image:: https://img.shields.io/github/actions/workflow/status/MitchellAW/CompuGlobal/test.yml?label=tests
14
17
  :target: https://github.com/MitchellAW/CompuGlobal/actions/workflows/test.yml
15
18
 
@@ -67,29 +70,6 @@ Basic Usage
67
70
 
68
71
  For documented examples, check `here.`_
69
72
 
70
- What's New
71
- ------------
72
- **0.3.0 - Breaking changes**
73
-
74
- These changes are to accommodate the extensive update to the APIs with new features:
75
-
76
- - All synchronous implementations of the package have been removed
77
- - The async endpoints persist and are now accessible via **compuglobal** or **compuglobal.aio** as done previously
78
- - The `Master Of All Science`_ API appears to be unavailable at this point in time and redirects to Frinkiac, I have added a **deprecation warning** to this API and it will remain unless the API returns
79
- - The package now requires **Python 3.13+**
80
- - Image, comic, and gif generation are all now performed using the API rather than from a Screencap
81
-
82
- .. code-block:: py
83
-
84
- # This is now async
85
- simpsons = compuglobal.Frinkiac()
86
-
87
- # Previous usage
88
- await screencap.get_gif_url()
89
-
90
- # New usage
91
- await simpsons.get_gif_url(screencap)
92
-
93
73
  Preview
94
74
  ------------
95
75
  .. image:: https://raw.githubusercontent.com/MitchellAW/MitchellAW.github.io/refs/heads/master/images/nothing-at-all.gif
@@ -11,16 +11,18 @@ from compuglobal.aio import (
11
11
  from compuglobal.errors import APIPageStatusError, NoSearchResultsFoundError
12
12
  from compuglobal.models.comic import ComicLayout, ComicOverlay, ComicPanel, ComicStrip
13
13
  from compuglobal.models.episode import Episode, EpisodeMetadata, EpisodeSummary
14
- from compuglobal.models.font import FontAlignment, FontColorRGB, FontFamily
14
+ from compuglobal.models.font import FontAlignment, FontColor, FontFamily
15
15
  from compuglobal.models.frame import Frame, FrameResult
16
+ from compuglobal.models.overlay import OverlayFormat
16
17
  from compuglobal.models.screencap import Screencap, ScreencapMoment
17
18
  from compuglobal.models.stream import Stream, StreamOverlay
18
19
  from compuglobal.models.subtitle import Subtitle
20
+ from compuglobal.models.timestamp import Timestamp
19
21
 
20
22
  __title__ = "compuglobal"
21
23
  __author__ = "MitchellAW"
22
24
  __license__ = "MIT"
23
- __version__ = "0.3.7"
25
+ __version__ = "0.4.0"
24
26
 
25
27
  __all__ = [
26
28
  "APIPageStatusError",
@@ -34,7 +36,7 @@ __all__ = [
34
36
  "EpisodeMetadata",
35
37
  "EpisodeSummary",
36
38
  "FontAlignment",
37
- "FontColorRGB",
39
+ "FontColor",
38
40
  "FontFamily",
39
41
  "Frame",
40
42
  "FrameResult",
@@ -42,9 +44,11 @@ __all__ = [
42
44
  "MasterOfAllScience",
43
45
  "Morbotron",
44
46
  "NoSearchResultsFoundError",
47
+ "OverlayFormat",
45
48
  "Screencap",
46
49
  "ScreencapMoment",
47
50
  "Stream",
48
51
  "StreamOverlay",
49
52
  "Subtitle",
53
+ "Timestamp",
50
54
  ]
@@ -1,6 +1,8 @@
1
1
  """Used for interacting with and building CGHMC APIs."""
2
2
 
3
3
  import json
4
+ import logging
5
+ from typing import overload
4
6
  from warnings import deprecated
5
7
 
6
8
  import aiohttp
@@ -15,19 +17,44 @@ from compuglobal.models.comic import ComicPanel, ComicStrip
15
17
  from compuglobal.models.episode import Episode, EpisodeSummary
16
18
  from compuglobal.models.font import FontFamily
17
19
  from compuglobal.models.frame import Frame, FrameResult
20
+ from compuglobal.models.overlay import OverlayFormat
18
21
  from compuglobal.models.screencap import Screencap, ScreencapMoment
19
22
  from compuglobal.models.stream import Stream
20
23
  from compuglobal.models.subtitle import Subtitle
21
24
 
25
+ log = logging.getLogger(__name__)
26
+
22
27
  """Contains the async API Wrappers used for accessing all the cghmc API endpoints."""
23
28
 
24
29
 
25
30
  class AsyncCompuGlobalAPI:
26
- """Represents a base API of the CGHMC family."""
31
+ """Represents a base API of the CGHMC family.
32
+
33
+ Parameters
34
+ ----------
35
+ session : aiohttp.ClientSession, optional
36
+ The client session to use for all API calls
37
+
38
+ Attributes
39
+ ----------
40
+ BASE_URL : str
41
+ The base url of the API
42
+ TITLE : str
43
+ The title of the API
44
+ DEFAULT_FORMAT : OverlayFormat
45
+ The default formatting to use in all comic/gif overlays
46
+ discovery : DiscoveryAPI
47
+ The discovery API with all discovery endpoints
48
+ media : MediaAPI
49
+ The media API with all media endpoints
50
+ metadata : MetadataAPI
51
+ The metadataAPI with all metadata endpoints
52
+
53
+ """
27
54
 
28
55
  BASE_URL: str
29
56
  TITLE: str
30
- DEFAULT_FONT: FontFamily
57
+ DEFAULT_FORMAT: OverlayFormat
31
58
  _MAX_ALLOWED_SUBTITLES = 4
32
59
 
33
60
  discovery: DiscoveryAPI = DiscoveryAPI()
@@ -35,57 +62,31 @@ class AsyncCompuGlobalAPI:
35
62
  metadata: MetadataAPI = MetadataAPI()
36
63
 
37
64
  def __init__(self, session: aiohttp.ClientSession) -> None:
38
- """Create an API using the given session and timeout.
39
-
40
- Parameters
41
- ----------
42
- session : aiohttp.ClientSession | None, optional
43
- The client session to use for all API calls
44
-
45
- """
46
65
  self.client = CompuGlobalAPIClient(base_url=self.BASE_URL, session=session)
47
- self.config = CompuGlobalAPIConfig(title=self.TITLE, default_font=self.DEFAULT_FONT)
66
+ self.config = CompuGlobalAPIConfig(title=self.TITLE, default_format=self.DEFAULT_FORMAT)
48
67
 
49
68
  async def get_screencap(
50
69
  self,
70
+ *,
51
71
  episode: str | None = None,
52
72
  timestamp: int | None = None,
53
- frame: Frame | None = None,
54
73
  ) -> Screencap:
55
- """Get the screencap for the given episode & timestamp, or a screencap of the Frame object.
74
+ """Get the screencap for the given episode & timestamp.
56
75
 
57
76
  Parameters
58
77
  ----------
59
- episode : str, optional
78
+ episode : str | None, optional
60
79
  An episode key
61
- timestamp : int, optional
80
+ timestamp : int | None, optional
62
81
  A timestamp of the screencap
63
- frame : Frame, optional
64
- A Frame object
65
82
 
66
83
  Returns
67
84
  -------
68
85
  Screencap
69
86
  The screencap for the given episode key and timestamp.
70
87
 
71
- Raises
72
- ------
73
- TypeError
74
- Must give only episode + timestamp, or a Frame object.
75
-
76
88
  """
77
- if isinstance(episode, str) and isinstance(timestamp, int):
78
- params = {"e": episode, "t": timestamp, "nearby": 1}
79
-
80
- elif isinstance(frame, Frame):
81
- params = {"e": frame.key, "t": frame.timestamp, "nearby": 1}
82
-
83
- else:
84
- invalid_args_error = (
85
- f"Expected str and int or compuglobal.Frame, but received {type(episode)},"
86
- f" {type(timestamp)} and {type(frame)} instead"
87
- )
88
- raise TypeError(invalid_args_error)
89
+ params = {"e": episode, "t": timestamp, "nearby": 1}
89
90
 
90
91
  request = self.discovery.CAPTION.build_request(self.client.base_url, query=params)
91
92
  caption = await self.client.handle_request(request)
@@ -103,14 +104,14 @@ class AsyncCompuGlobalAPI:
103
104
  ----------
104
105
  search_text : str
105
106
  The search text to query
106
- season_minimum: int | None, optional
107
+ season_minimum : int | None, optional
107
108
  The minimum season allowed in the search results
108
- season_maximum: int | None, optional
109
+ season_maximum : int | None, optional
109
110
  The maximum season allowed in the search results
110
111
 
111
112
  Returns
112
113
  -------
113
- List[Frame]
114
+ list[FrameResult]
114
115
  A list of all frames found containing the search text.
115
116
 
116
117
  Raises
@@ -129,6 +130,13 @@ class AsyncCompuGlobalAPI:
129
130
  if len(search_results) > 0:
130
131
  return [FrameResult.model_validate(result) for result in search_results]
131
132
 
133
+ log.debug(
134
+ "No search results found | base_url=%s | search_text=%s | season_minimum=%s | season_maximum=%s",
135
+ self.BASE_URL,
136
+ search_text,
137
+ season_minimum,
138
+ season_maximum,
139
+ )
132
140
  raise NoSearchResultsFoundError
133
141
 
134
142
  async def search_for_screencap(
@@ -143,9 +151,9 @@ class AsyncCompuGlobalAPI:
143
151
  ----------
144
152
  search_text : str
145
153
  The search text to query
146
- season_minimum: int | None, optional
154
+ season_minimum : int | None, optional
147
155
  The minimum season allowed in the search
148
- season_maximum: int | None, optional
156
+ season_maximum : int | None, optional
149
157
  The maximum season allowed in the search
150
158
 
151
159
  Returns
@@ -156,7 +164,7 @@ class AsyncCompuGlobalAPI:
156
164
  """
157
165
  search_results = await self.search(search_text, season_minimum=season_minimum, season_maximum=season_maximum)
158
166
  result = search_results[0]
159
- return await self.get_screencap(result.key, result.timestamp)
167
+ return await self.get_screencap(episode=result.key, timestamp=result.timestamp)
160
168
 
161
169
  async def get_random_screencap(
162
170
  self,
@@ -165,9 +173,16 @@ class AsyncCompuGlobalAPI:
165
173
  ) -> Screencap:
166
174
  """Get a random TV Show screencap.
167
175
 
176
+ Parameters
177
+ ----------
178
+ season_minimum : int | None, optional
179
+ Minimum season number allowed in random result
180
+ season_maximum : int | None, optional
181
+ Maximum season number allowed in random result
182
+
168
183
  Returns
169
184
  -------
170
- compuglobal.Screencap
185
+ Screencap
171
186
  A random screencap object.
172
187
 
173
188
  """
@@ -208,7 +223,7 @@ class AsyncCompuGlobalAPI:
208
223
 
209
224
  Returns
210
225
  -------
211
- List[Subtitle]
226
+ list[Subtitle]
212
227
  The list of subtitles
213
228
 
214
229
  """
@@ -222,7 +237,7 @@ class AsyncCompuGlobalAPI:
222
237
 
223
238
  Returns
224
239
  -------
225
- List[ScreencapMoment]
240
+ list[ScreencapMoment]
226
241
  List of random screencap moments
227
242
 
228
243
  """
@@ -235,7 +250,7 @@ class AsyncCompuGlobalAPI:
235
250
 
236
251
  Returns
237
252
  -------
238
- List[EpisodeSummary]
253
+ list[EpisodeSummary]
239
254
  A list of episode summaries
240
255
 
241
256
  """
@@ -248,18 +263,18 @@ class AsyncCompuGlobalAPI:
248
263
 
249
264
  Parameters
250
265
  ----------
251
- key: str
266
+ key : str
252
267
  The episode key of the screencap.
253
- timestamp: int
268
+ timestamp : int
254
269
  The timestamp of the screencap.
255
- before: int
270
+ before : int
256
271
  The number of milliseconds before the timestamp.
257
- after: int
272
+ after : int
258
273
  The number of milliseconds after the timestamp.
259
274
 
260
275
  Returns
261
276
  -------
262
- list
277
+ list[Frame]
263
278
  A list of valid frames before and after the timestamp of
264
279
  the episode.
265
280
 
@@ -274,6 +289,11 @@ class AsyncCompuGlobalAPI:
274
289
  async def get_image_url(self, screencap: Screencap) -> str:
275
290
  """Get the direct image url for the screencap without any caption.
276
291
 
292
+ Parameters
293
+ ----------
294
+ screencap : Screencap
295
+ The screencap to use for generating the image url
296
+
277
297
  Returns
278
298
  -------
279
299
  str
@@ -283,15 +303,22 @@ class AsyncCompuGlobalAPI:
283
303
  path_params = {"key": screencap.frame.key, "timestamp": screencap.frame.timestamp}
284
304
  return self.media.IMAGE.build_encoded_url(self.client.base_url, path_params=path_params)
285
305
 
286
- async def get_comic_panel_url(self, screencap: Screencap, subtitles: list[Subtitle] | None = None) -> str:
306
+ async def get_comic_panel_url(
307
+ self,
308
+ screencap: Screencap,
309
+ subtitles: list[Subtitle] | None = None,
310
+ overlay_format: OverlayFormat | None = None,
311
+ ) -> str:
287
312
  """Get the URL for a single comic panel showing the given screencap with subtitles.
288
313
 
289
314
  Parameters
290
315
  ----------
291
316
  screencap : Screencap
292
317
  The screencap to use in the comic panel
293
- subtitles : List[Subtitle], optional
318
+ subtitles : list[Subtitle] | None, optional
294
319
  A list of subtitles to overlay in the comic panel
320
+ overlay_format : OverlayFormat | None, optional
321
+ The formatting to use in the comic panel overlay (subtitle)
295
322
 
296
323
  Returns
297
324
  -------
@@ -299,25 +326,30 @@ class AsyncCompuGlobalAPI:
299
326
  The url of the comic panel
300
327
 
301
328
  """
302
- if subtitles is None:
303
- subtitles = screencap.subtitles
329
+ screencap, subtitles, overlay_format = self._resolve_overlay_inputs(screencap, subtitles, overlay_format)
304
330
 
305
- screencap = screencap.model_copy(update={"subtitles": subtitles})
331
+ panel = ComicPanel.from_screencap(screencap=screencap, overlay_format=overlay_format)
306
332
 
307
- panel = ComicPanel.from_screencap(screencap=screencap, font=self.config.default_font)
308
-
309
- params = {"b64": panel.get_encoded()}
333
+ params = {"b64": panel.encoded}
310
334
  return self.media.COMIC_PANEL.build_encoded_url(self.client.base_url, query=params)
311
335
 
312
- async def get_comic_strip_url(self, screencap: Screencap, subtitles: list[Subtitle] | None = None) -> str:
336
+ async def get_comic_strip_url(
337
+ self,
338
+ screencap: Screencap,
339
+ subtitles: list[Subtitle] | None = None,
340
+ overlay_format: OverlayFormat | list[OverlayFormat] | None = None,
341
+ ) -> str:
313
342
  """Get the URL for a comic strip showing the given screencap with subtitles.
314
343
 
315
344
  Parameters
316
345
  ----------
317
346
  screencap : Screencap
318
347
  The screencap to use in the comic strip
319
- subtitles : List[Subtitle], optional
348
+ subtitles : list[Subtitle] | None, optional
320
349
  The subtitles to overlay in the comic strip
350
+ overlay_format : OverlayFormat | list[OverlayFormat] | None, optional
351
+ The formatting to use in the comic strip overlays (subtitleS). See :meth:`OverlayFormat.normalise` for
352
+ full details on how formats are resolved.
321
353
 
322
354
  Returns
323
355
  -------
@@ -325,28 +357,63 @@ class AsyncCompuGlobalAPI:
325
357
  The url of the comic strip
326
358
 
327
359
  """
328
- if subtitles is None:
329
- subtitles = screencap.subtitles
360
+ screencap, subtitles, overlay_format = self._resolve_overlay_inputs(screencap, subtitles, overlay_format)
330
361
 
331
- if len(subtitles) > self._MAX_ALLOWED_SUBTITLES:
332
- subtitles = subtitles[: self._MAX_ALLOWED_SUBTITLES]
362
+ comic_strip = ComicStrip.from_screencap(screencap=screencap, overlay_format=overlay_format)
363
+ params = {"b64": comic_strip.encoded, "layout": comic_strip.layout}
364
+ return self.media.COMIC_STRIP.build_encoded_url(self.client.base_url, query=params)
333
365
 
334
- # Change subtitles
335
- screencap = screencap.model_copy(update={"subtitles": subtitles})
366
+ async def get_comic_maker_url(
367
+ self,
368
+ screencap: Screencap,
369
+ subtitles: list[Subtitle] | None = None,
370
+ overlay_format: OverlayFormat | list[OverlayFormat] | None = None,
371
+ ) -> str:
372
+ """Get a url for making a comic with the given screencap, subtitles, and overlay format(s).
336
373
 
337
- comic_strip = ComicStrip.from_screencap(screencap=screencap, font_family=self.config.default_font)
338
- params = {"b64": comic_strip.get_encoded(), "layout": comic_strip.layout}
339
- return self.media.COMIC_STRIP.build_encoded_url(self.client.base_url, query=params)
374
+ Parameters
375
+ ----------
376
+ screencap : Screencap
377
+ The screencap to make the comic with
378
+ subtitles : list[Subtitle] | None, optional
379
+ The subtitles to override in the comic maker, by default None
380
+ overlay_format : OverlayFormat | list[OverlayFormat] | None, optional
381
+ The subtitle/overlay formatting to override in the comic maker, by default None
382
+
383
+ Returns
384
+ -------
385
+ str
386
+ The url for making the comic
387
+
388
+ """
389
+ screencap, subtitles, overlay_format = self._resolve_overlay_inputs(screencap, subtitles, overlay_format)
390
+ path_params = {"key": screencap.frame.key, "timestamp": screencap.frame.timestamp}
391
+
392
+ strip = ComicStrip.from_screencap(screencap=screencap, overlay_format=overlay_format)
340
393
 
341
- async def get_gif_url(self, screencap: Screencap, subtitles: list[Subtitle] | None = None) -> str:
394
+ return self.media.COMIC_MAKER.build_encoded_url(
395
+ base_url=self.BASE_URL,
396
+ path_params=path_params,
397
+ query={"b64": strip.encoded, "layout": strip.layout},
398
+ )
399
+
400
+ async def get_gif_url(
401
+ self,
402
+ screencap: Screencap,
403
+ subtitles: list[Subtitle] | None = None,
404
+ overlay_format: OverlayFormat | list[OverlayFormat] | None = None,
405
+ ) -> str:
342
406
  """Get the URL for a gif of the given screencap with default or given subtitles.
343
407
 
344
408
  Parameters
345
409
  ----------
346
410
  screencap : Screencap
347
411
  The screencap to use for the gif
348
- subtitles : List[Subtitle], optional
412
+ subtitles : list[Subtitle] | None, optional
349
413
  The subtitles to overlay in the gif
414
+ overlay_format : OverlayFormat | list[OverlayFormat] | None, optional
415
+ The formatting to use in the gif overlays (subtitles). See :meth:`OverlayFormat.normalise` for
416
+ full details on how formats are resolved.
350
417
 
351
418
  Returns
352
419
  -------
@@ -354,16 +421,9 @@ class AsyncCompuGlobalAPI:
354
421
  The URL of the gif, or a comic strip as a fallback if gif rendering fails.
355
422
 
356
423
  """
357
- if subtitles is None:
358
- subtitles = screencap.subtitles
424
+ screencap, subtitles, overlay_format = self._resolve_overlay_inputs(screencap, subtitles, overlay_format)
359
425
 
360
- if len(subtitles) > self._MAX_ALLOWED_SUBTITLES:
361
- subtitles = subtitles[: self._MAX_ALLOWED_SUBTITLES]
362
-
363
- # Change subtitles
364
- screencap = screencap.model_copy(update={"subtitles": subtitles})
365
-
366
- stream = Stream.from_screencap(screencap=screencap, font_family=self.config.default_font)
426
+ stream = Stream.from_screencap(screencap=screencap, overlay_format=overlay_format)
367
427
 
368
428
  request = self.media.RENDER_GIF.build_request(self.client.base_url, body=stream)
369
429
  request.body = [request.body]
@@ -377,13 +437,93 @@ class AsyncCompuGlobalAPI:
377
437
 
378
438
  return await self.get_comic_strip_url(screencap)
379
439
 
440
+ async def get_gif_maker_url(
441
+ self,
442
+ screencap: Screencap,
443
+ subtitles: list[Subtitle] | None = None,
444
+ overlay_format: OverlayFormat | list[OverlayFormat] | None = None,
445
+ ) -> str:
446
+ """Get a url for making a comic with the given screencap, subtitles, and overlay format(s).
447
+
448
+ Parameters
449
+ ----------
450
+ screencap : Screencap
451
+ The screencap to make the comic with
452
+ subtitles : list[Subtitle] | None, optional
453
+ The subtitles to override in the comic maker, by default None
454
+ overlay_format : OverlayFormat | list[OverlayFormat] | None, optional
455
+ The subtitle/overlay formatting to override in the comic maker, by default None
456
+
457
+ Returns
458
+ -------
459
+ str
460
+ The url for making the comic
461
+
462
+ """
463
+ screencap, subtitles, overlay_format = self._resolve_overlay_inputs(screencap, subtitles, overlay_format)
464
+
465
+ path_params = {
466
+ "key": screencap.frame.key,
467
+ "start_timestamp": screencap.start,
468
+ "end_timestamp": screencap.end,
469
+ }
470
+
471
+ stream = Stream.from_screencap(screencap=screencap, overlay_format=overlay_format)
472
+
473
+ return self.media.GIF_MAKER.build_encoded_url(
474
+ base_url=self.BASE_URL,
475
+ path_params=path_params,
476
+ query={"b64": stream.encoded},
477
+ )
478
+
479
+ @overload
480
+ def _resolve_overlay_inputs(
481
+ self,
482
+ screencap: Screencap,
483
+ subtitles: list[Subtitle] | None = None,
484
+ overlay_format: OverlayFormat | None = None,
485
+ ) -> tuple[Screencap, list[Subtitle], OverlayFormat]: ...
486
+
487
+ @overload
488
+ def _resolve_overlay_inputs(
489
+ self,
490
+ screencap: Screencap,
491
+ subtitles: list[Subtitle] | None = None,
492
+ overlay_format: OverlayFormat | list[OverlayFormat] | None = None,
493
+ ) -> tuple[Screencap, list[Subtitle], OverlayFormat | list[OverlayFormat]]: ...
494
+
495
+ def _resolve_overlay_inputs(
496
+ self,
497
+ screencap: Screencap,
498
+ subtitles: list[Subtitle] | None = None,
499
+ overlay_format: OverlayFormat | list[OverlayFormat] | None = None,
500
+ ) -> tuple[Screencap, list[Subtitle], OverlayFormat | list[OverlayFormat]]:
501
+
502
+ # Use default format if not given
503
+ overlay_format = overlay_format or self.config.default_format
504
+ if overlay_format != self.config.default_format:
505
+ log.debug("Using custom overlay format | screencap=%s | overlay_format=%s", screencap, overlay_format)
506
+
507
+ # Use screencap subtitles if not given
508
+ subtitles = subtitles or screencap.subtitles
509
+ if subtitles != screencap.subtitles:
510
+ log.debug("Using custom subtitles | screencap=%s | subtitles=%s", screencap, subtitles)
511
+
512
+ # Prevent too many subtitles being used
513
+ subtitles = subtitles[: self._MAX_ALLOWED_SUBTITLES]
514
+
515
+ # Change subtitles
516
+ screencap = screencap.model_copy(update={"subtitles": subtitles})
517
+
518
+ return screencap, subtitles, overlay_format
519
+
380
520
 
381
521
  class CapitalBeatUs(AsyncCompuGlobalAPI):
382
522
  """An API Wrapper for accessing CapitalBeatUs API endpoints (West Wing)."""
383
523
 
384
524
  BASE_URL = "https://capitalbeat.us"
385
525
  TITLE = "West Wing"
386
- DEFAULT_FONT = FontFamily.IMPACT
526
+ DEFAULT_FORMAT = OverlayFormat(font_family=FontFamily.IMPACT)
387
527
 
388
528
 
389
529
  class Frinkiac(AsyncCompuGlobalAPI):
@@ -391,7 +531,7 @@ class Frinkiac(AsyncCompuGlobalAPI):
391
531
 
392
532
  BASE_URL = "https://frinkiac.com"
393
533
  TITLE = "Simpsons"
394
- DEFAULT_FONT = FontFamily.AKBAR
534
+ DEFAULT_FORMAT = OverlayFormat(font_family=FontFamily.AKBAR)
395
535
 
396
536
 
397
537
  @deprecated("The MasterOfAllScience API is deprecated, and currently redirects to Frinkiac")
@@ -400,7 +540,7 @@ class MasterOfAllScience(AsyncCompuGlobalAPI):
400
540
 
401
541
  BASE_URL = "https://masterofallscience.com"
402
542
  TITLE = "Rick and Morty"
403
- DEFAULT_FONT = FontFamily.IMPACT
543
+ DEFAULT_FORMAT = OverlayFormat(font_family=FontFamily.IMPACT)
404
544
 
405
545
 
406
546
  class Morbotron(AsyncCompuGlobalAPI):
@@ -408,4 +548,4 @@ class Morbotron(AsyncCompuGlobalAPI):
408
548
 
409
549
  BASE_URL = "https://morbotron.com"
410
550
  TITLE = "Futurama"
411
- DEFAULT_FONT = FontFamily.FR_BOLD
551
+ DEFAULT_FORMAT = OverlayFormat(font_family=FontFamily.FR_BOLD)