Compare commits

...
7 Commits
Author SHA1 Message Date
hyugogirubato 1f1433a3c4 Release v3.1.0 2023-06-04 15:57:30 +02:00
hyugogirubato ffca3239cf Update README.md 2023-02-11 18:52:31 +01:00
hyugogirubato a57f1c96a8 remove ide files 2023-02-08 20:52:01 +01:00
hyugogirubato f5debc6f56 Support remote files + binary files 2023-02-08 19:51:08 +01:00
hyugogirubato 40cb3759f2 fix item_wrap 2023-02-08 19:03:45 +01:00
hyugogirubato 9ff1f4940c Add Bookmark & Key for page 2023-02-08 16:49:59 +01:00
hyugogirubato 11aada0be7 fixpage name 2023-02-07 21:13:54 +01:00
26 changed files with 541 additions and 571 deletions
-101
View File
@@ -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/
-23
View File
@@ -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
+34
View File
@@ -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
+57 -33
View File
@@ -1,40 +1,64 @@
# pydvdfab # PyCBZHelper
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Release](https://img.shields.io/github/release-date/hyugogirubato/pycbzhelper?style=plastic)](https://github.com/hyugogirubato/pycbzhelper/releases)
![Total Downloads](https://img.shields.io/github/downloads/hyugogirubato/pycbzhelper/total.svg?style=plastic)
Python library to create a cbz file with metadata. [![License](https://img.shields.io/github/license/hyugogirubato/pycbzhelper)](https://github.com/hyugogirubato/pycbzhelper/blob/master/LICENSE)
[![Release](https://img.shields.io/github/release-date/hyugogirubato/pycbzhelper)](https://github.com/hyugogirubato/pycbzhelper/releases)
[![Latest Version](https://img.shields.io/pypi/v/pycbzhelper)](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>
+2 -1
View File
@@ -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"
+20 -27
View File
@@ -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"
] ]
+7 -3
View File
@@ -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."""
+115 -84
View File
@@ -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:
raise InvalidKeyValue(f"Key must be an existing file path: {key}")
page_type = file.get("Type")
if page_type and page_type not in KEYS_PAGE_TYPE:
raise InvalidKeyValue("Key must be special type: Type")
page_double = file.get("DoublePage")
if page_double and get_key_value(page_double) not in ["Yes", "No"]:
raise InvalidKeyValue("Key must be boolean: DoublePage")
else: else:
raise InvalidKeyValue(f"ERROR: Key must be a list: {key}") raise InvalidKeyValue(f"Key must be a list: {key}")
# NOTE: Set metadata xml_pages = []
pages = [] pages = kwargs.get("Pages") or []
if kwargs.get('Pages'): if len(pages) > 0:
# [{'File': 'FILE_PATH', 'Type': 'FrontCover', 'DoublePage': False}] # [{"File": "FILE_PATH", "Type": "FrontCover", "DoublePage": False, "Bookmark": "", "Key": ""}]
pages.append(" <Pages>") kwargs["PageCount"] = len(pages)
kwargs['PageCount'] = len(kwargs.get('Pages')) xml_pages.append(" <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( for i, file in enumerate(pages):
page.get('DoublePage', False)) == 'Yes' else ' <Page' page_file = file["File"]
pages.append( if isinstance(page_file, str):
item + ' Image="{image}" ImageHeight="{height}" ImageSize="{size}" ImageWidth="{width}" Type="{type}" />'.format( r = requests.get(page_file)
double="False" if get_key_value(page.get('DoublePage', False)) == 'No' else "True", 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}")
+6 -9
View File
@@ -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
+37 -26
View File
@@ -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"
) )
+42
View File
@@ -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}")
-43
View File
@@ -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
)