Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a005ef8127 | ||
|
|
0e3e3840c7 | ||
|
|
4fbb7fd73a | ||
|
|
2edbd2f0c7 | ||
|
|
75ceec8a44 | ||
|
|
af878ca167 | ||
|
|
ea59025499 | ||
|
|
da535bdc67 | ||
|
|
c9a4d94816 | ||
|
|
17ceafb384 | ||
|
|
ac00ee20a3 | ||
|
|
cc974077e3 | ||
|
|
eebe00ee5c | ||
|
|
6bef8e8de2 | ||
|
|
fe4bb165d4 | ||
|
|
8eaff56f6a | ||
|
|
1f1433a3c4 | ||
|
|
ffca3239cf | ||
|
|
a57f1c96a8 | ||
|
|
f5debc6f56 | ||
|
|
40cb3759f2 | ||
|
|
9ff1f4940c | ||
|
|
11aada0be7 |
@@ -1,3 +1,7 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||||
|
|
||||||
|
### Python ###
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
@@ -8,7 +12,6 @@ __pycache__/
|
|||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
@@ -21,9 +24,11 @@ parts/
|
|||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
@@ -38,13 +43,17 @@ pip-delete-this-directory.txt
|
|||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
htmlcov/
|
htmlcov/
|
||||||
.tox/
|
.tox/
|
||||||
|
.nox/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
@@ -53,6 +62,8 @@ coverage.xml
|
|||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
local_settings.py
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
instance/
|
instance/
|
||||||
@@ -65,27 +76,61 @@ instance/
|
|||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# pyenv
|
# IPython
|
||||||
.python-version
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
# celery beat schedule file
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
# SageMath parsed files
|
# SageMath parsed files
|
||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# dotenv
|
# Environments
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# virtualenv
|
|
||||||
.venv
|
.venv
|
||||||
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@@ -99,3 +144,33 @@ ENV/
|
|||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
### Python Patch ###
|
||||||
|
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||||
|
poetry.toml
|
||||||
|
|
||||||
|
# ruff
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# LSP config files
|
||||||
|
pyrightconfig.json
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
|
||||||
|
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [3.2.0] - 2024-03-17
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- New code structure.
|
||||||
|
- Deleting temporary files usage.
|
||||||
|
- Automatic correction of variable types.
|
||||||
|
- Simplifying constants.
|
||||||
|
- Binary input support for pages.
|
||||||
|
- Project `pycbzhelper` renamed to `cbz`.
|
||||||
|
|
||||||
|
## [3.1.2] - 2023-08-13
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed missing `Tags` key, thanks to @RivMt
|
||||||
|
|
||||||
|
## [3.1.1] - 2023-06-04
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update `README.md`.
|
||||||
|
|
||||||
|
## [3.1.0] - 2023-06-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Installation with pip.
|
||||||
|
- Support for non-existent location.
|
||||||
|
- Support for web loading errors of pages.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed page support.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- New name of some variables.
|
||||||
|
- Automatic cleanup of temporary files.
|
||||||
|
- New location for temporary files.
|
||||||
|
- File path now uses `pathlib`.
|
||||||
|
|
||||||
|
## [3.0.1] - 2023-02-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
[3.2.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.2.0
|
||||||
|
[3.1.2]: https://github.com/hyugogirubato/cbz/releases/tag/v3.1.2
|
||||||
|
[3.1.1]: https://github.com/hyugogirubato/cbz/releases/tag/v3.1.1
|
||||||
|
[3.1.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.1.0
|
||||||
|
[3.0.1]: https://github.com/hyugogirubato/cbz/releases/tag/v3.0.1
|
||||||
@@ -1,40 +1,74 @@
|
|||||||
# pydvdfab
|
# CBZ
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
||||||
[](https://github.com/hyugogirubato/pycbzhelper/releases)
|
|
||||||

|
|
||||||
|
|
||||||
Python library to create a cbz file with metadata.
|
[](https://github.com/hyugogirubato/cbz/blob/main/LICENSE)
|
||||||
|
[](https://github.com/hyugogirubato/cbz/releases)
|
||||||
|
[](https://pypi.org/project/cbz/)
|
||||||
|
|
||||||
# Usage
|
CBZ is a Python package designed to facilitate the creation, manipulation, and extraction of comic book archive files in the CBZ format. It allows users to programmatically generate CBZ files from a collection of images, add metadata to the archives, and unpack existing CBZ files. This library is ideal for comic book enthusiasts, archivists, and developers working on applications involving digital comic book distributions.
|
||||||
|
|
||||||
### Basic Usage
|
## Features
|
||||||
|
|
||||||
```python
|
- **Create CBZ Files**: Pack a series of images into a CBZ file with ease.
|
||||||
>>> import os
|
- **Extract CBZ Files**: Unpack images and metadata from existing CBZ files.
|
||||||
>>> from pycbzhelper import Helper
|
- **Manage Metadata**: Add, update, and retrieve metadata from CBZ files, including titles, authors, volume numbers, and more.
|
||||||
>>> metadata = {
|
- **Support for ComicInfo.xml**: Utilizes the ComicInfo.xml standard for embedding comic book metadata within CBZ files.
|
||||||
>>> "Title": "T1 - Arrête de me chauffer, Nagatoro",
|
- **Flexible Image Handling**: Load images from various formats and ensure they are correctly formatted and ordered within the CBZ file.
|
||||||
>>> "Series": "Arrête de me chauffer, Nagatoro",
|
- **High-Level API**: Offers a simple, high-level API for common tasks, while also providing access to lower-level functions for advanced usage.
|
||||||
>>> "Number": "1",
|
|
||||||
>>> "Count": 8,
|
## Installation
|
||||||
>>> "Volume": 1
|
|
||||||
>>> ...
|
To install the CBZ library, you can use pip:
|
||||||
>>> }
|
|
||||||
>>> ...
|
```bash
|
||||||
>>> helper = Helper(metadata)
|
pip install cbz
|
||||||
>>> 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
|
|
||||||
... )
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Installation
|
## Quick Start
|
||||||
|
|
||||||
To install, you can either clone the repository and run `python setup.py install`
|
Here's a quick example of how to create a CBZ file from a series of images:
|
||||||
|
|
||||||
## About
|
```python
|
||||||
- Graphical metadata editing software [here](https://github.com/comictagger/comictagger)
|
from pathlib import Path
|
||||||
- Standard ComicInfo file structure information [here](https://github.com/Kussie/ComicInfoStandard)
|
from cbz.page import PageInfo
|
||||||
- New version of ComicInfo file structure [here](https://github.com/anansi-project/comicinfo)
|
from cbz.comic import ComicInfo
|
||||||
|
from cbz.constants import PageType, YesNo, Manga, AgeRating, Format
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Define the path to your images
|
||||||
|
images_path = Path('path/to/your/images')
|
||||||
|
|
||||||
|
# Load images and create page objects
|
||||||
|
pages = [PageInfo.load(path) for path in images_path.iterdir()]
|
||||||
|
|
||||||
|
# Create a ComicInfo object with your comic's metadata
|
||||||
|
comic = ComicInfo.from_pages(
|
||||||
|
pages=pages,
|
||||||
|
title='Your Comic Title',
|
||||||
|
series='Your Comic Series',
|
||||||
|
number='1',
|
||||||
|
language_iso='en',
|
||||||
|
format=Format.WEB_COMIC,
|
||||||
|
black_white=YesNo.NO,
|
||||||
|
manga=Manga.NO,
|
||||||
|
age_rating=AgeRating.PENDING
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pack the comic into a CBZ file
|
||||||
|
cbz_content = comic.pack()
|
||||||
|
|
||||||
|
# Save the CBZ file
|
||||||
|
cbz_path = Path('your_comic.cbz')
|
||||||
|
cbz_path.write_bytes(cbz_content)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For detailed documentation, including the full API reference and more examples, please refer to the [official CBZ Library documentation](https://en.wikipedia.org/wiki/Comic_book_archive).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The CBZ Library is released under the MIT License. See [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
This library was developed with the needs of comic book fans and digital archivists in mind. We hope it helps you in managing and enjoying your comic book collections.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from .comic import ComicInfo
|
||||||
|
from .constants import *
|
||||||
|
from .page import PageInfo
|
||||||
|
|
||||||
|
__version__ = '3.2.0'
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
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
|
||||||
|
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', '')),
|
||||||
|
'Series': str(kwargs.get('series', '')),
|
||||||
|
'Number': str(kwargs.get('number', '')),
|
||||||
|
'Count': int(kwargs.get('count', -1)),
|
||||||
|
'Volume': int(kwargs.get('volume', -1)),
|
||||||
|
'AlternateSeries': str(kwargs.get('alternate_series', '')),
|
||||||
|
'AlternateNumber': str(kwargs.get('alternate_number', '')),
|
||||||
|
'AlternateCount': int(kwargs.get('alternate_count', -1)),
|
||||||
|
'Summary': str(kwargs.get('summary', '')),
|
||||||
|
'Notes': str(kwargs.get('notes', '')),
|
||||||
|
'Year': int(kwargs.get('year', -1)),
|
||||||
|
'Month': int(kwargs.get('month', -1)),
|
||||||
|
'Day': int(kwargs.get('day', -1)),
|
||||||
|
'Writer': str(kwargs.get('writer', '')),
|
||||||
|
'Penciller': str(kwargs.get('penciller', '')),
|
||||||
|
'Inker': str(kwargs.get('inker', '')),
|
||||||
|
'Colorist': str(kwargs.get('colorist', '')),
|
||||||
|
'Letterer': str(kwargs.get('letterer', '')),
|
||||||
|
'CoverArtist': str(kwargs.get('cover_artist', '')),
|
||||||
|
'Editor': str(kwargs.get('editor', '')),
|
||||||
|
'Translator': str(kwargs.get('translator', '')),
|
||||||
|
'Publisher': str(kwargs.get('publisher', '')),
|
||||||
|
'Imprint': str(kwargs.get('imprint', '')),
|
||||||
|
'Genre': str(kwargs.get('genre', '')),
|
||||||
|
'Tags': str(kwargs.get('tags', '')),
|
||||||
|
'Web': str(kwargs.get('web', '')),
|
||||||
|
'PageCount': len(self.__pages),
|
||||||
|
'LanguageISO': language(kwargs.get('language_iso', '')),
|
||||||
|
'Format': Format(kwargs.get('format', Format.UNKNOWN)),
|
||||||
|
'EAN': str(kwargs.get('ean', '')),
|
||||||
|
'BlackAndWhite': YesNo(kwargs.get('black_white', YesNo.UNKNOWN)),
|
||||||
|
'Manga': Manga(kwargs.get('manga', Manga.UNKNOWN)),
|
||||||
|
'Characters': str(kwargs.get('characters', '')),
|
||||||
|
'Teams': str(kwargs.get('teams', '')),
|
||||||
|
'Locations': str(kwargs.get('locations', '')),
|
||||||
|
'ScanInformation': str(kwargs.get('scan_information', '')),
|
||||||
|
'StoryArc': str(kwargs.get('story_arc', '')),
|
||||||
|
'StoryArcNumber': str(kwargs.get('story_arc_number', '')),
|
||||||
|
'SeriesGroup': str(kwargs.get('series_group', '')),
|
||||||
|
'AgeRating': AgeRating(kwargs.get('age_rating', AgeRating.UNKNOWN)),
|
||||||
|
'Pages': [{'Image': i, **page.dumps()} for i, page in enumerate(pages)],
|
||||||
|
'CommunityRating': rating(kwargs.get('community_rating', -1)),
|
||||||
|
'MainCharacterOrTeam': str(kwargs.get('main_character_or_team', '')),
|
||||||
|
'Review': str(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}')
|
||||||
|
with Path(path).open(mode='rb') as f:
|
||||||
|
return cls(cls.__unpack(f.read()))
|
||||||
|
|
||||||
|
def __unpack(self, data: bytes) -> tuple[[PageInfo], dict]:
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def pack(self) -> bytes:
|
||||||
|
zip_buffer = io.BytesIO()
|
||||||
|
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_STORED) as zip_file:
|
||||||
|
zip_file.writestr(
|
||||||
|
'ComicInfo.xml',
|
||||||
|
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()
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class YesNo(Enum):
|
||||||
|
UNKNOWN = 'Unknown'
|
||||||
|
NO = 'No'
|
||||||
|
YES = 'Yes'
|
||||||
|
|
||||||
|
|
||||||
|
class Manga(Enum):
|
||||||
|
UNKNOWN = 'Unknown'
|
||||||
|
NO = 'No'
|
||||||
|
YES = 'Yes'
|
||||||
|
RIGHT_LEFT = 'YesAndRightToLeft'
|
||||||
|
|
||||||
|
|
||||||
|
class PageType(Enum):
|
||||||
|
FRONT_COVER = 'FrontCover'
|
||||||
|
INNER_COVER = 'InnerCover'
|
||||||
|
ROUNDUP = 'Roundup'
|
||||||
|
STORY = 'Story'
|
||||||
|
ADVERTISEMENT = 'Advertisement'
|
||||||
|
EDITORIAL = 'Editorial'
|
||||||
|
LETTERS = 'Letters'
|
||||||
|
PREVIEW = 'Preview'
|
||||||
|
BACK_COVER = 'BackCover'
|
||||||
|
OTHER = 'Other'
|
||||||
|
DELETED = 'Deleted'
|
||||||
|
|
||||||
|
|
||||||
|
class AgeRating(Enum):
|
||||||
|
UNKNOWN = 'Unknown'
|
||||||
|
ADULTS18 = 'Adults Only 18+'
|
||||||
|
CHILDHOOD = 'Early Childhood'
|
||||||
|
EVERYONE = 'Everyone'
|
||||||
|
EVERYONE10 = 'Everyone 10+'
|
||||||
|
G = 'G'
|
||||||
|
KIDS = 'Kids to Adults'
|
||||||
|
M = 'M'
|
||||||
|
MA15 = 'MA15+'
|
||||||
|
MATURE17 = 'Mature 17+'
|
||||||
|
PG = 'PG'
|
||||||
|
R18 = 'R18+'
|
||||||
|
PENDING = 'Rating Pending'
|
||||||
|
TEEN = 'Teen'
|
||||||
|
X18 = 'X18+'
|
||||||
|
|
||||||
|
|
||||||
|
class Format(Enum):
|
||||||
|
UNKNOWN = 'Unknown'
|
||||||
|
# ONE_SHOT = '1 Shot'
|
||||||
|
# ONE_SHOT = '1/2',
|
||||||
|
# ONE_SHOT = '1-Shot'
|
||||||
|
ANNOTATION = 'Annotation'
|
||||||
|
# ANNOTATIONS = 'Annotations'
|
||||||
|
ANNUAL = 'Annual'
|
||||||
|
ANTHOLOGY = 'Anthology'
|
||||||
|
# BLACK_WHITE = 'B&W'
|
||||||
|
# BLACK_WHITE = 'B/W'
|
||||||
|
# BLACK_WHITE = 'B&&W'
|
||||||
|
BLACK_WHITE = 'Black & White'
|
||||||
|
# BOX_SET = 'Box Set'
|
||||||
|
BOX_SET = 'Box-Set'
|
||||||
|
CROSSOVER = 'Crossover'
|
||||||
|
DIRECTOR_CUT = 'Director\'s Cut'
|
||||||
|
EPILOGUE = 'Epilogue'
|
||||||
|
EVENT = 'Event'
|
||||||
|
FCBD = 'FCBD'
|
||||||
|
FLYER = 'Flyer'
|
||||||
|
# GIANT_SIZE = 'Giant'
|
||||||
|
# GIANT_SIZE = 'Giant Size'
|
||||||
|
GIANT_SIZE = 'Giant-Size'
|
||||||
|
GRAPHIC_NOVEL = 'Graphic Novel'
|
||||||
|
# HARDCOVER = 'Hardcover'
|
||||||
|
HARDCOVER = 'Hard-Cover'
|
||||||
|
# KING_SIZE = 'King'
|
||||||
|
# KING_SIZE = 'King Size'
|
||||||
|
KING_SIZE = 'King-Size'
|
||||||
|
LIMITED_SERIES = 'Limited Series'
|
||||||
|
MAGAZINE = 'Magazine'
|
||||||
|
NSFW = 'NSFW'
|
||||||
|
# ONE_SHOT = 'One Shot'
|
||||||
|
ONE_SHOT = 'One-Shot'
|
||||||
|
POINT1 = 'Point 1'
|
||||||
|
PREVIEW = 'Preview'
|
||||||
|
PROLOGUE = 'Prologue'
|
||||||
|
REFERENCE = 'Reference'
|
||||||
|
REVIEW = 'Review'
|
||||||
|
REVIEWED = 'Reviewed'
|
||||||
|
SCANLATION = 'Scanlation'
|
||||||
|
SCRIPT = 'Script'
|
||||||
|
SERIES = 'Series'
|
||||||
|
SKETCH = 'Sketch'
|
||||||
|
SPECIAL = 'Special'
|
||||||
|
# TRADE_PAPER_BACK = 'TPB'
|
||||||
|
TRADE_PAPER_BACK = 'Trade Paper Back'
|
||||||
|
# WEB_COMIC = 'WebComic'
|
||||||
|
WEB_COMIC = 'Web Comic'
|
||||||
|
# YEAR_ONE = 'Year 1'
|
||||||
|
YEAR_ONE = 'Year One'
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from cbz import utils
|
||||||
|
from cbz.constants import PageType
|
||||||
|
|
||||||
|
|
||||||
|
class PageInfo:
|
||||||
|
|
||||||
|
def __init__(self, content: bytes, **kwargs):
|
||||||
|
image = Image.open(BytesIO(content))
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
def dumps(self) -> dict:
|
||||||
|
return utils.dumps(self.__info)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return json.dumps(self.dumps(), indent=2)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def loads(cls, data: Union[str, bytes], **kwargs) -> PageInfo:
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = base64.b64decode(data)
|
||||||
|
if not isinstance(data, bytes):
|
||||||
|
raise ValueError(f'Expecting Bytes or Base64 input, got {data!r}')
|
||||||
|
return cls(data, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, path: Union[Path, str], **kwargs) -> PageInfo:
|
||||||
|
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:
|
||||||
|
return cls(f.read(), **kwargs)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
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'}
|
||||||
@@ -1,75 +1,75 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:element name="ComicInfo" nillable="true" type="ComicInfo" />
|
<xs:element name="ComicInfo" nillable="true" type="ComicInfo"/>
|
||||||
<xs:complexType name="ComicInfo">
|
<xs:complexType name="ComicInfo">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Title" type="xs:string" />
|
<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="Series" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Number" 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="Count" type="xs:int"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Volume" 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="AlternateSeries" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateNumber" 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="-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="Summary" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Notes" 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="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="Month" type="xs:int"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Writer" type="xs:string" />
|
<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="Penciller" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Inker" 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="Colorist" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Letterer" 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="CoverArtist" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Editor" 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="Publisher" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Imprint" 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="Genre" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Web" 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="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="LanguageISO" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Format" 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="BlackAndWhite" type="YesNo"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="Manga" 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:element minOccurs="0" maxOccurs="1" name="Pages" type="ArrayOfComicPageInfo"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:simpleType name="YesNo">
|
<xs:simpleType name="YesNo">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="No" />
|
<xs:enumeration value="No"/>
|
||||||
<xs:enumeration value="Yes" />
|
<xs:enumeration value="Yes"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:complexType name="ArrayOfComicPageInfo">
|
<xs:complexType name="ArrayOfComicPageInfo">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo" />
|
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:complexType name="ComicPageInfo">
|
<xs:complexType name="ComicPageInfo">
|
||||||
<xs:attribute name="Image" type="xs:int" use="required" />
|
<xs:attribute name="Image" type="xs:int" use="required"/>
|
||||||
<xs:attribute default="Story" name="Type" type="ComicPageType" />
|
<xs:attribute default="Story" name="Type" type="ComicPageType"/>
|
||||||
<xs:attribute default="false" name="DoublePage" type="xs:boolean" />
|
<xs:attribute default="false" name="DoublePage" type="xs:boolean"/>
|
||||||
<xs:attribute default="0" name="ImageSize" type="xs:long" />
|
<xs:attribute default="0" name="ImageSize" type="xs:long"/>
|
||||||
<xs:attribute default="" name="Key" type="xs:string" />
|
<xs:attribute default="" name="Key" type="xs:string"/>
|
||||||
<xs:attribute default="-1" name="ImageWidth" type="xs:int" />
|
<xs:attribute default="-1" name="ImageWidth" type="xs:int"/>
|
||||||
<xs:attribute default="-1" name="ImageHeight" type="xs:int" />
|
<xs:attribute default="-1" name="ImageHeight" type="xs:int"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:simpleType name="ComicPageType">
|
<xs:simpleType name="ComicPageType">
|
||||||
<xs:list>
|
<xs:list>
|
||||||
<xs:simpleType>
|
<xs:simpleType>
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="FrontCover" />
|
<xs:enumeration value="FrontCover"/>
|
||||||
<xs:enumeration value="InnerCover" />
|
<xs:enumeration value="InnerCover"/>
|
||||||
<xs:enumeration value="Roundup" />
|
<xs:enumeration value="Roundup"/>
|
||||||
<xs:enumeration value="Story" />
|
<xs:enumeration value="Story"/>
|
||||||
<xs:enumeration value="Advertisement" />
|
<xs:enumeration value="Advertisement"/>
|
||||||
<xs:enumeration value="Editorial" />
|
<xs:enumeration value="Editorial"/>
|
||||||
<xs:enumeration value="Letters" />
|
<xs:enumeration value="Letters"/>
|
||||||
<xs:enumeration value="Preview" />
|
<xs:enumeration value="Preview"/>
|
||||||
<xs:enumeration value="BackCover" />
|
<xs:enumeration value="BackCover"/>
|
||||||
<xs:enumeration value="Other" />
|
<xs:enumeration value="Other"/>
|
||||||
<xs:enumeration value="Deleted" />
|
<xs:enumeration value="Deleted"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
</xs:list>
|
</xs:list>
|
||||||
@@ -1,63 +1,63 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:element name="ComicInfo" nillable="true" type="ComicInfo" />
|
<xs:element name="ComicInfo" nillable="true" type="ComicInfo"/>
|
||||||
<xs:complexType name="ComicInfo">
|
<xs:complexType name="ComicInfo">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Title" type="xs:string" />
|
<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="Series" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Number" 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="Count" type="xs:int"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Volume" 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="AlternateSeries" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateNumber" 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="-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="Summary" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Notes" 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="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="Month" type="xs:int"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Day" 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="Writer" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Penciller" 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="Inker" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Colorist" 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="Letterer" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="CoverArtist" 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="Editor" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Publisher" 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="Imprint" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Genre" 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="" 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="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="LanguageISO" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Format" 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="BlackAndWhite" type="YesNo"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="Manga" type="Manga" />
|
<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="Characters" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Teams" 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="Locations" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="ScanInformation" 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="StoryArc" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="SeriesGroup" 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" default="Unknown" name="AgeRating" type="AgeRating"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" name="Pages" type="ArrayOfComicPageInfo" />
|
<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" name="CommunityRating" type="Rating"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="MainCharacterOrTeam" type="xs:string" />
|
<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:element minOccurs="0" maxOccurs="1" default="" name="Review" type="xs:string"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:simpleType name="YesNo">
|
<xs:simpleType name="YesNo">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="No" />
|
<xs:enumeration value="No"/>
|
||||||
<xs:enumeration value="Yes" />
|
<xs:enumeration value="Yes"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:simpleType name="Manga">
|
<xs:simpleType name="Manga">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="No" />
|
<xs:enumeration value="No"/>
|
||||||
<xs:enumeration value="Yes" />
|
<xs:enumeration value="Yes"/>
|
||||||
<xs:enumeration value="YesAndRightToLeft" />
|
<xs:enumeration value="YesAndRightToLeft"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:simpleType name="Rating">
|
<xs:simpleType name="Rating">
|
||||||
@@ -69,53 +69,53 @@
|
|||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:simpleType name="AgeRating">
|
<xs:simpleType name="AgeRating">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="Adults Only 18+" />
|
<xs:enumeration value="Adults Only 18+"/>
|
||||||
<xs:enumeration value="Early Childhood" />
|
<xs:enumeration value="Early Childhood"/>
|
||||||
<xs:enumeration value="Everyone" />
|
<xs:enumeration value="Everyone"/>
|
||||||
<xs:enumeration value="Everyone 10+" />
|
<xs:enumeration value="Everyone 10+"/>
|
||||||
<xs:enumeration value="G" />
|
<xs:enumeration value="G"/>
|
||||||
<xs:enumeration value="Kids to Adults" />
|
<xs:enumeration value="Kids to Adults"/>
|
||||||
<xs:enumeration value="M" />
|
<xs:enumeration value="M"/>
|
||||||
<xs:enumeration value="MA15+" />
|
<xs:enumeration value="MA15+"/>
|
||||||
<xs:enumeration value="Mature 17+" />
|
<xs:enumeration value="Mature 17+"/>
|
||||||
<xs:enumeration value="PG" />
|
<xs:enumeration value="PG"/>
|
||||||
<xs:enumeration value="R18+" />
|
<xs:enumeration value="R18+"/>
|
||||||
<xs:enumeration value="Rating Pending" />
|
<xs:enumeration value="Rating Pending"/>
|
||||||
<xs:enumeration value="Teen" />
|
<xs:enumeration value="Teen"/>
|
||||||
<xs:enumeration value="X18+" />
|
<xs:enumeration value="X18+"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:complexType name="ArrayOfComicPageInfo">
|
<xs:complexType name="ArrayOfComicPageInfo">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo" />
|
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:complexType name="ComicPageInfo">
|
<xs:complexType name="ComicPageInfo">
|
||||||
<xs:attribute name="Image" type="xs:int" use="required" />
|
<xs:attribute name="Image" type="xs:int" use="required"/>
|
||||||
<xs:attribute default="Story" name="Type" type="ComicPageType" />
|
<xs:attribute default="Story" name="Type" type="ComicPageType"/>
|
||||||
<xs:attribute default="false" name="DoublePage" type="xs:boolean" />
|
<xs:attribute default="false" name="DoublePage" type="xs:boolean"/>
|
||||||
<xs:attribute default="0" name="ImageSize" type="xs:long" />
|
<xs:attribute default="0" name="ImageSize" type="xs:long"/>
|
||||||
<xs:attribute default="" name="Key" type="xs:string" />
|
<xs:attribute default="" name="Key" type="xs:string"/>
|
||||||
<xs:attribute default="" name="Bookmark" 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="ImageWidth" type="xs:int"/>
|
||||||
<xs:attribute default="-1" name="ImageHeight" type="xs:int" />
|
<xs:attribute default="-1" name="ImageHeight" type="xs:int"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:simpleType name="ComicPageType">
|
<xs:simpleType name="ComicPageType">
|
||||||
<xs:list>
|
<xs:list>
|
||||||
<xs:simpleType>
|
<xs:simpleType>
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="FrontCover" />
|
<xs:enumeration value="FrontCover"/>
|
||||||
<xs:enumeration value="InnerCover" />
|
<xs:enumeration value="InnerCover"/>
|
||||||
<xs:enumeration value="Roundup" />
|
<xs:enumeration value="Roundup"/>
|
||||||
<xs:enumeration value="Story" />
|
<xs:enumeration value="Story"/>
|
||||||
<xs:enumeration value="Advertisement" />
|
<xs:enumeration value="Advertisement"/>
|
||||||
<xs:enumeration value="Editorial" />
|
<xs:enumeration value="Editorial"/>
|
||||||
<xs:enumeration value="Letters" />
|
<xs:enumeration value="Letters"/>
|
||||||
<xs:enumeration value="Preview" />
|
<xs:enumeration value="Preview"/>
|
||||||
<xs:enumeration value="BackCover" />
|
<xs:enumeration value="BackCover"/>
|
||||||
<xs:enumeration value="Other" />
|
<xs:enumeration value="Other"/>
|
||||||
<xs:enumeration value="Deleted" />
|
<xs:enumeration value="Deleted"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
</xs:list>
|
</xs:list>
|
||||||
@@ -1,66 +1,66 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:element name="ComicInfo" nillable="true" type="ComicInfo" />
|
<xs:element name="ComicInfo" nillable="true" type="ComicInfo"/>
|
||||||
<xs:complexType name="ComicInfo">
|
<xs:complexType name="ComicInfo">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Title" type="xs:string" />
|
<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="Series" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Number" 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="Count" type="xs:int"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Volume" 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="AlternateSeries" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="AlternateNumber" 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="-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="Summary" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Notes" 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="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="Month" type="xs:int"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="-1" name="Day" 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="Writer" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Penciller" 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="Inker" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Colorist" 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="Letterer" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="CoverArtist" 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="Editor" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Translator" 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="Publisher" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Imprint" 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="Genre" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Tags" 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="" 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="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="LanguageISO" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Format" 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="BlackAndWhite" type="YesNo"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="Manga" type="Manga" />
|
<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="Characters" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Teams" 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="Locations" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="ScanInformation" 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="StoryArc" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="StoryArcNumber" 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="" name="SeriesGroup" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="Unknown" name="AgeRating" type="AgeRating" />
|
<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="Pages" type="ArrayOfComicPageInfo"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" name="CommunityRating" type="Rating" />
|
<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="MainCharacterOrTeam" type="xs:string"/>
|
||||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Review" type="xs:string" />
|
<xs:element minOccurs="0" maxOccurs="1" default="" name="Review" type="xs:string"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:simpleType name="YesNo">
|
<xs:simpleType name="YesNo">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="No" />
|
<xs:enumeration value="No"/>
|
||||||
<xs:enumeration value="Yes" />
|
<xs:enumeration value="Yes"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:simpleType name="Manga">
|
<xs:simpleType name="Manga">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="No" />
|
<xs:enumeration value="No"/>
|
||||||
<xs:enumeration value="Yes" />
|
<xs:enumeration value="Yes"/>
|
||||||
<xs:enumeration value="YesAndRightToLeft" />
|
<xs:enumeration value="YesAndRightToLeft"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:simpleType name="Rating">
|
<xs:simpleType name="Rating">
|
||||||
@@ -72,53 +72,53 @@
|
|||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:simpleType name="AgeRating">
|
<xs:simpleType name="AgeRating">
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="Unknown" />
|
<xs:enumeration value="Unknown"/>
|
||||||
<xs:enumeration value="Adults Only 18+" />
|
<xs:enumeration value="Adults Only 18+"/>
|
||||||
<xs:enumeration value="Early Childhood" />
|
<xs:enumeration value="Early Childhood"/>
|
||||||
<xs:enumeration value="Everyone" />
|
<xs:enumeration value="Everyone"/>
|
||||||
<xs:enumeration value="Everyone 10+" />
|
<xs:enumeration value="Everyone 10+"/>
|
||||||
<xs:enumeration value="G" />
|
<xs:enumeration value="G"/>
|
||||||
<xs:enumeration value="Kids to Adults" />
|
<xs:enumeration value="Kids to Adults"/>
|
||||||
<xs:enumeration value="M" />
|
<xs:enumeration value="M"/>
|
||||||
<xs:enumeration value="MA15+" />
|
<xs:enumeration value="MA15+"/>
|
||||||
<xs:enumeration value="Mature 17+" />
|
<xs:enumeration value="Mature 17+"/>
|
||||||
<xs:enumeration value="PG" />
|
<xs:enumeration value="PG"/>
|
||||||
<xs:enumeration value="R18+" />
|
<xs:enumeration value="R18+"/>
|
||||||
<xs:enumeration value="Rating Pending" />
|
<xs:enumeration value="Rating Pending"/>
|
||||||
<xs:enumeration value="Teen" />
|
<xs:enumeration value="Teen"/>
|
||||||
<xs:enumeration value="X18+" />
|
<xs:enumeration value="X18+"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
<xs:complexType name="ArrayOfComicPageInfo">
|
<xs:complexType name="ArrayOfComicPageInfo">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo" />
|
<xs:element minOccurs="0" maxOccurs="unbounded" name="Page" nillable="true" type="ComicPageInfo"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:complexType name="ComicPageInfo">
|
<xs:complexType name="ComicPageInfo">
|
||||||
<xs:attribute name="Image" type="xs:int" use="required" />
|
<xs:attribute name="Image" type="xs:int" use="required"/>
|
||||||
<xs:attribute default="Story" name="Type" type="ComicPageType" />
|
<xs:attribute default="Story" name="Type" type="ComicPageType"/>
|
||||||
<xs:attribute default="false" name="DoublePage" type="xs:boolean" />
|
<xs:attribute default="false" name="DoublePage" type="xs:boolean"/>
|
||||||
<xs:attribute default="0" name="ImageSize" type="xs:long" />
|
<xs:attribute default="0" name="ImageSize" type="xs:long"/>
|
||||||
<xs:attribute default="" name="Key" type="xs:string" />
|
<xs:attribute default="" name="Key" type="xs:string"/>
|
||||||
<xs:attribute default="" name="Bookmark" 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="ImageWidth" type="xs:int"/>
|
||||||
<xs:attribute default="-1" name="ImageHeight" type="xs:int" />
|
<xs:attribute default="-1" name="ImageHeight" type="xs:int"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
<xs:simpleType name="ComicPageType">
|
<xs:simpleType name="ComicPageType">
|
||||||
<xs:list>
|
<xs:list>
|
||||||
<xs:simpleType>
|
<xs:simpleType>
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:string">
|
||||||
<xs:enumeration value="FrontCover" />
|
<xs:enumeration value="FrontCover"/>
|
||||||
<xs:enumeration value="InnerCover" />
|
<xs:enumeration value="InnerCover"/>
|
||||||
<xs:enumeration value="Roundup" />
|
<xs:enumeration value="Roundup"/>
|
||||||
<xs:enumeration value="Story" />
|
<xs:enumeration value="Story"/>
|
||||||
<xs:enumeration value="Advertisement" />
|
<xs:enumeration value="Advertisement"/>
|
||||||
<xs:enumeration value="Editorial" />
|
<xs:enumeration value="Editorial"/>
|
||||||
<xs:enumeration value="Letters" />
|
<xs:enumeration value="Letters"/>
|
||||||
<xs:enumeration value="Preview" />
|
<xs:enumeration value="Preview"/>
|
||||||
<xs:enumeration value="BackCover" />
|
<xs:enumeration value="BackCover"/>
|
||||||
<xs:enumeration value="Other" />
|
<xs:enumeration value="Other"/>
|
||||||
<xs:enumeration value="Deleted" />
|
<xs:enumeration value="Deleted"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
</xs:list>
|
</xs:list>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from cbz.comic import ComicInfo
|
||||||
|
from cbz.constants import PageType, YesNo, Manga, AgeRating, Format
|
||||||
|
from cbz.page import PageInfo
|
||||||
|
|
||||||
|
PARENT = Path()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# @Info: Load comic pages
|
||||||
|
paths = list((PARENT / 'images').iterdir())
|
||||||
|
pages = [PageInfo.load(
|
||||||
|
path=path,
|
||||||
|
type=PageType.FRONT_COVER if i == 0 else PageType.BACK_COVER if i == len(paths) - 1 else PageType.STORY
|
||||||
|
) for i, path in enumerate(paths)]
|
||||||
|
|
||||||
|
# @Info: Load comic metadata
|
||||||
|
comic = ComicInfo.from_pages(
|
||||||
|
pages=pages,
|
||||||
|
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',
|
||||||
|
language_iso='fr',
|
||||||
|
format=Format.PREVIEW,
|
||||||
|
black_white=YesNo.YES,
|
||||||
|
manga=Manga.RIGHT_LEFT,
|
||||||
|
age_rating=AgeRating.EVERYONE10,
|
||||||
|
community_rating=5,
|
||||||
|
ean='9782490676569'
|
||||||
|
)
|
||||||
|
|
||||||
|
# @Info: Pack cbz
|
||||||
|
cbz_content = comic.pack()
|
||||||
|
|
||||||
|
# @Info: Write cbz
|
||||||
|
cbz_path = PARENT / 'Arrête de me chauffer, Nagatoro.cbz'
|
||||||
|
cbz_path.write_bytes(cbz_content)
|
||||||
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 619 KiB After Width: | Height: | Size: 619 KiB |
|
Before Width: | Height: | Size: 592 KiB After Width: | Height: | Size: 592 KiB |
|
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 680 KiB |
|
Before Width: | Height: | Size: 590 KiB After Width: | Height: | Size: 590 KiB |
|
Before Width: | Height: | Size: 492 KiB After Width: | Height: | Size: 492 KiB |
|
Before Width: | Height: | Size: 479 KiB After Width: | Height: | Size: 479 KiB |
|
Before Width: | Height: | Size: 549 KiB After Width: | Height: | Size: 549 KiB |
@@ -1,4 +0,0 @@
|
|||||||
from .helper import *
|
|
||||||
from .comicinfo import *
|
|
||||||
|
|
||||||
__version__ = "3.0.1"
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
"""
|
|
||||||
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'
|
|
||||||
]
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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."""
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
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}")
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,30 +1,42 @@
|
|||||||
"""Setup module"""
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
with open('README.md', 'r') as fh:
|
|
||||||
LONG_DESCRIPTION = fh.read()
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='pycbzhelper',
|
name='cbz',
|
||||||
version='3.0.1',
|
version='3.2.0',
|
||||||
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='hyugogirubato',
|
||||||
author_email='hyugogirubato@gmail.com',
|
author_email='hyugogirubato@gmail.com',
|
||||||
license='GNU GPLv3',
|
description='CBZ is a Python package designed to facilitate the creation, manipulation, and extraction of comic book archive files in the CBZ format.',
|
||||||
packages=['pycbzhelper'],
|
long_description=open('README.md').read(),
|
||||||
install_requires=['json2xml', 'langcodes', 'pillow'],
|
long_description_content_type='text/markdown',
|
||||||
|
url='https://github.com/hyugogirubato/cbz',
|
||||||
|
packages=find_packages(),
|
||||||
|
license='GPL-3.0-only',
|
||||||
|
keywords=[
|
||||||
|
'metadata',
|
||||||
|
'manga',
|
||||||
|
'comics',
|
||||||
|
'ebooks',
|
||||||
|
'cbz',
|
||||||
|
'webtoons'
|
||||||
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Environment :: Console',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
'Intended Audience :: Developers',
|
||||||
|
'Intended Audience :: End Users/Desktop',
|
||||||
|
'Natural Language :: English',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.5',
|
|
||||||
'Programming Language :: Python :: 3.6',
|
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
'Topic :: Utilities'
|
'Topic :: Utilities'
|
||||||
]
|
],
|
||||||
|
install_requires=[
|
||||||
|
'requests',
|
||||||
|
'xmltodict',
|
||||||
|
'langcodes',
|
||||||
|
'Pillow'
|
||||||
|
],
|
||||||
|
python_requires='>=3.7'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||