From 2a508f0265a971e6c88957c63a6b626b6172e3eb Mon Sep 17 00:00:00 2001 From: Oleskii Pyskun Date: Tue, 2 Jul 2024 10:08:55 +0300 Subject: [PATCH] Implementation of page controller --- cbz/comic.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ cbz/constants.py | 12 ++++++++++ cbz/models.py | 54 +++++++++++++++++++-------------------------- cbz/page.py | 52 ++++++++++++++++++++++++++++--------------- cbz/utils.py | 37 +++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 49 deletions(-) diff --git a/cbz/comic.py b/cbz/comic.py index f0010ee..e840c36 100644 --- a/cbz/comic.py +++ b/cbz/comic.py @@ -118,3 +118,60 @@ class ComicInfo(ComicModel): result = zip_buffer.getvalue() zip_buffer.close() return result + + def get_page(self, index: int) -> PageInfo: + """ + Get page by index + :param index: + :return: + """ + return self.__pages[index] + + def show_page(self, index: int) -> None: + """ + Display page by index + :param index: + :return: + """ + self.__pages[index].show() + + def delete_page(self, index: int) -> PageInfo: + """ + Delete page by index + :param index: + :return: deleted page + """ + return self.__pages.pop(index) + + def add_page(self, page: PageInfo) -> list[PageInfo]: + """ + Add new page to the book + :param page: + :return: new list of pages + """ + self.__pages.append(page) + return self.__pages + + def insert_page(self, index: int, page: PageInfo) -> list[PageInfo]: + """ + Add new page to position + :param page: + :param index: + :return: + """ + self.__pages.insert(index, page) + return self.__pages + + def get_pages_count(self) -> int: + """ + Get count of pages + :return: + """ + return len(self.__pages) + + def get_all_pages(self) -> list[PageInfo]: + """ + Get all pages of book + :return: + """ + return self.__pages diff --git a/cbz/constants.py b/cbz/constants.py index 622a170..952f008 100644 --- a/cbz/constants.py +++ b/cbz/constants.py @@ -162,3 +162,15 @@ FIELDS = ( (("language_iso", "LanguageISO"), ("language_iso", (ValidLanguage, str))), (("community_rating", "CommunityRating"), ("community_rating", (ValidRating, float, int, str))), ) + +PAGE_FIELDS = ( + # model: (key, xml key): (variable name, (expected type, second expected type,...)) + # in case of multiple expected formats, value cast to first type in tuple + (("type", "Type"), ("type", (PageType, str))), + (("double", "DoublePage"), ("double", (bool,))), + (("key", "Key"), ("key", (str,))), + (("bookmark", "Bookmark"), ("bookmark", (str,))), + (("image_size", "ImageSize"), ("_image_size", (int,))), + (("image_width", "ImageWidth"), ("_image_width", (int,))), + (("image_height", "ImageHeight"), ("_image_height", (int,))), +) \ No newline at end of file diff --git a/cbz/models.py b/cbz/models.py index e602d12..2430a0c 100644 --- a/cbz/models.py +++ b/cbz/models.py @@ -1,5 +1,5 @@ -from cbz.constants import YesNo, Manga, AgeRating, Format, FIELDS -from typing import Any +from cbz.constants import YesNo, Manga, AgeRating, Format, PageType, FIELDS, PAGE_FIELDS +from cbz.utils import _get, _set class ComicModel: @@ -48,35 +48,25 @@ class ComicModel: _filepath: str = "" def __init__(self, kwargs: dict): - """ - Set class variables from input dictionary, check types of input and cast values to correct type - :param kwargs: dictionary of input data - """ - for kwarg_key, kwarg_value in kwargs.items(): - for keys, values in FIELDS: - if kwarg_key in keys: - variable, types = values - if hasattr(self, variable): - setattr(self, variable, self._check_type(kwarg_key, kwarg_value, types)) - break - - @staticmethod - def _check_type(key: str, value: Any, types: tuple) -> Any: - """ - Check type of value, and cast this value to first type of the types - :param key: name of key of variable (using only for best error information) - :param value: value for a check - :param types: list of the allowed types - :return: value with new type - """ - if not isinstance(value, types): - raise ValueError( - f"Unexpected type of {key}, got: {type(value).__name__}, expected: {[i.__name__ for i in types]}") - return types[0](value) + _set(self, FIELDS, kwargs) def _get(self) -> dict: - """ - Create dictionary from variables by FIELDS - :return: dictionary with variables value - """ - return {key[1]: self._check_type(value[0], getattr(self, value[0]), value[1]) for key, value in FIELDS} + return _get(self, FIELDS) + + +class PageModel: + suffix: str + _content: bytes + type: PageType = PageType.STORY + double: bool = False + _image_size: int = 0 + key: str = "" + bookmark: str = "" + _image_width: int = 0 + _image_height: int = 0 + + def __init__(self, kwargs: dict): + _set(self, PAGE_FIELDS, kwargs) + + def _get(self) -> dict: + return _get(self, PAGE_FIELDS) diff --git a/cbz/page.py b/cbz/page.py index 320d687..35a7ae1 100644 --- a/cbz/page.py +++ b/cbz/page.py @@ -3,35 +3,45 @@ from __future__ import annotations import base64 import json from io import BytesIO -from typing import Union - from pathlib import Path +from typing import Union from PIL import Image from cbz import utils -from cbz.constants import PageType +from cbz.models import PageModel -class PageInfo: +class PageInfo(PageModel): def __init__(self, content: bytes, **kwargs): - with Image.open(BytesIO(content)) as image: + super(PageInfo, self).__init__(kwargs) + self.content = content + + @property + def content(self) -> bytes: + """ + content getter + :return: + """ + return self._content + + @content.setter + def content(self, value) -> None: + """ + Set image info by current content + :param value: + :return: + """ + with Image.open(BytesIO(value)) as image: self.suffix = f'.{image.format.lower()}' - self.content = content - self.__info = { - # 'Image': 0, - 'Type': PageType(kwargs.get('type', PageType.STORY)), - 'DoublePage': bool(kwargs.get('double', False)), - 'ImageSize': len(content), - 'Key': str(kwargs.get('key', '')), - 'Bookmark': str(kwargs.get('bookmark', '')), - 'ImageWidth': int(image.width), - 'ImageHeight': int(image.height) - } + self._image_width = int(image.width) + self._image_height = int(image.height) + self._image_size = len(value) + self._content = value def dumps(self) -> dict: - return utils.dumps(self.__info) + return utils.dumps(self._get()) def __repr__(self) -> str: return json.dumps(self.dumps(), indent=2) @@ -50,3 +60,11 @@ class PageInfo: raise ValueError(f'Expecting Path object or path string, got {path!r}') with Path(path).open(mode='rb') as f: return cls(f.read(), **kwargs) + + def show(self) -> None: + """ + display this page + :return: + """ + with Image.open(BytesIO(self.content)) as image: + image.show() diff --git a/cbz/utils.py b/cbz/utils.py index ee7dbcd..71d7038 100644 --- a/cbz/utils.py +++ b/cbz/utils.py @@ -1,5 +1,42 @@ from enum import Enum +from typing import Any def dumps(data: dict) -> dict: return {k: v.value if isinstance(v, Enum) else v for k, v in data.items() if v and v != -1 and v != 'Unknown'} + + +def _check_type(key: str, value: Any, types: tuple) -> Any: + """ + Check type of value, and cast this value to first type of the types + :param key: name of key of variable (using only for best error information) + :param value: value for a check + :param types: list of the allowed types + :return: value with new type + """ + if not isinstance(value, types): + raise ValueError( + f"Unexpected type of {key}, got: {type(value).__name__}, expected: {[i.__name__ for i in types]}") + return types[0](value) + + +def _get(obj: Any, fields: tuple) -> dict: + """ + Create dictionary from variables by PAGE_FIELDS + :return: dictionary with variables value + """ + return {key[1]: _check_type(value[0], getattr(obj, value[0]), value[1]) for key, value in fields} + + +def _set(obj: Any, fields: tuple, data_dict: dict) -> None: + """ + Set class variables from input dictionary, check types of input and cast values to correct type + :param kwargs: dictionary of input data + """ + for kwarg_key, kwarg_value in data_dict.items(): + for keys, values in fields: + if kwarg_key in keys: + variable, types = values + if hasattr(obj, variable): + setattr(obj, variable, _check_type(kwarg_key, kwarg_value, types)) + break