11import logging
22from collections .abc import Generator
3+ from enum import Enum
34from pathlib import Path
45from platform import python_version
56from typing import Annotated , Protocol , TypeVar
1718
1819app = Typer ()
1920LOGGER = logging .getLogger ("mediux-posters" )
20- T = TypeVar ("T" , bound = "HasUsername" )
21-
22-
23- class HasUsername (Protocol ):
24- username : str
2521
2622
2723@app .callback (invoke_without_command = True )
@@ -71,6 +67,13 @@ def setup(
7167 return settings , mediux , service_list
7268
7369
70+ T = TypeVar ("T" , bound = "MediuxSet" )
71+
72+
73+ class MediuxSet (Protocol ):
74+ username : str
75+
76+
7477def filter_sets (
7578 set_list : list [T ], priority_usernames : list [str ], only_priority_usernames : bool
7679) -> Generator [T ]:
@@ -86,6 +89,93 @@ def filter_sets(
8689 yield from [x for x in set_list if x .username not in priority_usernames ]
8790
8891
92+ class MediaTypeChoice (str , Enum ):
93+ SHOW = MediaType .SHOW .value
94+ COLLECTION = MediaType .COLLECTION .value
95+ MOVIE = MediaType .MOVIE .value
96+
97+
98+ @app .command (
99+ name = "sync" , help = "Synchronize posters by fetching data from Mediux and updating your services."
100+ )
101+ def sync_posters (
102+ skip_mediatypes : Annotated [
103+ list [MediaTypeChoice ],
104+ Option (
105+ "--skip-type" ,
106+ "-T" ,
107+ show_default = False ,
108+ case_sensitive = False ,
109+ default_factory = list ,
110+ help = "List of MediaTypes to skip. "
111+ "Specify this option multiple times for skipping multiple types." ,
112+ ),
113+ ],
114+ skip_libraries : Annotated [
115+ list [str ],
116+ Option (
117+ "--skip-library" ,
118+ "-L" ,
119+ show_default = False ,
120+ default_factory = list ,
121+ help = "List of libraries to skip. "
122+ "Specify this option multiple times for skipping multiple libraries. " ,
123+ ),
124+ ],
125+ start : Annotated [
126+ int , Option ("--start" , "-s" , help = "The starting index for processing media." )
127+ ] = 0 ,
128+ end : Annotated [
129+ int , Option ("--end" , "-e" , help = "The ending index for processing media." )
130+ ] = 100_000 ,
131+ full_clean : Annotated [
132+ bool ,
133+ Option (
134+ "--full-clean" , "-C" , show_default = False , help = "Delete the whole cache before starting."
135+ ),
136+ ] = False ,
137+ debug : Annotated [
138+ bool ,
139+ Option (
140+ "--debug" ,
141+ help = "Enable debug mode to show extra logging information for troubleshooting." ,
142+ ),
143+ ] = False ,
144+ ) -> None :
145+ settings , mediux , service_list = setup (full_clean = full_clean , debug = debug )
146+ skip_mediatypes = [x .value for x in skip_mediatypes ]
147+
148+ for idx , service in enumerate (service_list ):
149+ CONSOLE .rule (
150+ f"[{ idx + 1 } /{ len (service_list )} ] { type (service ).__name__ } Service" ,
151+ align = "left" ,
152+ style = "title" ,
153+ )
154+ for media_type in MediaType :
155+ if media_type .value in skip_mediatypes :
156+ continue
157+ with CONSOLE .status (
158+ f"[{ type (service ).__name__ } ] Fetching { media_type .name .lower ().capitalize ()} media"
159+ ):
160+ entries = service .list (media_type = media_type , skip_libraries = skip_libraries )[
161+ start :end
162+ ]
163+ for index , entry in enumerate (entries ):
164+ CONSOLE .rule (
165+ f"[{ index + 1 } /{ len (entries )} ] { entry .display_name } [tmdb-{ entry .tmdb_id } ]" ,
166+ align = "left" ,
167+ style = "subtitle" ,
168+ )
169+ LOGGER .info (
170+ "[%s] Searching Mediux for '%s' sets" ,
171+ type (service ).__name__ ,
172+ entry .display_name ,
173+ )
174+ set_list = mediux .list_sets (media_type = media_type , tmdb_id = entry .tmdb_id )
175+ for set_data in filter_sets (set_list = set_list , settings = settings , mediux = mediux ):
176+ LOGGER .info ("Downloading '%s' by '%s'" , set_data .set_title , set_data .username )
177+
178+
89179@app .command (
90180 name = "manual" , help = "Manually set posters for specific Mediux media using a file or URLs."
91181)
@@ -144,6 +234,16 @@ def manual_posters(
144234 )
145235 url_list = [x .strip () for x in file .read_text ().splitlines ()] if file else urls
146236 for index , entry in enumerate (url_list ):
237+ if entry .startswith (f"{ Mediux .WEB_URL } /sets" ):
238+ set_posters (
239+ settings = settings ,
240+ mediux = mediux ,
241+ service = service ,
242+ url = entry ,
243+ simple_clean = simple_clean ,
244+ debug = debug ,
245+ )
246+ continue
147247 media_type = (
148248 MediaType .SHOW
149249 if entry .startswith (f"{ Mediux .WEB_URL } /{ MediaType .SHOW } s" )
@@ -157,15 +257,7 @@ def manual_posters(
157257 continue
158258 tmdb_id = int (entry .split ("/" )[- 1 ])
159259 with CONSOLE .status (f"Searching { type (service ).__name__ } for TMDB id: '{ tmdb_id } '" ):
160- obj = (
161- service .get_show (tmdb_id = tmdb_id )
162- if media_type is MediaType .SHOW
163- else service .get_collection (tmdb_id = tmdb_id )
164- if media_type is MediaType .COLLECTION
165- else service .get_movie (tmdb_id = tmdb_id )
166- if media_type is MediaType .MOVIE
167- else None
168- )
260+ obj = service .get (media_type = media_type , tmdb_id = tmdb_id )
169261 if not obj :
170262 LOGGER .warning (
171263 "[%s] Unable to find a %s with a Tmdb Id of '%d'" ,
@@ -196,20 +288,10 @@ def manual_posters(
196288 / slugify (movie .display_name )
197289 )
198290 try :
199- set_list = (
200- mediux .list_show_sets (
201- tmdb_id = tmdb_id , exclude_usernames = settings .exclude_usernames
202- )
203- if media_type is MediaType .SHOW
204- else mediux .list_collection_sets (
205- tmdb_id = tmdb_id , exclude_usernames = settings .exclude_usernames
206- )
207- if media_type is MediaType .COLLECTION
208- else mediux .list_movie_sets (
209- tmdb_id = tmdb_id , exclude_usernames = settings .exclude_usernames
210- )
211- if media_type is MediaType .MOVIE
212- else None
291+ set_list = mediux .list_sets (
292+ media_type = media_type ,
293+ tmdb_id = tmdb_id ,
294+ exclude_usernames = settings .exclude_usernames ,
213295 )
214296 except ServiceError as err :
215297 LOGGER .error (err )
@@ -222,113 +304,57 @@ def manual_posters(
222304 LOGGER .info ("Downloading '%s' by '%s'" , set_data .set_title , set_data .username )
223305
224306
225- @app .command (name = "set" , help = "Manually set posters for specific Mediux sets using a file or URLs." )
226307def set_posters (
227- file : Annotated [
228- Path | None ,
229- Option (
230- dir_okay = False ,
231- exists = True ,
232- show_default = False ,
233- help = "Path to a file containing URLs of Mediux sets, one per line. "
234- "If set, the file must exist and cannot be a directory." ,
235- ),
236- ] = None ,
237- urls : Annotated [
238- list [str ] | None ,
239- Option (
240- "--url" ,
241- "-u" ,
242- show_default = False ,
243- help = "List of URLs of Mediux sets to process. "
244- "Specify this option multiple times for multiple URLs." ,
245- ),
246- ] = None ,
247- full_clean : Annotated [
248- bool ,
249- Option (
250- "--full-clean" , "-C" , show_default = False , help = "Delete the whole cache before starting."
251- ),
252- ] = False ,
253- simple_clean : Annotated [
254- bool ,
255- Option (
256- "--simple-clean" ,
257- "-c" ,
258- show_default = False ,
259- help = "Delete the cache of each media instead of the whole cache." ,
260- ),
261- ] = False ,
262- debug : Annotated [
263- bool ,
264- Option (
265- "--debug" ,
266- help = "Enable debug mode to show extra logging information for troubleshooting." ,
267- ),
268- ] = False ,
308+ settings : Settings , # noqa: ARG001
309+ mediux : Mediux ,
310+ service : BaseService ,
311+ url : str ,
312+ simple_clean : bool = False ,
313+ debug : bool = False , # noqa: ARG001
269314) -> None :
270- settings , mediux , service_list = setup (full_clean = full_clean , debug = debug )
315+ set_id = int (url .split ("/" )[- 1 ])
316+ set_data = (
317+ mediux .get_show_set (set_id = set_id )
318+ or mediux .get_collection_set (set_id = set_id )
319+ or mediux .get_movie_set (set_id = set_id )
320+ )
321+ if tmdb_id := (
322+ set_data .show .tmdb_id
323+ if isinstance (set_data , ShowSet )
324+ else set_data .collection .tmdb_id
325+ if isinstance (set_data , CollectionSet )
326+ else set_data .movie .tmdb_id
327+ if isinstance (set_data , MovieSet )
328+ else None
329+ ):
330+ return
271331
272- for idx , service in enumerate (service_list ):
273- CONSOLE .rule (
274- f"[{ idx + 1 } /{ len (service_list )} ] { type (service ).__name__ } Service" ,
275- align = "left" ,
276- style = "title" ,
277- )
278- url_list = [x .strip () for x in file .read_text ().splitlines ()] if file else urls
279- for index , entry in enumerate (url_list ):
280- if not entry .startswith (f"{ Mediux .WEB_URL } /sets" ):
281- continue
282- set_id = int (entry .split ("/" )[- 1 ])
283- set_data = (
284- mediux .get_show_set (set_id = set_id )
285- or mediux .get_collection_set (set_id = set_id )
286- or mediux .get_movie_set (set_id = set_id )
332+ with CONSOLE .status (
333+ f"Searching { type (service ).__name__ } for '{ set_data .set_title } [{ tmdb_id } ]'"
334+ ):
335+ obj = service .find (tmdb_id = tmdb_id )
336+ if not obj :
337+ LOGGER .warning (
338+ "[%s] Unable to find any media with a Tmdb Id of '%d'" ,
339+ type (service ).__name__ ,
340+ tmdb_id ,
287341 )
288- if tmdb_id := (
289- set_data .show .tmdb_id
290- if isinstance (set_data , ShowSet )
291- else set_data .collection .tmdb_id
292- if isinstance (set_data , CollectionSet )
293- else set_data .movie .tmdb_id
294- if isinstance (set_data , MovieSet )
295- else None
296- ):
297- continue
342+ return
298343
299- with CONSOLE .status (
300- f"Searching { type (service ).__name__ } for '{ set_data .get ('set_name' )} [{ tmdb_id } ]'"
301- ):
302- obj = service .find (tmdb_id = tmdb_id )
303- if not obj :
304- LOGGER .warning (
305- "[%s] Unable to find any media with a Tmdb Id of '%d'" ,
306- type (service ).__name__ ,
307- tmdb_id ,
308- )
309- continue
310- CONSOLE .rule (
311- f"[{ index + 1 } /{ len (url_list )} ] { obj .display_name } [tmdb-{ obj .tmdb_id } ]" ,
312- align = "left" ,
313- style = "subtitle" ,
314- )
315- if simple_clean :
316- LOGGER .info ("Cleaning %s cache" , obj .display_name )
344+ if simple_clean :
345+ LOGGER .info ("Cleaning %s cache" , obj .display_name )
346+ delete_folder (
347+ folder = get_cache_root () / "covers" / obj .mediatype .value / slugify (obj .display_name )
348+ )
349+ if isinstance (obj , Collection ):
350+ for movie in obj .movies :
317351 delete_folder (
318352 folder = get_cache_root ()
319353 / "covers"
320- / obj .mediatype .value
321- / slugify (obj .display_name )
354+ / movie .mediatype .value
355+ / slugify (movie .display_name )
322356 )
323- if isinstance (obj , Collection ):
324- for movie in obj .movies :
325- delete_folder (
326- folder = get_cache_root ()
327- / "covers"
328- / movie .mediatype .value
329- / slugify (movie .display_name )
330- )
331- LOGGER .info ("Downloading '%s' by '%s'" , set_data .set_title , set_data .username )
357+ LOGGER .info ("Downloading '%s' by '%s'" , set_data .set_title , set_data .username )
332358
333359
334360if __name__ == "__main__" :
0 commit comments