Init
@@ -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/
|
||||
@@ -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
|
||||
@@ -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
|
||||
[](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)
|
||||
@@ -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("<?xml version='1.0' encoding='utf-8'?>")
|
||||
comic_info.append('<ComicInfo>')
|
||||
|
||||
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]}</{key}>')
|
||||
elif key in comic_config['Manga'] and metadata[key] in ['No', 'Yes', 'YesAndRightToLeft']:
|
||||
comic_info.append(f' <{key}>{metadata[key]}</{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]}</{key}>')
|
||||
elif key in comic_config['str']:
|
||||
comic_info.append(f' <{key}>{metadata[key]}</{key}>')
|
||||
elif key in comic_config['int']:
|
||||
comic_info.append(f' <{key}>{metadata[key]}</{key}>')
|
||||
elif key in comic_config['float']:
|
||||
comic_info.append(f' <{key}>{metadata[key]}</{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]}</{key}>')
|
||||
elif key in comic_config['ArrayOfComicPageInfo']:
|
||||
comic_info.append(' <Pages>')
|
||||
for item in metadata[key]:
|
||||
item_keys =item.keys()
|
||||
line = []
|
||||
|
||||
line.append(' <Page')
|
||||
|
||||
# default
|
||||
if not 'Type' in item_keys:
|
||||
metadata[key]['Type'] = 'Story'
|
||||
if not 'DoublePage' in item_keys:
|
||||
metadata[key]['DoublePage'] = False
|
||||
|
||||
for item_key in item_keys:
|
||||
if item_key in page_config['int']:
|
||||
line.append(f'{item_key}="{metadata[key][item_key]}"')
|
||||
elif item_key in page_config['bool']:
|
||||
value = 'true' if metadata[key][item_key] else 'false'
|
||||
line.append(f'{item_key}="{value}"')
|
||||
elif item_key in page_config['long']:
|
||||
line.append(f'{item_key}="{metadata[key][item_key]}"')
|
||||
elif item_key in page_config['str']:
|
||||
line.append(f'{item_key}="{metadata[key][item_key]}"')
|
||||
elif item_key in page_config['ComicPageType'] and metadata[key][item_key] in ['FrontCover', 'InnerCover', 'Roundup', 'Story',
|
||||
'Advertisment', 'Editorial', 'Letters', 'Preview',
|
||||
'BackCover', 'Other', 'Deleted']:
|
||||
line.append(f'{item_key}="{metadata[key][item_key]}"')
|
||||
line.append('/>')
|
||||
comic_info.append(' '.join(line))
|
||||
|
||||
comic_info.append(' </Pages>')
|
||||
comic_info.append('</ComicInfo>')
|
||||
|
||||
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)
|
||||
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 619 KiB |
|
After Width: | Height: | Size: 592 KiB |
|
After Width: | Height: | Size: 680 KiB |
|
After Width: | Height: | Size: 590 KiB |
|
After Width: | Height: | Size: 492 KiB |
|
After Width: | Height: | Size: 479 KiB |
|
After Width: | Height: | Size: 549 KiB |
@@ -0,0 +1,4 @@
|
||||
from .helper import *
|
||||
from .comicinfo import *
|
||||
|
||||
__version__ = "3.0.1"
|
||||
@@ -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'
|
||||
]
|
||||
@@ -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."""
|
||||
@@ -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(" <Pages>")
|
||||
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 = ' <Page DoublePage="True"' if get_key_value(
|
||||
page.get('DoublePage', False)) == 'Yes' else ' <Page'
|
||||
pages.append(
|
||||
item + ' Image="{image}" ImageHeight="{height}" ImageSize="{size}" ImageWidth="{width}" Type="{type}" />'.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(" </Pages>")
|
||||
del kwargs['Pages']
|
||||
pages.append("</ComicInfo>")
|
||||
|
||||
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(['<?xml version="1.0" ?>', '<ComicInfo>', '</ComicInfo>'])
|
||||
print('WARNING: No metadata to create.')
|
||||
xml_data = xml_data.replace('<?xml version="1.0" ?>', '<?xml version="1.0" encoding="utf-8"?>')
|
||||
xml_data = xml_data.replace('</ComicInfo>', '\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}")
|
||||
@@ -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
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="ComicInfo" nillable="true" type="ComicInfo" />
|
||||
<xs:complexType name="ComicInfo">
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Title" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Series" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Number" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Count" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Volume" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateSeries" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateNumber" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="AlternateCount" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Summary" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Notes" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Year" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Month" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Writer" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Penciller" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Inker" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Colorist" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Letterer" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="CoverArtist" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Editor" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Publisher" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Imprint" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Genre" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Web" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="0" name="PageCount" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="LanguageISO" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Format" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="BlackAndWhite" type="YesNo" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="Manga" type="YesNo" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Pages" type="ArrayOfComicPageInfo" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="YesNo">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="No" />
|
||||
<xs:enumeration value="Yes" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="ArrayOfComicPageInfo">
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComicPageInfo">
|
||||
<xs:attribute name="Image" type="xs:int" use="required" />
|
||||
<xs:attribute default="Story" name="Type" type="ComicPageType" />
|
||||
<xs:attribute default="false" name="DoublePage" type="xs:boolean" />
|
||||
<xs:attribute default="0" name="ImageSize" type="xs:long" />
|
||||
<xs:attribute default="" name="Key" type="xs:string" />
|
||||
<xs:attribute default="-1" name="ImageWidth" type="xs:int" />
|
||||
<xs:attribute default="-1" name="ImageHeight" type="xs:int" />
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="ComicPageType">
|
||||
<xs:list>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="FrontCover" />
|
||||
<xs:enumeration value="InnerCover" />
|
||||
<xs:enumeration value="Roundup" />
|
||||
<xs:enumeration value="Story" />
|
||||
<xs:enumeration value="Advertisement" />
|
||||
<xs:enumeration value="Editorial" />
|
||||
<xs:enumeration value="Letters" />
|
||||
<xs:enumeration value="Preview" />
|
||||
<xs:enumeration value="BackCover" />
|
||||
<xs:enumeration value="Other" />
|
||||
<xs:enumeration value="Deleted" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:list>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="ComicInfo" nillable="true" type="ComicInfo" />
|
||||
<xs:complexType name="ComicInfo">
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Title" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Series" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Number" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Count" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Volume" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateSeries" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateNumber" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="AlternateCount" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Summary" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Notes" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Year" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Month" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Day" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Writer" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Penciller" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Inker" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Colorist" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Letterer" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="CoverArtist" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Editor" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Publisher" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Imprint" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Genre" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Web" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="0" name="PageCount" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="LanguageISO" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Format" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="BlackAndWhite" type="YesNo" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="Manga" type="Manga" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Characters" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Teams" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Locations" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="ScanInformation" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="StoryArc" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="SeriesGroup" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="AgeRating" type="AgeRating" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Pages" type="ArrayOfComicPageInfo" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="CommunityRating" type="Rating" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="MainCharacterOrTeam" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Review" type="xs:string" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="YesNo">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="No" />
|
||||
<xs:enumeration value="Yes" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Manga">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="No" />
|
||||
<xs:enumeration value="Yes" />
|
||||
<xs:enumeration value="YesAndRightToLeft" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Rating">
|
||||
<xs:restriction base="xs:decimal">
|
||||
<xs:minInclusive value="0"/>
|
||||
<xs:maxInclusive value="5"/>
|
||||
<xs:fractionDigits value="2"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="AgeRating">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="Adults Only 18+" />
|
||||
<xs:enumeration value="Early Childhood" />
|
||||
<xs:enumeration value="Everyone" />
|
||||
<xs:enumeration value="Everyone 10+" />
|
||||
<xs:enumeration value="G" />
|
||||
<xs:enumeration value="Kids to Adults" />
|
||||
<xs:enumeration value="M" />
|
||||
<xs:enumeration value="MA15+" />
|
||||
<xs:enumeration value="Mature 17+" />
|
||||
<xs:enumeration value="PG" />
|
||||
<xs:enumeration value="R18+" />
|
||||
<xs:enumeration value="Rating Pending" />
|
||||
<xs:enumeration value="Teen" />
|
||||
<xs:enumeration value="X18+" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="ArrayOfComicPageInfo">
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComicPageInfo">
|
||||
<xs:attribute name="Image" type="xs:int" use="required" />
|
||||
<xs:attribute default="Story" name="Type" type="ComicPageType" />
|
||||
<xs:attribute default="false" name="DoublePage" type="xs:boolean" />
|
||||
<xs:attribute default="0" name="ImageSize" type="xs:long" />
|
||||
<xs:attribute default="" name="Key" type="xs:string" />
|
||||
<xs:attribute default="" name="Bookmark" type="xs:string" />
|
||||
<xs:attribute default="-1" name="ImageWidth" type="xs:int" />
|
||||
<xs:attribute default="-1" name="ImageHeight" type="xs:int" />
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="ComicPageType">
|
||||
<xs:list>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="FrontCover" />
|
||||
<xs:enumeration value="InnerCover" />
|
||||
<xs:enumeration value="Roundup" />
|
||||
<xs:enumeration value="Story" />
|
||||
<xs:enumeration value="Advertisement" />
|
||||
<xs:enumeration value="Editorial" />
|
||||
<xs:enumeration value="Letters" />
|
||||
<xs:enumeration value="Preview" />
|
||||
<xs:enumeration value="BackCover" />
|
||||
<xs:enumeration value="Other" />
|
||||
<xs:enumeration value="Deleted" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:list>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="ComicInfo" nillable="true" type="ComicInfo" />
|
||||
<xs:complexType name="ComicInfo">
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Title" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Series" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Number" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Count" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Volume" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateSeries" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateNumber" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="AlternateCount" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Summary" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Notes" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Year" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Month" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Day" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Writer" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Penciller" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Inker" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Colorist" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Letterer" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="CoverArtist" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Editor" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Translator" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Publisher" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Imprint" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Genre" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Tags" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Web" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="0" name="PageCount" type="xs:int" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="LanguageISO" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Format" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="BlackAndWhite" type="YesNo" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="Manga" type="Manga" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Characters" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Teams" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Locations" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="ScanInformation" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="StoryArc" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="StoryArcNumber" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="SeriesGroup" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="AgeRating" type="AgeRating" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Pages" type="ArrayOfComicPageInfo" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="CommunityRating" type="Rating" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="MainCharacterOrTeam" type="xs:string" />
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Review" type="xs:string" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="YesNo">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="No" />
|
||||
<xs:enumeration value="Yes" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Manga">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="No" />
|
||||
<xs:enumeration value="Yes" />
|
||||
<xs:enumeration value="YesAndRightToLeft" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="Rating">
|
||||
<xs:restriction base="xs:decimal">
|
||||
<xs:minInclusive value="0"/>
|
||||
<xs:maxInclusive value="5"/>
|
||||
<xs:fractionDigits value="1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="AgeRating">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Unknown" />
|
||||
<xs:enumeration value="Adults Only 18+" />
|
||||
<xs:enumeration value="Early Childhood" />
|
||||
<xs:enumeration value="Everyone" />
|
||||
<xs:enumeration value="Everyone 10+" />
|
||||
<xs:enumeration value="G" />
|
||||
<xs:enumeration value="Kids to Adults" />
|
||||
<xs:enumeration value="M" />
|
||||
<xs:enumeration value="MA15+" />
|
||||
<xs:enumeration value="Mature 17+" />
|
||||
<xs:enumeration value="PG" />
|
||||
<xs:enumeration value="R18+" />
|
||||
<xs:enumeration value="Rating Pending" />
|
||||
<xs:enumeration value="Teen" />
|
||||
<xs:enumeration value="X18+" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="ArrayOfComicPageInfo">
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="ComicPageInfo">
|
||||
<xs:attribute name="Image" type="xs:int" use="required" />
|
||||
<xs:attribute default="Story" name="Type" type="ComicPageType" />
|
||||
<xs:attribute default="false" name="DoublePage" type="xs:boolean" />
|
||||
<xs:attribute default="0" name="ImageSize" type="xs:long" />
|
||||
<xs:attribute default="" name="Key" type="xs:string" />
|
||||
<xs:attribute default="" name="Bookmark" type="xs:string" />
|
||||
<xs:attribute default="-1" name="ImageWidth" type="xs:int" />
|
||||
<xs:attribute default="-1" name="ImageHeight" type="xs:int" />
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="ComicPageType">
|
||||
<xs:list>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="FrontCover" />
|
||||
<xs:enumeration value="InnerCover" />
|
||||
<xs:enumeration value="Roundup" />
|
||||
<xs:enumeration value="Story" />
|
||||
<xs:enumeration value="Advertisement" />
|
||||
<xs:enumeration value="Editorial" />
|
||||
<xs:enumeration value="Letters" />
|
||||
<xs:enumeration value="Preview" />
|
||||
<xs:enumeration value="BackCover" />
|
||||
<xs:enumeration value="Other" />
|
||||
<xs:enumeration value="Deleted" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:list>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -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'
|
||||
]
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||