Make ImageMetadata a dataclass

This commit is contained in:
Timmy Welch 2024-07-19 16:18:57 -07:00
parent 219ede2d5d
commit a7a9d38428
11 changed files with 418 additions and 206 deletions

View File

@ -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,

View File

@ -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",

View File

@ -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":

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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})")

View File

@ -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:

View File

@ -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()

View File

@ -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")

View File

@ -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)