17 Commits
Author SHA1 Message Date
hyugogirubato 871716971a release v3.4.5 2025-10-19 19:48:27 +02:00
hyugogirubato 597cb41b00 more flexible suffix check 2025-10-19 19:48:13 +02:00
hyugogirubato a489ae17fb check invalid file content 2025-10-19 19:47:52 +02:00
hyugogirubato abc69d4673 Linking content check 2025-10-19 19:47:27 +02:00
hyugogirubato 6a1111a6bb release v3.4.4 2025-09-21 10:41:42 +02:00
hyugogirubato 220f19fe46 add additional image format 2025-09-20 19:43:52 +02:00
hyugogirubato bbab9ba3b3 Merge pull request #24 from hyugogirubato/dependabot/github_actions/actions/setup-python-6
Bump actions/setup-python from 5 to 6
2025-09-17 21:20:51 +02:00
hyugogirubato 5824a8428a add thrid-party plugins licences 2025-09-17 21:19:50 +02:00
hyugogirubato 23f7e37561 load pillow extra plugins 2025-09-17 21:16:40 +02:00
hyugogirubato 5cc4732238 add pillow extra plugins 2025-09-17 21:16:27 +02:00
hyugogirubato 9d5d539311 force sort archive filenames 2025-09-17 20:48:20 +02:00
dependabot[bot] f8274ae26b Bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 14:42:19 +00:00
hyugogirubato c045c548de release v3.4.4 2025-08-31 21:21:53 +02:00
hyugogirubato 7355e5edc0 Merge pull request #23 from hyugogirubato/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-31 21:14:36 +02:00
hyugogirubato 5eee972d75 Merge pull request #22 from tssujt/single
Fix ComicInfo XML parsing for single page comic
2025-08-31 21:14:07 +02:00
dependabot[bot] 3604a4e98f Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 23:05:20 +00:00
tssujt 19c1b01ea6 fix ComicInfo XML parsing for single page comic 2025-08-01 14:27:05 +08:00
9 changed files with 99 additions and 25 deletions
+2 -2
View File
@@ -10,10 +10,10 @@ jobs:
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
+30
View File
@@ -4,6 +4,33 @@ 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.4.5] - 2025-10-19
### Changed
- More flexible suffix checking to support complex archive paths.
- Improved validation in `PageInfo.load()` to handle empty or invalid data correctly.
## [3.4.4] - 2025-09-21
### Added
- Added `[pillow]` extension to support `.jxl` and `.avif` images.
### Fixed
- Files in the input archives are now sorted alphabetically.
## [3.4.3] - 2025-08-31
### Changed
- Bump actions/checkout from 4 to 5.
### Fixed
- Fix ComicInfo XML parsing for single page comic.
## [3.4.2] - 2025-07-26
### Added
@@ -205,6 +232,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Initial release.
[3.4.5]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.5
[3.4.4]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.4
[3.4.3]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.3
[3.4.2]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.2
[3.4.1]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.1
[3.4.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.0
+3 -1
View File
@@ -216,10 +216,12 @@ This project uses the following third-party libraries:
- **[langcodes](https://pypi.org/project/langcodes/)** - MIT License
- **[Pillow](https://pypi.org/project/Pillow/)** - HPND License
- **[pillow-avif-plugin](https://pypi.org/project/pillow-avif-plugin/)** - MIT License
- **[pillow-jxl-plugin](https://pypi.org/project/pillow-jxl-plugin/)** - MIT License
- **[pypdf](https://pypi.org/project/pypdf/)** - BSD License
- **[rarfile](https://pypi.org/project/rarfile/)** - ISC License
- **[xmltodict](https://pypi.org/project/xmltodict/)** - MIT License
* * *
---
© hyugogirubato 2025
+7
View File
@@ -1,2 +1,9 @@
from .comic import ComicInfo
from .page import PageInfo
# Register optional Pillow plugins
for _plugin in ('pillow_avif', 'pillow_jxl'):
try:
__import__(_plugin)
except ImportError:
pass
+19 -13
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import logging
import zipfile
from datetime import datetime, timezone
@@ -156,12 +157,12 @@ class ComicInfo(ComicModel):
ComicInfo: An instance of ComicInfo.
"""
pages = []
names = archive_file.namelist()
names = sorted(archive_file.namelist())
comic_info = {}
if XML_NAME in names:
with archive_file.open(XML_NAME, 'r') as f:
comic_info = xmltodict.parse(f.read()).get('ComicInfo', {})
comic_info = xmltodict.parse(f.read(), force_list=('Page',)).get('ComicInfo', {})
names.remove(XML_NAME)
comic = ComicInfo.__extract_info(
@@ -172,17 +173,22 @@ class ComicInfo(ComicModel):
pages_info = comic_info.get('Pages', {}).get('Page', [])
for i, name in enumerate(names):
suffix = Path(name).suffix
if suffix:
assert suffix in IMAGE_FORMAT, f'Unsupported image format: {suffix}'
with archive_file.open(name, 'r') as f:
page_info = {}
if i < len(pages_info):
page_info = ComicInfo.__extract_info(
items=pages_info[i],
fields=PAGE_FIELDS
)
page_info['name'] = Path(name).name
pages.append(PageInfo.loads(data=f.read(), **page_info))
# Some archives may contain folders or hidden files (e.g., ".extra/" or "__MACOSX/.DS_Store")
# that could corrupt the suffix detection and cause invalid image handling.
if not suffix or suffix not in IMAGE_FORMAT:
logging.warning(f'Skipping unsupported or invalid file: {name!r} (suffix={suffix!r})')
continue
with archive_file.open(name, 'r') as f:
page_info = {}
if i < len(pages_info):
page_info = ComicInfo.__extract_info(
items=pages_info[i],
fields=PAGE_FIELDS
)
page_info['name'] = Path(name).name
pages.append(PageInfo.loads(data=f.read(), **page_info))
return ComicInfo.from_pages(pages=pages, **comic)
+8 -6
View File
@@ -5,12 +5,14 @@ from langcodes import Language
XML_NAME = 'ComicInfo.xml'
IMAGE_FORMAT = {
'.jpeg', '.jpg', # Joint Photographic Experts Group
'.png', # Portable Network Graphics
'.gif', # Graphics Interchange Format
'.bmp', # Bitmap Image File
'.tiff', '.tif', # Tagged Image File Format
'.webp' # Web Picture Format
'.jpeg', '.jpg', # JPEG (Joint Photographic Experts Group)
'.png', # PNG (Portable Network Graphics)
'.gif', # GIF (Graphics Interchange Format)
'.bmp', # BMP (Bitmap Image File)
'.tiff', '.tif', # TIFF (Tagged Image File Format)
'.webp', # WebP (Web Picture Format, by Google)
'.jxl', # JPEG XL (Next-generation JPEG format)
'.avif' # AVIF (AV1 Image File Format, based on AV1 codec)
}
+3 -1
View File
@@ -75,6 +75,8 @@ class PageInfo(PageModel):
data = base64.b64decode(data)
if not isinstance(data, bytes):
raise ValueError(f'Expecting Bytes or Base64 input, got {data!r}')
if not data.strip():
raise ValueError(f'Empty or null data provided (length={len(data)})')
return cls(data, **kwargs)
@classmethod
@@ -98,7 +100,7 @@ class PageInfo(PageModel):
path = Path(path)
with path.open(mode='rb') as f:
kwargs.setdefault('name', path.name)
return cls(f.read(), **kwargs)
return cls.loads(f.read(), **kwargs)
def show(self) -> None:
"""
+7 -2
View File
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "cbz"
version = "3.4.2"
version = "3.4.5"
description = "CBZ simplifies creating, managing, and viewing comic book files in CBZ, CBR, and PDF formats, offering seamless packaging, metadata handling and built-in viewing capabilities."
license = "MIT"
authors = ["hyugogirubato <65763543+hyugogirubato@users.noreply.github.com>"]
@@ -46,6 +46,12 @@ Pillow = ">=10.4.0"
pypdf = ">=5.7.0"
rarfile = ">=4.2"
xmltodict = ">=0.14.2"
pillow-avif-plugin = { version = ">=1.5.2", optional = true, markers = "python_version >= '3.9'" }
pillow-jxl-plugin = { version = ">=1.3.4", optional = true, markers = "python_version >= '3.9'" }
[tool.poetry.extras]
pillow = ["pillow-avif-plugin", "pillow-jxl-plugin"]
[tool.poetry.scripts]
cbzplayer = "cbz.__main__:main"
@@ -55,7 +61,6 @@ name = "localpypi"
url = "https://pypi.org/simple/"
priority = "primary"
[tool.poetry.group.dev.dependencies]
pytest = ">=7.4.0,<9.0.0"
pytest-cov = ">=5.0.0"
+20
View File
@@ -1,3 +1,4 @@
import tempfile
from pathlib import Path
from typing import List
@@ -157,3 +158,22 @@ class TestComicInfo:
assert comic.title == 'Empty Test'
assert len(comic.pages) == 0
def test_single_page_comic_load(self, images_dir):
"""Test loading comic with a single page."""
image_paths = sorted(list(images_dir.iterdir()))[:1]
pages = [PageInfo.load(path=path) for path in image_paths]
comic = ComicInfo.from_pages(pages=pages, title="Single Page Test")
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir) / "single_page.cbz"
data = comic.pack()
with open(temp_path, "wb") as f:
f.write(data)
assert temp_path.exists()
comic_loaded = ComicInfo.from_cbz(temp_path)
assert comic_loaded.title == "Single Page Test"
assert len(comic_loaded.pages) == 1