Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions perdoo/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from datetime import date
from enum import Enum
from io import BytesIO
from pathlib import Path
from platform import python_version
from typing import Annotated
Expand Down Expand Up @@ -129,17 +130,37 @@ def get_search_details(


def load_page_info(entry: Comic, comic_info: ComicInfo) -> list[Page]:
from PIL import Image # noqa: PLC0415

from perdoo.metadata.comic_info import PageType # noqa: PLC0415

pages = set()
image_files = [
x
for x in entry.archive.get_filename_list()
if Path(x).suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS
]
for idx, file in enumerate(image_files):
img_file = Path(file)
is_final_page = idx == len(image_files) - 1
page = next((x for x in comic_info.pages if x.image == idx), None)
pages.add(Page.from_path(file=img_file, index=idx, is_final_page=is_final_page, page=page))
if page:
page_type = page.type
elif idx == 0:
page_type = PageType.FRONT_COVER
elif idx == len(image_files) - 1:
page_type = PageType.BACK_COVER
else:
page_type = PageType.STORY
if not page:
page = Page(image=idx)
page.type = page_type
page_bytes = entry.archive.read_file(file)
page.image_size = len(page_bytes)
with Image.open(BytesIO(page_bytes)) as page_data:
width, height = page_data.size
page.double_page = width >= height
page.image_height = height
page.image_width = width
pages.add(page)
return sorted(pages)


Expand Down
37 changes: 4 additions & 33 deletions perdoo/metadata/comic_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@
from collections.abc import Callable
from datetime import date
from enum import Enum
from pathlib import Path

from natsort import humansorted, ns
from PIL import Image
from pydantic import HttpUrl, NonNegativeFloat
from pydantic_xml import attr, computed_attr, element, wrapped

from perdoo.metadata._base import PascalModel
from perdoo.settings import Naming

try:
from typing import Self # Python >= 3.11
except ImportError:
from typing_extensions import Self # Python < 3.11


LOGGER = logging.getLogger(__name__)


Expand All @@ -41,7 +33,7 @@ class YesNo(Enum):
YES = "Yes"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "YesNo":
for entry in YesNo:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand All @@ -59,7 +51,7 @@ class Manga(Enum):
YES_AND_RIGHT_TO_LEFT = "YesAndRightToLeft"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "Manga":
for entry in Manga:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand Down Expand Up @@ -88,7 +80,7 @@ class AgeRating(Enum):
X18 = "X18+"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "AgeRating":
for entry in AgeRating:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand All @@ -113,7 +105,7 @@ class PageType(Enum):
DELETED = "Deleted"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "PageType":
for entry in PageType:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand Down Expand Up @@ -147,27 +139,6 @@ def __eq__(self, other) -> bool: # noqa: ANN001
def __hash__(self) -> int:
return hash((type(self), self.image))

@staticmethod
def from_path(file: Path, index: int, is_final_page: bool, page: Self | None) -> Self:
if page:
page_type = page.type
elif index == 0:
page_type = PageType.FRONT_COVER
elif is_final_page:
page_type = PageType.BACK_COVER
else:
page_type = PageType.STORY
with Image.open(file) as img:
width, height = img.size
return Page(
image=index,
type=page_type,
double_page=width >= height,
image_size=file.stat().st_size,
image_height=height,
image_width=width,
)


class ComicInfo(PascalModel):
age_rating: AgeRating = element(default=AgeRating.UNKNOWN)
Expand Down
22 changes: 12 additions & 10 deletions perdoo/metadata/metron_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@
from enum import Enum
from typing import Generic, TypeVar

from pydantic import HttpUrl, NonNegativeInt, PositiveInt
from pydantic import HttpUrl, NonNegativeInt, PositiveInt, field_validator
from pydantic_xml import attr, computed_attr, element, wrapped

from perdoo.metadata._base import PascalModel
from perdoo.settings import Naming

try:
from typing import Self # Python >= 3.11
except ImportError:
from typing_extensions import Self # Python < 3.11

LOGGER = logging.getLogger(__name__)
T = TypeVar("T")

Expand All @@ -49,7 +44,7 @@ class AgeRating(Enum):
ADULT = "Adult"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "AgeRating":
for entry in AgeRating:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand Down Expand Up @@ -142,7 +137,7 @@ class Role(Enum):
OTHER = "Other"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "Role":
for entry in Role:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand Down Expand Up @@ -191,7 +186,7 @@ class InformationSource(Enum):
LEAGUE_OF_COMIC_GEEKS = "League of Comic Geeks"

@staticmethod
def load(value: str) -> Self:
def load(value: str) -> "InformationSource":
for entry in InformationSource:
if entry.value.replace(" ", "").casefold() == value.replace(" ", "").casefold():
return entry
Expand Down Expand Up @@ -366,7 +361,7 @@ class MetronInfo(PascalModel):
universes: list[Universe] = wrapped(
path="Universes", entity=element(tag="Universe", default_factory=list)
)
urls: list[Url] = wrapped(path="URLS", entity=element(tag="URLs", default_factory=list))
urls: list[Url] = wrapped(path="URLs", entity=element(tag="URL", default_factory=list))

@computed_attr(ns="xsi", name="noNamespaceSchemaLocation")
def schema_location(self) -> str:
Expand All @@ -389,6 +384,13 @@ def get_filename(self, settings: Naming) -> str:
seperator=settings.seperator,
)

@field_validator("last_modified", mode="before")
def ensure_timezone(cls, v: str | datetime | None) -> str | datetime | None:
if isinstance(v, datetime) and v.tzinfo is None:
timezone = datetime.now().astimezone().tzinfo
return v.replace(tzinfo=timezone)
return v


PATTERN_MAP: dict[str, Callable[[MetronInfo], str | int | None]] = {
"cover-date": lambda x: x.cover_date,
Expand Down
Loading