Compare commits

...
8 Commits
Author SHA1 Message Date
hyugogirubato 0b21040b95 Release v3.3.3 2024-07-18 22:12:15 +02:00
hyugogirubato f52e112de9 Fixed saving pages in XML 2024-07-18 22:11:24 +02:00
hyugogirubato 2435ca396d Update .gitignore 2024-07-18 22:11:01 +02:00
hyugogirubato 5bc82581a3 Removed MD5 display 2024-07-18 22:10:51 +02:00
hyugogirubato 188b2e0164 New information fields in the XML 2024-07-18 22:10:26 +02:00
hyugogirubato 04b77d5ff9 Updated ComicInfo.xsd 2024-07-18 22:10:13 +02:00
hyugogirubato 3f777d30d6 Fix jpg format key 2024-07-18 20:58:42 +02:00
hyugogirubato 6847e7f281 Added pdf support 2024-07-18 00:11:23 +02:00
10 changed files with 86 additions and 29 deletions
+2 -1
View File
@@ -172,4 +172,5 @@ pyrightconfig.json
### CBZ ### ### CBZ ###
*.cbz *.cbz
*.pdf *.pdf
*.zip
+23
View File
@@ -4,6 +4,28 @@ 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.3.3] - 2024-07-18
### Added
- Reference links for XML schema.
- New information fields in the XML.
### 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 ## [3.3.2] - 2024-07-18
### Added ### Added
@@ -125,6 +147,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Initial release. - Initial release.
[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.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.1]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.1
[3.3.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.0 [3.3.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.3.0
+1 -1
View File
@@ -19,7 +19,7 @@ def main() -> None:
# Create ComicInfo object from CBZ file # Create ComicInfo object from CBZ file
try: 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 # Launch the CBZ player
comic_info.show() comic_info.show()
except Exception as e: except Exception as e:
+20 -7
View File
@@ -1,11 +1,11 @@
from __future__ import annotations from __future__ import annotations
import zipfile import zipfile
from datetime import datetime
from enum import Enum from enum import Enum
from io import BytesIO from io import BytesIO
from typing import Union from typing import Union
from functools import cache
from pathlib import Path from pathlib import Path
@@ -147,7 +147,7 @@ class ComicInfo(ComicModel):
if XML_NAME in names: if XML_NAME in names:
with zf.open(XML_NAME, 'r') as f: 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) names.remove(XML_NAME)
comic = __info( comic = __info(
@@ -155,7 +155,7 @@ class ComicInfo(ComicModel):
fields=COMIC_FIELDS fields=COMIC_FIELDS
) )
pages_info = comic_info.get('Pages', []) 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: if suffix:
@@ -201,16 +201,28 @@ class ComicInfo(ComicModel):
items={k: v for k, v in self.__dict__.items() if not k.startswith('_')}, items={k: v for k, v in self.__dict__.items() if not k.startswith('_')},
fields=COMIC_FIELDS) fields=COMIC_FIELDS)
comic_pages = []
comic_info['Pages'] = [] comic_info['Pages'] = []
for page in self.pages: for i, page in enumerate(self.pages):
page_info = __info( page_info = __info(
items={k: v for k, v in page.__dict__.items() if not k.startswith('_')}, items={k: v for k, v in page.__dict__.items() if not k.startswith('_')},
fields=PAGE_FIELDS) 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] + ''
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 return comic_info
@cache
def pack(self) -> bytes: def pack(self) -> bytes:
""" """
Pack the comic information and pages into a CBZ file format. Pack the comic information and pages into a CBZ file format.
@@ -220,9 +232,10 @@ class ComicInfo(ComicModel):
""" """
zip_buffer = BytesIO() zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_STORED) as zf: with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_STORED) as zf:
content = xmltodict.unparse({'ComicInfo': self.get_info()}, pretty=True)
zf.writestr( zf.writestr(
XML_NAME, 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): for i, page in enumerate(self.pages):
zf.writestr(f'page-{i + 1:03d}{page.suffix}', page.content) zf.writestr(f'page-{i + 1:03d}{page.suffix}', page.content)
+17 -9
View File
@@ -5,7 +5,7 @@ from langcodes import Language
XML_NAME = 'ComicInfo.xml' XML_NAME = 'ComicInfo.xml'
IMAGE_FORMAT = { IMAGE_FORMAT = {
'.jpeg', ' .jpg', # Joint Photographic Experts Group '.jpeg', '.jpg', # Joint Photographic Experts Group
'.png', # Portable Network Graphics '.png', # Portable Network Graphics
'.gif', # Graphics Interchange Format '.gif', # Graphics Interchange Format
'.bmp', # Bitmap Image File '.bmp', # Bitmap Image File
@@ -169,15 +169,23 @@ COMIC_FIELDS = {
'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),
'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 = { PAGE_FIELDS = {
'type': ('Type', PageType), 'image': ('@Image', int),
'double': ('DoublePage', bool), 'type': ('@Type', PageType),
'key': ('Key', str), 'double': ('@DoublePage', bool),
'bookmark': ('Bookmark', str), 'key': ('@Key', str),
'image_size': ('ImageSize', int), 'bookmark': ('@Bookmark', str),
'image_width': ('ImageWidth', int), 'image_size': ('@ImageSize', int),
'image_height': ('ImageHeight', int) 'image_width': ('@ImageWidth', int),
'image_height': ('@ImageHeight', int)
} }
+7
View File
@@ -102,6 +102,13 @@ class ComicModel(BaseModel):
review: str review: str
language_iso: LanguageISO language_iso: LanguageISO
community_rating: Rating 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): def __init__(self, **kwargs):
""" """
+5 -7
View File
@@ -1,4 +1,3 @@
import hashlib
import tkinter as tk import tkinter as tk
import os import os
@@ -235,17 +234,16 @@ class Player:
# Display summary for the first page # Display summary for the first page
infos = self.comic_info.get_info() 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.config(state=NORMAL)
self.summary_text.delete('1.0', tk.END) self.summary_text.delete('1.0', tk.END)
for key, value in infos.items(): for key, value in infos.items():
self.summary_text.insert(tk.END, f'{key}\n', 'bold') if not (key.startswith('@') or key == 'Pages'):
self.summary_text.insert(tk.END, f'{value}\n\n', 'normal') 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.config(state=DISABLED)
self.summary_text.place(relwidth=1, relheight=1) self.summary_text.place(relwidth=1, relheight=1)
+7
View File
@@ -46,6 +46,13 @@
<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: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:sequence>
</xs:complexType> </xs:complexType>
<xs:simpleType name="YesNo"> <xs:simpleType name="YesNo">
Generated
+3 -3
View File
@@ -333,13 +333,13 @@ reference = "localpypi"
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "71.0.0" version = "71.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-71.0.0-py3-none-any.whl", hash = "sha256:f06fbe978a91819d250a30e0dc4ca79df713d909e24438a42d0ec300fc52247f"}, {file = "setuptools-71.0.3-py3-none-any.whl", hash = "sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207"},
{file = "setuptools-71.0.0.tar.gz", hash = "sha256:98da3b8aca443b9848a209ae4165e2edede62633219afa493a58fbba57f72e2e"}, {file = "setuptools-71.0.3.tar.gz", hash = "sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d"},
] ]
[package.extras] [package.extras]
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry] [tool.poetry]
name = "cbz" name = "cbz"
version = "3.3.2" version = "3.3.3"
description = "CBZ simplifies creating, managing, and viewing comic book files in CBZ format, offering seamless packaging, metadata handling, and built-in viewing capabilities" 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" license = "MIT"
authors = ["hyugogirubato <65763543+hyugogirubato@users.noreply.github.com>"] authors = ["hyugogirubato <65763543+hyugogirubato@users.noreply.github.com>"]