mirror of
https://github.com/hyugogirubato/cbz.git
synced 2026-09-26 13:31:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04feaa7027 | ||
|
|
8b8069e38d | ||
|
|
d357ad3e1d | ||
|
|
b234a46e00 | ||
|
|
e6d77739e1 | ||
|
|
0421d99c44 | ||
|
|
17084154a4 | ||
|
|
cba561c2ec | ||
|
|
0b21040b95 | ||
|
|
f52e112de9 | ||
|
|
2435ca396d | ||
|
|
5bc82581a3 | ||
|
|
188b2e0164 | ||
|
|
04b77d5ff9 | ||
|
|
3f777d30d6 | ||
|
|
6847e7f281 |
+2
-1
@@ -172,4 +172,5 @@ pyrightconfig.json
|
||||
|
||||
### CBZ ###
|
||||
*.cbz
|
||||
*.pdf
|
||||
*.pdf
|
||||
*.zip
|
||||
@@ -4,6 +4,42 @@ 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.3.5] - 2024-07-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `Path` import using `pathlib`.
|
||||
|
||||
## [3.3.4] - 2024-07-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove invalid dict key.
|
||||
- Fix invalid utc format.
|
||||
|
||||
## [3.3.3] - 2024-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- Reference links for XML schema.
|
||||
- New information fields in the XML.
|
||||
- Support PDF files via `cbzplayer` command.
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed MD5 display in the player (irrelevant).
|
||||
- The displayed size in the player now reflects the total size of all images.
|
||||
- Simplified the closing XML `Page` tag.
|
||||
- Sorted `Page` keys in order.
|
||||
- Updated `ComicInfo.xsd` defining the CBZ standard.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the `.jpg` constant preventing the use of certain images.
|
||||
- Fixed memory error related to the `pack()` method cache.
|
||||
- Fixed saving pages in XML to comply with the standard.
|
||||
- Added missing mandatory keys in the XML.
|
||||
|
||||
## [3.3.2] - 2024-07-18
|
||||
|
||||
### Added
|
||||
@@ -125,6 +161,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
- Initial release.
|
||||
|
||||
[3.3.5]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.5
|
||||
[3.3.4]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.4
|
||||
[3.3.3]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.3
|
||||
[3.3.2]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.2
|
||||
[3.3.1]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.1
|
||||
[3.3.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.0
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ def main() -> None:
|
||||
|
||||
# Create ComicInfo object from CBZ file
|
||||
try:
|
||||
comic_info = ComicInfo.from_cbz(path=comic_path)
|
||||
comic_info = ComicInfo.from_pdf(comic_path) if comic_path.suffix == '.pdf' else ComicInfo.from_cbz(comic_path)
|
||||
# Launch the CBZ player
|
||||
comic_info.show()
|
||||
except Exception as e:
|
||||
|
||||
+20
-8
@@ -1,11 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
|
||||
from enum import Enum
|
||||
from io import BytesIO
|
||||
from typing import Union
|
||||
from functools import cache
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
@@ -147,7 +147,7 @@ class ComicInfo(ComicModel):
|
||||
|
||||
if XML_NAME in names:
|
||||
with zf.open(XML_NAME, 'r') as f:
|
||||
comic_info = xmltodict.parse(f.read(), force_list=('Pages',)).get('ComicInfo', {})
|
||||
comic_info = xmltodict.parse(f.read()).get('ComicInfo', {})
|
||||
names.remove(XML_NAME)
|
||||
|
||||
comic = __info(
|
||||
@@ -155,7 +155,7 @@ class ComicInfo(ComicModel):
|
||||
fields=COMIC_FIELDS
|
||||
)
|
||||
|
||||
pages_info = comic_info.get('Pages', [])
|
||||
pages_info = comic_info.get('Pages', {}).get('Page', [])
|
||||
for i, name in enumerate(names):
|
||||
suffix = Path(name).suffix
|
||||
if suffix:
|
||||
@@ -201,16 +201,27 @@ class ComicInfo(ComicModel):
|
||||
items={k: v for k, v in self.__dict__.items() if not k.startswith('_')},
|
||||
fields=COMIC_FIELDS)
|
||||
|
||||
comic_info['Pages'] = []
|
||||
for page in self.pages:
|
||||
comic_pages = []
|
||||
for i, page in enumerate(self.pages):
|
||||
page_info = __info(
|
||||
items={k: v for k, v in page.__dict__.items() if not k.startswith('_')},
|
||||
fields=PAGE_FIELDS)
|
||||
comic_info['Pages'].append(page_info)
|
||||
page_info['@Image'] = i
|
||||
comic_pages.append(dict(sorted(page_info.items())))
|
||||
|
||||
# https://github.com/anansi-project/rfcs/issues/3#issuecomment-671631676
|
||||
utcnow = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
|
||||
comic_info.update({
|
||||
'@xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
|
||||
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'FileSize': comic_info.get('FileSize', sum(p.image_size for p in self.pages)),
|
||||
'FileCreationTime': comic_info.get('FileCreationTime', utcnow),
|
||||
'FileModifiedTime': comic_info.get('FileModifiedTime', utcnow),
|
||||
'PageCount': len(self.pages),
|
||||
'Pages': {'Page': comic_pages}
|
||||
})
|
||||
return comic_info
|
||||
|
||||
@cache
|
||||
def pack(self) -> bytes:
|
||||
"""
|
||||
Pack the comic information and pages into a CBZ file format.
|
||||
@@ -220,9 +231,10 @@ class ComicInfo(ComicModel):
|
||||
"""
|
||||
zip_buffer = BytesIO()
|
||||
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_STORED) as zf:
|
||||
content = xmltodict.unparse({'ComicInfo': self.get_info()}, pretty=True)
|
||||
zf.writestr(
|
||||
XML_NAME,
|
||||
xmltodict.unparse({'ComicInfo': self.get_info()}, pretty=True).encode('utf-8')
|
||||
content.replace('></Page>', ' />').encode('utf-8')
|
||||
)
|
||||
for i, page in enumerate(self.pages):
|
||||
zf.writestr(f'page-{i + 1:03d}{page.suffix}', page.content)
|
||||
|
||||
+17
-9
@@ -5,7 +5,7 @@ from langcodes import Language
|
||||
|
||||
XML_NAME = 'ComicInfo.xml'
|
||||
IMAGE_FORMAT = {
|
||||
'.jpeg', ' .jpg', # Joint Photographic Experts Group
|
||||
'.jpeg', '.jpg', # Joint Photographic Experts Group
|
||||
'.png', # Portable Network Graphics
|
||||
'.gif', # Graphics Interchange Format
|
||||
'.bmp', # Bitmap Image File
|
||||
@@ -169,15 +169,23 @@ COMIC_FIELDS = {
|
||||
'main_character_or_team': ('MainCharacterOrTeam', str),
|
||||
'review': ('Review', str),
|
||||
'language_iso': ('LanguageISO', LanguageISO),
|
||||
'community_rating': ('CommunityRating', Rating)
|
||||
'community_rating': ('CommunityRating', Rating),
|
||||
'added': ('Added', str),
|
||||
'released': ('Released', str),
|
||||
'file_size': ('FileSize', int),
|
||||
'file_modified_time': ('FileModifiedTime', str),
|
||||
'file_creation_time': ('FileCreationTime', str),
|
||||
'book_price': ('BookPrice', str),
|
||||
'custom_values_store': ('CustomValuesStore', str)
|
||||
}
|
||||
|
||||
PAGE_FIELDS = {
|
||||
'type': ('Type', PageType),
|
||||
'double': ('DoublePage', bool),
|
||||
'key': ('Key', str),
|
||||
'bookmark': ('Bookmark', str),
|
||||
'image_size': ('ImageSize', int),
|
||||
'image_width': ('ImageWidth', int),
|
||||
'image_height': ('ImageHeight', int)
|
||||
'image': ('@Image', int),
|
||||
'type': ('@Type', PageType),
|
||||
'double': ('@DoublePage', bool),
|
||||
'key': ('@Key', str),
|
||||
'bookmark': ('@Bookmark', str),
|
||||
'image_size': ('@ImageSize', int),
|
||||
'image_width': ('@ImageWidth', int),
|
||||
'image_height': ('@ImageHeight', int)
|
||||
}
|
||||
|
||||
@@ -102,6 +102,13 @@ class ComicModel(BaseModel):
|
||||
review: str
|
||||
language_iso: LanguageISO
|
||||
community_rating: Rating
|
||||
added: str
|
||||
released: str
|
||||
file_size: int
|
||||
file_modified_time: str
|
||||
file_creation_time: str
|
||||
book_price: str
|
||||
custom_values_store: str
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
|
||||
+5
-7
@@ -1,4 +1,3 @@
|
||||
import hashlib
|
||||
import tkinter as tk
|
||||
import os
|
||||
|
||||
@@ -235,17 +234,16 @@ class Player:
|
||||
|
||||
# Display summary for the first page
|
||||
infos = self.comic_info.get_info()
|
||||
packed = self.comic_info.pack()
|
||||
infos['Pages'] = len(infos['Pages'])
|
||||
infos['Size'] = readable_size(len(packed), decimal=2)
|
||||
infos['MD5'] = hashlib.md5(packed).hexdigest()
|
||||
|
||||
self.summary_text.config(state=NORMAL)
|
||||
self.summary_text.delete('1.0', tk.END)
|
||||
|
||||
for key, value in infos.items():
|
||||
self.summary_text.insert(tk.END, f'{key}\n', 'bold')
|
||||
self.summary_text.insert(tk.END, f'{value}\n\n', 'normal')
|
||||
if not (key.startswith('@') or key == 'Pages'):
|
||||
if key == 'FileSize':
|
||||
value = readable_size(value)
|
||||
self.summary_text.insert(tk.END, f'{key}\n', 'bold')
|
||||
self.summary_text.insert(tk.END, f'{value}\n\n', 'normal')
|
||||
|
||||
self.summary_text.config(state=DISABLED)
|
||||
self.summary_text.place(relwidth=1, relheight=1)
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
from PIL.IcoImagePlugin import IcoFile
|
||||
from path import Path
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def default_attr(value: any) -> any:
|
||||
|
||||
@@ -46,6 +46,13 @@
|
||||
<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="Review" type="xs:string"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Added" type="xs:string"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="Released" type="xs:string"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="FileSize" type="xs:int"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="FileModifiedTime" type="xs:string"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="FileCreationTime" type="xs:string"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="BookPrice" type="xs:string"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" default="" name="CustomValuesStore" type="xs:string"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="YesNo">
|
||||
|
||||
Generated
+5
-5
@@ -333,19 +333,19 @@ reference = "localpypi"
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "71.0.0"
|
||||
version = "71.1.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-71.0.0-py3-none-any.whl", hash = "sha256:f06fbe978a91819d250a30e0dc4ca79df713d909e24438a42d0ec300fc52247f"},
|
||||
{file = "setuptools-71.0.0.tar.gz", hash = "sha256:98da3b8aca443b9848a209ae4165e2edede62633219afa493a58fbba57f72e2e"},
|
||||
{file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"},
|
||||
{file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (<7.4)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry]
|
||||
name = "cbz"
|
||||
version = "3.3.2"
|
||||
version = "3.3.5"
|
||||
description = "CBZ simplifies creating, managing, and viewing comic book files in CBZ format, offering seamless packaging, metadata handling, and built-in viewing capabilities"
|
||||
license = "MIT"
|
||||
authors = ["hyugogirubato <65763543+hyugogirubato@users.noreply.github.com>"]
|
||||
|
||||
Reference in New Issue
Block a user