Compare commits

..
19 Commits
Author SHA1 Message Date
legonzaur 45da4c11f4 fix: allow comi.number to be a float 2025-12-30 15:25:50 +01:00
legonzaur 1e23fad6b5 fix: allow Comic.number to be a float 2025-12-30 15:25:46 +01:00
legonzaur 4ebee2a6cf chore: formatting 2025-12-30 15:25:19 +01:00
legonzaur ef4eff4868 fix pyproject toml to work with uv 2025-12-30 15:24:47 +01:00
hyugogirubato d37be87c2f Merge pull request #29 from hyugogirubato/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-30 14:59:33 +01:00
dependabot[bot] 0b011a7efc Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [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/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 15:58:42 +00:00
hyugogirubato 62b8cf5739 simplify suffix check 2025-11-04 00:00:46 +01:00
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
9 changed files with 238 additions and 182 deletions
+2 -2
View File
@@ -10,10 +10,10 @@ jobs:
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ] python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ]
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
+19
View File
@@ -4,6 +4,23 @@ 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). 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 ## [3.4.3] - 2025-08-31
### Changed ### Changed
@@ -215,6 +232,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Initial release. - 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.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.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.1]: https://github.com/hyugogirubato/cbz/releases/tag/v3.4.1
+2
View File
@@ -216,6 +216,8 @@ This project uses the following third-party libraries:
- **[langcodes](https://pypi.org/project/langcodes/)** - MIT License - **[langcodes](https://pypi.org/project/langcodes/)** - MIT License
- **[Pillow](https://pypi.org/project/Pillow/)** - HPND 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 - **[pypdf](https://pypi.org/project/pypdf/)** - BSD License
- **[rarfile](https://pypi.org/project/rarfile/)** - ISC License - **[rarfile](https://pypi.org/project/rarfile/)** - ISC License
- **[xmltodict](https://pypi.org/project/xmltodict/)** - MIT License - **[xmltodict](https://pypi.org/project/xmltodict/)** - MIT License
+7
View File
@@ -1,2 +1,9 @@
from .comic import ComicInfo from .comic import ComicInfo
from .page import PageInfo from .page import PageInfo
# Register optional Pillow plugins
for _plugin in ('pillow_avif', 'pillow_jxl'):
try:
__import__(_plugin)
except ImportError:
pass
+9 -3
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import logging
import zipfile import zipfile
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -156,7 +157,7 @@ class ComicInfo(ComicModel):
ComicInfo: An instance of ComicInfo. ComicInfo: An instance of ComicInfo.
""" """
pages = [] pages = []
names = archive_file.namelist() names = sorted(archive_file.namelist())
comic_info = {} comic_info = {}
if XML_NAME in names: if XML_NAME in names:
@@ -172,8 +173,13 @@ class ComicInfo(ComicModel):
pages_info = comic_info.get('Pages', {}).get('Page', []) pages_info = comic_info.get('Pages', {}).get('Page', [])
for i, name in enumerate(names): for i, name in enumerate(names):
suffix = Path(name).suffix suffix = Path(name).suffix
if suffix:
assert suffix in IMAGE_FORMAT, f'Unsupported image format: {suffix}' # 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 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: with archive_file.open(name, 'r') as f:
page_info = {} page_info = {}
if i < len(pages_info): if i < len(pages_info):
+140 -134
View File
@@ -3,189 +3,195 @@ from typing import Union
from langcodes import Language from langcodes import Language
XML_NAME = 'ComicInfo.xml' XML_NAME = "ComicInfo.xml"
IMAGE_FORMAT = { IMAGE_FORMAT = {
'.jpeg', '.jpg', # Joint Photographic Experts Group ".jpeg",
'.png', # Portable Network Graphics ".jpg", # JPEG (Joint Photographic Experts Group)
'.gif', # Graphics Interchange Format ".png", # PNG (Portable Network Graphics)
'.bmp', # Bitmap Image File ".gif", # GIF (Graphics Interchange Format)
'.tiff', '.tif', # Tagged Image File Format ".bmp", # BMP (Bitmap Image File)
'.webp' # Web Picture Format ".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)
} }
class YesNo(Enum): class YesNo(Enum):
UNKNOWN = 'Unknown' UNKNOWN = "Unknown"
NO = 'No' NO = "No"
YES = 'Yes' YES = "Yes"
class Manga(Enum): class Manga(Enum):
UNKNOWN = 'Unknown' UNKNOWN = "Unknown"
NO = 'No' NO = "No"
YES = 'Yes' YES = "Yes"
RIGHT_LEFT = 'YesAndRightToLeft' RIGHT_LEFT = "YesAndRightToLeft"
class PageType(Enum): class PageType(Enum):
FRONT_COVER = 'FrontCover' FRONT_COVER = "FrontCover"
INNER_COVER = 'InnerCover' INNER_COVER = "InnerCover"
ROUNDUP = 'Roundup' ROUNDUP = "Roundup"
STORY = 'Story' STORY = "Story"
ADVERTISEMENT = 'Advertisement' ADVERTISEMENT = "Advertisement"
EDITORIAL = 'Editorial' EDITORIAL = "Editorial"
LETTERS = 'Letters' LETTERS = "Letters"
PREVIEW = 'Preview' PREVIEW = "Preview"
BACK_COVER = 'BackCover' BACK_COVER = "BackCover"
OTHER = 'Other' OTHER = "Other"
DELETED = 'Deleted' DELETED = "Deleted"
class AgeRating(Enum): class AgeRating(Enum):
UNKNOWN = 'Unknown' UNKNOWN = "Unknown"
ADULTS18 = 'Adults Only 18+' ADULTS18 = "Adults Only 18+"
CHILDHOOD = 'Early Childhood' CHILDHOOD = "Early Childhood"
EVERYONE = 'Everyone' EVERYONE = "Everyone"
EVERYONE10 = 'Everyone 10+' EVERYONE10 = "Everyone 10+"
G = 'G' G = "G"
KIDS = 'Kids to Adults' KIDS = "Kids to Adults"
M = 'M' M = "M"
MA15 = 'MA15+' MA15 = "MA15+"
MATURE17 = 'Mature 17+' MATURE17 = "Mature 17+"
PG = 'PG' PG = "PG"
R18 = 'R18+' R18 = "R18+"
PENDING = 'Rating Pending' PENDING = "Rating Pending"
TEEN = 'Teen' TEEN = "Teen"
X18 = 'X18+' X18 = "X18+"
class Format(Enum): class Format(Enum):
UNKNOWN = 'Unknown' UNKNOWN = "Unknown"
# ONE_SHOT = '1 Shot' # ONE_SHOT = '1 Shot'
# ONE_SHOT = '1/2', # ONE_SHOT = '1/2',
# ONE_SHOT = '1-Shot' # ONE_SHOT = '1-Shot'
ANNOTATION = 'Annotation' ANNOTATION = "Annotation"
# ANNOTATIONS = 'Annotations' # ANNOTATIONS = 'Annotations'
ANNUAL = 'Annual' ANNUAL = "Annual"
ANTHOLOGY = 'Anthology' ANTHOLOGY = "Anthology"
# BLACK_WHITE = 'B&W' # BLACK_WHITE = 'B&W'
# BLACK_WHITE = 'B/W' # BLACK_WHITE = 'B/W'
# BLACK_WHITE = 'B&&W' # BLACK_WHITE = 'B&&W'
BLACK_WHITE = 'Black & White' BLACK_WHITE = "Black & White"
# BOX_SET = 'Box Set' # BOX_SET = 'Box Set'
BOX_SET = 'Box-Set' BOX_SET = "Box-Set"
CROSSOVER = 'Crossover' CROSSOVER = "Crossover"
DIRECTOR_CUT = 'Director\'s Cut' DIRECTOR_CUT = "Director's Cut"
EPILOGUE = 'Epilogue' EPILOGUE = "Epilogue"
EVENT = 'Event' EVENT = "Event"
FCBD = 'FCBD' FCBD = "FCBD"
FLYER = 'Flyer' FLYER = "Flyer"
# GIANT_SIZE = 'Giant' # GIANT_SIZE = 'Giant'
# GIANT_SIZE = 'Giant Size' # GIANT_SIZE = 'Giant Size'
GIANT_SIZE = 'Giant-Size' GIANT_SIZE = "Giant-Size"
GRAPHIC_NOVEL = 'Graphic Novel' GRAPHIC_NOVEL = "Graphic Novel"
# HARDCOVER = 'Hardcover' # HARDCOVER = 'Hardcover'
HARDCOVER = 'Hard-Cover' HARDCOVER = "Hard-Cover"
# KING_SIZE = 'King' # KING_SIZE = 'King'
# KING_SIZE = 'King Size' # KING_SIZE = 'King Size'
KING_SIZE = 'King-Size' KING_SIZE = "King-Size"
LIMITED_SERIES = 'Limited Series' LIMITED_SERIES = "Limited Series"
MAGAZINE = 'Magazine' MAGAZINE = "Magazine"
NSFW = 'NSFW' NSFW = "NSFW"
# ONE_SHOT = 'One Shot' # ONE_SHOT = 'One Shot'
ONE_SHOT = 'One-Shot' ONE_SHOT = "One-Shot"
POINT1 = 'Point 1' POINT1 = "Point 1"
PREVIEW = 'Preview' PREVIEW = "Preview"
PROLOGUE = 'Prologue' PROLOGUE = "Prologue"
REFERENCE = 'Reference' REFERENCE = "Reference"
REVIEW = 'Review' REVIEW = "Review"
REVIEWED = 'Reviewed' REVIEWED = "Reviewed"
SCANLATION = 'Scanlation' SCANLATION = "Scanlation"
SCRIPT = 'Script' SCRIPT = "Script"
SERIES = 'Series' SERIES = "Series"
SKETCH = 'Sketch' SKETCH = "Sketch"
SPECIAL = 'Special' SPECIAL = "Special"
# TRADE_PAPER_BACK = 'TPB' # TRADE_PAPER_BACK = 'TPB'
TRADE_PAPER_BACK = 'Trade Paper Back' TRADE_PAPER_BACK = "Trade Paper Back"
# WEB_COMIC = 'WebComic' # WEB_COMIC = 'WebComic'
WEB_COMIC = 'Web Comic' WEB_COMIC = "Web Comic"
# YEAR_ONE = 'Year 1' # YEAR_ONE = 'Year 1'
YEAR_ONE = 'Year One' YEAR_ONE = "Year One"
class Rating(float): class Rating(float):
def __new__(cls, value: Union[int, float] = -1) -> float: def __new__(cls, value: Union[int, float] = -1) -> float:
assert -1 <= float(value) <= 5, f'Rating must be between 0 and 5, input {value}' assert -1 <= float(value) <= 5, f"Rating must be between 0 and 5, input {value}"
return super().__new__(cls, value) return super().__new__(cls, value)
class LanguageISO(str): class LanguageISO(str):
def __new__(cls, value: str = '') -> str: def __new__(cls, value: str = "") -> str:
assert not value or Language.get(str(value)).is_valid(), f'Invalid {value} language' assert (
not value or Language.get(str(value)).is_valid()
), f"Invalid {value} language"
return super().__new__(cls, value) return super().__new__(cls, value)
COMIC_FIELDS = { COMIC_FIELDS = {
'title': ('Title', str), "title": ("Title", str),
'series': ('Series', str), "series": ("Series", str),
'number': ('Number', int), "number": ("Number", float),
'count': ('Count', int), "count": ("Count", int),
'volume': ('Volume', int), "volume": ("Volume", int),
'alternate_series': ('AlternateSeries', str), "alternate_series": ("AlternateSeries", str),
'alternate_number': ('AlternateNumber', int), "alternate_number": ("AlternateNumber", int),
'alternate_count': ('AlternateCount', int), "alternate_count": ("AlternateCount", int),
'summary': ('Summary', str), "summary": ("Summary", str),
'notes': ('Notes', str), "notes": ("Notes", str),
'year': ('Year', int), "year": ("Year", int),
'month': ('Month', int), "month": ("Month", int),
'day': ('Day', int), "day": ("Day", int),
'writer': ('Writer', str), "writer": ("Writer", str),
'penciller': ('Penciller', str), "penciller": ("Penciller", str),
'inker': ('Inker', str), "inker": ("Inker", str),
'colorist': ('Colorist', str), "colorist": ("Colorist", str),
'letterer': ('Letterer', str), "letterer": ("Letterer", str),
'cover_artist': ('CoverArtist', str), "cover_artist": ("CoverArtist", str),
'editor': ('Editor', str), "editor": ("Editor", str),
'translator': ('Translator', str), "translator": ("Translator", str),
'publisher': ('Publisher', str), "publisher": ("Publisher", str),
'imprint': ('Imprint', str), "imprint": ("Imprint", str),
'genre': ('Genre', str), "genre": ("Genre", str),
'tags': ('Tags', str), "tags": ("Tags", str),
'web': ('Web', str), "web": ("Web", str),
'format': ('Format', Format), "format": ("Format", Format),
'ean': ('EAN', str), "ean": ("EAN", str),
'black_white': ('BlackAndWhite', YesNo), "black_white": ("BlackAndWhite", YesNo),
'manga': ('Manga', Manga), "manga": ("Manga", Manga),
'characters': ('Characters', str), "characters": ("Characters", str),
'teams': ('Teams', str), "teams": ("Teams", str),
'locations': ('Locations', str), "locations": ("Locations", str),
'scan_information': ('ScanInformation', str), "scan_information": ("ScanInformation", str),
'story_arc': ('StoryArc', str), "story_arc": ("StoryArc", str),
'story_arc_number': ('StoryArcNumber', int), "story_arc_number": ("StoryArcNumber", int),
'series_group': ('SeriesGroup', str), "series_group": ("SeriesGroup", str),
'age_rating': ('AgeRating', AgeRating), "age_rating": ("AgeRating", AgeRating),
'main_character_or_team': ('MainCharacterOrTeam', str), "main_character_or_team": ("MainCharacterOrTeam", str),
'review': ('Review', str), "review": ("Review", str),
'language_iso': ('LanguageISO', LanguageISO), "language_iso": ("LanguageISO", LanguageISO),
'community_rating': ('CommunityRating', Rating), "community_rating": ("CommunityRating", Rating),
'added': ('Added', str), "added": ("Added", str),
'released': ('Released', str), "released": ("Released", str),
'file_size': ('FileSize', int), "file_size": ("FileSize", int),
'file_modified_time': ('FileModifiedTime', str), "file_modified_time": ("FileModifiedTime", str),
'file_creation_time': ('FileCreationTime', str), "file_creation_time": ("FileCreationTime", str),
'book_price': ('BookPrice', str), "book_price": ("BookPrice", str),
'custom_values_store': ('CustomValuesStore', str) "custom_values_store": ("CustomValuesStore", str),
} }
PAGE_FIELDS = { PAGE_FIELDS = {
'image': ('@Image', int), "image": ("@Image", int),
'type': ('@Type', PageType), "type": ("@Type", PageType),
'double': ('@DoublePage', bool), "double": ("@DoublePage", bool),
'key': ('@Key', str), "key": ("@Key", str),
'bookmark': ('@Bookmark', str), "bookmark": ("@Bookmark", str),
'image_size': ('@ImageSize', int), "image_size": ("@ImageSize", int),
'image_width': ('@ImageWidth', int), "image_width": ("@ImageWidth", int),
'image_height': ('@ImageHeight', int) "image_height": ("@ImageHeight", int),
} }
+30 -5
View File
@@ -1,5 +1,19 @@
from cbz.constants import COMIC_FIELDS, PAGE_FIELDS, Format, YesNo, Manga, AgeRating, LanguageISO, Rating, PageType from cbz.constants import (
COMIC_FIELDS,
PAGE_FIELDS,
Format,
YesNo,
Manga,
AgeRating,
LanguageISO,
Rating,
PageType,
)
from cbz.utils import verify_attr, default_attr from cbz.utils import verify_attr, default_attr
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any
class BaseModel: class BaseModel:
@@ -21,7 +35,7 @@ class BaseModel:
# Set default values for each attribute based on its type # Set default values for each attribute based on its type
setattr(self, key, kwargs.get(key, default_attr(field_type))) setattr(self, key, kwargs.get(key, default_attr(field_type)))
def __setattr__(self, key: str, value: any) -> None: def __setattr__(self, key: str, value: "Any") -> None:
""" """
Sets the value of an attribute and verifies its type. Sets the value of an attribute and verifies its type.
@@ -39,6 +53,9 @@ class BaseModel:
value = field_type(value) value = field_type(value)
# Verify that the assigned value matches the expected type # Verify that the assigned value matches the expected type
verify_attr(field_type, key, value) verify_attr(field_type, key, value)
if isinstance(value, float):
if value.is_integer():
value = int(value)
except (AttributeError, KeyError): except (AttributeError, KeyError):
pass pass
super().__setattr__(key, value) super().__setattr__(key, value)
@@ -50,9 +67,15 @@ class BaseModel:
Returns: Returns:
str: A string representation of the object, displaying its class name and attribute key-value pairs. str: A string representation of the object, displaying its class name and attribute key-value pairs.
""" """
return '{name}({items})'.format( return "{name}({items})".format(
name=self.__class__.__name__, name=self.__class__.__name__,
items=', '.join([f'{k}={repr(v)}' for k, v in self.__dict__.items() if not k.startswith('_')]) items=", ".join(
[
f"{k}={repr(v)}"
for k, v in self.__dict__.items()
if not k.startswith("_")
]
),
) )
@@ -60,9 +83,10 @@ class ComicModel(BaseModel):
""" """
Model for representing comic book metadata. Model for representing comic book metadata.
""" """
title: str title: str
series: str series: str
number: int number: float
count: int count: int
volume: int volume: int
alternate_series: str alternate_series: str
@@ -124,6 +148,7 @@ class PageModel(BaseModel):
""" """
Model for representing comic book pages. Model for representing comic book pages.
""" """
type: PageType type: PageType
double: bool double: bool
image_size: int image_size: int
+3 -1
View File
@@ -75,6 +75,8 @@ class PageInfo(PageModel):
data = base64.b64decode(data) data = base64.b64decode(data)
if not isinstance(data, bytes): if not isinstance(data, bytes):
raise ValueError(f'Expecting Bytes or Base64 input, got {data!r}') 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) return cls(data, **kwargs)
@classmethod @classmethod
@@ -98,7 +100,7 @@ class PageInfo(PageModel):
path = Path(path) path = Path(path)
with path.open(mode='rb') as f: with path.open(mode='rb') as f:
kwargs.setdefault('name', path.name) kwargs.setdefault('name', path.name)
return cls(f.read(), **kwargs) return cls.loads(f.read(), **kwargs)
def show(self) -> None: def show(self) -> None:
""" """
+17 -28
View File
@@ -1,15 +1,10 @@
[build-system] [project]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "cbz" name = "cbz"
version = "3.4.3" 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." 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" license = "MIT"
authors = ["hyugogirubato <65763543+hyugogirubato@users.noreply.github.com>"] authors = [{name="hyugogirubato", email="65763543+hyugogirubato@users.noreply.github.com"}]
readme = "README.md" readme = "README.md"
repository = "https://github.com/hyugogirubato/cbz"
keywords = [ keywords = [
"python", "python",
"cbz", "cbz",
@@ -29,35 +24,29 @@ classifiers = [
"Topic :: Utilities", "Topic :: Utilities",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
] ]
include = [ requires-python = ">=3.8,<4.0"
{ path = "CHANGELOG.md", format = "sdist" },
{ path = "README.md", format = "sdist" }, dependencies= [
{ path = "LICENSE", format = "sdist" }, "langcodes>=3.4.0",
"Pillow>=10.4.0",
"pypdf >=5.7.0",
"rarfile>=4.2",
"xmltodict>=0.14.2"
] ]
[tool.poetry.urls] [tool.poetry.urls]
"Issues" = "https://github.com/hyugogirubato/cbz/issues" "Issues" = "https://github.com/hyugogirubato/cbz/issues"
"Changelog" = "https://github.com/hyugogirubato/cbz/blob/main/CHANGELOG.md" "Changelog" = "https://github.com/hyugogirubato/cbz/blob/main/CHANGELOG.md"
[tool.poetry.dependencies]
python = ">=3.8,<4.0"
langcodes = ">=3.4.0"
Pillow = ">=10.4.0"
pypdf = ">=5.7.0"
rarfile = ">=4.2"
xmltodict = ">=0.14.2"
[tool.poetry.scripts] [project.optional-dependencies]
cbzplayer = "cbz.__main__:main" pillow = ["pillow-avif-plugin>=1.5.2", "pillow-jxl-plugin>=1.3.4"]
[dependency-groups]
dev = ["pytest>=7.4.0,<9.0.0", "pytest-cov>=5.0.0"]
[[tool.poetry.source]]
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"
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]