Make ImageMetadata a dataclass
This commit is contained in:
parent
219ede2d5d
commit
a7a9d38428
@ -339,8 +339,7 @@ class ComicArchive:
|
||||
md.apply_default_page_list(self.get_page_name_list())
|
||||
if calc_page_sizes:
|
||||
for index, p in enumerate(md.pages):
|
||||
idx = int(p["image_index"])
|
||||
|
||||
idx = p.display_index
|
||||
if self.pil_available:
|
||||
try:
|
||||
from PIL import Image
|
||||
@ -348,13 +347,9 @@ class ComicArchive:
|
||||
self.pil_available = True
|
||||
except ImportError:
|
||||
self.pil_available = False
|
||||
if (
|
||||
"size" not in p
|
||||
or "height" not in p
|
||||
or "width" not in p
|
||||
or ("double_page" not in p and detect_double_page)
|
||||
):
|
||||
if p.byte_size is None or p.height is None or p.width is None or p.double_page is None:
|
||||
data = self.get_page(idx)
|
||||
p.byte_size = len(data)
|
||||
if data:
|
||||
try:
|
||||
if isinstance(data, bytes):
|
||||
@ -363,19 +358,16 @@ class ComicArchive:
|
||||
im = Image.open(io.StringIO(data))
|
||||
w, h = im.size
|
||||
|
||||
p["size"] = str(len(data))
|
||||
p["height"] = str(h)
|
||||
p["width"] = str(w)
|
||||
p.height = h
|
||||
p.width = w
|
||||
if detect_double_page:
|
||||
p["double_page"] = utils.is_double_page(p)
|
||||
p.double_page = p.is_double_page()
|
||||
except Exception as e:
|
||||
logger.warning("Error decoding image [%s] %s :: image %s", e, self.path, index)
|
||||
p["size"] = str(len(data))
|
||||
|
||||
else:
|
||||
if "size" not in p:
|
||||
if p.byte_size is not None:
|
||||
data = self.get_page(idx)
|
||||
p["size"] = str(len(data))
|
||||
p.byte_size = len(data)
|
||||
|
||||
def metadata_from_filename(
|
||||
self,
|
||||
|
@ -25,9 +25,9 @@ import copy
|
||||
import dataclasses
|
||||
import logging
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any, TypedDict, Union, overload
|
||||
from typing import TYPE_CHECKING, Any, Union, overload
|
||||
|
||||
from typing_extensions import NamedTuple, Required
|
||||
from typing_extensions import NamedTuple
|
||||
|
||||
from comicapi import merge, utils
|
||||
from comicapi._url import Url, parse_url
|
||||
@ -42,7 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
REMOVE = object()
|
||||
|
||||
|
||||
class PageType:
|
||||
class PageType(merge.StrEnum):
|
||||
"""
|
||||
These page info classes are exactly the same as the CIX scheme, since
|
||||
it's unique
|
||||
@ -61,15 +61,37 @@ class PageType:
|
||||
Deleted = "Deleted"
|
||||
|
||||
|
||||
class ImageMetadata(TypedDict, total=False):
|
||||
@dataclasses.dataclass
|
||||
class PageMetadata:
|
||||
filename: str
|
||||
type: str
|
||||
bookmark: str
|
||||
double_page: bool
|
||||
image_index: Required[int]
|
||||
size: str
|
||||
height: str
|
||||
width: str
|
||||
display_index: int
|
||||
archive_index: int
|
||||
# These are optional because getting this info requires reading in each page
|
||||
double_page: bool | None = None
|
||||
byte_size: int | None = None
|
||||
height: int | None = None
|
||||
width: int | None = None
|
||||
|
||||
def set_type(self, value: str) -> None:
|
||||
values = {x.casefold(): x for x in PageType}
|
||||
self.type = values.get(value.casefold(), value)
|
||||
|
||||
def is_double_page(self) -> bool:
|
||||
w = self.width or 0
|
||||
h = self.height or 0
|
||||
return self.double_page or (w >= h and w > 0 and h > 0)
|
||||
|
||||
def __lt__(self, other: Any) -> bool:
|
||||
if not isinstance(other, PageMetadata):
|
||||
return False
|
||||
return self.archive_index < other.archive_index
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, PageMetadata):
|
||||
return False
|
||||
return self.archive_index == other.archive_index
|
||||
|
||||
|
||||
Credit = merge.Credit
|
||||
@ -150,13 +172,13 @@ class GenericMetadata:
|
||||
scan_info: str | None = None
|
||||
|
||||
tags: set[str] = dataclasses.field(default_factory=set)
|
||||
pages: list[ImageMetadata] = dataclasses.field(default_factory=list)
|
||||
pages: list[PageMetadata] = dataclasses.field(default_factory=list)
|
||||
page_count: int | None = None
|
||||
|
||||
characters: set[str] = dataclasses.field(default_factory=set)
|
||||
teams: set[str] = dataclasses.field(default_factory=set)
|
||||
locations: set[str] = dataclasses.field(default_factory=set)
|
||||
credits: list[Credit] = dataclasses.field(default_factory=list)
|
||||
credits: list[merge.Credit] = dataclasses.field(default_factory=list)
|
||||
|
||||
# Some CoMet-only items
|
||||
price: float | None = None
|
||||
@ -300,31 +322,34 @@ class GenericMetadata:
|
||||
self.page_count = assign(self.page_count, new_md.page_count)
|
||||
|
||||
def apply_default_page_list(self, page_list: Sequence[str]) -> None:
|
||||
# generate a default page list, with the first page marked as the cover
|
||||
"""apply a default page list, with the first page marked as the cover"""
|
||||
|
||||
# Create a dictionary of all pages in the metadata
|
||||
pages = self.pages
|
||||
# Create a dictionary in the weird case that the metadata doesn't match the archive
|
||||
pages = {p.archive_index: p for p in self.pages}
|
||||
cover_set = False
|
||||
# Go through each page in the archive
|
||||
# The indexes should always match up
|
||||
# It might be a good idea to validate that each page in `pages` is found
|
||||
|
||||
# It might be a good idea to validate that each page in `pages` is found in page_list
|
||||
for i, filename in enumerate(page_list):
|
||||
if i < len(pages):
|
||||
pages[i]["filename"] = filename
|
||||
else:
|
||||
pages.append(ImageMetadata(image_index=i, filename=filename))
|
||||
page = pages.get(i, PageMetadata(archive_index=i, display_index=i, filename="", type="", bookmark=""))
|
||||
page.filename = filename
|
||||
pages[i] = page
|
||||
|
||||
# Check if we know what the cover is
|
||||
cover_set = pages[i].get("type", None) == PageType.FrontCover or cover_set
|
||||
cover_set = page.type == PageType.FrontCover or cover_set
|
||||
self.pages = sorted(pages.values())
|
||||
|
||||
# Set the cover to the first image if we don't know what the cover is
|
||||
self.page_count = len(self.pages)
|
||||
if self.page_count != len(page_list):
|
||||
logger.warning("Wrong count of pages: expected %d got %d", len(self.pages), len(page_list))
|
||||
# Set the cover to the first image acording to hte display index if we don't know what the cover is
|
||||
if not cover_set:
|
||||
self.pages[0]["type"] = PageType.FrontCover
|
||||
first_page = self.get_archive_page_index(0)
|
||||
self.pages[first_page].type = PageType.FrontCover
|
||||
|
||||
def get_archive_page_index(self, pagenum: int) -> int:
|
||||
# convert the displayed page number to the page index of the file in the archive
|
||||
"""convert the displayed page number to the page index of the file in the archive"""
|
||||
if pagenum < len(self.pages):
|
||||
return int(self.pages[pagenum]["image_index"])
|
||||
return int(sorted(self.pages, key=lambda p: p.display_index)[pagenum].archive_index)
|
||||
|
||||
return 0
|
||||
|
||||
@ -334,28 +359,28 @@ class GenericMetadata:
|
||||
return [0]
|
||||
coverlist = []
|
||||
for p in self.pages:
|
||||
if p.get("type", "") == PageType.FrontCover:
|
||||
coverlist.append(int(p["image_index"]))
|
||||
if p.type == PageType.FrontCover:
|
||||
coverlist.append(p.archive_index)
|
||||
|
||||
if len(coverlist) == 0:
|
||||
coverlist.append(self.pages[0].get("image_index", 0))
|
||||
coverlist.append(self.get_archive_page_index(0))
|
||||
|
||||
return coverlist
|
||||
|
||||
@overload
|
||||
def add_credit(self, person: Credit) -> None: ...
|
||||
def add_credit(self, person: merge.Credit) -> None: ...
|
||||
|
||||
@overload
|
||||
def add_credit(self, person: str, role: str, primary: bool = False) -> None: ...
|
||||
|
||||
def add_credit(self, person: str | Credit, role: str | None = None, primary: bool = False) -> None:
|
||||
def add_credit(self, person: str | merge.Credit, role: str | None = None, primary: bool = False) -> None:
|
||||
|
||||
credit: Credit
|
||||
if isinstance(person, Credit):
|
||||
credit: merge.Credit
|
||||
if isinstance(person, merge.Credit):
|
||||
credit = person
|
||||
else:
|
||||
assert role is not None
|
||||
credit = Credit(person=person, role=role, primary=primary)
|
||||
credit = merge.Credit(person=person, role=role, primary=primary)
|
||||
|
||||
if credit.role is None:
|
||||
raise TypeError("GenericMetadata.add_credit takes either a Credit object or a person name and role")
|
||||
@ -526,46 +551,252 @@ md_test: GenericMetadata = GenericMetadata(
|
||||
teams={"Fahrenheit"},
|
||||
locations=set(utils.split("lonely cottage ", ",")),
|
||||
credits=[
|
||||
Credit(primary=False, person="Dara Naraghi", role="Writer"),
|
||||
Credit(primary=False, person="Esteve Polls", role="Penciller"),
|
||||
Credit(primary=False, person="Esteve Polls", role="Inker"),
|
||||
Credit(primary=False, person="Neil Uyetake", role="Letterer"),
|
||||
Credit(primary=False, person="Sam Kieth", role="Cover"),
|
||||
Credit(primary=False, person="Ted Adams", role="Editor"),
|
||||
merge.Credit(primary=False, person="Dara Naraghi", role="Writer"),
|
||||
merge.Credit(primary=False, person="Esteve Polls", role="Penciller"),
|
||||
merge.Credit(primary=False, person="Esteve Polls", role="Inker"),
|
||||
merge.Credit(primary=False, person="Neil Uyetake", role="Letterer"),
|
||||
merge.Credit(primary=False, person="Sam Kieth", role="Cover"),
|
||||
merge.Credit(primary=False, person="Ted Adams", role="Editor"),
|
||||
],
|
||||
tags=set(),
|
||||
pages=[
|
||||
ImageMetadata(
|
||||
image_index=0, height="1280", size="195977", width="800", type=PageType.FrontCover, filename="!cover.jpg"
|
||||
PageMetadata(
|
||||
archive_index=0,
|
||||
display_index=0,
|
||||
height=1280,
|
||||
byte_size=195977,
|
||||
width=800,
|
||||
type=PageType.FrontCover,
|
||||
filename="!cover.jpg",
|
||||
bookmark="",
|
||||
),
|
||||
ImageMetadata(image_index=1, height="2039", size="611993", width="1327", filename="01.jpg"),
|
||||
ImageMetadata(image_index=2, height="2039", size="783726", width="1327", filename="02.jpg"),
|
||||
ImageMetadata(image_index=3, height="2039", size="679584", width="1327", filename="03.jpg"),
|
||||
ImageMetadata(image_index=4, height="2039", size="788179", width="1327", filename="04.jpg"),
|
||||
ImageMetadata(image_index=5, height="2039", size="864433", width="1327", filename="05.jpg"),
|
||||
ImageMetadata(image_index=6, height="2039", size="765606", width="1327", filename="06.jpg"),
|
||||
ImageMetadata(image_index=7, height="2039", size="876427", width="1327", filename="07.jpg"),
|
||||
ImageMetadata(image_index=8, height="2039", size="852622", width="1327", filename="08.jpg"),
|
||||
ImageMetadata(image_index=9, height="2039", size="800205", width="1327", filename="09.jpg"),
|
||||
ImageMetadata(image_index=10, height="2039", size="746243", width="1326", filename="10.jpg"),
|
||||
ImageMetadata(image_index=11, height="2039", size="718062", width="1327", filename="11.jpg"),
|
||||
ImageMetadata(image_index=12, height="2039", size="532179", width="1326", filename="12.jpg"),
|
||||
ImageMetadata(image_index=13, height="2039", size="686708", width="1327", filename="13.jpg"),
|
||||
ImageMetadata(image_index=14, height="2039", size="641907", width="1327", filename="14.jpg"),
|
||||
ImageMetadata(image_index=15, height="2039", size="805388", width="1327", filename="15.jpg"),
|
||||
ImageMetadata(image_index=16, height="2039", size="668927", width="1326", filename="16.jpg"),
|
||||
ImageMetadata(image_index=17, height="2039", size="710605", width="1327", filename="17.jpg"),
|
||||
ImageMetadata(image_index=18, height="2039", size="761398", width="1326", filename="18.jpg"),
|
||||
ImageMetadata(image_index=19, height="2039", size="743807", width="1327", filename="19.jpg"),
|
||||
ImageMetadata(image_index=20, height="2039", size="552911", width="1326", filename="20.jpg"),
|
||||
ImageMetadata(image_index=21, height="2039", size="556827", width="1327", filename="21.jpg"),
|
||||
ImageMetadata(image_index=22, height="2039", size="675078", width="1326", filename="22.jpg"),
|
||||
ImageMetadata(
|
||||
PageMetadata(
|
||||
archive_index=1,
|
||||
display_index=1,
|
||||
height=2039,
|
||||
byte_size=611993,
|
||||
width=1327,
|
||||
filename="01.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=2,
|
||||
display_index=2,
|
||||
height=2039,
|
||||
byte_size=783726,
|
||||
width=1327,
|
||||
filename="02.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=3,
|
||||
display_index=3,
|
||||
height=2039,
|
||||
byte_size=679584,
|
||||
width=1327,
|
||||
filename="03.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=4,
|
||||
display_index=4,
|
||||
height=2039,
|
||||
byte_size=788179,
|
||||
width=1327,
|
||||
filename="04.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=5,
|
||||
display_index=5,
|
||||
height=2039,
|
||||
byte_size=864433,
|
||||
width=1327,
|
||||
filename="05.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=6,
|
||||
display_index=6,
|
||||
height=2039,
|
||||
byte_size=765606,
|
||||
width=1327,
|
||||
filename="06.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=7,
|
||||
display_index=7,
|
||||
height=2039,
|
||||
byte_size=876427,
|
||||
width=1327,
|
||||
filename="07.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=8,
|
||||
display_index=8,
|
||||
height=2039,
|
||||
byte_size=852622,
|
||||
width=1327,
|
||||
filename="08.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=9,
|
||||
display_index=9,
|
||||
height=2039,
|
||||
byte_size=800205,
|
||||
width=1327,
|
||||
filename="09.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=10,
|
||||
display_index=10,
|
||||
height=2039,
|
||||
byte_size=746243,
|
||||
width=1326,
|
||||
filename="10.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=11,
|
||||
display_index=11,
|
||||
height=2039,
|
||||
byte_size=718062,
|
||||
width=1327,
|
||||
filename="11.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=12,
|
||||
display_index=12,
|
||||
height=2039,
|
||||
byte_size=532179,
|
||||
width=1326,
|
||||
filename="12.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=13,
|
||||
display_index=13,
|
||||
height=2039,
|
||||
byte_size=686708,
|
||||
width=1327,
|
||||
filename="13.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=14,
|
||||
display_index=14,
|
||||
height=2039,
|
||||
byte_size=641907,
|
||||
width=1327,
|
||||
filename="14.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=15,
|
||||
display_index=15,
|
||||
height=2039,
|
||||
byte_size=805388,
|
||||
width=1327,
|
||||
filename="15.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=16,
|
||||
display_index=16,
|
||||
height=2039,
|
||||
byte_size=668927,
|
||||
width=1326,
|
||||
filename="16.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=17,
|
||||
display_index=17,
|
||||
height=2039,
|
||||
byte_size=710605,
|
||||
width=1327,
|
||||
filename="17.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=18,
|
||||
display_index=18,
|
||||
height=2039,
|
||||
byte_size=761398,
|
||||
width=1326,
|
||||
filename="18.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=19,
|
||||
display_index=19,
|
||||
height=2039,
|
||||
byte_size=743807,
|
||||
width=1327,
|
||||
filename="19.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=20,
|
||||
display_index=20,
|
||||
height=2039,
|
||||
byte_size=552911,
|
||||
width=1326,
|
||||
filename="20.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=21,
|
||||
display_index=21,
|
||||
height=2039,
|
||||
byte_size=556827,
|
||||
width=1327,
|
||||
filename="21.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
archive_index=22,
|
||||
display_index=22,
|
||||
height=2039,
|
||||
byte_size=675078,
|
||||
width=1326,
|
||||
filename="22.jpg",
|
||||
bookmark="",
|
||||
type="",
|
||||
),
|
||||
PageMetadata(
|
||||
bookmark="Interview",
|
||||
image_index=23,
|
||||
height="2032",
|
||||
size="800965",
|
||||
width="1338",
|
||||
archive_index=23,
|
||||
display_index=23,
|
||||
height=2032,
|
||||
byte_size=800965,
|
||||
width=1338,
|
||||
type=PageType.Letters,
|
||||
filename="23.jpg",
|
||||
),
|
||||
@ -583,7 +814,7 @@ __all__ = (
|
||||
"Url",
|
||||
"parse_url",
|
||||
"PageType",
|
||||
"ImageMetadata",
|
||||
"PageMetadata",
|
||||
"Credit",
|
||||
"ComicSeries",
|
||||
"MetadataOrigin",
|
||||
|
@ -24,7 +24,7 @@ from typing import Any
|
||||
from comicapi import utils
|
||||
from comicapi.archivers import Archiver
|
||||
from comicapi.comicarchive import ComicArchive
|
||||
from comicapi.genericmetadata import GenericMetadata, ImageMetadata, PageType
|
||||
from comicapi.genericmetadata import GenericMetadata, PageMetadata, PageType
|
||||
from comicapi.tags import Tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -194,8 +194,8 @@ class CoMet(Tag):
|
||||
date_str += f"-{md.month:02}"
|
||||
assign("date", date_str)
|
||||
|
||||
page = md.get_cover_page_index_list()[0]
|
||||
assign("coverImage", md.pages[page]["filename"])
|
||||
cover_index = md.get_cover_page_index_list()[0]
|
||||
assign("coverImage", md.pages[cover_index].filename)
|
||||
|
||||
# loop thru credits, and build a list for each role that CoMet supports
|
||||
for credit in metadata.credits:
|
||||
@ -265,7 +265,15 @@ class CoMet(Tag):
|
||||
page_list = ca.get_page_name_list()
|
||||
if cover_filename in page_list:
|
||||
cover_index = page_list.index(cover_filename)
|
||||
md.pages = [ImageMetadata(image_index=cover_index, filename=cover_filename, type=PageType.FrontCover)]
|
||||
md.pages = [
|
||||
PageMetadata(
|
||||
archive_index=cover_index,
|
||||
display_index=0,
|
||||
filename=cover_filename,
|
||||
type=PageType.FrontCover,
|
||||
bookmark="",
|
||||
)
|
||||
]
|
||||
|
||||
reading_direction = utils.xlate(get("readingDirection"))
|
||||
if reading_direction is not None and reading_direction == "rtl":
|
||||
|
@ -17,12 +17,11 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import OrderedDict
|
||||
from typing import Any
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.archivers import Archiver
|
||||
from comicapi.genericmetadata import GenericMetadata, ImageMetadata
|
||||
from comicapi.genericmetadata import GenericMetadata, PageMetadata
|
||||
from comicapi.tags import Tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -253,24 +252,23 @@ class ComicRack(Tag):
|
||||
else:
|
||||
pages_node = ET.SubElement(root, "Pages")
|
||||
|
||||
for page_dict in md.pages:
|
||||
for page in md.pages:
|
||||
page_node = ET.SubElement(pages_node, "Page")
|
||||
page_node.attrib = {}
|
||||
if "bookmark" in page_dict:
|
||||
page_node.attrib["Bookmark"] = str(page_dict["bookmark"])
|
||||
if "double_page" in page_dict:
|
||||
page_node.attrib["DoublePage"] = str(page_dict["double_page"])
|
||||
if "image_index" in page_dict:
|
||||
page_node.attrib["Image"] = str(page_dict["image_index"])
|
||||
if "height" in page_dict:
|
||||
page_node.attrib["ImageHeight"] = str(page_dict["height"])
|
||||
if "size" in page_dict:
|
||||
page_node.attrib["ImageSize"] = str(page_dict["size"])
|
||||
if "width" in page_dict:
|
||||
page_node.attrib["ImageWidth"] = str(page_dict["width"])
|
||||
if "type" in page_dict:
|
||||
page_node.attrib["Type"] = str(page_dict["type"])
|
||||
page_node.attrib = OrderedDict(sorted(page_node.attrib.items()))
|
||||
page_node.attrib = {"Image": str(page.display_index)}
|
||||
if page.bookmark:
|
||||
page_node.attrib["Bookmark"] = page.bookmark
|
||||
if page.type:
|
||||
page_node.attrib["Type"] = page.type
|
||||
|
||||
if page.double_page is not None:
|
||||
page_node.attrib["DoublePage"] = str(page.double_page)
|
||||
if page.height is not None:
|
||||
page_node.attrib["ImageHeight"] = str(page.height)
|
||||
if page.byte_size is not None:
|
||||
page_node.attrib["ImageSize"] = str(page.byte_size)
|
||||
if page.width is not None:
|
||||
page_node.attrib["ImageWidth"] = str(page.width)
|
||||
page_node.attrib = dict(sorted(page_node.attrib.items()))
|
||||
|
||||
ET.indent(root)
|
||||
|
||||
@ -352,20 +350,23 @@ class ComicRack(Tag):
|
||||
if pages_node is not None:
|
||||
for i, page in enumerate(pages_node):
|
||||
p: dict[str, Any] = page.attrib
|
||||
md_page = ImageMetadata(image_index=int(p.get("Image", i)))
|
||||
md_page = PageMetadata(
|
||||
filename="", # cr doesn't record the filename it just assumes it's always ordered the same
|
||||
display_index=int(p.get("Image", i)),
|
||||
archive_index=i,
|
||||
bookmark=p.get("Bookmark", ""),
|
||||
type="",
|
||||
)
|
||||
md_page.set_type(p.get("Type", ""))
|
||||
|
||||
if "Bookmark" in p:
|
||||
md_page["bookmark"] = p["Bookmark"]
|
||||
if "DoublePage" in p:
|
||||
md_page["double_page"] = True if p["DoublePage"].casefold() in ("yes", "true", "1") else False
|
||||
if "ImageHeight" in p:
|
||||
md_page["height"] = p["ImageHeight"]
|
||||
if "ImageSize" in p:
|
||||
md_page["size"] = p["ImageSize"]
|
||||
if "ImageWidth" in p:
|
||||
md_page["width"] = p["ImageWidth"]
|
||||
if "Type" in p:
|
||||
md_page["type"] = p["Type"]
|
||||
if isinstance(p.get("DoublePage", None), str):
|
||||
md_page.double_page = p["DoublePage"].casefold() in ("yes", "true", "1")
|
||||
if p.get("ImageHeight", "").isnumeric():
|
||||
md_page.height = int(float(p["ImageHeight"]))
|
||||
if p.get("ImageWidth", "").isnumeric():
|
||||
md_page.width = int(float(p["ImageWidth"]))
|
||||
if p.get("ImageSize", "").isnumeric():
|
||||
md_page.byte_size = int(float(p["ImageSize"]))
|
||||
|
||||
md.pages.append(md_page)
|
||||
|
||||
|
@ -26,7 +26,7 @@ from collections import defaultdict
|
||||
from collections.abc import Iterable, Mapping
|
||||
from enum import Enum, auto
|
||||
from shutil import which # noqa: F401
|
||||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||
from typing import Any, TypeVar, cast
|
||||
|
||||
from comicfn2dict import comicfn2dict
|
||||
|
||||
@ -36,8 +36,6 @@ from comicapi._url import LocationParseError as LocationParseError # noqa: F401
|
||||
from comicapi._url import Url as Url
|
||||
from comicapi._url import parse_url as parse_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from comicapi.genericmetadata import ImageMetadata
|
||||
try:
|
||||
import icu
|
||||
|
||||
@ -131,12 +129,6 @@ def _custom_key(tup: Any) -> Any:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def is_double_page(page: ImageMetadata) -> bool:
|
||||
w = int(page.get("width", 0))
|
||||
h = int(page.get("height", 0))
|
||||
return page.get("double_page") or (w >= h and w > 0 and h > 0)
|
||||
|
||||
|
||||
def os_sorted(lst: Iterable[T]) -> Iterable[T]:
|
||||
import natsort
|
||||
|
||||
|
@ -399,7 +399,7 @@ class IssueIdentifier:
|
||||
assert md
|
||||
covers: list[tuple[str, Image.Image]] = []
|
||||
for cover_index in range(1, min(3, ca.get_number_of_pages())):
|
||||
image_data = ca.get_page(cover_index)
|
||||
image_data = ca.get_page(md.get_archive_page_index(cover_index))
|
||||
covers.extend(self._process_cover(f"{cover_index}", image_data))
|
||||
return covers
|
||||
|
||||
|
@ -102,9 +102,9 @@ class PageBrowserWindow(QtWidgets.QDialog):
|
||||
|
||||
def set_page(self) -> None:
|
||||
if not self.metadata.is_empty:
|
||||
archive_page_index = self.metadata.get_archive_page_index(self.current_page_num)
|
||||
selected_page_index = self.metadata.get_archive_page_index(self.current_page_num)
|
||||
else:
|
||||
archive_page_index = self.current_page_num
|
||||
selected_page_index = self.current_page_num
|
||||
|
||||
self.pageWidget.set_page(archive_page_index)
|
||||
self.setWindowTitle(f"Page Browser - Page {self.current_page_num + 1} (of {self.page_count}) ")
|
||||
self.pageWidget.set_page(selected_page_index)
|
||||
self.setWindowTitle(f"Page Browser - Page {self.current_page_num+1} (of {self.page_count})")
|
||||
|
@ -21,7 +21,7 @@ import logging
|
||||
from PyQt5 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
from comicapi.genericmetadata import GenericMetadata, ImageMetadata, PageType
|
||||
from comicapi.genericmetadata import GenericMetadata, PageMetadata, PageType
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui.qtutils import enable_widget
|
||||
@ -118,7 +118,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.first_front_page: int | None = None
|
||||
|
||||
self.comic_archive: ComicArchive | None = None
|
||||
self.pages_list: list[ImageMetadata] = []
|
||||
self.pages_list: list[PageMetadata] = []
|
||||
self.tag_ids: list[str] = []
|
||||
|
||||
def set_blur(self, blur: bool) -> None:
|
||||
@ -156,23 +156,23 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
row = self.comic_archive.get_scanner_page_index()
|
||||
if row is None:
|
||||
return
|
||||
page_dict: ImageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
page: PageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
||||
page_dict["type"] = PageType.Deleted
|
||||
page.type = PageType.Deleted
|
||||
|
||||
item = self.listWidget.item(row)
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page_dict)
|
||||
item.setText(self.list_entry_text(page_dict))
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page)
|
||||
item.setText(self.list_entry_text(page))
|
||||
self.change_page()
|
||||
|
||||
def identify_double_page(self) -> None:
|
||||
if self.comic_archive is None:
|
||||
return
|
||||
md = GenericMetadata(pages=self.get_page_list())
|
||||
double_pages = [x.get("double_page", False) for x in md.pages]
|
||||
double_pages = [bool(x.double_page) for x in md.pages]
|
||||
self.comic_archive.apply_archive_info_to_metadata(md, True, True)
|
||||
self.set_data(self.comic_archive, pages_list=md.pages)
|
||||
if double_pages != [x.get("double_page", False) for x in md.pages]:
|
||||
if double_pages != [bool(x.double_page) for x in md.pages]:
|
||||
self.modified.emit()
|
||||
|
||||
def select_page_type_item(self, idx: int) -> None:
|
||||
@ -272,93 +272,81 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
i = self.cbPageType.findData(pagetype)
|
||||
self.cbPageType.setCurrentIndex(i)
|
||||
|
||||
page = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
self.chkDoublePage.setChecked(page.get("double_page", False))
|
||||
page: PageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
self.chkDoublePage.setChecked(bool(page.double_page))
|
||||
|
||||
self.leBookmark.setText(page.get("bookmark", ""))
|
||||
|
||||
idx = int(page["image_index"])
|
||||
self.leBookmark.setText(page.bookmark)
|
||||
|
||||
if self.comic_archive is not None:
|
||||
self.pageWidget.set_archive(self.comic_archive, idx)
|
||||
self.pageWidget.set_archive(self.comic_archive, page.archive_index)
|
||||
|
||||
def get_first_front_cover(self) -> int:
|
||||
front_cover = 0
|
||||
if self.listWidget.count() > 0:
|
||||
front_cover = int(self.listWidget.item(0).data(QtCore.Qt.ItemDataRole.UserRole).get("image_index", 0))
|
||||
page: PageMetadata = self.listWidget.item(0).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
front_cover = page.archive_index
|
||||
for i in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(i)
|
||||
page_dict: ImageMetadata = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
if page_dict.get("type", "") == PageType.FrontCover:
|
||||
front_cover = int(page_dict["image_index"])
|
||||
page = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
if page.type == PageType.FrontCover:
|
||||
front_cover = page.archive_index
|
||||
break
|
||||
return front_cover
|
||||
|
||||
def get_current_page_type(self) -> str:
|
||||
row = self.listWidget.currentRow()
|
||||
page_dict: ImageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
return page_dict.get("type", "")
|
||||
page: PageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
return page.type
|
||||
|
||||
def set_current_page_type(self, t: str) -> None:
|
||||
rows = self.listWidget.selectionModel().selectedRows()
|
||||
for index in rows:
|
||||
row = index.row()
|
||||
page_dict: ImageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
page: PageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
||||
if t == "":
|
||||
if "type" in page_dict:
|
||||
del page_dict["type"]
|
||||
else:
|
||||
page_dict["type"] = t
|
||||
page.type = t
|
||||
|
||||
item = self.listWidget.item(row)
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page_dict)
|
||||
item.setText(self.list_entry_text(page_dict))
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page)
|
||||
item.setText(self.list_entry_text(page))
|
||||
|
||||
def toggle_double_page(self) -> None:
|
||||
rows = self.listWidget.selectionModel().selectedRows()
|
||||
for index in rows:
|
||||
row = index.row()
|
||||
page_dict: ImageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
page: PageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
||||
cbx = self.sender()
|
||||
|
||||
if isinstance(cbx, QtWidgets.QCheckBox) and cbx.isChecked():
|
||||
if not page_dict.get("double_page", False):
|
||||
page_dict["double_page"] = True
|
||||
self.modified.emit()
|
||||
elif "double_page" in page_dict:
|
||||
del page_dict["double_page"]
|
||||
if isinstance(cbx, QtWidgets.QCheckBox):
|
||||
page.double_page = cbx.isChecked()
|
||||
self.modified.emit()
|
||||
|
||||
item = self.listWidget.item(row)
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page_dict)
|
||||
item.setText(self.list_entry_text(page_dict))
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page)
|
||||
item.setText(self.list_entry_text(page))
|
||||
|
||||
self.listWidget.setFocus()
|
||||
|
||||
def save_bookmark(self) -> None:
|
||||
row = self.listWidget.currentRow()
|
||||
page_dict: ImageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
page: PageMetadata = self.listWidget.item(row).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
||||
current_bookmark = page_dict.get("bookmark", "")
|
||||
previous_bookmark = page.bookmark
|
||||
new_bookmark = self.leBookmark.text().strip()
|
||||
|
||||
if self.leBookmark.text().strip():
|
||||
new_bookmark = str(self.leBookmark.text().strip())
|
||||
if current_bookmark != new_bookmark:
|
||||
page_dict["bookmark"] = new_bookmark
|
||||
self.modified.emit()
|
||||
elif current_bookmark != "":
|
||||
del page_dict["bookmark"]
|
||||
self.modified.emit()
|
||||
if previous_bookmark == new_bookmark:
|
||||
return
|
||||
|
||||
page.bookmark = new_bookmark
|
||||
self.modified.emit()
|
||||
item = self.listWidget.item(row)
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page_dict)
|
||||
item.setText(self.list_entry_text(page_dict))
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, page)
|
||||
item.setText(self.list_entry_text(page))
|
||||
|
||||
self.listWidget.setFocus()
|
||||
|
||||
def set_data(self, comic_archive: ComicArchive, pages_list: list[ImageMetadata]) -> None:
|
||||
def set_data(self, comic_archive: ComicArchive, pages_list: list[PageMetadata]) -> None:
|
||||
self.cbxBlur.setChecked(self.blur)
|
||||
self.comic_archive = comic_archive
|
||||
self.pages_list = pages_list
|
||||
@ -372,7 +360,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.listWidget.itemSelectionChanged.disconnect(self.change_page)
|
||||
|
||||
self.listWidget.clear()
|
||||
for p in pages_list:
|
||||
for p in sorted(pages_list, key=lambda p: p.display_index):
|
||||
item = QtWidgets.QListWidgetItem(self.list_entry_text(p))
|
||||
item.setData(QtCore.Qt.ItemDataRole.UserRole, p)
|
||||
|
||||
@ -381,24 +369,26 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.listWidget.itemSelectionChanged.connect(self.change_page)
|
||||
self.listWidget.setCurrentRow(0)
|
||||
|
||||
def list_entry_text(self, page_dict: ImageMetadata) -> str:
|
||||
text = str(int(page_dict["image_index"]) + 1)
|
||||
if page_type := page_dict.get("type", ""):
|
||||
if page_type in self.pageTypeNames:
|
||||
text += " (" + self.pageTypeNames[page_type] + ")"
|
||||
def list_entry_text(self, page: PageMetadata) -> str:
|
||||
# indexes start at 0 but we display starting at 1. This should be consistent for all indexes in ComicTagger
|
||||
text = str(int(page.archive_index) + 1)
|
||||
if page.type:
|
||||
if page.type.casefold() in {x.casefold() for x in PageType}:
|
||||
text += " (" + self.pageTypeNames[PageType(page.type)] + ")"
|
||||
else:
|
||||
text += " (Error: " + page_type + ")"
|
||||
if page_dict.get("double_page", False):
|
||||
text += f" (Unknown: {page.type})"
|
||||
if page.double_page:
|
||||
text += " ②"
|
||||
if page_dict.get("bookmark", ""):
|
||||
if page.bookmark:
|
||||
text += " 🔖"
|
||||
return text
|
||||
|
||||
def get_page_list(self) -> list[ImageMetadata]:
|
||||
page_list = []
|
||||
def get_page_list(self) -> list[PageMetadata]:
|
||||
page_list: list[PageMetadata] = []
|
||||
for i in range(self.listWidget.count()):
|
||||
item = self.listWidget.item(i)
|
||||
page_list.append(item.data(QtCore.Qt.ItemDataRole.UserRole))
|
||||
page_list[i].display_index = i
|
||||
return page_list
|
||||
|
||||
def emit_front_cover_change(self) -> None:
|
||||
|
@ -2068,12 +2068,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def recalc_page_dimensions(self) -> None:
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
for p in self.metadata.pages:
|
||||
if "size" in p:
|
||||
del p["size"]
|
||||
if "height" in p:
|
||||
del p["height"]
|
||||
if "width" in p:
|
||||
del p["width"]
|
||||
p.byte_size = None
|
||||
p.height = None
|
||||
p.width = None
|
||||
self.set_dirty_flag()
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
|
@ -34,7 +34,7 @@ def test_getPageNameList():
|
||||
def test_page_type_read(cbz):
|
||||
md = cbz.read_tags("cr")
|
||||
|
||||
assert isinstance(md.pages[0]["type"], str)
|
||||
assert md.pages[0].type == comicapi.genericmetadata.PageType.FrontCover
|
||||
|
||||
|
||||
def test_read_tags(cbz, md_saved):
|
||||
@ -94,7 +94,7 @@ def test_save_cbi_rar(tmp_path, md_saved):
|
||||
def test_page_type_write(tmp_comic):
|
||||
md = tmp_comic.read_tags("cr")
|
||||
t = md.pages[0]
|
||||
t["type"] = ""
|
||||
t.type = ""
|
||||
|
||||
assert tmp_comic.write_tags(md, "cr")
|
||||
|
||||
|
@ -15,7 +15,8 @@ def test_apply_default_page_list(tmp_path):
|
||||
md.pages = []
|
||||
md.apply_default_page_list(["testing"])
|
||||
|
||||
assert isinstance(md.pages[0]["image_index"], int)
|
||||
assert md.pages[0].display_index == 0
|
||||
assert md.pages[0].archive_index == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("md, new, expected", testing.comicdata.metadata)
|
||||
|
Loading…
Reference in New Issue
Block a user