bump version

This commit is contained in:
hyugogirubato
2026-04-06 15:01:01 +02:00
parent 0ecf0f843b
commit 95472d4ac4
20 changed files with 2198 additions and 1461 deletions
+233 -57
View File
@@ -4,14 +4,16 @@ CBZ is a Python library designed for creating, manipulating, and viewing comic b
## Features
- 🚀 Seamless Installation via [pip](#installation)
- 📚 Pack images into CBZ format for comics and manga
- 📝 Extract and manage title, series, format, and more
- 🖼️ Handle comic pages with attributes like type and format
- 📦 Unpack CBZ and CBR files to retrieve comic information, or extract images from PDF files
- 🛠️ Built-in player for viewing CBZ, CBR, and PDF comics
- 📚 Full CBR (RAR) format support for reading existing archives
- ❤️ Fully Open-Source! Pull Requests Welcome
- Seamless installation via [pip](#installation)
- Pack images into CBZ format for comics and manga
- Extract and manage metadata: title, series, format, and more
- Handle comic pages with attributes like type, dimensions, and bookmarks
- Unpack CBZ and CBR files to retrieve comic information, or extract images from PDF files
- Built-in player for viewing CBZ, CBR, and PDF comics
- Sequence protocol: iterate, index, and slice comic pages directly
- Dataclass-based models with automatic XML mapping and strict type validation
- Image support: JPEG, PNG, GIF, BMP, TIFF, WebP, JPEG XL, AVIF
- Fully open-source! Pull requests welcome
## Installation
@@ -21,56 +23,60 @@ Install CBZ from PyPI using pip:
pip install cbz
```
With AVIF and JPEG XL support:
```shell
pip install cbz[pillow]
```
## Quick Start
Here's a quick example of how to create a CBZ file from a series of images:
````python
```python
from pathlib import Path
from cbz.comic import ComicInfo
from cbz.constants import PageType, YesNo, Manga, AgeRating, Format
from cbz.page import PageInfo
from cbz import ComicInfo, PageInfo, PageType, Format, YesNo, Manga, AgeRating
PARENT = Path(__file__).parent
if __name__ == "__main__":
paths = sorted(Path("path/to/your/images").iterdir())
if __name__ == '__main__':
paths = list(Path('path/to/your/images').iterdir())
# Load each page from the 'images' folder into a list of PageInfo objects
# Load each page from the images folder into a list of PageInfo objects
pages = [
PageInfo.load(
path=path,
type=PageType.FRONT_COVER if i == 0 else PageType.BACK_COVER if i == len(paths) - 1 else PageType.STORY
type=(
PageType.FRONT_COVER if i == 0
else PageType.BACK_COVER if i == len(paths) - 1
else PageType.STORY
),
)
for i, path in enumerate(paths)
]
# Create a ComicInfo object using ComicInfo.from_pages() method
# Create a ComicInfo object with metadata
comic = ComicInfo.from_pages(
pages=pages,
title='Your Comic Title',
series='Your Comic Series',
title="Your Comic Title",
series="Your Comic Series",
number=1,
language_iso='en',
language_iso="en",
format=Format.WEB_COMIC,
black_white=YesNo.NO,
manga=Manga.NO,
age_rating=AgeRating.PENDING
age_rating=AgeRating.RATING_PENDING,
)
# Show the comic using the show()
# Display the comic in the built-in reader
comic.show()
# Pack the comic book content into a CBZ file format
# Save directly as a CBZ file
comic.save("your_comic.cbz")
# Or pack to bytes for custom handling
cbz_content = comic.pack()
# Define the path where the CBZ file will be saved
cbz_path = PARENT / 'your_comic.cbz'
# Write the CBZ content to the specified path
cbz_path.write_bytes(cbz_content)
````
Path("your_comic.cbz").write_bytes(cbz_content)
```
## Player
@@ -84,17 +90,17 @@ CBZ includes a command-line player for viewing comic book files in multiple form
### Usage
````shell
```shell
usage: cbzplayer [-h] <file>
Launch CBZ player with a comic book file
CBZ/CBR/PDF comic reader
positional arguments:
<file> Path to the CBZ, CBR, or PDF comic book file.
<file> Path to the CBZ, CBR or PDF comic book file.
options:
-h, --help show this help message and exit
````
-h, --help show this help message and exit
```
### Examples
@@ -109,6 +115,17 @@ cbzplayer my_comic.cbr
cbzplayer my_comic.pdf
```
### Keyboard Shortcuts
| Shortcut | Action |
|---------------------|-------------------|
| Left / Right arrows | Navigate pages |
| + / - | Zoom in / out |
| Ctrl+Q | Quit |
| Mouse wheel | Vertical scroll |
| Shift+Mouse wheel | Horizontal scroll |
| Ctrl+Mouse wheel | Zoom |
### Requirements for CBR Support
CBR file support requires:
@@ -128,30 +145,47 @@ For installation instructions and compatibility details, see the [rarfile docume
The `ComicInfo` class represents a comic book with metadata and pages. It supports initialization from a list of `PageInfo` objects:
```python
from cbz.comic import ComicInfo
from cbz.constants import PageType, YesNo, Manga, AgeRating, Format
from cbz.page import PageInfo
from cbz import ComicInfo, PageInfo, PageType, Format, YesNo, Manga, AgeRating
# Example usage:
pages = [
PageInfo.load(path='/path/to/page1.jpg', type=PageType.FRONT_COVER),
PageInfo.load(path='/path/to/page2.jpg', type=PageType.STORY),
PageInfo.load(path='/path/to/page3.jpg', type=PageType.BACK_COVER),
PageInfo.load(path="page1.jpg", type=PageType.FRONT_COVER),
PageInfo.load(path="page2.jpg", type=PageType.STORY),
PageInfo.load(path="page3.jpg", type=PageType.BACK_COVER),
]
comic = ComicInfo.from_pages(
pages=pages,
title='My Comic',
series='Comic Series',
title="My Comic",
series="Comic Series",
number=1,
language_iso='en',
language_iso="en",
format=Format.WEB_COMIC,
black_white=YesNo.NO,
manga=Manga.NO,
age_rating=AgeRating.PENDING
age_rating=AgeRating.RATING_PENDING,
)
```
You can also create pages directly from bytes or base64-encoded data:
```python
page = PageInfo.loads(data=image_bytes, name="page.jpg", type=PageType.STORY)
```
### Sequence Protocol
`ComicInfo` implements the full sequence protocol, so you can interact with pages directly:
```python
len(comic) # Number of pages
comic[0] # First page
comic[-1] # Last page
comic[1:3] # Slice of pages
for page in comic: # Iteration
print(page.image_width, page.image_height)
page in comic # Containment check
```
### Extracting Metadata
Retrieve comic information as a dictionary using `get_info()`:
@@ -161,39 +195,181 @@ info = comic.get_info()
print(info)
```
### Packing into CBZ Format
### Packing and Saving
Pack the comic into a CBZ file format:
Pack the comic into CBZ format as bytes:
```python
cbz_content = comic.pack()
```
Or save directly to disk (more memory-efficient for large archives):
```python
comic.save("output.cbz")
```
### Loading from Different Formats
Load a comic from an existing CBZ file (with metadata):
```python
comic_from_cbz = ComicInfo.from_cbz('/path/to/your_comic.cbz')
comic = ComicInfo.from_cbz("your_comic.cbz")
```
Load a comic from an existing CBR file (with metadata):
```python
comic_from_cbr = ComicInfo.from_cbr('/path/to/your_comic.cbr')
comic = ComicInfo.from_cbr("your_comic.cbr")
```
Load a comic from a PDF file (images only, no metadata):
```python
comic_from_pdf = ComicInfo.from_pdf('/path/to/your_comic.pdf')
comic = ComicInfo.from_pdf("your_comic.pdf")
```
**Notes**:
**Notes:**
- CBR support requires an external RAR extraction tool. For detailed compatibility information and advanced configuration, see the [rarfile documentation](https://github.com/markokr/rarfile).
- PDF files only provide image content; comic metadata (title, series, etc.) is not available from PDF files.
### Page Properties
Each `PageInfo` object exposes the following properties, automatically extracted from the image content:
```python
page = comic[0]
page.content # bytes - raw image data
page.image_width # int - width in pixels
page.image_height # int - height in pixels
page.image_size # int - file size in bytes
page.suffix # str - file extension (.jpg, .png, etc.)
page.name # str - original file name
page.type # PageType - page type (FrontCover, Story, etc.)
page.bookmark # str - bookmark / chapter name
page.double # bool - double page spread
```
### Metadata Fields
All [ComicInfo.xml](docs/RFC-CBZ.md) v2.1 metadata fields are supported as dataclass attributes:
| Attribute | Type | Default | Description |
|--------------------------|--------------------|-----------|------------------------------------------|
| `title` | `str` | `""` | Issue title |
| `series` | `str` | `""` | Series name |
| `number` | `Optional[int]` | `None` | Issue number |
| `count` | `Optional[int]` | `None` | Total number of issues |
| `volume` | `Optional[int]` | `None` | Volume number |
| `year` | `Optional[int]` | `None` | Publication year |
| `month` | `Optional[int]` | `None` | Publication month |
| `day` | `Optional[int]` | `None` | Publication day |
| `writer` | `str` | `""` | Writer(s), comma-separated |
| `penciller` | `str` | `""` | Pencil artist(s) |
| `inker` | `str` | `""` | Inker(s) |
| `colorist` | `str` | `""` | Colorist(s) |
| `letterer` | `str` | `""` | Letterer(s) |
| `cover_artist` | `str` | `""` | Cover artist(s) |
| `editor` | `str` | `""` | Editor(s) |
| `translator` | `str` | `""` | Translator(s) |
| `publisher` | `str` | `""` | Publisher |
| `imprint` | `str` | `""` | Publisher imprint |
| `genre` | `str` | `""` | Genre(s), comma-separated |
| `tags` | `str` | `""` | Tags, comma-separated |
| `web` | `str` | `""` | Web URL |
| `language_iso` | `LanguageISO` | `""` | ISO language code (e.g., `"en"`, `"fr"`) |
| `format` | `Format` | `UNKNOWN` | Publication format |
| `black_white` | `YesNo` | `UNKNOWN` | Black and white |
| `manga` | `Manga` | `UNKNOWN` | Manga / reading direction |
| `age_rating` | `AgeRating` | `UNKNOWN` | Content age rating |
| `community_rating` | `Optional[Rating]` | `None` | Community rating (0.0-5.0) |
| `summary` | `str` | `""` | Synopsis / description |
| `characters` | `str` | `""` | Character names, comma-separated |
| `teams` | `str` | `""` | Team names, comma-separated |
| `locations` | `str` | `""` | Locations, comma-separated |
| `story_arc` | `str` | `""` | Story arc name |
| `story_arc_number` | `Optional[int]` | `None` | Position in story arc |
| `main_character_or_team` | `str` | `""` | Primary character or team |
| `scan_information` | `str` | `""` | Scan / digitization notes |
| `ean` | `str` | `""` | EAN / ISBN |
| `book_price` | `str` | `""` | Cover price |
### Enumerations
```python
from cbz import PageType, Format, YesNo, Manga, AgeRating
# Page types
PageType.FRONT_COVER # Front cover
PageType.STORY # Story page (default)
PageType.BACK_COVER # Back cover
PageType.INNER_COVER # Inner cover / dust jacket
PageType.ADVERTISEMENT # Advertisement
PageType.EDITORIAL # Editorial / credits
PageType.LETTERS # Letters page
PageType.PREVIEW # Preview of upcoming issues
PageType.ROUNDUP # Recap / summary
PageType.OTHER # Other
PageType.DELETED # Marked for deletion
# Publication formats
Format.SERIES # Regular series
Format.GRAPHIC_NOVEL # Graphic novel
Format.WEB_COMIC # Webcomic
Format.ONE_SHOT # One-shot
Format.TRADE_PAPERBACK # Trade paperback
Format.ANNUAL # Annual
Format.ANTHOLOGY # Anthology
Format.LIMITED_SERIES # Limited series
Format.MAGAZINE # Magazine
# ... and more
# Reading direction
Manga.UNKNOWN # Not specified
Manga.NO # Western (left to right)
Manga.YES # Manga
Manga.YES_AND_RIGHT_TO_LEFT # Manga (right to left)
# Age ratings
AgeRating.UNKNOWN # Not rated
AgeRating.EVERYONE # All ages
AgeRating.TEEN # Teens
AgeRating.MATURE_17_PLUS # Mature 17+
AgeRating.RATING_PENDING # Rating pending
# ... and more
```
### Error Handling
The library provides a hierarchy of specific exceptions:
```python
from cbz import CBZError, InvalidImageError, EmptyArchiveError, InvalidMetadataError
try:
comic = ComicInfo.from_cbz("corrupted.cbz")
except InvalidMetadataError:
print("ComicInfo.xml is invalid or corrupted")
except EmptyArchiveError:
print("No valid images found in the archive")
except InvalidImageError:
print("An image in the archive could not be read")
except CBZError:
print("General CBZ error")
```
## Format Specification
A complete RFC specification of the CBZ format is available in [`docs/RFC-CBZ.md`](docs/RFC-CBZ.md).
The ComicInfo.xml XSD schemas (v1.0, v2.0, v2.1) are in [`docs/schema/`](docs/schema/).
## Changelog
See [`CHANGELOG.md`](CHANGELOG.md) for the full version history, including migration notes for v4.0.
## Contributors
<a href="https://github.com/hyugogirubato"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/65763543?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="hyugogirubato"/></a>
@@ -204,11 +380,11 @@ comic_from_pdf = ComicInfo.from_pdf('/path/to/your_comic.pdf')
<a href="https://github.com/domenicoblanco"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9018104?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="domenicoblanco"/></a>
<a href="https://github.com/RivMt"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40086827?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="RivMt"/></a>
<a href="https://github.com/flolep2607"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/24566964?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="flolep2607"/></a>
<a href="https://github.com/chase-roohms"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/131704514?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="chase-roohms"/></a>
## Licensing
This software is licensed under the terms of [MIT License](https://github.com/hyugogirubato/cbz/blob/main/LICENSE).
You can find a copy of the license in the LICENSE file in the root folder.
This software is licensed under the terms of [MIT License](https://github.com/hyugogirubato/cbz/blob/main/LICENSE). You can find a copy of the license in the LICENSE file in the root folder.
### Third-Party Licenses
@@ -224,4 +400,4 @@ This project uses the following third-party libraries:
---
© hyugogirubato 2025
© hyugogirubato