Skip to content

Commit 8b6355a

Browse files
Metron updates (#26)
- Update with new MetronInfo format statuses - Tidy up main - Unlink THEN move when converting archives
1 parent 54eb48e commit 8b6355a

14 files changed

Lines changed: 109 additions & 135 deletions

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: v0.5.2
3+
rev: v0.5.6
44
hooks:
55
- id: ruff-format
66
- id: ruff

perdoo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"get_data_root",
88
"setup_logging",
99
]
10-
__version__ = "0.2.1"
10+
__version__ = "0.3.0"
1111

1212
import logging
1313
import os

perdoo/__main__.py

Lines changed: 81 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pathlib import Path
88
from platform import python_version
99
from tempfile import TemporaryDirectory
10+
from typing import cast
1011

1112
from pydantic import ValidationError
1213
from 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

186169
def 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

227200
def 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

262234
def 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

305277
def 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)

perdoo/archives/cb7.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,6 @@ def convert(old_archive: BaseArchive) -> CB7Archive | None:
8484
if not archive_file:
8585
return None
8686
new_filepath = old_archive.path.parent / f"{old_archive.path.stem}.cb7"
87-
shutil.move(archive_file, new_filepath)
8887
old_archive.path.unlink(missing_ok=True)
88+
shutil.move(archive_file, new_filepath)
8989
return CB7Archive(path=new_filepath)

perdoo/archives/cbt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,6 @@ def convert(old_archive: BaseArchive) -> CBTArchive | None:
7979
if not archive_file:
8080
return None
8181
new_filepath = old_archive.path.parent / f"{old_archive.path.stem}.cbt"
82-
shutil.move(archive_file, new_filepath)
8382
old_archive.path.unlink(missing_ok=True)
83+
shutil.move(archive_file, new_filepath)
8484
return CBTArchive(path=new_filepath)

perdoo/archives/cbz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ def convert(old_archive: BaseArchive) -> CBZArchive | None:
6767
if not archive_file:
6868
return None
6969
new_filepath = old_archive.path.parent / f"{old_archive.path.stem}.cbz"
70-
shutil.move(archive_file, new_filepath)
7170
old_archive.path.unlink(missing_ok=True)
71+
shutil.move(archive_file, new_filepath)
7272
return CBZArchive(path=new_filepath)

0 commit comments

Comments
 (0)