77from pathlib import Path
88from platform import python_version
99from tempfile import TemporaryDirectory
10+ from typing import cast
1011
1112from pydantic import ValidationError
1213from rich .prompt import Prompt
@@ -39,8 +40,7 @@ def convert_collection(path: Path, output: OutputFormat) -> None:
3940 OutputFormat .CB7 : (".cb7" , CB7Archive ),
4041 OutputFormat .CBT : (".cbt" , CBTArchive ),
4142 }.get (output , (".cbz" , CBZArchive ))
42- formats = list (ARCHIVE_EXTENSIONS )
43- formats .remove (format_ )
43+ formats = [ext for ext in ARCHIVE_EXTENSIONS if ext != format_ ]
4444 for file in list_files (path , * formats ):
4545 with CONSOLE .status (
4646 f"Converting { file .name } to { output .name } " , spinner = "simpleDotsScrolling"
@@ -57,110 +57,92 @@ def read_meta_file(cls: type[InfoModel], filename: str) -> InfoModel | None:
5757 return cls .from_bytes (content = archive .read_file (filename = filename ))
5858 return None
5959
60- # region Read Metadata
6160 try :
62- metadata = read_meta_file (cls = Metadata , filename = "/Metadata.xml" ) or read_meta_file (
63- cls = Metadata , filename = "Metadata.xml"
61+ metadata = read_meta_file (Metadata , "/Metadata.xml" ) or read_meta_file (
62+ Metadata , "Metadata.xml"
6463 )
6564 if metadata :
65+ metadata = cast (Metadata , metadata )
6666 meta = metadata .meta
6767 details = Details (
6868 series = Identifications (
6969 search = metadata .issue .series .title ,
70- comicvine = get_metadata_id (
71- resources = metadata .issue .series .resources , source = Source .COMICVINE
72- ),
70+ comicvine = get_metadata_id (metadata .issue .series .resources , Source .COMICVINE ),
7371 league = get_metadata_id (
74- resources = metadata .issue .series .resources ,
75- source = Source .LEAGUE_OF_COMIC_GEEKS ,
76- ),
77- marvel = get_metadata_id (
78- resources = metadata .issue .series .resources , source = Source .MARVEL
79- ),
80- metron = get_metadata_id (
81- resources = metadata .issue .series .resources , source = Source .METRON
72+ metadata .issue .series .resources , Source .LEAGUE_OF_COMIC_GEEKS
8273 ),
74+ marvel = get_metadata_id (metadata .issue .series .resources , Source .MARVEL ),
75+ metron = get_metadata_id (metadata .issue .series .resources , Source .METRON ),
8376 ),
8477 issue = Identifications (
8578 search = metadata .issue .number ,
86- comicvine = get_metadata_id (
87- resources = metadata .issue .resources , source = Source .COMICVINE
88- ),
89- league = get_metadata_id (
90- resources = metadata .issue .resources , source = Source .LEAGUE_OF_COMIC_GEEKS
91- ),
92- marvel = get_metadata_id (
93- resources = metadata .issue .resources , source = Source .MARVEL
94- ),
95- metron = get_metadata_id (
96- resources = metadata .issue .resources , source = Source .METRON
97- ),
79+ comicvine = get_metadata_id (metadata .issue .resources , Source .COMICVINE ),
80+ league = get_metadata_id (metadata .issue .resources , Source .LEAGUE_OF_COMIC_GEEKS ),
81+ marvel = get_metadata_id (metadata .issue .resources , Source .MARVEL ),
82+ metron = get_metadata_id (metadata .issue .resources , Source .METRON ),
9883 ),
9984 )
10085 return meta , details
10186 except ValidationError :
10287 LOGGER .error ("%s contains an invalid Metadata file" , archive .path .name ) # noqa: TRY400
103- # endregion
10488
105- # region Read MetronInfo
10689 try :
107- metron_info = read_meta_file (cls = MetronInfo , filename = "/MetronInfo.xml" ) or read_meta_file (
108- cls = MetronInfo , filename = "MetronInfo.xml"
90+ metron_info = read_meta_file (MetronInfo , "/MetronInfo.xml" ) or read_meta_file (
91+ MetronInfo , "MetronInfo.xml"
10992 )
11093 if metron_info :
94+ metron_info = cast (MetronInfo , metron_info )
95+ series_id = metron_info .series .id if metron_info .id else None
96+ issue_id = metron_info .id .primary .value if metron_info .id else None
11197 details = Details (
11298 series = Identifications (
11399 search = metron_info .series .name ,
114- comicvine = metron_info . series . id
115- if metron_info .id and metron_info . id .source == InformationSource .COMIC_VINE
100+ comicvine = series_id
101+ if metron_info .id . primary .source == InformationSource .COMIC_VINE
116102 else None ,
117- league = metron_info .series .id
118- if metron_info .id
119- and metron_info .id .source == InformationSource .LEAGUE_OF_COMIC_GEEKS
103+ league = series_id
104+ if metron_info .id .primary .source == InformationSource .LEAGUE_OF_COMIC_GEEKS
120105 else None ,
121- marvel = metron_info . series . id
122- if metron_info .id and metron_info . id .source == InformationSource .MARVEL
106+ marvel = series_id
107+ if metron_info .id . primary .source == InformationSource .MARVEL
123108 else None ,
124- metron = metron_info . series . id
125- if metron_info .id and metron_info . id .source == InformationSource .METRON
109+ metron = series_id
110+ if metron_info .id . primary .source == InformationSource .METRON
126111 else None ,
127112 ),
128113 issue = Identifications (
129114 search = metron_info .number ,
130- comicvine = metron_info . id . value
131- if metron_info .id and metron_info . id .source == InformationSource .COMIC_VINE
115+ comicvine = issue_id
116+ if metron_info .id . primary .source == InformationSource .COMIC_VINE
132117 else None ,
133- league = metron_info .id .value
134- if metron_info .id
135- and metron_info .id .source == InformationSource .LEAGUE_OF_COMIC_GEEKS
118+ league = issue_id
119+ if metron_info .id .primary .source == InformationSource .LEAGUE_OF_COMIC_GEEKS
136120 else None ,
137- marvel = metron_info . id . value
138- if metron_info .id and metron_info . id .source == InformationSource .MARVEL
121+ marvel = issue_id
122+ if metron_info .id . primary .source == InformationSource .MARVEL
139123 else None ,
140- metron = metron_info . id . value
141- if metron_info .id and metron_info . id .source == InformationSource .METRON
124+ metron = issue_id
125+ if metron_info .id . primary .source == InformationSource .METRON
142126 else None ,
143127 ),
144128 )
145129 return Meta (date_ = date .today (), tool = Tool (value = "MetronInfo" )), details
146130 except ValidationError :
147131 LOGGER .error ("%s contains an invalid MetronInfo file" , archive .path .name ) # noqa: TRY400
148- # endregion
149132
150- # region Read ComicInfo
151133 try :
152- comic_info = read_meta_file (cls = ComicInfo , filename = "/ComicInfo.xml" ) or read_meta_file (
153- cls = ComicInfo , filename = "ComicInfo.xml"
134+ comic_info = read_meta_file (ComicInfo , "/ComicInfo.xml" ) or read_meta_file (
135+ ComicInfo , "ComicInfo.xml"
154136 )
155137 if comic_info :
138+ comic_info = cast (ComicInfo , comic_info )
156139 details = Details (
157140 series = Identifications (search = comic_info .series ),
158141 issue = Identifications (search = comic_info .number ),
159142 )
160143 return Meta (date_ = date .today (), tool = Tool (value = "ComicInfo" )), details
161144 except ValidationError :
162145 LOGGER .error ("%s contains an invalid ComicInfo file" , archive .path .name ) # noqa: TRY400
163- # endregion
164146
165147 return None , None
166148
@@ -173,67 +155,58 @@ def load_archives(
173155 archive = get_archive (path = file )
174156 LOGGER .debug ("Reading %s" , file .stem )
175157 meta , details = read_meta (archive = archive )
176- if not meta or not details :
177- archives .append ((file , archive , details ))
178- continue
179- difference = abs (date .today () - meta .date_ )
180- if force or meta .tool != Tool () or difference .days >= 28 :
158+ if (
159+ not meta
160+ or not details
161+ or force
162+ or meta .tool != Tool ()
163+ or abs (date .today () - meta .date_ ).days >= 28
164+ ):
181165 archives .append ((file , archive , details ))
182- continue
183166 return archives
184167
185168
186169def fetch_from_services (
187170 settings : Settings , details : Details
188171) -> tuple [Metadata | None , MetronInfo | None , ComicInfo | None ]:
189- marvel = None
190- if settings .marvel and settings .marvel .public_key and settings .marvel .private_key :
191- marvel = Marvel (settings = settings .marvel )
192- metron = None
193- if settings .metron and settings .metron .username and settings .metron .password :
194- metron = Metron (settings = settings .metron )
195- comicvine = None
196- if settings .comicvine and settings .comicvine .api_key :
197- comicvine = Comicvine (settings .comicvine )
198- league = None
199- if (
200- settings .league_of_comic_geeks
172+ services = {
173+ Service .COMICVINE : Comicvine (settings .comicvine )
174+ if settings .comicvine and settings .comicvine .api_key
175+ else None ,
176+ Service .LEAGUE_OF_COMIC_GEEKS : League (settings .league_of_comic_geeks )
177+ if settings .league_of_comic_geeks
201178 and settings .league_of_comic_geeks .client_id
202179 and settings .league_of_comic_geeks .client_secret
203- ):
204- league = League (settings .league_of_comic_geeks )
205- if not marvel and not metron and not comicvine and not league :
206- LOGGER .warning ("No external services configured" )
207- return None , None , None
208-
209- services = {
210- Service .COMICVINE : comicvine ,
211- Service .LEAGUE_OF_COMIC_GEEKS : league ,
212- Service .MARVEL : marvel ,
213- Service .METRON : metron ,
180+ else None ,
181+ Service .MARVEL : Marvel (settings .marvel )
182+ if settings .marvel and settings .marvel .public_key and settings .marvel .private_key
183+ else None ,
184+ Service .METRON : Metron (settings .metron )
185+ if settings .metron and settings .metron .username and settings .metron .password
186+ else None ,
214187 }
215188
216189 for service_name in settings .service_order :
217190 service = services [service_name ]
218- if not service :
219- continue
220- LOGGER . info ( "Fetching details from %s" , type ( service ). __name__ )
221- metadata , metron_info , comic_info = service . fetch ( details = details )
222- if metadata and metron_info and comic_info :
223- return metadata , metron_info , comic_info
191+ if service :
192+ LOGGER . info ( "Fetching details from %s" , type ( service ). __name__ )
193+ metadata , metron_info , comic_info = service . fetch ( details = details )
194+ if metadata and metron_info and comic_info :
195+ return metadata , metron_info , comic_info
196+ LOGGER . warning ( "No external services configured or data incomplete" )
224197 return None , None , None
225198
226199
227200def generate_filename (root : Path , extension : str , metadata : Metadata ) -> Path :
228- publisher_filename = metadata .issue .series .publisher .title
229- series_filename = (
201+ publisher_filename = sanitize ( metadata .issue .series .publisher .title )
202+ series_filename = sanitize (
230203 f"{ metadata .issue .series .title } v{ metadata .issue .series .volume } "
231204 if metadata .issue .series .volume > 1
232205 else metadata .issue .series .title
233206 )
234207
235208 number_str = (
236- f"_#{ metadata .issue .number .zfill (3 if metadata .issue .format == Format .COMIC else 2 )} "
209+ f"_#{ metadata .issue .number .zfill (3 if metadata .issue .format == Format .SINGLE_ISSUE else 2 )} "
237210 if metadata .issue .number
238211 else ""
239212 )
@@ -244,19 +217,18 @@ def generate_filename(root: Path, extension: str, metadata: Metadata) -> Path:
244217 Format .HARDCOVER : "_HC" ,
245218 Format .TRADE_PAPERBACK : "_TP" ,
246219 }.get (metadata .issue .format , "" )
247- if metadata .issue .format in {Format .ANNUAL , Format .DIGITAL_CHAPTER }:
248- issue_filename = sanitize (value = series_filename ) + format_str + number_str
249- elif metadata .issue .format in {Format .GRAPHIC_NOVEL , Format .HARDCOVER , Format .TRADE_PAPERBACK }:
250- issue_filename = sanitize (value = series_filename ) + number_str + format_str
220+
221+ if metadata .issue .format in {
222+ Format .GRAPHIC_NOVEL ,
223+ Format .HARDCOVER ,
224+ Format .TRADE_PAPERBACK ,
225+ Format .OMNIBUS ,
226+ }:
227+ issue_filename = f"{ series_filename } { number_str } { format_str } "
251228 else :
252- issue_filename = sanitize ( value = series_filename ) + number_str
229+ issue_filename = f" { series_filename } { format_str } { number_str } "
253230
254- return (
255- root
256- / sanitize (value = publisher_filename )
257- / sanitize (value = series_filename )
258- / f"{ issue_filename } .{ extension } "
259- )
231+ return root / publisher_filename / series_filename / f"{ issue_filename } .{ extension } "
260232
261233
262234def rename_images (folder : Path , filename : str ) -> None :
@@ -276,7 +248,7 @@ def process_pages(
276248 from perdoo .models .metadata import Page as MetadataPage
277249 from perdoo .models .metron_info import Page as MetronPage
278250
279- rename_images (folder = folder , filename = filename )
251+ rename_images (folder , filename )
280252 image_list = list_files (folder , * IMAGE_EXTENSIONS )
281253 metadata_pages = set ()
282254 metron_info_pages = set ()
@@ -304,7 +276,6 @@ def process_pages(
304276
305277def start (settings : Settings , force : bool = False ) -> None :
306278 LOGGER .info ("Starting Perdoo" )
307-
308279 convert_collection (path = settings .input_folder , output = settings .output .format )
309280
310281 with CONSOLE .status (
@@ -325,6 +296,7 @@ def start(settings: Settings, force: bool = False) -> None:
325296 if not metadata :
326297 LOGGER .warning ("Not enough information to organize and rename this comic, skipping" )
327298 continue
299+
328300 new_file = generate_filename (
329301 root = settings .output_folder , extension = settings .output .format .value , metadata = metadata
330302 )
@@ -344,6 +316,7 @@ def start(settings: Settings, force: bool = False) -> None:
344316 filename = new_file .stem ,
345317 )
346318 metadata .meta = Meta (date_ = date .today ())
319+
347320 files = list_files (temp_folder , * IMAGE_EXTENSIONS )
348321 if settings .output .create_metadata :
349322 metadata_file = temp_folder / "Metadata.xml"
@@ -357,6 +330,7 @@ def start(settings: Settings, force: bool = False) -> None:
357330 comic_info_file = temp_folder / "ComicInfo.xml"
358331 comic_info .to_file (file = comic_info_file )
359332 files .append (comic_info_file )
333+
360334 status .update (f"Archiving { new_file .stem } " )
361335 archive_file = archive .archive_files (
362336 src = temp_folder , output_name = archive .path .stem , files = files
@@ -366,6 +340,7 @@ def start(settings: Settings, force: bool = False) -> None:
366340 continue
367341 archive .path .unlink (missing_ok = True )
368342 shutil .move (archive_file , archive .path )
343+
369344 if file .relative_to (settings .input_folder ) != new_file .relative_to (settings .output_folder ):
370345 LOGGER .info (
371346 "Organizing comic, moving file to %s" , new_file .relative_to (settings .output_folder )
0 commit comments