diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bbc71c --- /dev/null +++ b/.gitignore @@ -0,0 +1,101 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..238144d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: python +matrix: + include: + - python: 3.4 + dist: trusty + sudo: false + - python: 3.5 + dist: trusty + sudo: false + - python: 3.5-dev + dist: trusty + sudo: false + - python: 3.6 + dist: trusty + sudo: false + - python: 3.6-dev + dist: trusty + sudo: false + - python: 3.7 + dist: xenial + sudo: true +install: + - python setup.py -q install diff --git a/README.md b/README.md index 690415a..ccc40bc 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,38 @@ -# CBZ-Helper -In order to be able to save digital books from images while saving metadata, this library will allow you to do it simply in python without having to go through a graphical client or external commands. +# pydvdfab +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +Python library to create a cbz file with metadata. + +# Usage + +### Basic Usage -## Use ```python -import cbzhelper - -if __name__ == '__main__': - pages = [{'page_number': 0, 'content': b'', 'double_page': False}] - helper = cbzhelper.Helper('eBook', 'Volume 1', replace=True) - for page in pages: - helper.addPage(page['page_number'], page['content'], double_page=page['double_page']) - helper.addMetadata({ - 'Title': 'Volume 1', - 'Series': 'Exemples', - 'Number': '1', - 'Volume': 1 - }) - helper.saveCBZ() - +>>> import os +>>> from pycbzhelper import Helper +>>> metadata = { +>>> "Title": "T1 - Arrête de me chauffer, Nagatoro", +>>> "Series": "Arrête de me chauffer, Nagatoro", +>>> "Number": "1", +>>> "Count": 8, +>>> "Volume": 1 +>>> ... +>>> } +>>> ... +>>> helper = Helper(metadata) +>>> helper.save_cbz( +... path=os.path.join("eBooks", "Arrête de me chauffer, Nagatoro"), +... file="T1 - Arrête de me chauffer, Nagatoro", +... clear=False, +... replace=True +... ) ``` ---- -*This scripts are created by __hyugogirubato__. -Find us on [discord](https://discord.com/invite/g6JzYbh) for more information on projects in development.* +# Installation + +To install, you can either clone the repository and run `python setup.py install` + +## About +- Graphical metadata editing software [here](https://github.com/comictagger/comictagger) +- Standard ComicInfo file structure information [here](https://github.com/Kussie/ComicInfoStandard) +- New version of ComicInfo file structure [here](https://github.com/anansi-project/comicinfo) \ No newline at end of file diff --git a/cbzhelper.py b/cbzhelper.py deleted file mode 100644 index dfdf6a6..0000000 --- a/cbzhelper.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -Project: cbzhelper -File: cbzhelper.py -Author: hyuogirubato -Date: 2022.08.30 -""" - -import os.path -import re -import shutil -import zipfile -from PIL import Image - - -def clear(value): - return re.sub('[^\w\-_\.\(\)\[\] ]', '_', value) - - -class Helper: - - def __init__(self, path, file, replace=True): - self.path = path - self.file = f"{clear(file)}.cbz" - self.output = os.path.join(self.path, self.file) - self.tmp = os.path.join(self.path, 'tmp') - self.pages = [] - if not os.path.exists(path): - os.makedirs(path) - if os.path.exists(self.output) and replace: - os.remove(self.output) - - - # https://github.com/Kussie/ComicInfoStandard/blob/main/ComicInfo.xsd - def addMetadata(self, metadata): - metadata['Pages'] = self.pages - - comic_config = { - 'str': ['Title', 'Series', 'Number', 'AlternateSeries', 'AlternateNumber', - 'Summary', 'Notes', 'Writer', 'Penciller', 'Inker', 'Colorist', - 'Letterer', 'CoverArtist', 'Editor', 'Publisher', 'Imprint', - 'Genre', 'Web', 'LanguageISO', 'Characters', 'Teams', - 'Locations', 'ScanInformation', 'StoryArc', 'SeriesGroup'], - 'int': ['Count', 'Volume', 'AlternateCount', 'Year', 'Month', 'PageCount'], - 'float': ['CommunityRating', 'BookPrice'], - 'YesNo': ['BlackAndWhite'], - 'Manga': ['Manga'], - 'AgeRating': ['AgeRating'], - 'ArrayOfComicPageInfo': ['Pages'], - 'Format': ['Format'] - } - - page_config = { - 'int': ['Image', 'ImageWidth', 'ImageHeight'], - 'bool': ['DoublePage'], - 'long': ['ImageSize'], - 'str': ['Key'], - 'ComicPageType': ['Type'] - } - - comic_info = [] - comic_info.append("") - comic_info.append('') - - metadata_keys = metadata.keys() - - # default - if not 'BlackAndWhite' in metadata_keys: - metadata['BlackAndWhite'] = 'Unknown' - if not 'Manga' in metadata_keys: - metadata['Manga'] = 'Unknown' - if not 'AgeRating' in metadata_keys: - metadata['AgeRating'] = 'Unknown' - - for key in metadata_keys: - if key in comic_config['YesNo'] and metadata[key] in ['No', 'Yes']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['Manga'] and metadata[key] in ['No', 'Yes', 'YesAndRightToLeft']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['AgeRating'] and metadata[key] in ['Everyone', 'G', 'Early Childhood', 'Everyone 10+', 'PG', - 'Kids to Adults', 'Teen', 'M', 'MA15+', 'Mature 17+', - 'R18+', 'X18+', 'Adults Only 18+', 'Rating Pending']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['str']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['int']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['float']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['Format'] and metadata[key] in ['.1', '-1', '1 Shot', '1/2', '1-Shot', 'Annotation', 'Annotations', - 'Annual', 'Anthology', 'B&W', 'B/W', 'B&&W', 'Black & White', 'Box Set', - 'Box-Set', 'Crossover', 'Director\'s Cut', 'Epilogue', 'Event', 'FCBD', - 'Flyer', 'Giant', 'Giant Size', 'Graphic Novel', 'Hardcover', 'Hard-Cover', - 'King', 'King Size', 'King-Size', 'Limited Series', 'Magazine', 'NSFW', - 'One Shot', 'One-Shot', 'Point 1', 'Preview', 'Prologue', 'Reference', 'Review', - 'Reviewed', 'Scanlation', 'Script', 'Series', 'Sketch', 'Special', 'TPB', - 'Trade Paper Back', 'WebComic', 'Web Comic', 'Year 1', 'Year One']: - comic_info.append(f' <{key}>{metadata[key]}') - elif key in comic_config['ArrayOfComicPageInfo']: - comic_info.append(' ') - for item in metadata[key]: - item_keys =item.keys() - line = [] - - line.append(' ') - comic_info.append(' '.join(line)) - - comic_info.append(' ') - comic_info.append('') - - with open(os.path.join(self.tmp, 'ComicInfo.xml'), mode='w', encoding='utf-8') as f: - f.write('\n'.join(comic_info)) - - - def addPage(self, image, content, double_page=False, page_type='DEFAULT', extension='jpg'): - page = os.path.join(self.tmp, f"page-{image:03d}.{extension}") - if not os.path.exists(page): - with open(page, mode='wb') as f: - f.write(content) - - properties = Image.open(page) - if page_type == 'DEFAULT' or not page_type in ['FrontCover', 'InnerCover', 'Roundup', 'Story', - 'Advertisment', 'Editorial', 'Letters', 'Preview', - 'BackCover', 'Other', 'Deleted']: - page_type = 'FrontCover' if image == 1 else 'Story' - - self.pages.append({ - 'Image': image, - 'Type': page_type, - 'DoublePage': double_page, - 'ImageSize': properties.size, - 'ImageWidth': properties.width, - 'ImageHeight': properties.height - }) - - def saveCBZ(self): - if os.path.exists(self.tmp): - if not os.path.exists(self.output): - cbz = zipfile.ZipFile(f"{self.output}.cbz", 'w', compression=zipfile.ZIP_STORED) - for file in sorted(os.listdir(self.tmp), key=len): - with open(os.path.join(self.tmp, file), mode='rb') as f: - cbz.writestr(file, data=f.read()) - cbz.close() - shutil.rmtree(self.tmp) diff --git a/images/page-000.jpg b/images/page-000.jpg new file mode 100644 index 0000000..19b675b Binary files /dev/null and b/images/page-000.jpg differ diff --git a/images/page-001.jpg b/images/page-001.jpg new file mode 100644 index 0000000..c40b1d3 Binary files /dev/null and b/images/page-001.jpg differ diff --git a/images/page-002.jpg b/images/page-002.jpg new file mode 100644 index 0000000..6a85c4f Binary files /dev/null and b/images/page-002.jpg differ diff --git a/images/page-003.jpg b/images/page-003.jpg new file mode 100644 index 0000000..23e6547 Binary files /dev/null and b/images/page-003.jpg differ diff --git a/images/page-004.jpg b/images/page-004.jpg new file mode 100644 index 0000000..2e1c2d1 Binary files /dev/null and b/images/page-004.jpg differ diff --git a/images/page-005.jpg b/images/page-005.jpg new file mode 100644 index 0000000..bbeb8bc Binary files /dev/null and b/images/page-005.jpg differ diff --git a/images/page-006.jpg b/images/page-006.jpg new file mode 100644 index 0000000..1d002db Binary files /dev/null and b/images/page-006.jpg differ diff --git a/images/page-007.jpg b/images/page-007.jpg new file mode 100644 index 0000000..9f2e2c5 Binary files /dev/null and b/images/page-007.jpg differ diff --git a/images/page-008.jpg b/images/page-008.jpg new file mode 100644 index 0000000..a7bd0e3 Binary files /dev/null and b/images/page-008.jpg differ diff --git a/images/page-009.jpg b/images/page-009.jpg new file mode 100644 index 0000000..a835f60 Binary files /dev/null and b/images/page-009.jpg differ diff --git a/images/page-010.jpg b/images/page-010.jpg new file mode 100644 index 0000000..b93badd Binary files /dev/null and b/images/page-010.jpg differ diff --git a/pycbzhelper/__init__.py b/pycbzhelper/__init__.py new file mode 100644 index 0000000..06de162 --- /dev/null +++ b/pycbzhelper/__init__.py @@ -0,0 +1,4 @@ +from .helper import * +from .comicinfo import * + +__version__ = "3.0.1" diff --git a/pycbzhelper/comicinfo.py b/pycbzhelper/comicinfo.py new file mode 100644 index 0000000..ac72aa8 --- /dev/null +++ b/pycbzhelper/comicinfo.py @@ -0,0 +1,44 @@ +""" +Refer: +- https://github.com/comictagger/comictagger +- https://github.com/Kussie/ComicInfoStandard +- https://github.com/anansi-project/comicinfo +""" + +KEYS_STRING = [ + 'Title', 'Series', 'Number', 'AlternateSeries', 'AlternateNumber', + 'Summary', 'Notes', 'Writer', 'Penciller', 'Inker', 'Colorist', + 'Letterer', 'CoverArtist', 'Editor', 'Publisher', 'Imprint', 'Genre', + 'Web', 'Characters', 'Teams', 'Locations', 'ScanInformation', + 'StoryArc', 'SeriesGroup', 'CommunityRating' +] + +KEYS_INT = [ + 'Count', 'Volume', 'AlternateCount', 'Year', 'Month', 'Day', 'PageCount' +] + +KEYS_SPECIAL = [ + 'BlackAndWhite', 'Manga', 'AgeRating', 'Pages', 'LanguageISO', 'Format', 'ESN' +] + +KEYS_AGE = [ + 'Adults Only 18+', 'Early Childhood', 'Everyone', 'Everyone 10+', + 'G', 'Kids to Adults', 'M', 'MA 15+', 'Mature 17+', 'PG', 'R18+', + 'Rating Pending', 'Teen', 'X18+', 'Rating Pending' +] + +KEYS_FORMAT = [ + '1 Shot', '1/2', '1-Shot', 'Annotation', 'Annotations', + 'Annual', 'Anthology', 'B&W', 'B/W', 'B&&W', 'Black & White', 'Box Set', + 'Box-Set', 'Crossover', "Director's Cut", 'Epilogue', 'Event', 'FCBD', + 'Flyer', 'Giant', 'Giant Size', 'Giant-Size', 'Graphic Novel', 'Hardcover', + 'Hard-Cover', 'King', 'King Size', 'King-Size', 'Limited Series', 'Magazine', + 'NSFW', 'One Shot', 'One-Shot', 'Point 1', 'Preview', 'Prologue', 'Reference', + 'Review', 'Reviewed', 'Scanlation', 'Script', 'Series', 'Sketch', 'Special', + 'TPB', 'Trade Paper Back', 'WebComic', 'Web Comic', 'Year 1', 'Year One' +] + +KEYS_PAGE_TYPE = [ + 'FrontCover', 'InnerCover', 'Roundup', 'Story', 'Advertisment', + 'Editorial', 'Letters', 'Preview', 'BackCover', 'Other', 'Deleted' +] diff --git a/pycbzhelper/exceptions.py b/pycbzhelper/exceptions.py new file mode 100644 index 0000000..8e47152 --- /dev/null +++ b/pycbzhelper/exceptions.py @@ -0,0 +1,14 @@ +class PyCBZHelperException(Exception): + """Exceptions used by pycbzhelper.""" + + +class InvalidKeyValue(PyCBZHelperException): + """The key value is invalid.""" + + +class MissingPageFile(PyCBZHelperException): + """No page available.""" + + +class InvalidFilePermission(PyCBZHelperException): + """Unable to delete existing file.""" diff --git a/pycbzhelper/helper.py b/pycbzhelper/helper.py new file mode 100644 index 0000000..1bbe03f --- /dev/null +++ b/pycbzhelper/helper.py @@ -0,0 +1,120 @@ +import os +import shutil +import zipfile + +from json2xml import json2xml +from langcodes import Language +from PIL import Image + +from pycbzhelper.comicinfo import KEYS_STRING, KEYS_INT, KEYS_SPECIAL, KEYS_AGE, KEYS_FORMAT, KEYS_PAGE_TYPE +from pycbzhelper.exceptions import InvalidKeyValue, MissingPageFile, InvalidFilePermission +from pycbzhelper.utils import get_key_value, delete_none, slugify + + +class Helper: + + def __init__(self, kwargs): + self._files = [] + self.metadata = self._get_metadata(kwargs) + + def _get_metadata(self, kwargs) -> str: + # NOTE: Check metadata + for key in KEYS_STRING: + if kwargs.get(key) and not isinstance(kwargs.get(key), str): + raise InvalidKeyValue(f"ERROR: Key must be string: {key}") + + for key in KEYS_INT: + if kwargs.get(key) and not isinstance(kwargs.get(key), int): + raise InvalidKeyValue(f"ERROR: Key must be integer: {key}") + + for key in KEYS_SPECIAL: + if kwargs.get(key): + if key == 'BlackAndWhite' and get_key_value(kwargs.get(key)) not in ['Yes', 'No']: + raise InvalidKeyValue(f"ERROR: Key must be boolean: {key}") + elif key == 'Manga' and get_key_value(kwargs.get(key)) not in ['YesAndRightToLeft', 'Yes', 'No']: + raise InvalidKeyValue(f"ERROR: Key must be boolean or special boolean: {key}") + elif key == 'AgeRating' and kwargs.get(key) not in KEYS_AGE: + raise InvalidKeyValue(f"ERROR: Key must be special age: {key}") + elif key == 'LanguageISO' and not Language.get(kwargs.get(key)).is_valid(): + raise InvalidKeyValue(f"ERROR: Key must be ISO language: {key}") + elif key == 'Format' and kwargs.get(key) not in KEYS_FORMAT: + raise InvalidKeyValue(f"ERROR: Key must be special format: {key}") + elif key == 'Pages': + if isinstance(kwargs.get(key), list): + for page in kwargs.get(key): + if not page.get('File') or not isinstance(page.get('File'), str) or not os.path.exists(page.get('File')): + raise InvalidKeyValue(f"ERROR: Key must be existing string path: {key}") + if page.get('Type') and not page.get('Type') in KEYS_PAGE_TYPE: + raise InvalidKeyValue("ERROR: Key must be special type: Type") + if page.get('DoublePage') and get_key_value(page.get('DoublePage')) not in ['Yes', 'No']: + raise InvalidKeyValue("ERROR: Key must be boolean: DoublePage") + else: + raise InvalidKeyValue(f"ERROR: Key must be a list: {key}") + + # NOTE: Set metadata + pages = [] + if kwargs.get('Pages'): + # [{'File': 'FILE_PATH', 'Type': 'FrontCover', 'DoublePage': False}] + pages.append(" ") + kwargs['PageCount'] = len(kwargs.get('Pages')) + for i in range(kwargs.get('PageCount')): + page = kwargs.get('Pages')[i] + properties = Image.open(page['File']) + self._files.append(page['File']) + if not page.get('Type'): + page['Type'] = 'FrontCover' if i == 0 else 'Story' + + item = ' '.format( + double="False" if get_key_value(page.get('DoublePage', False)) == 'No' else "True", + image=i, + height=properties.height, + size=len(properties.fp.read()), + width=properties.width, + type=page['Type'] + ) + ) + pages.append(" ") + del kwargs['Pages'] + pages.append("") + + json_data = {} + for key in KEYS_STRING + KEYS_INT + KEYS_SPECIAL: + json_data[key] = get_key_value(kwargs.get(key)) + + xml_data = json2xml.Json2xml(delete_none(json_data), wrapper="ComicInfo", pretty=True, attr_type=False).to_xml() + if not xml_data: + xml_data = '\n'.join(['', '', '']) + print('WARNING: No metadata to create.') + xml_data = xml_data.replace('', '') + xml_data = xml_data.replace('', '\n'.join(pages)) + return xml_data.strip() + + def save_cbz(self, path: str, file: str, clear: bool = False, replace: bool = True) -> None: + if len(self._files) == 0: + raise MissingPageFile('ERROR: No pages available.') + + output = os.path.join(path, f"{slugify(file, allow_unicode=False)}.cbz") + if not os.path.exists(path): + os.makedirs(path) + if os.path.exists(output) and not replace: + raise InvalidFilePermission('ERROR: File already exists. The replace option is not enabled.') + elif os.path.exists(output): + os.remove(output) + + clear_path = [] + cbz = zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_STORED) + for file in self._files: + if os.path.dirname(file) not in clear_path: + clear_path.append(os.path.dirname(file)) + with open(file, mode='rb') as f: + cbz.writestr(os.path.basename(file), data=f.read()) + cbz.writestr('ComicInfo.xml', data=self.metadata.encode('utf-8')) + cbz.close() + print(f"INFO: File create: {output}") + if clear: + for path in clear_path: + shutil.rmtree(path) + print(f"INFO: Folder deleted: {path}") diff --git a/pycbzhelper/utils.py b/pycbzhelper/utils.py new file mode 100644 index 0000000..80acd45 --- /dev/null +++ b/pycbzhelper/utils.py @@ -0,0 +1,47 @@ +from __future__ import annotations +from typing import Union + +import unicodedata + + +def get_key_value(_value) -> str: + if isinstance(_value, str): + if _value in ['YesAndRightToLeft', 'Yes', 'No']: + return _value + elif isinstance(_value, bool): + return 'Yes' if _value else 'No' + return _value + + +def delete_none(_dict: dict) -> dict: + """Delete None values recursively from all of the dictionaries, tuples, lists, sets""" + if isinstance(_dict, dict): + for key, value in list(_dict.items()): + if isinstance(value, (list, dict, tuple, set)): + _dict[key] = delete_none(value) + elif value is None or key is None: + del _dict[key] + if value == {}: + del _dict[key] + + elif isinstance(_dict, (list, set, tuple)): + _dict = type(_dict)(delete_none(item) for item in _dict if item is not None) + return _dict + + +def slugify(value: Union[str, int], allow_unicode: bool = False) -> str: + """ + Taken from https://github.com/django/django/blob/master/django/utils/text.py + Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated + dashes to single dashes. Remove characters that aren't alphanumerics, + underscores, or hyphens. Convert to lowercase. Also strip leading and + trailing whitespace, dashes, and underscores. + """ + value = str(value) + if allow_unicode: + value = unicodedata.normalize('NFKC', value) + else: + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') + # value = re.sub(r'[^\w\s-]', '', value.lower()) + # return re.sub(r'[-\s]+', '-', value).strip('-_') + return value diff --git a/schema/v1.0/ComicInfo.xsd b/schema/v1.0/ComicInfo.xsd new file mode 100644 index 0000000..556c3a0 --- /dev/null +++ b/schema/v1.0/ComicInfo.xsd @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/v2.0/ComicInfo.xsd b/schema/v2.0/ComicInfo.xsd new file mode 100644 index 0000000..6732fe8 --- /dev/null +++ b/schema/v2.0/ComicInfo.xsd @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schema/v2.1/ComicInfo.xsd b/schema/v2.1/ComicInfo.xsd new file mode 100644 index 0000000..02ea57c --- /dev/null +++ b/schema/v2.1/ComicInfo.xsd @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3c76193 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +"""Setup module""" + +from setuptools import setup + +with open('README.md', 'r') as fh: + LONG_DESCRIPTION = fh.read() + +setup( + name='pycbzhelper', + version='3.0.1', + description='Python library to create a cbz file with metadata.', + long_description=LONG_DESCRIPTION, + long_description_content_type='text/markdown', + url='https://github.com/hyugogirubato/pycbzhelper', + author='hyugogirubato', + author_email='hyugogirubato@gmail.com', + license='GNU GPLv3', + packages=['pycbzhelper'], + install_requires=['json2xml', 'langcodes', 'pillow'], + classifiers=[ + 'Environment :: Console', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Utilities' + ] +) diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..6e1393a --- /dev/null +++ b/tests.py @@ -0,0 +1,43 @@ +import os + +import pycbzhelper + +if __name__ == '__main__': + pages = [] + for i in range(11): + pages.append({'File': os.path.join('images', f"page-{i:03d}.jpg")}) + + metadata = { + 'Title': 'T1 - Arrête de me chauffer, Nagatoro', + 'Series': 'Arrête de me chauffer, Nagatoro', + 'Number': '1', + 'Count': 8, + 'Volume': 1, + 'Summary': 'Nagatoro est en seconde. Pleine d\u2019assurance, joueuse, moqueuse, elle se d\u00e9couvre un jour un passe-temps favori : martyriser son \u201cSenpai\u201d, lyc\u00e9en de premi\u00e8re timide et mal dans sa peau. Nagatoro taquine, agace, aguiche, va parfois trop loin... mais qu\u2019a-t-elle vraiment derri\u00e8re la t\u00eate ? Et si derri\u00e8re ses moqueries elle cachait une v\u00e9ritable affection ? Et si finalement, ses farces permettaient \u00e0 Senpai de s\u2019affirmer ?', + 'Year': 2021, + 'Month': 3, + 'Day': 12, + 'Writer': 'Nanashi', + 'Inker': 'Nanashi', + 'Editor': 'Noeve Grafx', + 'Publisher': 'Noeve Grafx', + 'Imprint': 'Noeve Grafx', + 'Genre': 'Shonen', + 'Web': 'http://www.izneo.com/en/manga/shonen/arrete-de-me-chauffer-nagatoro-37560/arrete-de-me-chauffer-nagatoro-86232', + 'LanguageISO': 'fr', + 'Format': 'Preview', + 'BlackAndWhite': True, + 'Manga': 'YesAndRightToLeft', + 'AgeRating': 'Everyone 10+', + 'CommunityRating': '5.0', + 'ean': '9782490676569', + 'Pages': pages + } + + helper = pycbzhelper.Helper(metadata) + helper.save_cbz( + path=os.path.join('eBooks', 'Arrête de me chauffer, Nagatoro'), + file='T1 - Arrête de me chauffer, Nagatoro', + clear=False, + replace=True + )