Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f1433a3c4 | ||
|
|
ffca3239cf | ||
|
|
a57f1c96a8 | ||
|
|
f5debc6f56 | ||
|
|
40cb3759f2 | ||
|
|
9ff1f4940c | ||
|
|
11aada0be7 |
@@ -1,101 +0,0 @@
|
|||||||
# 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/
|
|
||||||
@@ -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,34 @@
|
|||||||
|
# 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.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.1.0]: https://github.com/hyugogirubato/pycbzhelper/releases/tag/v3.1.0
|
||||||
|
[3.0.1]: https://github.com/hyugogirubato/pycbzhelper/releases/tag/v3.0.1
|
||||||
@@ -1,40 +1,64 @@
|
|||||||
# pydvdfab
|
# PyCBZHelper
|
||||||
[](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/pycbzhelper/blob/master/LICENSE)
|
||||||
|
[](https://github.com/hyugogirubato/pycbzhelper/releases)
|
||||||
|
[](https://pypi.org/project/pycbzhelper/)
|
||||||
|
|
||||||
# Usage
|
PyCBZHelper is a Python library for creating CBZ (Comic Book Zip) files with metadata. It provides functionality to
|
||||||
|
generate a CBZ file from a list of image pages and associated comic book metadata.
|
||||||
|
|
||||||
### Basic Usage
|
## Features
|
||||||
|
|
||||||
```python
|
- Create CBZ files from images.
|
||||||
>>> import os
|
- Generate ComicInfo.xml metadata.
|
||||||
>>> from pycbzhelper import Helper
|
- Support for various metadata fields.
|
||||||
>>> metadata = {
|
- Handle page files from local disk or web URLs.
|
||||||
>>> "Title": "T1 - Arrête de me chauffer, Nagatoro",
|
- Automatic cleanup of temporary files.
|
||||||
>>> "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
|
|
||||||
... )
|
|
||||||
```
|
|
||||||
|
|
||||||
# Installation
|
## Installation
|
||||||
|
|
||||||
To install, you can either clone the repository and run `python setup.py install`
|
You can install PyCBZHelper using pip:
|
||||||
|
|
||||||
## About
|
````shell
|
||||||
- Graphical metadata editing software [here](https://github.com/comictagger/comictagger)
|
pip install pycbzhelper
|
||||||
- Standard ComicInfo file structure information [here](https://github.com/Kussie/ComicInfoStandard)
|
````
|
||||||
- New version of ComicInfo file structure [here](https://github.com/anansi-project/comicinfo)
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Here's a basic example of how to use PyCBZHelper:
|
||||||
|
|
||||||
|
````python
|
||||||
|
from pycbzhelper import Helper
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PARENT = Path(__name__).resolve().parent
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Define metadata for the comic
|
||||||
|
metadata = {
|
||||||
|
"Title": "My Comic",
|
||||||
|
"Series": "Comic Series",
|
||||||
|
"Number": "1",
|
||||||
|
"Pages": [
|
||||||
|
{"File": PARENT / "image1.jpg"},
|
||||||
|
{"File": PARENT / "image2.jpg"},
|
||||||
|
]
|
||||||
|
# Add more metadata fields here
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define the path to the output CBZ file
|
||||||
|
output_path = PARENT / "output.cbz"
|
||||||
|
|
||||||
|
# Create an instance of the Helper class
|
||||||
|
helper = Helper(metadata)
|
||||||
|
|
||||||
|
# Create the CBZ file
|
||||||
|
helper.create_cbz(output_path)
|
||||||
|
````
|
||||||
|
|
||||||
|
For more information on how to use PyCBZHelper, please refer to
|
||||||
|
the [documentation](https://github.com/hyugogirubato/pycbzhelper/blob/master/docs/schema).
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
This project is licensed under the [GPL v3 License](https://github.com/hyugogirubato/pycbzhelper/blob/master/LICENSE).
|
||||||
|
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,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>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from .helper import *
|
from .helper import *
|
||||||
from .comicinfo import *
|
from .comicinfo import *
|
||||||
|
from .utils import slugify
|
||||||
|
|
||||||
__version__ = "3.0.1"
|
__version__ = "3.1.0"
|
||||||
|
|||||||
@@ -1,44 +1,37 @@
|
|||||||
"""
|
|
||||||
Refer:
|
|
||||||
- https://github.com/comictagger/comictagger
|
|
||||||
- https://github.com/Kussie/ComicInfoStandard
|
|
||||||
- https://github.com/anansi-project/comicinfo
|
|
||||||
"""
|
|
||||||
|
|
||||||
KEYS_STRING = [
|
KEYS_STRING = [
|
||||||
'Title', 'Series', 'Number', 'AlternateSeries', 'AlternateNumber',
|
"Title", "Series", "Number", "AlternateSeries", "AlternateNumber",
|
||||||
'Summary', 'Notes', 'Writer', 'Penciller', 'Inker', 'Colorist',
|
"Summary", "Notes", "Writer", "Penciller", "Inker", "Colorist",
|
||||||
'Letterer', 'CoverArtist', 'Editor', 'Publisher', 'Imprint', 'Genre',
|
"Letterer", "CoverArtist", "Editor", "Publisher", "Imprint", "Genre",
|
||||||
'Web', 'Characters', 'Teams', 'Locations', 'ScanInformation',
|
"Web", "Characters", "Teams", "Locations", "ScanInformation",
|
||||||
'StoryArc', 'SeriesGroup', 'CommunityRating'
|
"StoryArc", "SeriesGroup", "CommunityRating"
|
||||||
]
|
]
|
||||||
|
|
||||||
KEYS_INT = [
|
KEYS_INT = [
|
||||||
'Count', 'Volume', 'AlternateCount', 'Year', 'Month', 'Day', 'PageCount'
|
"Count", "Volume", "AlternateCount", "Year", "Month", "Day", "PageCount"
|
||||||
]
|
]
|
||||||
|
|
||||||
KEYS_SPECIAL = [
|
KEYS_SPECIAL = [
|
||||||
'BlackAndWhite', 'Manga', 'AgeRating', 'Pages', 'LanguageISO', 'Format', 'ESN'
|
"BlackAndWhite", "Manga", "AgeRating", "Pages", "LanguageISO", "Format", "ESN"
|
||||||
]
|
]
|
||||||
|
|
||||||
KEYS_AGE = [
|
KEYS_AGE = [
|
||||||
'Adults Only 18+', 'Early Childhood', 'Everyone', 'Everyone 10+',
|
"Adults Only 18+", "Early Childhood", "Everyone", "Everyone 10+",
|
||||||
'G', 'Kids to Adults', 'M', 'MA 15+', 'Mature 17+', 'PG', 'R18+',
|
"G", "Kids to Adults", "M", "MA 15+", "Mature 17+", "PG", "R18+",
|
||||||
'Rating Pending', 'Teen', 'X18+', 'Rating Pending'
|
"Rating Pending", "Teen", "X18+", "Rating Pending"
|
||||||
]
|
]
|
||||||
|
|
||||||
KEYS_FORMAT = [
|
KEYS_FORMAT = [
|
||||||
'1 Shot', '1/2', '1-Shot', 'Annotation', 'Annotations',
|
"1 Shot", "1/2", "1-Shot", "Annotation", "Annotations",
|
||||||
'Annual', 'Anthology', 'B&W', 'B/W', 'B&&W', 'Black & White', 'Box Set',
|
"Annual", "Anthology", "B&W", "B/W", "B&&W", "Black & White", "Box Set",
|
||||||
'Box-Set', 'Crossover', "Director's Cut", 'Epilogue', 'Event', 'FCBD',
|
"Box-Set", "Crossover", "Director's Cut", "Epilogue", "Event", "FCBD",
|
||||||
'Flyer', 'Giant', 'Giant Size', 'Giant-Size', 'Graphic Novel', 'Hardcover',
|
"Flyer", "Giant", "Giant Size", "Giant-Size", "Graphic Novel", "Hardcover",
|
||||||
'Hard-Cover', 'King', 'King Size', 'King-Size', 'Limited Series', 'Magazine',
|
"Hard-Cover", "King", "King Size", "King-Size", "Limited Series", "Magazine",
|
||||||
'NSFW', 'One Shot', 'One-Shot', 'Point 1', 'Preview', 'Prologue', 'Reference',
|
"NSFW", "One Shot", "One-Shot", "Point 1", "Preview", "Prologue", "Reference",
|
||||||
'Review', 'Reviewed', 'Scanlation', 'Script', 'Series', 'Sketch', 'Special',
|
"Review", "Reviewed", "Scanlation", "Script", "Series", "Sketch", "Special",
|
||||||
'TPB', 'Trade Paper Back', 'WebComic', 'Web Comic', 'Year 1', 'Year One'
|
"TPB", "Trade Paper Back", "WebComic", "Web Comic", "Year 1", "Year One"
|
||||||
]
|
]
|
||||||
|
|
||||||
KEYS_PAGE_TYPE = [
|
KEYS_PAGE_TYPE = [
|
||||||
'FrontCover', 'InnerCover', 'Roundup', 'Story', 'Advertisment',
|
"FrontCover", "InnerCover", "Roundup", "Story", "Advertisment",
|
||||||
'Editorial', 'Letters', 'Preview', 'BackCover', 'Other', 'Deleted'
|
"Editorial", "Letters", "Preview", "BackCover", "Other", "Deleted"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class PyCBZHelperException(Exception):
|
class PyCBZHelperException(Exception):
|
||||||
"""Exceptions used by pycbzhelper."""
|
"""Exceptions used by PyCBZHelper."""
|
||||||
|
|
||||||
|
|
||||||
class InvalidKeyValue(PyCBZHelperException):
|
class InvalidKeyValue(PyCBZHelperException):
|
||||||
@@ -10,5 +10,9 @@ class MissingPageFile(PyCBZHelperException):
|
|||||||
"""No page available."""
|
"""No page available."""
|
||||||
|
|
||||||
|
|
||||||
class InvalidFilePermission(PyCBZHelperException):
|
class InvalidFileExtension(PyCBZHelperException):
|
||||||
"""Unable to delete existing file."""
|
"""Invalid file extension."""
|
||||||
|
|
||||||
|
|
||||||
|
class FileNotFound(PyCBZHelperException):
|
||||||
|
"""The specified source file was not found."""
|
||||||
|
|||||||
@@ -1,120 +1,151 @@
|
|||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
import requests
|
||||||
from json2xml import json2xml
|
from json2xml import json2xml
|
||||||
from langcodes import Language
|
from langcodes import Language
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from pycbzhelper.comicinfo import KEYS_STRING, KEYS_INT, KEYS_SPECIAL, KEYS_AGE, KEYS_FORMAT, KEYS_PAGE_TYPE
|
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.exceptions import InvalidKeyValue, MissingPageFile, FileNotFound, InvalidFileExtension
|
||||||
from pycbzhelper.utils import get_key_value, delete_none, slugify
|
from pycbzhelper.utils import get_key_value, delete_none
|
||||||
|
|
||||||
|
TMP = Path.home().resolve() / ".cbzhelper"
|
||||||
|
|
||||||
|
|
||||||
class Helper:
|
class Helper:
|
||||||
|
|
||||||
def __init__(self, kwargs):
|
def __init__(self, kwargs):
|
||||||
self._files = []
|
self._pages = []
|
||||||
self.metadata = self._get_metadata(kwargs)
|
self.comic_info = self._comic_info(kwargs)
|
||||||
|
|
||||||
def _get_metadata(self, kwargs) -> str:
|
def _comic_info(self, kwargs) -> str:
|
||||||
# NOTE: Check metadata
|
|
||||||
for key in KEYS_STRING:
|
for key in KEYS_STRING:
|
||||||
if kwargs.get(key) and not isinstance(kwargs.get(key), str):
|
value = kwargs.get(key)
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be string: {key}")
|
if value is not None and not isinstance(value, str):
|
||||||
|
raise InvalidKeyValue(f"Key must be string: {key}")
|
||||||
|
|
||||||
for key in KEYS_INT:
|
for key in KEYS_INT:
|
||||||
if kwargs.get(key) and not isinstance(kwargs.get(key), int):
|
value = kwargs.get(key)
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be integer: {key}")
|
if value is not None and not isinstance(value, int):
|
||||||
|
raise InvalidKeyValue(f"Key must be integer: {key}")
|
||||||
|
|
||||||
for key in KEYS_SPECIAL:
|
for key in KEYS_SPECIAL:
|
||||||
if kwargs.get(key):
|
value = kwargs.get(key)
|
||||||
if key == 'BlackAndWhite' and get_key_value(kwargs.get(key)) not in ['Yes', 'No']:
|
if value is not None:
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be boolean: {key}")
|
if key == "BlackAndWhite" and get_key_value(value) not in ["Yes", "No"]:
|
||||||
elif key == 'Manga' and get_key_value(kwargs.get(key)) not in ['YesAndRightToLeft', 'Yes', 'No']:
|
raise InvalidKeyValue(f"Key must be boolean: {key}")
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be boolean or special boolean: {key}")
|
elif key == "Manga" and get_key_value(value) not in ["YesAndRightToLeft", "Yes", "No"]:
|
||||||
elif key == 'AgeRating' and kwargs.get(key) not in KEYS_AGE:
|
raise InvalidKeyValue(f"Key must be boolean or special boolean: {key}")
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be special age: {key}")
|
elif key == "AgeRating" and value not in KEYS_AGE:
|
||||||
elif key == 'LanguageISO' and not Language.get(kwargs.get(key)).is_valid():
|
raise InvalidKeyValue(f"Key must be special age: {key}")
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be ISO language: {key}")
|
elif key == "LanguageISO" and not Language.get(value).is_valid():
|
||||||
elif key == 'Format' and kwargs.get(key) not in KEYS_FORMAT:
|
raise InvalidKeyValue(f"Key must be ISO language: {key}")
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be special format: {key}")
|
elif key == "Format" and value not in KEYS_FORMAT:
|
||||||
elif key == 'Pages':
|
raise InvalidKeyValue(f"Key must be special format: {key}")
|
||||||
if isinstance(kwargs.get(key), list):
|
elif key == "Pages":
|
||||||
for page in kwargs.get(key):
|
if isinstance(value, list):
|
||||||
if not page.get('File') or not isinstance(page.get('File'), str) or not os.path.exists(page.get('File')):
|
for file in value:
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be existing string path: {key}")
|
page_file = file.get("File")
|
||||||
if page.get('Type') and not page.get('Type') in KEYS_PAGE_TYPE:
|
if isinstance(page_file, Path):
|
||||||
raise InvalidKeyValue("ERROR: Key must be special type: Type")
|
if not page_file.is_file():
|
||||||
if page.get('DoublePage') and get_key_value(page.get('DoublePage')) not in ['Yes', 'No']:
|
raise FileNotFound("File does not exist.")
|
||||||
raise InvalidKeyValue("ERROR: Key must be boolean: DoublePage")
|
elif isinstance(page_file, str):
|
||||||
|
if not page_file.startswith("http"):
|
||||||
|
raise InvalidKeyValue(f"Key must be an existing file path: {key}")
|
||||||
else:
|
else:
|
||||||
raise InvalidKeyValue(f"ERROR: Key must be a list: {key}")
|
raise InvalidKeyValue(f"Key must be an existing file path: {key}")
|
||||||
|
|
||||||
# NOTE: Set metadata
|
page_type = file.get("Type")
|
||||||
pages = []
|
if page_type and page_type not in KEYS_PAGE_TYPE:
|
||||||
if kwargs.get('Pages'):
|
raise InvalidKeyValue("Key must be special type: Type")
|
||||||
# [{'File': 'FILE_PATH', 'Type': 'FrontCover', 'DoublePage': False}]
|
page_double = file.get("DoublePage")
|
||||||
pages.append(" <Pages>")
|
if page_double and get_key_value(page_double) not in ["Yes", "No"]:
|
||||||
kwargs['PageCount'] = len(kwargs.get('Pages'))
|
raise InvalidKeyValue("Key must be boolean: DoublePage")
|
||||||
for i in range(kwargs.get('PageCount')):
|
else:
|
||||||
page = kwargs.get('Pages')[i]
|
raise InvalidKeyValue(f"Key must be a list: {key}")
|
||||||
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(
|
xml_pages = []
|
||||||
page.get('DoublePage', False)) == 'Yes' else ' <Page'
|
pages = kwargs.get("Pages") or []
|
||||||
pages.append(
|
if len(pages) > 0:
|
||||||
item + ' Image="{image}" ImageHeight="{height}" ImageSize="{size}" ImageWidth="{width}" Type="{type}" />'.format(
|
# [{"File": "FILE_PATH", "Type": "FrontCover", "DoublePage": False, "Bookmark": "", "Key": ""}]
|
||||||
double="False" if get_key_value(page.get('DoublePage', False)) == 'No' else "True",
|
kwargs["PageCount"] = len(pages)
|
||||||
|
xml_pages.append(" <Pages>")
|
||||||
|
|
||||||
|
for i, file in enumerate(pages):
|
||||||
|
page_file = file["File"]
|
||||||
|
if isinstance(page_file, str):
|
||||||
|
r = requests.get(page_file)
|
||||||
|
r.raise_for_status()
|
||||||
|
content = r.content
|
||||||
|
else:
|
||||||
|
content = page_file.read_bytes()
|
||||||
|
|
||||||
|
page_file = TMP / f"page-{i:03d}.jpg"
|
||||||
|
page_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
page_file.write_bytes(content)
|
||||||
|
self._pages.append(page_file)
|
||||||
|
|
||||||
|
properties = Image.open(page_file)
|
||||||
|
page_type = file.get("Type") or "FrontCover" if i == 0 else "Story"
|
||||||
|
|
||||||
|
items = [" <Page"]
|
||||||
|
page_double = get_key_value(file.get("DoublePage", False))
|
||||||
|
if page_double == "Yes":
|
||||||
|
items.append('DoublePage="True"')
|
||||||
|
|
||||||
|
page_bookmark = file.get("Bookmark")
|
||||||
|
if page_bookmark:
|
||||||
|
items.append(f'Bookmark="{page_bookmark}"')
|
||||||
|
|
||||||
|
page_key = file.get("Key")
|
||||||
|
if page_key:
|
||||||
|
items.append(f'Key="{page_key}"')
|
||||||
|
|
||||||
|
xml_pages.append(
|
||||||
|
" ".join(
|
||||||
|
items) + ' Image="{image}" ImageHeight="{height}" ImageSize="{size}" ImageWidth="{width}" Type="{type}"/>'.format(
|
||||||
|
double=str(page_double == "Yes"),
|
||||||
image=i,
|
image=i,
|
||||||
height=properties.height,
|
height=properties.height,
|
||||||
size=len(properties.fp.read()),
|
size=len(properties.fp.read()),
|
||||||
width=properties.width,
|
width=properties.width,
|
||||||
type=page['Type']
|
type=page_type
|
||||||
)
|
))
|
||||||
)
|
xml_pages.append(" </Pages>")
|
||||||
pages.append(" </Pages>")
|
xml_pages.append("</ComicInfo>")
|
||||||
del kwargs['Pages']
|
kwargs.pop("Pages", None)
|
||||||
pages.append("</ComicInfo>")
|
|
||||||
|
|
||||||
json_data = {}
|
dict_data = {}
|
||||||
for key in KEYS_STRING + KEYS_INT + KEYS_SPECIAL:
|
for key in KEYS_STRING + KEYS_INT + KEYS_SPECIAL:
|
||||||
json_data[key] = get_key_value(kwargs.get(key))
|
dict_data[key] = get_key_value(kwargs.get(key))
|
||||||
|
|
||||||
|
xml_data = json2xml.Json2xml(delete_none(dict_data), wrapper="ComicInfo", pretty=True, attr_type=False, item_wrap=False).to_xml()
|
||||||
|
|
||||||
xml_data = json2xml.Json2xml(delete_none(json_data), wrapper="ComicInfo", pretty=True, attr_type=False).to_xml()
|
|
||||||
if not xml_data:
|
if not xml_data:
|
||||||
xml_data = '\n'.join(['<?xml version="1.0" ?>', '<ComicInfo>', '</ComicInfo>'])
|
xml_data = "\n".join(['<?xml version="1.0" ?>', "<ComicInfo>", "</ComicInfo>"])
|
||||||
print('WARNING: No metadata to create.')
|
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('<?xml version="1.0" ?>', '<?xml version="1.0" encoding="utf-8"?>')
|
||||||
xml_data = xml_data.replace('</ComicInfo>', '\n'.join(pages))
|
xml_data = xml_data.replace("</ComicInfo>", "\n".join(xml_pages))
|
||||||
return xml_data.strip()
|
return xml_data.strip()
|
||||||
|
|
||||||
def save_cbz(self, path: str, file: str, clear: bool = False, replace: bool = True) -> None:
|
def create_cbz(self, path: Path) -> None:
|
||||||
if len(self._files) == 0:
|
if not len(self._pages) > 0:
|
||||||
raise MissingPageFile('ERROR: No pages available.')
|
raise MissingPageFile("No pages available.")
|
||||||
|
if path.suffix != ".cbz":
|
||||||
output = os.path.join(path, f"{slugify(file, allow_unicode=False)}.cbz")
|
raise InvalidFileExtension("Invalid file extension.")
|
||||||
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)
|
|
||||||
|
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
cbz = zipfile.ZipFile(path, "w", compression=zipfile.ZIP_STORED)
|
||||||
clear_path = []
|
clear_path = []
|
||||||
cbz = zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_STORED)
|
|
||||||
for file in self._files:
|
for i, page in enumerate(self._pages):
|
||||||
if os.path.dirname(file) not in clear_path:
|
clear_path.append(page)
|
||||||
clear_path.append(os.path.dirname(file))
|
cbz.writestr(f"page-{i + 1:03d}{page.suffix}", data=page.read_bytes())
|
||||||
with open(file, mode='rb') as f:
|
|
||||||
cbz.writestr(os.path.basename(file), data=f.read())
|
cbz.writestr("ComicInfo.xml", data=self.comic_info.encode("utf-8"))
|
||||||
cbz.writestr('ComicInfo.xml', data=self.metadata.encode('utf-8'))
|
|
||||||
cbz.close()
|
cbz.close()
|
||||||
print(f"INFO: File create: {output}")
|
|
||||||
if clear:
|
self._pages = []
|
||||||
for path in clear_path:
|
shutil.rmtree(TMP)
|
||||||
shutil.rmtree(path)
|
|
||||||
print(f"INFO: Folder deleted: {path}")
|
|
||||||
|
|||||||
@@ -4,13 +4,10 @@ from typing import Union
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
|
||||||
def get_key_value(_value) -> str:
|
def get_key_value(value: Union[str, bool]) -> str:
|
||||||
if isinstance(_value, str):
|
if isinstance(value, bool):
|
||||||
if _value in ['YesAndRightToLeft', 'Yes', 'No']:
|
return "Yes" if value else "No"
|
||||||
return _value
|
return value
|
||||||
elif isinstance(_value, bool):
|
|
||||||
return 'Yes' if _value else 'No'
|
|
||||||
return _value
|
|
||||||
|
|
||||||
|
|
||||||
def delete_none(_dict: dict) -> dict:
|
def delete_none(_dict: dict) -> dict:
|
||||||
@@ -39,9 +36,9 @@ def slugify(value: Union[str, int], allow_unicode: bool = False) -> str:
|
|||||||
"""
|
"""
|
||||||
value = str(value)
|
value = str(value)
|
||||||
if allow_unicode:
|
if allow_unicode:
|
||||||
value = unicodedata.normalize('NFKC', value)
|
value = unicodedata.normalize("NFKC", value)
|
||||||
else:
|
else:
|
||||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
|
value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
|
||||||
# value = re.sub(r'[^\w\s-]', '', value.lower())
|
# value = re.sub(r'[^\w\s-]', '', value.lower())
|
||||||
# return re.sub(r'[-\s]+', '-', value).strip('-_')
|
# return re.sub(r'[-\s]+', '-', value).strip('-_')
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
"""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="pycbzhelper",
|
||||||
version='3.0.1',
|
version="3.1.0",
|
||||||
description='Python library to create a cbz file with metadata.',
|
author="hyugogirubato",
|
||||||
long_description=LONG_DESCRIPTION,
|
author_email="hyugogirubato@gmail.com",
|
||||||
long_description_content_type='text/markdown',
|
description="Python library for creating CBZ files with metadata.",
|
||||||
url='https://github.com/hyugogirubato/pycbzhelper',
|
long_description=open("README.md").read(),
|
||||||
author='hyugogirubato',
|
long_description_content_type="text/markdown",
|
||||||
author_email='hyugogirubato@gmail.com',
|
url="https://github.com/hyugogirubato/pycbzhelper",
|
||||||
license='GNU GPLv3',
|
packages=find_packages(),
|
||||||
packages=['pycbzhelper'],
|
license="GPL-3.0-only",
|
||||||
install_requires=['json2xml', 'langcodes', 'pillow'],
|
keywords=[
|
||||||
|
"metadata",
|
||||||
|
"manga",
|
||||||
|
"comics",
|
||||||
|
"ebooks",
|
||||||
|
"cbz"
|
||||||
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Environment :: Console',
|
"Development Status :: 5 - Production/Stable",
|
||||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
"Intended Audience :: Developers",
|
||||||
'Operating System :: OS Independent',
|
"Intended Audience :: End Users/Desktop",
|
||||||
'Programming Language :: Python :: 3.4',
|
"Natural Language :: English",
|
||||||
'Programming Language :: Python :: 3.5',
|
"Operating System :: OS Independent",
|
||||||
'Programming Language :: Python :: 3.6',
|
"Programming Language :: Python :: 3",
|
||||||
'Programming Language :: Python :: 3.7',
|
"Programming Language :: Python :: 3.7",
|
||||||
'Topic :: Utilities'
|
"Programming Language :: Python :: 3.8",
|
||||||
]
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Topic :: Utilities"
|
||||||
|
],
|
||||||
|
install_requires=[
|
||||||
|
"requests",
|
||||||
|
"json2xml",
|
||||||
|
"langcodes",
|
||||||
|
"Pillow"
|
||||||
|
],
|
||||||
|
python_requires=">=3.7"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pycbzhelper
|
||||||
|
|
||||||
|
PARENT = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pages = []
|
||||||
|
for i in range(11):
|
||||||
|
pages.append({"File": (PARENT / "docs" / "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
|
||||||
|
}
|
||||||
|
|
||||||
|
path = PARENT / "eBooks" / "Arrête de me chauffer, Nagatoro.cbz"
|
||||||
|
helper = pycbzhelper.Helper(metadata)
|
||||||
|
helper.create_cbz(path=path)
|
||||||
|
print(f"I: File created: {path}")
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||