Skip to content

Commit c167250

Browse files
Replace archives module with darkseid
Currently only uses darkseid for archive management, metadata is still handled internally
1 parent 9d777bc commit c167250

24 files changed

Lines changed: 784 additions & 1645 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.11.11
3+
rev: v0.12.2
44
hooks:
55
- id: ruff-format
66
- id: ruff-check

perdoo/__main__.py

Lines changed: 106 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
from typing import Annotated
77

88
from comicfn2dict import comicfn2dict
9+
from darkseid.comic import SUPPORTED_IMAGE_EXTENSIONS
910
from typer import Argument, Context, Exit, Option, Typer
1011

1112
from perdoo import __version__, get_cache_root, setup_logging
12-
from perdoo.archives import CBRArchive, get_archive
1313
from perdoo.cli import archive_app, settings_app
14+
from perdoo.comic import Comic, ComicArchiveError, ComicMetadataError
1415
from perdoo.console import CONSOLE
15-
from perdoo.main import clean_archive, convert_file, rename_file, save_metadata, sync_metadata
16-
from perdoo.metadata import ComicInfo, MetronInfo, get_metadata
17-
from perdoo.metadata.metron_info import InformationSource
16+
from perdoo.metadata import ComicInfo, MetronInfo
17+
from perdoo.metadata.comic_info import Page
18+
from perdoo.metadata.metron_info import Id, InformationSource
1819
from perdoo.services import BaseService, Comicvine, Marvel, Metron
1920
from perdoo.settings import Service, Services, Settings
2021
from perdoo.utils import (
@@ -40,7 +41,7 @@ class SyncOption(Enum):
4041
@staticmethod
4142
def load(value: str) -> "SyncOption":
4243
for entry in SyncOption:
43-
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
44+
if entry.value.casefold() == value.casefold():
4445
return entry
4546
raise ValueError(f"'{value}' isn't a valid SyncOption")
4647

@@ -73,54 +74,93 @@ def get_services(settings: Services) -> dict[Service, BaseService]:
7374
return output
7475

7576

77+
def _load_comics(target: Path) -> list[Comic]:
78+
comics = []
79+
files = list_files(target) if target.is_dir() else [target]
80+
for file in files:
81+
try:
82+
comics.append(Comic(file=file))
83+
except (ComicArchiveError, ComicMetadataError) as err: # noqa: PERF203
84+
LOGGER.error("Failed to load '%s' as a Comic: %s", file, err)
85+
return comics
86+
87+
88+
def _get_id_value(ids: list[Id], source: InformationSource) -> str | None:
89+
return next((x.value for x in ids if x.source == source), None)
90+
91+
92+
def _create_search_from_metron(metron_info: MetronInfo) -> Search:
93+
series_id = metron_info.series.id
94+
source = next((x.source for x in metron_info.ids if x.primary), None)
95+
return Search(
96+
series=SeriesSearch(
97+
name=metron_info.series.name,
98+
volume=metron_info.series.volume,
99+
year=metron_info.series.start_year,
100+
comicvine=series_id if source == InformationSource.COMIC_VINE else None,
101+
marvel=series_id if source == InformationSource.MARVEL else None,
102+
metron=series_id if source == InformationSource.METRON else None,
103+
),
104+
issue=IssueSearch(
105+
number=metron_info.number,
106+
comicvine=_get_id_value(metron_info.ids, InformationSource.COMIC_VINE),
107+
marvel=_get_id_value(metron_info.ids, InformationSource.MARVEL),
108+
metron=_get_id_value(metron_info.ids, InformationSource.METRON),
109+
),
110+
)
111+
112+
113+
def _create_search_from_comic_info(comic_info: ComicInfo) -> Search:
114+
volume = comic_info.volume if comic_info.volume else None
115+
year = volume if volume and volume > 1900 else None
116+
volume = volume if volume and volume < 1900 else None
117+
return Search(
118+
series=SeriesSearch(name=comic_info.series, volume=volume, year=year),
119+
issue=IssueSearch(number=comic_info.number),
120+
)
121+
122+
123+
def _create_search_from_filename(fallback_title: str) -> Search:
124+
series_name = comicfn2dict(fallback_title).get("series", fallback_title).replace("-", " ")
125+
return Search(series=SeriesSearch(name=series_name), issue=IssueSearch())
126+
127+
76128
def get_search_details(
77129
metadata: tuple[MetronInfo | None, ComicInfo | None], fallback_title: str
78130
) -> Search:
79131
metron_info, comic_info = metadata
80-
81132
if metron_info and metron_info.series and metron_info.series.name:
82-
series_id = metron_info.series.id
83-
source = next(iter(x.source for x in metron_info.ids if x.primary), None)
84-
return Search(
85-
series=SeriesSearch(
86-
name=metron_info.series.name,
87-
volume=metron_info.series.volume,
88-
year=metron_info.series.start_year,
89-
comicvine=series_id if source == InformationSource.COMIC_VINE else None,
90-
marvel=series_id if source == InformationSource.MARVEL else None,
91-
metron=series_id if source == InformationSource.METRON else None,
92-
),
93-
issue=IssueSearch(
94-
number=metron_info.number,
95-
comicvine=next(
96-
iter(
97-
x.value for x in metron_info.ids if x.source == InformationSource.COMIC_VINE
98-
),
99-
None,
100-
),
101-
marvel=next(
102-
iter(x.value for x in metron_info.ids if x.source == InformationSource.MARVEL),
103-
None,
104-
),
105-
metron=next(
106-
iter(x.value for x in metron_info.ids if x.source == InformationSource.METRON),
107-
None,
108-
),
109-
),
110-
)
133+
return _create_search_from_metron(metron_info)
111134
if comic_info and comic_info.series:
112-
return Search(
113-
series=SeriesSearch(
114-
name=comic_info.series,
115-
volume=comic_info.volume
116-
if comic_info.volume and comic_info.volume < 1900
117-
else None,
118-
year=comic_info.volume if comic_info.volume and comic_info.volume > 1900 else None,
119-
),
120-
issue=IssueSearch(number=comic_info.number),
121-
)
122-
series_name = comicfn2dict(fallback_title).get("series", fallback_title).replace("-", " ")
123-
return Search(series=SeriesSearch(name=series_name), issue=IssueSearch())
135+
return _create_search_from_comic_info(comic_info)
136+
return _create_search_from_filename(fallback_title)
137+
138+
139+
def load_page_info(entry: Comic, comic_info: ComicInfo) -> list[Page]:
140+
pages = set()
141+
image_files = [
142+
x
143+
for x in entry.archive.get_filename_list()
144+
if Path(x).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS
145+
]
146+
for idx, file in enumerate(image_files):
147+
img_file = Path(file)
148+
is_final_page = idx == len(image_files) - 1
149+
page = next((x for x in comic_info.pages if x.image == idx), None)
150+
pages.add(Page.from_path(file=img_file, index=idx, is_final_page=is_final_page, page=page))
151+
return sorted(pages)
152+
153+
154+
def sync_metadata(
155+
search: Search, services: dict[Service, BaseService | None], settings: Settings
156+
) -> tuple[MetronInfo | None, ComicInfo | None]:
157+
for service_name in settings.services.order:
158+
if service := services.get(service_name):
159+
LOGGER.info("Searching %s for matching issue", type(service).__name__)
160+
metron_info, comic_info = service.fetch(search=search)
161+
if metron_info or comic_info:
162+
return metron_info, comic_info
163+
return None, None
124164

125165

126166
@app.command(name="import", help="Import comics into your collection using Perdoo.")
@@ -178,8 +218,8 @@ def run(
178218
settings = Settings.load()
179219
settings.save()
180220
if debug:
181-
Settings.display(
182-
extras={
221+
CONSOLE.print(
222+
{
183223
"target": target,
184224
"flags.skip-convert": skip_convert,
185225
"flags.sync": sync,
@@ -196,28 +236,18 @@ def run(
196236
LOGGER.warning("No external services configured")
197237
sync = SyncOption.SKIP
198238

199-
entries = []
200-
for file in list_files(target) if target.is_dir() else [target]:
201-
try:
202-
entries.append(get_archive(file))
203-
except NotImplementedError as nie: # noqa: PERF203
204-
LOGGER.error("%s, Skipping", nie)
205-
206-
for index, entry in enumerate(entries):
239+
comics = _load_comics(target=target)
240+
for index, entry in enumerate(comics):
207241
CONSOLE.rule(
208-
f"[{index + 1}/{len(entries)}] Importing {entry.path.name}",
242+
f"[{index + 1}/{len(comics)}] Importing {entry.path.name}",
209243
align="left",
210244
style="subtitle",
211245
)
212246
if not skip_convert:
213-
with CONSOLE.status(
214-
f"Converting to '{settings.output.format}'", spinner="simpleDotsScrolling"
215-
):
216-
entry = convert_file(entry, output_format=settings.output.format)
217-
if entry is None or isinstance(entry, CBRArchive):
218-
continue
247+
with CONSOLE.status("Converting to 'CBZ'", spinner="simpleDotsScrolling"):
248+
entry.convert_to_cbz()
219249

220-
metadata = get_metadata(archive=entry, debug=debug)
250+
metadata: tuple[MetronInfo | None, ComicInfo | None] = (entry.metron_info, entry.comic_info)
221251

222252
if sync != SyncOption.SKIP:
223253
search = get_search_details(metadata=metadata, fallback_title=entry.path.stem)
@@ -233,12 +263,20 @@ def run(
233263

234264
if not skip_clean:
235265
with CONSOLE.status("Cleaning Archive", spinner="simpleDotsScrolling"):
236-
clean_archive(entry=entry, settings=settings)
237-
save_metadata(entry=entry, metadata=metadata, settings=settings)
266+
entry.clean_archive()
267+
if settings.output.metron_info.create:
268+
entry.write_metadata(metadata=metadata[0])
269+
if settings.output.comic_info.create:
270+
metadata[1].pages = (
271+
load_page_info(entry=entry, comic_info=metadata[1])
272+
if settings.output.comic_info.handle_pages
273+
else []
274+
)
275+
entry.write_metadata(metadata=metadata[1])
238276

239277
if not skip_rename:
240278
with CONSOLE.status("Renaming based on metadata", spinner="simpleDotsScrolling"):
241-
rename_file(entry=entry, metadata=metadata, settings=settings, target=target.parent)
279+
entry.rename(naming=settings.output.naming, output_folder=settings.output.folder)
242280

243281
with CONSOLE.status("Cleaning up empty folders"):
244282
delete_empty_folders(folder=target)

perdoo/archives/__init__.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

perdoo/archives/_base.py

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)