forked from mirrors/cbz
+139
-53
@@ -11,6 +11,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from cbz import utils
|
from cbz import utils
|
||||||
from cbz.constants import YesNo, Manga, AgeRating, Format, xml_name
|
from cbz.constants import YesNo, Manga, AgeRating, Format, xml_name
|
||||||
|
from cbz.models import ComicModel
|
||||||
from cbz.page import PageInfo
|
from cbz.page import PageInfo
|
||||||
|
|
||||||
|
|
||||||
@@ -25,59 +26,101 @@ def language(value: str) -> str:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ComicInfo:
|
class ComicInfo(ComicModel):
|
||||||
|
|
||||||
def __init__(self, pages: [PageInfo], **kwargs):
|
def __init__(self, pages: [PageInfo], **kwargs):
|
||||||
self.__pages = pages
|
self.__pages = pages
|
||||||
self.__info = {
|
# TODO: need change to loop with setattr
|
||||||
'Title': str(kwargs.get('title', kwargs.get('Title', ''))),
|
self.title = str(kwargs.get('title', kwargs.get('Title', '')))
|
||||||
'Series': str(kwargs.get('series', kwargs.get('Series', ''))),
|
self.series = str(kwargs.get('series', kwargs.get('Series', '')))
|
||||||
'Number': str(kwargs.get('number', kwargs.get('Number', ''))),
|
self.number = str(kwargs.get('number', kwargs.get('Number', '')))
|
||||||
'Count': int(kwargs.get('count', kwargs.get('Count', -1))),
|
self.count = int(kwargs.get('count', kwargs.get('Count', -1)))
|
||||||
'Volume': int(kwargs.get('volume', kwargs.get('Volume', -1))),
|
self.volume = int(kwargs.get('volume', kwargs.get('Volume', -1)))
|
||||||
'AlternateSeries': str(kwargs.get('alternate_series', kwargs.get('AlternateSeries', ''))),
|
self.alternate_series = str(kwargs.get('alternate_series', kwargs.get('AlternateSeries', '')))
|
||||||
'AlternateNumber': str(kwargs.get('alternate_number', kwargs.get('AlternateNumber', ''))),
|
self.alternate_number = str(kwargs.get('alternate_number', kwargs.get('AlternateNumber', '')))
|
||||||
'AlternateCount': int(kwargs.get('alternate_count', kwargs.get('AlternateCount', -1))),
|
self.alternate_count = int(kwargs.get('alternate_count', kwargs.get('AlternateCount', -1)))
|
||||||
'Summary': str(kwargs.get('summary', kwargs.get('Summary', ''))),
|
self.summary = str(kwargs.get('summary', kwargs.get('Summary', '')))
|
||||||
'Notes': str(kwargs.get('notes', kwargs.get('Notes', ''))),
|
self.notes = str(kwargs.get('notes', kwargs.get('Notes', '')))
|
||||||
'Year': int(kwargs.get('year', kwargs.get('Year', -1))),
|
self.year = int(kwargs.get('year', kwargs.get('Year', -1)))
|
||||||
'Month': int(kwargs.get('month', kwargs.get('Month', -1))),
|
self.month = int(kwargs.get('month', kwargs.get('Month', -1)))
|
||||||
'Day': int(kwargs.get('day', kwargs.get('Day', -1))),
|
self.day = int(kwargs.get('day', kwargs.get('Day', -1)))
|
||||||
'Writer': str(kwargs.get('writer', kwargs.get('Writer', ''))),
|
self.writer = str(kwargs.get('writer', kwargs.get('Writer', '')))
|
||||||
'Penciller': str(kwargs.get('penciller', kwargs.get('Penciller', ''))),
|
self.penciller = str(kwargs.get('penciller', kwargs.get('Penciller', '')))
|
||||||
'Inker': str(kwargs.get('inker', kwargs.get('Inker', ''))),
|
self.inker = str(kwargs.get('inker', kwargs.get('Inker', '')))
|
||||||
'Colorist': str(kwargs.get('colorist', kwargs.get('Colorist', ''))),
|
self.colorist = str(kwargs.get('colorist', kwargs.get('Colorist', '')))
|
||||||
'Letterer': str(kwargs.get('letterer', kwargs.get('Letterer', ''))),
|
self.letterer = str(kwargs.get('letterer', kwargs.get('Letterer', '')))
|
||||||
'CoverArtist': str(kwargs.get('cover_artist', kwargs.get('CoverArtist', ''))),
|
self.cover_artist = str(kwargs.get('cover_artist', kwargs.get('CoverArtist', '')))
|
||||||
'Editor': str(kwargs.get('editor', kwargs.get('Editor', ''))),
|
self.editor = str(kwargs.get('editor', kwargs.get('Editor', '')))
|
||||||
'Translator': str(kwargs.get('translator', kwargs.get('Translator', ''))),
|
self.translator = str(kwargs.get('translator', kwargs.get('Translator', '')))
|
||||||
'Publisher': str(kwargs.get('publisher', kwargs.get('Publisher', ''))),
|
self.publisher = str(kwargs.get('publisher', kwargs.get('Publisher', '')))
|
||||||
'Imprint': str(kwargs.get('imprint', kwargs.get('imprint', 'Imprint'))),
|
self.imprint = str(kwargs.get('imprint', kwargs.get('imprint', 'Imprint')))
|
||||||
'Genre': str(kwargs.get('genre', kwargs.get('Genre', ''))),
|
self.genre = str(kwargs.get('genre', kwargs.get('Genre', '')))
|
||||||
'Tags': str(kwargs.get('tags', kwargs.get('Tags', ''))),
|
self.tags = str(kwargs.get('tags', kwargs.get('Tags', '')))
|
||||||
'Web': str(kwargs.get('web', kwargs.get('Web', ''))),
|
self.web = str(kwargs.get('web', kwargs.get('Web', '')))
|
||||||
'PageCount': len(self.__pages),
|
self.language_iso = language(kwargs.get('language_iso', kwargs.get('LanguageISO', '')))
|
||||||
'LanguageISO': language(kwargs.get('language_iso', kwargs.get('LanguageISO', ''))),
|
self.format = Format(kwargs.get('format', kwargs.get('Format', Format.UNKNOWN)))
|
||||||
'Format': Format(kwargs.get('format', kwargs.get('Format', Format.UNKNOWN))),
|
self.ean = str(kwargs.get('ean', kwargs.get('EAN', '')))
|
||||||
'EAN': str(kwargs.get('ean', kwargs.get('EAN', ''))),
|
self.black_white = YesNo(kwargs.get('black_white', kwargs.get('BlackAndWhite', YesNo.UNKNOWN)))
|
||||||
'BlackAndWhite': YesNo(kwargs.get('black_white', kwargs.get('BlackAndWhite', YesNo.UNKNOWN))),
|
self.manga = Manga(kwargs.get('manga', kwargs.get('Manga', Manga.UNKNOWN)))
|
||||||
'Manga': Manga(kwargs.get('manga', kwargs.get('Manga', Manga.UNKNOWN))),
|
self.characters = str(kwargs.get('characters', kwargs.get('Characters', '')))
|
||||||
'Characters': str(kwargs.get('characters', kwargs.get('Characters', ''))),
|
self.teams = str(kwargs.get('teams', kwargs.get('Teams', '')))
|
||||||
'Teams': str(kwargs.get('teams', kwargs.get('Teams', ''))),
|
self.locations = str(kwargs.get('locations', kwargs.get('Locations', '')))
|
||||||
'Locations': str(kwargs.get('locations', kwargs.get('Locations', ''))),
|
self.scan_information = str(kwargs.get('scan_information', kwargs.get('ScanInformation', '')))
|
||||||
'ScanInformation': str(kwargs.get('scan_information', kwargs.get('ScanInformation', ''))),
|
self.story_arc = str(kwargs.get('story_arc', kwargs.get('StoryArc', '')))
|
||||||
'StoryArc': str(kwargs.get('story_arc', kwargs.get('StoryArc', ''))),
|
self.story_arc_number = str(kwargs.get('story_arc_number', kwargs.get('StoryArcNumber', '')))
|
||||||
'StoryArcNumber': str(kwargs.get('story_arc_number', kwargs.get('StoryArcNumber', ''))),
|
self.series_group = str(kwargs.get('series_group', kwargs.get('series_group', '')))
|
||||||
'SeriesGroup': str(kwargs.get('series_group', kwargs.get('series_group', ''))),
|
self.age_rating = AgeRating(kwargs.get('age_rating', kwargs.get('AgeRating', AgeRating.UNKNOWN)))
|
||||||
'AgeRating': AgeRating(kwargs.get('age_rating', kwargs.get('AgeRating', AgeRating.UNKNOWN))),
|
self.community_rating = rating(kwargs.get('community_rating', kwargs.get('CommunityRating', -1)))
|
||||||
'Pages': [{'Image': i, **page.dumps()} for i, page in enumerate(pages)],
|
self.main_character_or_team = str(kwargs.get('main_character_or_team', kwargs.get('MainCharacterOrTeam', '')))
|
||||||
'CommunityRating': rating(kwargs.get('community_rating', kwargs.get('CommunityRating', -1))),
|
self.review = str(kwargs.get('review', kwargs.get('Review', '')))
|
||||||
'MainCharacterOrTeam': str(kwargs.get('main_character_or_team', kwargs.get('MainCharacterOrTeam', ''))),
|
|
||||||
'Review': str(kwargs.get('review', kwargs.get('Review', '')))
|
|
||||||
}
|
|
||||||
|
|
||||||
def dumps(self) -> dict:
|
def dumps(self) -> dict:
|
||||||
return utils.dumps(self.__info)
|
# TODO: need change to loop with getattr
|
||||||
|
return utils.dumps({
|
||||||
|
'Title': str(self.title),
|
||||||
|
'Series': str(self.series),
|
||||||
|
'Number': str(self.number),
|
||||||
|
'Count': int(self.count),
|
||||||
|
'Volume': int(self.volume),
|
||||||
|
'AlternateSeries': str(self.alternate_series),
|
||||||
|
'AlternateNumber': str(self.alternate_number),
|
||||||
|
'AlternateCount': int(self.alternate_count),
|
||||||
|
'Summary': str(self.summary),
|
||||||
|
'Notes': str(self.notes),
|
||||||
|
'Year': int(self.year),
|
||||||
|
'Month': int(self.month),
|
||||||
|
'Day': int(self.day),
|
||||||
|
'Writer': str(self.writer),
|
||||||
|
'Penciller': str(self.penciller),
|
||||||
|
'Inker': str(self.inker),
|
||||||
|
'Colorist': str(self.colorist),
|
||||||
|
'Letterer': str(self.letterer),
|
||||||
|
'CoverArtist': str(self.cover_artist),
|
||||||
|
'Editor': str(self.editor),
|
||||||
|
'Translator': str(self.translator),
|
||||||
|
'Publisher': str(self.publisher),
|
||||||
|
'Imprint': str(self.imprint),
|
||||||
|
'Genre': str(self.genre),
|
||||||
|
'Tags': str(self.tags),
|
||||||
|
'Web': str(self.web),
|
||||||
|
'PageCount': len(self.__pages),
|
||||||
|
'LanguageISO': language(self.language_iso),
|
||||||
|
'Format': self.format,
|
||||||
|
'EAN': str(self.ean),
|
||||||
|
'BlackAndWhite': self.black_white,
|
||||||
|
'Manga': self.manga,
|
||||||
|
'Characters': str(self.characters),
|
||||||
|
'Teams': str(self.teams),
|
||||||
|
'Locations': str(self.locations),
|
||||||
|
'ScanInformation': str(self.scan_information),
|
||||||
|
'StoryArc': str(self.story_arc),
|
||||||
|
'StoryArcNumber': str(self.story_arc_number),
|
||||||
|
'SeriesGroup': str(self.series_group),
|
||||||
|
'AgeRating': self.age_rating,
|
||||||
|
'Pages': [{'Image': i, **page.dumps()} for i, page in enumerate(self.__pages)],
|
||||||
|
'CommunityRating': rating(self.community_rating),
|
||||||
|
'MainCharacterOrTeam': str(self.main_character_or_team),
|
||||||
|
'Review': str(self.review)
|
||||||
|
})
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return json.dumps(self.dumps(), indent=2)
|
return json.dumps(self.dumps(), indent=2)
|
||||||
@@ -88,9 +131,51 @@ class ComicInfo:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_cbz(cls, path: Union[Path, str]) -> ComicInfo:
|
def from_cbz(cls, path: Union[Path, str]) -> ComicInfo:
|
||||||
|
"""
|
||||||
|
read comic from cbz/cbx file
|
||||||
|
:param path: path to the file
|
||||||
|
:return: Exemplar of ComicInfo class
|
||||||
|
"""
|
||||||
if not isinstance(path, (Path, str)):
|
if not isinstance(path, (Path, str)):
|
||||||
raise ValueError(f'Expecting Path object or path string, got {path!r}')
|
raise ValueError(f'Expecting Path object or path string, got {path!r}')
|
||||||
return cls(**cls.__unpack(Path(path)))
|
exemplar = cls(**cls.__unpack(Path(path)))
|
||||||
|
exemplar._filepath = path
|
||||||
|
return exemplar
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_info(path: Union[Path, str]) -> dict:
|
||||||
|
"""
|
||||||
|
Get from file only info, without images loading
|
||||||
|
:return: dictionary with comic info
|
||||||
|
"""
|
||||||
|
if not isinstance(path, (Path, str)):
|
||||||
|
raise ValueError(f'Expecting Path object or path string, got {path!r}')
|
||||||
|
with Path(path).open(mode='rb') as f:
|
||||||
|
with zipfile.ZipFile(f, 'r', zipfile.ZIP_STORED) as zip_file:
|
||||||
|
xml_file = zip_file.open(xml_name)
|
||||||
|
info = xmltodict.parse(xml_file.read()).get("ComicInfo", {})
|
||||||
|
xml_file.close()
|
||||||
|
return info
|
||||||
|
|
||||||
|
def to_cbz(self, path: Union[Path, str]) -> None:
|
||||||
|
"""
|
||||||
|
Save comic to file
|
||||||
|
:param path: path for a save (will be overwritten if already exists)
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not isinstance(path, (Path, str)):
|
||||||
|
raise ValueError(f'Expecting Path object or path string, got {path!r}')
|
||||||
|
self._filepath = path
|
||||||
|
with Path(path).open(mode='wb') as f:
|
||||||
|
f.write(self.pack())
|
||||||
|
|
||||||
|
def save(self) -> None:
|
||||||
|
"""
|
||||||
|
Save changes to opened from file or already saved to file comic
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
assert self._filepath, "Use to_cbz or from_cbz before save changes"
|
||||||
|
self.to_cbz(self._filepath)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __unpack(file_path: Path) -> dict:
|
def __unpack(file_path: Path) -> dict:
|
||||||
@@ -98,7 +183,7 @@ class ComicInfo:
|
|||||||
_files = zip_file.namelist()
|
_files = zip_file.namelist()
|
||||||
assert xml_name in _files, f"{xml_name} not found in: {zip_file.filename}"
|
assert xml_name in _files, f"{xml_name} not found in: {zip_file.filename}"
|
||||||
xml_file = zip_file.open(xml_name)
|
xml_file = zip_file.open(xml_name)
|
||||||
info = xmltodict.parse(xml_file.read()).get("ComicInfo")
|
info = xmltodict.parse(xml_file.read()).get("ComicInfo", {})
|
||||||
xml_file.close()
|
xml_file.close()
|
||||||
_files.remove(xml_name)
|
_files.remove(xml_name)
|
||||||
info["pages"] = list()
|
info["pages"] = list()
|
||||||
@@ -118,5 +203,6 @@ class ComicInfo:
|
|||||||
|
|
||||||
for i, page in enumerate(self.__pages):
|
for i, page in enumerate(self.__pages):
|
||||||
zip_file.writestr(f'page-{i + 1:03d}{page.suffix}', page.content)
|
zip_file.writestr(f'page-{i + 1:03d}{page.suffix}', page.content)
|
||||||
|
result = zip_buffer.getvalue()
|
||||||
return zip_buffer.getvalue()
|
zip_buffer.close()
|
||||||
|
return result
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
from cbz.constants import YesNo, Manga, AgeRating, Format
|
||||||
|
|
||||||
|
|
||||||
|
class ComicModel:
|
||||||
|
title: str = ""
|
||||||
|
series: str = ""
|
||||||
|
number: str = ""
|
||||||
|
count: int = -1
|
||||||
|
volume: int = -1
|
||||||
|
alternate_series: str = ""
|
||||||
|
alternate_number: str = ""
|
||||||
|
alternate_count: int = -1
|
||||||
|
summary: str = ""
|
||||||
|
notes: str = ""
|
||||||
|
year: int = -1
|
||||||
|
month: int = -1
|
||||||
|
day: int = -1
|
||||||
|
writer: str = ""
|
||||||
|
penciller: str = ""
|
||||||
|
inker: str = ""
|
||||||
|
colorist: str = ""
|
||||||
|
letterer: str = ""
|
||||||
|
cover_artist: str = ""
|
||||||
|
editor: str = ""
|
||||||
|
translator: str = ""
|
||||||
|
publisher: str = ""
|
||||||
|
imprint: str = ""
|
||||||
|
genre: str = ""
|
||||||
|
tags: str = ""
|
||||||
|
web: str = ""
|
||||||
|
language_iso: str = ""
|
||||||
|
format: Format = Format.UNKNOWN
|
||||||
|
ean: str = ""
|
||||||
|
black_white: YesNo = YesNo.UNKNOWN
|
||||||
|
manga: Manga = Manga.UNKNOWN
|
||||||
|
characters: str = ""
|
||||||
|
teams: str = ""
|
||||||
|
locations: str = ""
|
||||||
|
scan_information: str = ""
|
||||||
|
story_arc: str = ""
|
||||||
|
story_arc_number: str = ""
|
||||||
|
series_group: str = ""
|
||||||
|
age_rating: AgeRating = AgeRating.UNKNOWN
|
||||||
|
community_rating: int = -1
|
||||||
|
main_character_or_team: str = ""
|
||||||
|
review: str = ""
|
||||||
|
_filepath: str = ""
|
||||||
+13
-14
@@ -16,20 +16,19 @@ from cbz.constants import PageType
|
|||||||
class PageInfo:
|
class PageInfo:
|
||||||
|
|
||||||
def __init__(self, content: bytes, **kwargs):
|
def __init__(self, content: bytes, **kwargs):
|
||||||
image = Image.open(BytesIO(content))
|
with Image.open(BytesIO(content)) as image:
|
||||||
|
self.suffix = f'.{image.format.lower()}'
|
||||||
self.suffix = f'.{image.format.lower()}'
|
self.content = content
|
||||||
self.content = content
|
self.__info = {
|
||||||
self.__info = {
|
# 'Image': 0,
|
||||||
# 'Image': 0,
|
'Type': PageType(kwargs.get('type', PageType.STORY)),
|
||||||
'Type': PageType(kwargs.get('type', PageType.STORY)),
|
'DoublePage': bool(kwargs.get('double', False)),
|
||||||
'DoublePage': bool(kwargs.get('double', False)),
|
'ImageSize': len(content),
|
||||||
'ImageSize': len(content),
|
'Key': str(kwargs.get('key', '')),
|
||||||
'Key': str(kwargs.get('key', '')),
|
'Bookmark': str(kwargs.get('bookmark', '')),
|
||||||
'Bookmark': str(kwargs.get('bookmark', '')),
|
'ImageWidth': int(image.width),
|
||||||
'ImageWidth': int(image.width),
|
'ImageHeight': int(image.height)
|
||||||
'ImageHeight': int(image.height)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def dumps(self) -> dict:
|
def dumps(self) -> dict:
|
||||||
return utils.dumps(self.__info)
|
return utils.dumps(self.__info)
|
||||||
|
|||||||
Reference in New Issue
Block a user