mirror of
https://github.com/hyugogirubato/cbz.git
synced 2026-09-26 13:31:00 +00:00
123 lines
5.9 KiB
Python
123 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import io
|
|
import json
|
|
import zipfile
|
|
from typing import Union
|
|
|
|
import xmltodict
|
|
from langcodes import Language
|
|
from pathlib import Path
|
|
|
|
from cbz import utils
|
|
from cbz.constants import YesNo, Manga, AgeRating, Format, xml_name
|
|
from cbz.page import PageInfo
|
|
|
|
|
|
def rating(value: int) -> int:
|
|
assert -1 <= value <= 5, 'Rating must be between 0 and 5'
|
|
return value
|
|
|
|
|
|
def language(value: str) -> str:
|
|
if value and not Language.get(value).is_valid():
|
|
raise ValueError('Invalid language')
|
|
return value
|
|
|
|
|
|
class ComicInfo:
|
|
|
|
def __init__(self, pages: [PageInfo], **kwargs):
|
|
self.__pages = pages
|
|
self.__info = {
|
|
'Title': str(kwargs.get('title', kwargs.get('Title', ''))),
|
|
'Series': str(kwargs.get('series', kwargs.get('Series', ''))),
|
|
'Number': str(kwargs.get('number', kwargs.get('Number', ''))),
|
|
'Count': int(kwargs.get('count', kwargs.get('Count', -1))),
|
|
'Volume': int(kwargs.get('volume', kwargs.get('Volume', -1))),
|
|
'AlternateSeries': str(kwargs.get('alternate_series', kwargs.get('AlternateSeries', ''))),
|
|
'AlternateNumber': str(kwargs.get('alternate_number', kwargs.get('AlternateNumber', ''))),
|
|
'AlternateCount': int(kwargs.get('alternate_count', kwargs.get('AlternateCount', -1))),
|
|
'Summary': str(kwargs.get('summary', kwargs.get('Summary', ''))),
|
|
'Notes': str(kwargs.get('notes', kwargs.get('Notes', ''))),
|
|
'Year': int(kwargs.get('year', kwargs.get('Year', -1))),
|
|
'Month': int(kwargs.get('month', kwargs.get('Month', -1))),
|
|
'Day': int(kwargs.get('day', kwargs.get('Day', -1))),
|
|
'Writer': str(kwargs.get('writer', kwargs.get('Writer', ''))),
|
|
'Penciller': str(kwargs.get('penciller', kwargs.get('Penciller', ''))),
|
|
'Inker': str(kwargs.get('inker', kwargs.get('Inker', ''))),
|
|
'Colorist': str(kwargs.get('colorist', kwargs.get('Colorist', ''))),
|
|
'Letterer': str(kwargs.get('letterer', kwargs.get('Letterer', ''))),
|
|
'CoverArtist': str(kwargs.get('cover_artist', kwargs.get('CoverArtist', ''))),
|
|
'Editor': str(kwargs.get('editor', kwargs.get('Editor', ''))),
|
|
'Translator': str(kwargs.get('translator', kwargs.get('Translator', ''))),
|
|
'Publisher': str(kwargs.get('publisher', kwargs.get('Publisher', ''))),
|
|
'Imprint': str(kwargs.get('imprint', kwargs.get('imprint', 'Imprint'))),
|
|
'Genre': str(kwargs.get('genre', kwargs.get('Genre', ''))),
|
|
'Tags': str(kwargs.get('tags', kwargs.get('Tags', ''))),
|
|
'Web': str(kwargs.get('web', kwargs.get('Web', ''))),
|
|
'PageCount': len(self.__pages),
|
|
'LanguageISO': language(kwargs.get('language_iso', kwargs.get('LanguageISO', ''))),
|
|
'Format': Format(kwargs.get('format', kwargs.get('Format', Format.UNKNOWN))),
|
|
'EAN': str(kwargs.get('ean', kwargs.get('EAN', ''))),
|
|
'BlackAndWhite': YesNo(kwargs.get('black_white', kwargs.get('BlackAndWhite', YesNo.UNKNOWN))),
|
|
'Manga': Manga(kwargs.get('manga', kwargs.get('Manga', Manga.UNKNOWN))),
|
|
'Characters': str(kwargs.get('characters', kwargs.get('Characters', ''))),
|
|
'Teams': str(kwargs.get('teams', kwargs.get('Teams', ''))),
|
|
'Locations': str(kwargs.get('locations', kwargs.get('Locations', ''))),
|
|
'ScanInformation': str(kwargs.get('scan_information', kwargs.get('ScanInformation', ''))),
|
|
'StoryArc': str(kwargs.get('story_arc', kwargs.get('StoryArc', ''))),
|
|
'StoryArcNumber': str(kwargs.get('story_arc_number', kwargs.get('StoryArcNumber', ''))),
|
|
'SeriesGroup': str(kwargs.get('series_group', kwargs.get('series_group', ''))),
|
|
'AgeRating': AgeRating(kwargs.get('age_rating', kwargs.get('AgeRating', AgeRating.UNKNOWN))),
|
|
'Pages': [{'Image': i, **page.dumps()} for i, page in enumerate(pages)],
|
|
'CommunityRating': rating(kwargs.get('community_rating', kwargs.get('CommunityRating', -1))),
|
|
'MainCharacterOrTeam': str(kwargs.get('main_character_or_team', kwargs.get('MainCharacterOrTeam', ''))),
|
|
'Review': str(kwargs.get('review', kwargs.get('Review', '')))
|
|
}
|
|
|
|
def dumps(self) -> dict:
|
|
return utils.dumps(self.__info)
|
|
|
|
def __repr__(self) -> str:
|
|
return json.dumps(self.dumps(), indent=2)
|
|
|
|
@classmethod
|
|
def from_pages(cls, pages: [PageInfo], **kwargs) -> ComicInfo:
|
|
return cls(pages, **kwargs)
|
|
|
|
@classmethod
|
|
def from_cbz(cls, path: Union[Path, str]) -> ComicInfo:
|
|
if not isinstance(path, (Path, str)):
|
|
raise ValueError(f'Expecting Path object or path string, got {path!r}')
|
|
return cls(**cls.__unpack(Path(path)))
|
|
|
|
@staticmethod
|
|
def __unpack(file_path: Path) -> dict:
|
|
with zipfile.ZipFile(file_path, 'r', zipfile.ZIP_STORED) as zip_file:
|
|
_files = zip_file.namelist()
|
|
assert xml_name in _files, f"{xml_name} not found in: {zip_file.filename}"
|
|
xml_file = zip_file.open(xml_name)
|
|
info = xmltodict.parse(xml_file.read()).get("ComicInfo")
|
|
xml_file.close()
|
|
_files.remove(xml_name)
|
|
info["pages"] = list()
|
|
for filename in _files:
|
|
page_file = zip_file.open(filename)
|
|
info["pages"].append(PageInfo.loads(page_file.read()))
|
|
page_file.close()
|
|
return info
|
|
|
|
def pack(self) -> bytes:
|
|
zip_buffer = io.BytesIO()
|
|
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_STORED) as zip_file:
|
|
zip_file.writestr(
|
|
xml_name,
|
|
xmltodict.unparse({'ComicInfo': self.dumps()}, pretty=True).encode('utf-8')
|
|
)
|
|
|
|
for i, page in enumerate(self.__pages):
|
|
zip_file.writestr(f'page-{i + 1:03d}{page.suffix}', page.content)
|
|
|
|
return zip_buffer.getvalue()
|