forked from mirrors/cbz
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b234a46e00 | ||
|
|
e6d77739e1 | ||
|
|
0421d99c44 | ||
|
|
17084154a4 | ||
|
|
cba561c2ec | ||
|
|
0b21040b95 | ||
|
|
f52e112de9 | ||
|
|
2435ca396d | ||
|
|
5bc82581a3 | ||
|
|
188b2e0164 | ||
|
|
04b77d5ff9 | ||
|
|
3f777d30d6 | ||
|
|
6847e7f281 | ||
|
|
15d7aaa094 | ||
|
|
7800205e87 | ||
|
|
963ecf9e6f | ||
|
|
baee10d922 | ||
|
|
140fd6c107 | ||
|
|
7fceb86a7b | ||
|
|
eec284a3ef | ||
|
|
fe6d92229c | ||
|
|
413f9d498e | ||
|
|
a90d9abf75 | ||
|
|
4fe38f0d5a | ||
|
|
153f5f79c5 |
+2
-1
@@ -172,4 +172,5 @@ pyrightconfig.json
|
|||||||
|
|
||||||
### CBZ ###
|
### CBZ ###
|
||||||
*.cbz
|
*.cbz
|
||||||
*.pdf
|
*.pdf
|
||||||
|
*.zip
|
||||||
@@ -4,6 +4,57 @@ 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.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
|
||||||
|
|
||||||
|
- Added zoom options for displaying images in the player.
|
||||||
|
- Added scroll functionality for image display in the player, especially useful when images are zoomed.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Set a default standard size.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix lag when stretching player.
|
||||||
|
- Fix XML parsing for CBZ files with a single page.
|
||||||
|
- Fix image positioning within the player.
|
||||||
|
|
||||||
|
### New Contributors
|
||||||
|
|
||||||
|
- [gokender](https://github.com/gokender)
|
||||||
|
|
||||||
## [3.3.1] - 2024-07-15
|
## [3.3.1] - 2024-07-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -104,6 +155,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
|
|
||||||
- Initial release.
|
- Initial release.
|
||||||
|
|
||||||
|
[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.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
|
||||||
[3.2.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.2.0
|
[3.2.0]: https://github.com/hyugogirubato/cbz/releases/tag/v3.2.0
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ comic_from_cbz = ComicInfo.from_cbz('/path/to/your_comic.cbz')
|
|||||||
<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/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/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/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/gokender"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3709740?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="gokender"/></a>
|
||||||
|
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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:
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
+20
-7
@@ -1,10 +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
|
||||||
|
|
||||||
@@ -154,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:
|
||||||
@@ -200,16 +201,27 @@ 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_info['Pages'] = []
|
comic_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] + '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
|
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.
|
||||||
@@ -219,9 +231,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
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
|||||||
+162
-39
@@ -1,5 +1,6 @@
|
|||||||
import hashlib
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
import os
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from tkinter.constants import DISABLED, NORMAL
|
from tkinter.constants import DISABLED, NORMAL
|
||||||
@@ -10,10 +11,10 @@ from pathlib import Path
|
|||||||
|
|
||||||
from cbz.comic import ComicInfo
|
from cbz.comic import ComicInfo
|
||||||
from cbz.page import PageInfo
|
from cbz.page import PageInfo
|
||||||
from cbz.utils import readable_size
|
from cbz.utils import readable_size, ico_to_png
|
||||||
import os
|
|
||||||
|
|
||||||
PARENT = Path(__file__).parent
|
PARENT = Path(__file__).parent
|
||||||
|
CTRL_KEY = 0x4 # Constant for Ctrl key
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
@@ -37,6 +38,10 @@ class Player:
|
|||||||
page_index_label (ttk.Label): Label showing the current page index.
|
page_index_label (ttk.Label): Label showing the current page index.
|
||||||
previous_width (int): Previous width of the main window.
|
previous_width (int): Previous width of the main window.
|
||||||
previous_height (int): Previous height of the main window.
|
previous_height (int): Previous height of the main window.
|
||||||
|
resize_timer (int or None): Timer ID for handling window resize delay.
|
||||||
|
max_zoom_factor (float or None): Maximum allowable zoom factor based on canvas size and image dimensions.
|
||||||
|
zoom_factor (float or None): Current zoom factor for image display.
|
||||||
|
img_original (PIL.Image.Image or None): Placeholder for the original image to be displayed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, comic_info: ComicInfo):
|
def __init__(self, comic_info: ComicInfo):
|
||||||
@@ -48,6 +53,9 @@ class Player:
|
|||||||
"""
|
"""
|
||||||
self.comic_info = comic_info
|
self.comic_info = comic_info
|
||||||
self.current_page = -1 # Track the current page index
|
self.current_page = -1 # Track the current page index
|
||||||
|
self.max_zoom_factor = None # Maximum allowable zoom factor based on canvas size and image dimensions
|
||||||
|
self.zoom_factor = None # Initial zoom factor (None means no zoom initially)
|
||||||
|
self.img_original = None # Placeholder for the original image
|
||||||
|
|
||||||
# Initialize tkinter window
|
# Initialize tkinter window
|
||||||
self.root = tk.Tk()
|
self.root = tk.Tk()
|
||||||
@@ -56,12 +64,8 @@ class Player:
|
|||||||
# Set initial window size
|
# Set initial window size
|
||||||
self.root.geometry(self._get_initial_geometry())
|
self.root.geometry(self._get_initial_geometry())
|
||||||
|
|
||||||
# Check if windows or linux for window icon
|
# Set the application icon
|
||||||
if os.name == 'nt':
|
self._set_icon()
|
||||||
windll.shell32.SetCurrentProcessExplicitAppUserModelID('cbz.player')
|
|
||||||
self.root.iconbitmap(PARENT / 'cbz.ico')
|
|
||||||
else:
|
|
||||||
self.root.wm_iconphoto(True, tk.PhotoImage(file=PARENT / 'cbz.png'))
|
|
||||||
|
|
||||||
# Create main frame for content
|
# Create main frame for content
|
||||||
self.main_frame = ttk.Frame(self.root)
|
self.main_frame = ttk.Frame(self.root)
|
||||||
@@ -78,7 +82,6 @@ class Player:
|
|||||||
self._init_summary_text()
|
self._init_summary_text()
|
||||||
|
|
||||||
# Display initial page content
|
# Display initial page content
|
||||||
# TODO: Support scrollable image and/or zoom when it is too large for the screen (eg: webtoon)
|
|
||||||
self.show_page()
|
self.show_page()
|
||||||
|
|
||||||
# Bind keys for navigation and window resize
|
# Bind keys for navigation and window resize
|
||||||
@@ -88,6 +91,23 @@ class Player:
|
|||||||
self.previous_width = self.root.winfo_width()
|
self.previous_width = self.root.winfo_width()
|
||||||
self.previous_height = self.root.winfo_height()
|
self.previous_height = self.root.winfo_height()
|
||||||
|
|
||||||
|
# Initialize resize timer
|
||||||
|
self.resize_timer = None
|
||||||
|
|
||||||
|
def _set_icon(self) -> None:
|
||||||
|
"""
|
||||||
|
Set the application icon for the window.
|
||||||
|
"""
|
||||||
|
image_path = PARENT / 'cbz.ico'
|
||||||
|
if os.name == 'nt':
|
||||||
|
# Set the application ID and icon on Windows
|
||||||
|
windll.shell32.SetCurrentProcessExplicitAppUserModelID('cbz.player')
|
||||||
|
self.root.iconbitmap(image_path)
|
||||||
|
else:
|
||||||
|
# Convert ICO to PNG for other operating systems
|
||||||
|
image = ico_to_png(image_path)
|
||||||
|
self.root.iconphoto(True, tk.PhotoImage(data=image.getvalue()))
|
||||||
|
|
||||||
def _get_window_title(self) -> str:
|
def _get_window_title(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get the window title based on the comic series and title.
|
Get the window title based on the comic series and title.
|
||||||
@@ -114,6 +134,10 @@ class Player:
|
|||||||
available_width = self.root.winfo_screenwidth() - 2 * margin_width
|
available_width = self.root.winfo_screenwidth() - 2 * margin_width
|
||||||
available_height = self.root.winfo_screenheight() - 2 * margin_height
|
available_height = self.root.winfo_screenheight() - 2 * margin_height
|
||||||
|
|
||||||
|
# Default screen size for initialization
|
||||||
|
screen_width = int(available_height * 0.63)
|
||||||
|
screen_height = int(available_height)
|
||||||
|
|
||||||
# Calculate scaling factors for width and height
|
# Calculate scaling factors for width and height
|
||||||
if self.comic_info.pages:
|
if self.comic_info.pages:
|
||||||
# Get the minimum dimensions from all pages
|
# Get the minimum dimensions from all pages
|
||||||
@@ -134,8 +158,13 @@ class Player:
|
|||||||
initial_width = int(image_width * scale_factor * 0.94)
|
initial_width = int(image_width * scale_factor * 0.94)
|
||||||
initial_height = int(image_height * scale_factor)
|
initial_height = int(image_height * scale_factor)
|
||||||
else:
|
else:
|
||||||
initial_width = int(available_height * 0.63)
|
# If no pages are available, default to screen size
|
||||||
initial_height = int(available_height)
|
initial_width = screen_width
|
||||||
|
initial_height = screen_height
|
||||||
|
|
||||||
|
# Ensure that the window's initial width is at least 50% of the screen width
|
||||||
|
if 100 / screen_width * initial_width < 50.0:
|
||||||
|
initial_width = screen_width
|
||||||
|
|
||||||
# Calculate initial position to center the window on the screen
|
# Calculate initial position to center the window on the screen
|
||||||
x = self.root.winfo_screenwidth() // 2 - initial_width // 2
|
x = self.root.winfo_screenwidth() // 2 - initial_width // 2
|
||||||
@@ -176,7 +205,7 @@ class Player:
|
|||||||
self.summary_text.tag_configure('normal', font=('Arial', 12), lmargin1=10, lmargin2=10)
|
self.summary_text.tag_configure('normal', font=('Arial', 12), lmargin1=10, lmargin2=10)
|
||||||
self.summary_text.config(state=DISABLED)
|
self.summary_text.config(state=DISABLED)
|
||||||
|
|
||||||
def show_page(self):
|
def show_page(self) -> None:
|
||||||
"""
|
"""
|
||||||
Display the current page content (either summary or image).
|
Display the current page content (either summary or image).
|
||||||
"""
|
"""
|
||||||
@@ -205,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)
|
||||||
@@ -229,26 +257,40 @@ class Player:
|
|||||||
|
|
||||||
# Display image centered on canvas
|
# Display image centered on canvas
|
||||||
page: PageInfo = self.comic_info.pages[self.current_page]
|
page: PageInfo = self.comic_info.pages[self.current_page]
|
||||||
img = Image.open(BytesIO(page.content))
|
self.img_original = Image.open(BytesIO(page.content))
|
||||||
|
self._display_image()
|
||||||
|
|
||||||
# Calculate scaling factor
|
def _display_image(self) -> None:
|
||||||
scale_width = self.canvas.winfo_width() / img.width
|
"""
|
||||||
scale_height = self.canvas.winfo_height() / img.height
|
Display the image on the canvas with zooming capabilities.
|
||||||
scale_factor = min(scale_width, scale_height)
|
"""
|
||||||
|
# Check if zoom factor is not set; initialize zoom factors
|
||||||
|
if not self.zoom_factor:
|
||||||
|
# Determine maximum zoom factor based on canvas size and original image size
|
||||||
|
self.max_zoom_factor = min(
|
||||||
|
self.canvas.winfo_width() / self.img_original.width,
|
||||||
|
self.canvas.winfo_height() / self.img_original.height
|
||||||
|
)
|
||||||
|
self.zoom_factor = self.max_zoom_factor
|
||||||
|
|
||||||
# Resize image
|
# Calculate new dimensions of the image based on current zoom factor
|
||||||
new_width = int(img.width * scale_factor)
|
new_width = int(self.img_original.width * self.zoom_factor)
|
||||||
new_height = int(img.height * scale_factor)
|
new_height = int(self.img_original.height * self.zoom_factor)
|
||||||
img = img.resize((new_width, new_height), Image.LANCZOS)
|
img = self.img_original.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
# Set image on canvas
|
# Convert the resized image to PhotoImage format compatible with Tkinter canvas
|
||||||
self.canvas.image = ImageTk.PhotoImage(img)
|
self.canvas.image = ImageTk.PhotoImage(img)
|
||||||
|
|
||||||
# Calculate coordinates to center the image on the canvas
|
# Calculate the position to center the image on the canvas
|
||||||
x = (self.canvas.winfo_width() - new_width) // 2
|
x = max(0, (self.canvas.winfo_width() - new_width) // 2)
|
||||||
y = (self.canvas.winfo_height() - new_height) // 2
|
y = max(0, (self.canvas.winfo_height() - new_height) // 2)
|
||||||
self.canvas.create_image(x, y, anchor=tk.NW, image=self.canvas.image)
|
self.canvas.create_image(x, y, anchor=tk.NW, image=self.canvas.image)
|
||||||
|
|
||||||
|
# Configure the scroll region of the canvas to allow scrolling if image is larger than canvas
|
||||||
|
canvas_width = max(new_width, self.canvas.winfo_width())
|
||||||
|
canvas_height = max(new_height, self.canvas.winfo_height())
|
||||||
|
self.canvas.config(scrollregion=(0, 0, canvas_width, canvas_height))
|
||||||
|
|
||||||
def _bind_keys(self) -> None:
|
def _bind_keys(self) -> None:
|
||||||
"""
|
"""
|
||||||
Bind keys for navigation (left/right arrow keys) and window events.
|
Bind keys for navigation (left/right arrow keys) and window events.
|
||||||
@@ -257,19 +299,79 @@ class Player:
|
|||||||
self.root.bind('<Left>', lambda event: self.on_previous())
|
self.root.bind('<Left>', lambda event: self.on_previous())
|
||||||
self.root.bind('<Right>', lambda event: self.one_next())
|
self.root.bind('<Right>', lambda event: self.one_next())
|
||||||
|
|
||||||
# Bind Ctrl+C to exit application
|
# Bind Ctrl+Q to exit application
|
||||||
self.root.bind('<Control-q>', lambda event: self.root.quit())
|
self.root.bind('<Control-q>', lambda event: self.root.quit())
|
||||||
|
|
||||||
# Bind the configure event to handle window resize
|
# Bind the configure event to handle window resize
|
||||||
self.root.bind('<Configure>', lambda event: self.on_window(event))
|
self.root.bind('<Configure>', lambda event: self.on_window(event))
|
||||||
|
|
||||||
|
# Bind mouse wheel events for zooming
|
||||||
|
self.root.bind('<Control-MouseWheel>', self.on_zoom)
|
||||||
|
self.canvas.bind_all("<MouseWheel>", self._on_mouse_wheel)
|
||||||
|
self.canvas.bind_all("<Shift-MouseWheel>", self._on_shift_mouse_wheel)
|
||||||
|
|
||||||
|
# Bind zoom controls to keyboard keys
|
||||||
|
self.root.bind('<KeyPress-plus>', lambda event: self.on_zoom_key(True))
|
||||||
|
self.root.bind('<KeyPress-minus>', lambda event: self.on_zoom_key(False))
|
||||||
|
|
||||||
|
def on_zoom_key(self, positive: bool) -> None:
|
||||||
|
"""
|
||||||
|
Handle zooming triggered by keyboard keys ('+' for zoom in, '-' for zoom out).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
positive (bool): True for zoom in, False for zoom out.
|
||||||
|
"""
|
||||||
|
# Create a synthetic event object to simulate mouse wheel scrolling
|
||||||
|
event = tk.Event()
|
||||||
|
event.delta = 120 if positive else -120
|
||||||
|
self.on_zoom(event)
|
||||||
|
|
||||||
|
def on_zoom(self, event) -> None:
|
||||||
|
"""
|
||||||
|
Handle zooming using Ctrl + Mouse Wheel.
|
||||||
|
"""
|
||||||
|
if self.current_page == -1: return
|
||||||
|
|
||||||
|
# Adjust zoom factor based on mouse wheel direction
|
||||||
|
if event.delta > 0:
|
||||||
|
# Increase zoom factor by 10% for zoom in
|
||||||
|
self.zoom_factor *= 1.1
|
||||||
|
elif event.delta < 0:
|
||||||
|
# Decrease zoom factor by 10% for zoom out
|
||||||
|
self.zoom_factor /= 1.1
|
||||||
|
|
||||||
|
# Ensure the zoom-out factor doesn't go below the original image size
|
||||||
|
min_zoom_factor = min(
|
||||||
|
self.canvas.winfo_width() / self.img_original.width,
|
||||||
|
self.canvas.winfo_height() / self.img_original.height
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.zoom_factor < min_zoom_factor:
|
||||||
|
self.zoom_factor = min_zoom_factor
|
||||||
|
|
||||||
|
# Update the displayed image with the new zoom factor
|
||||||
|
self._display_image()
|
||||||
|
|
||||||
|
def _on_mouse_wheel(self, event) -> None:
|
||||||
|
"""
|
||||||
|
Handle vertical scroll using Mouse Wheel.
|
||||||
|
"""
|
||||||
|
if self.current_page == -1 or (event.state & CTRL_KEY): return
|
||||||
|
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
||||||
|
|
||||||
|
def _on_shift_mouse_wheel(self, event) -> None:
|
||||||
|
"""
|
||||||
|
Handle horizontal scroll using Shift + Mouse Wheel.
|
||||||
|
"""
|
||||||
|
if self.current_page == -1 or (event.state & CTRL_KEY): return
|
||||||
|
self.canvas.xview_scroll(int(-1 * (event.delta / 120)), "units")
|
||||||
|
|
||||||
def on_previous(self) -> None:
|
def on_previous(self) -> None:
|
||||||
"""
|
"""
|
||||||
Navigate to the previous page.
|
Navigate to the previous page.
|
||||||
"""
|
"""
|
||||||
if self.current_page > -1:
|
if self.current_page == -1: return
|
||||||
self.current_page -= 1
|
self.current_page -= 1
|
||||||
self.show_page()
|
self.show_page()
|
||||||
|
|
||||||
def one_next(self) -> None:
|
def one_next(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -287,8 +389,29 @@ class Player:
|
|||||||
if event.width != self.previous_width or event.height != self.previous_height:
|
if event.width != self.previous_width or event.height != self.previous_height:
|
||||||
self.previous_width = event.width
|
self.previous_width = event.width
|
||||||
self.previous_height = event.height
|
self.previous_height = event.height
|
||||||
# Update the image display on canvas when the window size changes
|
|
||||||
self.show_page()
|
# Cancel any existing resize timer
|
||||||
|
if self.resize_timer:
|
||||||
|
self.root.after_cancel(self.resize_timer)
|
||||||
|
|
||||||
|
# Set a new timer to call show_page after a delay
|
||||||
|
self.resize_timer = self.root.after(200, self._on_resize)
|
||||||
|
|
||||||
|
def _on_resize(self) -> None:
|
||||||
|
"""
|
||||||
|
Handle resizing of the window and adjust image display accordingly.
|
||||||
|
"""
|
||||||
|
if self.current_page == -1: return
|
||||||
|
|
||||||
|
# Reset zoom factor to None if it's equal to the maximum zoom factor
|
||||||
|
if self.zoom_factor == self.max_zoom_factor:
|
||||||
|
self.zoom_factor = None
|
||||||
|
|
||||||
|
# Update the displayed image with the current zoom factor
|
||||||
|
self._display_image()
|
||||||
|
|
||||||
|
# Reset the resize timer
|
||||||
|
self.resize_timer = None
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL.IcoImagePlugin import IcoFile
|
||||||
|
from path import Path
|
||||||
|
|
||||||
|
|
||||||
def default_attr(value: any) -> any:
|
def default_attr(value: any) -> any:
|
||||||
@@ -77,3 +82,28 @@ def readable_size(size: int, decimal: int = 2) -> str:
|
|||||||
if size < 1024:
|
if size < 1024:
|
||||||
return f'{size:.{decimal}f} {unit}'
|
return f'{size:.{decimal}f} {unit}'
|
||||||
size /= 1024
|
size /= 1024
|
||||||
|
|
||||||
|
|
||||||
|
def ico_to_png(path: Path) -> BytesIO:
|
||||||
|
"""
|
||||||
|
Converts the largest icon in an ICO file to PNG format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (Path): Path to the ICO file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BytesIO: In-memory PNG file of the largest icon.
|
||||||
|
"""
|
||||||
|
# Open the ICO file and read its content
|
||||||
|
image = Image.open(BytesIO(path.read_bytes()))
|
||||||
|
assert image.format == 'ICO', 'Unsupported image format'
|
||||||
|
|
||||||
|
# Get the ICO file object and find the largest icon size
|
||||||
|
icon: IcoFile = image.ico
|
||||||
|
max_size = max(icon.sizes(), key=lambda x: x[0] + x[1])
|
||||||
|
largest_image = icon.getimage(size=max_size)
|
||||||
|
|
||||||
|
# Save the largest icon as PNG format to an in-memory BytesIO object
|
||||||
|
content = BytesIO()
|
||||||
|
largest_image.save(content, format='PNG')
|
||||||
|
return content
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip-favicon">
|
||||||
|
<rect width="512" height="512"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g id="favicon" clip-path="url(#clip-favicon)">
|
||||||
|
<g id="clipboard" transform="translate(-23.514 -24.026)">
|
||||||
|
<g id="_000000ff" data-name="#000000ff" transform="translate(23.966 24.026)">
|
||||||
|
<path id="Path_1" data-name="Path 1" d="M169.389,31.663c.738-3.359,3.051-6.52,6.531-7.335,4.846-1.377,8.954,2.28,12.808,4.6,43.612,29.3,76.068,74.56,90.341,125.088.363,1.817,1.278-.463,1.333-1.046,8.733-27.819,26.068-52.313,47.1-72.225,24.086-22.775,52.72-40.4,83.073-53.491,3.579-1.344,7.148-3.491,11.1-3.15a8.832,8.832,0,0,1,7.511,7.577c1.6,8.767,2.963,17.588,4.5,26.366,15.154-5.4,30.3-10.87,45.573-15.914a8.733,8.733,0,0,1,10.87,4.692c4.78,10.628,9.251,21.41,13.976,32.071,5.991-3.106,11.861-6.454,17.885-9.493a8.785,8.785,0,0,1,11.267,2.5c1.6,2,1.718,4.648,1.762,7.093q-.033,159.185-.011,318.381c-.066,4.053.275,8.128-.275,12.159-.562,3.227-3.315,5.374-6.046,6.806Q429.13,469.256,329.709,522.4c-6.806,3.557-13.447,7.632-20.9,9.747a112.762,112.762,0,0,1-36.465,3.7c-11.322-.7-22.819-2.742-32.93-8.117q-102.72-54.7-205.43-109.482c-3.392-1.927-7.72-3.348-9.273-7.291-1.09-3.293-.628-6.839-.727-10.242q.1-161.415.044-322.842a8.992,8.992,0,0,1,5.33-8.844c2.941-1.388,6.322-.518,9.009,1.024,7.709,4.108,15.4,8.249,23.117,12.335,2.357-11.366,4.427-22.8,6.916-34.141.98-4.692,6.156-7.544,10.716-6.377,28.051,4.5,54.736,15.1,80.121,27.61q5.039-18.932,10.154-37.819M360.546,75.5c-19.714,14.736-37.8,32.4-50.176,53.844,30.143-29.229,67.7-49.5,106.509-64.758-.991-6.52-2.225-13-3.2-19.515A266.629,266.629,0,0,0,360.546,75.5M175.1,78.259C204.686,95.352,231.68,116.685,256,140.638c-14.482-37.236-39.615-70.661-72.39-93.656-3.018,10.374-5.628,20.87-8.5,31.278M83.761,60.616C81.845,70.782,79.719,80.9,77.737,91.057,134.059,120.947,190.259,151.09,246.57,181c8.48,3.106,17.632,3.976,26.575,4.725C237.4,142.268,193.211,105.088,142.164,80.9c-18.678-8.756-38.161-16.09-58.4-20.286m293.745,41.575a255.536,255.536,0,0,0-91.564,83.557c9.1-.848,18.447-1.674,27.037-4.945q87.737-46.867,175.529-93.59-5.617-12.885-11.178-25.782c-34.009,11.608-68.282,23.271-99.824,40.76M41.636,91.784c.055,103.579-.154,207.148.1,310.727q3.948,1.949,7.83,4.053c5.551,3.172,11.222,6.167,16.938,9.031v.165c1.905.98,3.811,1.971,5.694,2.974q81.608,43.563,163.238,87.06.05-152.66,0-305.32c-.011-1.828-.022-3.656-.044-5.474Q138.469,143.458,41.636,91.784M323.629,195.022c-.066,3.2-.077,6.4-.066,9.6q.033,150.595.022,301.2,93.866-49.84,187.577-99.945l-.055-.385c2.048-.936,4.1-1.905,6.156-2.852.308-103.6.044-207.225.132-310.837q-96.889,51.608-193.767,103.216m-70.507,5.991q-.1,137.049.011,274.1c12.974,1.806,26.178,2.654,39.251,1.355a119.414,119.414,0,0,0,13.557-1.586q-.017-137,0-274c-.925.187-2.753.551-3.667.738-12.148,1.6-24.416,2.533-36.652,1.289v-.33c-1.949-.154-3.9-.319-5.848-.485-2.225-.319-4.438-.694-6.652-1.079m-.033,291.74q-.033,11.085.011,22.17c2.709.793,5.441,1.531,8.15,2.346l4.284.661c10.385,1.189,21.1,1.4,31.344-.936a73.568,73.568,0,0,0,8.965-2.1c.2-7.357.055-14.714.1-22.059-4.042.352-8.106.584-12.1,1.311A214.472,214.472,0,0,1,253.09,492.753Z" transform="translate(-23.966 -24.026)"/>
|
||||||
|
<path id="Path_2" data-name="Path 2" d="M457.7,142.458c5.264-3.128,12.742,1.057,12.786,7.181.242,18.018.066,36.046.088,54.064a9.319,9.319,0,0,1-5.507,8.756q-67.318,35.7-134.659,71.322c-3.095,1.542-6.09,3.733-9.659,3.855a8.814,8.814,0,0,1-8.689-8.855c-.121-17.7-.044-35.4-.033-53.095a9.162,9.162,0,0,1,5.253-8.932q70.209-37.153,140.419-74.3M329.611,230.167c.011,1.476.011,2.941.022,4.416-.033,9.945,0,19.89-.011,29.824Q388.8,233.1,447.937,201.731c1.641-.947,3.3-1.839,4.978-2.72.121-11.388.022-22.775.055-34.163Q391.3,197.524,329.611,230.167Z" transform="translate(5.217 -12.134)"/>
|
||||||
|
<path id="Path_3" data-name="Path 3" d="M253.906,212.509c5.419-2.148,11.905,2.445,11.663,8.271.132,53.954.022,107.919.055,161.883.374,4.251-2.313,8.623-6.564,9.637-5.308,1.619-11.245-2.918-11-8.469-.121-54-.022-108-.055-161.993A9.061,9.061,0,0,1,253.906,212.509Z" transform="translate(-1.269 -4.983)"/>
|
||||||
|
<path id="Path_4" data-name="Path 4" d="M253.861,384.521c5.408-2.17,11.916,2.39,11.707,8.2.143,8.48.033,16.971.044,25.463a9.075,9.075,0,0,1-5.892,9.35c-5.374,2.137-11.861-2.39-11.652-8.172q-.182-13.315-.011-26.641A8.733,8.733,0,0,1,253.861,384.521Z" transform="translate(-1.268 12.444)"/>
|
||||||
|
</g>
|
||||||
|
<g id="_fcf3e7ff" data-name="#fcf3e7ff" transform="translate(77.737 45.066)">
|
||||||
|
<path id="Path_5" data-name="Path 5" d="M334.2,73.56a266.629,266.629,0,0,1,53.128-30.43c.98,6.52,2.214,13,3.2,19.515C351.718,77.9,314.163,98.174,284.02,127.4,296.4,105.96,314.483,88.3,334.2,73.56Z" transform="translate(-51.388 -43.13)" fill="#fcf3e7"/>
|
||||||
|
<path id="Path_6" data-name="Path 6" d="M161.2,76.148c2.874-10.407,5.485-20.9,8.5-31.278,32.775,23,57.908,56.421,72.39,93.656C217.775,114.573,190.782,93.24,161.2,76.148Z" transform="translate(-63.832 -42.954)" fill="#fcf3e7"/>
|
||||||
|
<path id="Path_7" data-name="Path 7" d="M78.814,57.25c20.242,4.2,39.725,11.531,58.4,20.286C188.264,101.721,232.449,138.9,268.2,182.36c-8.943-.749-18.095-1.619-26.575-4.725C185.312,147.724,129.112,117.58,72.79,87.691,74.772,77.536,76.9,67.415,78.814,57.25Z" transform="translate(-72.79 -41.699)" fill="#fcf3e7"/>
|
||||||
|
<path id="Path_8" data-name="Path 8" d="M353.4,98.75c31.542-17.489,65.815-29.152,99.824-40.76q5.551,12.9,11.178,25.782-87.7,46.9-175.529,93.59c-8.59,3.271-17.941,4.1-27.037,4.945A255.536,255.536,0,0,1,353.4,98.75Z" transform="translate(-53.635 -41.624)" fill="#fcf3e7"/>
|
||||||
|
</g>
|
||||||
|
<g id="_c68315ff" data-name="#c68315ff" transform="translate(41.606 91.784)">
|
||||||
|
<path id="Path_9" data-name="Path 9" d="M40.013,85.55q96.905,51.542,193.756,103.216c.022,1.817.033,3.645.044,5.474-7.2-3.414-14.152-7.335-21.2-11.057-47.2-25.242-94.5-50.286-141.707-75.54q-2.874-1.569-5.782-3.073c-5.595-2.919-11.1-6.013-16.751-8.822-.3,5.573-.319,11.156-.231,16.729q.05,115.655.011,231.289c-.132,18.855.275,37.72-.209,56.564q-3.882-2.1-7.83-4.053C39.859,292.7,40.068,189.129,40.013,85.55Z" transform="translate(-39.983 -85.55)" fill="#c68315"/>
|
||||||
|
</g>
|
||||||
|
<g id="_e2910dff" data-name="#e2910dff" transform="translate(292.385 91.806)">
|
||||||
|
<path id="Path_10" data-name="Path 10" d="M296.066,188.786Q393.021,137.31,489.833,85.57c-.088,103.612.176,207.236-.132,310.837-2.059.947-4.108,1.916-6.156,2.852-.3-1.971-.584-3.954-.7-5.936q0-147.093.011-294.218a127.8,127.8,0,0,0-14.394,7.291C411,137.112,353.379,167.53,296,198.389,295.989,195.185,296,191.991,296.066,188.786Z" transform="translate(-264.822 -85.57)" fill="#e2910d"/>
|
||||||
|
<path id="Path_11" data-name="Path 11" d="M277.58,185.348c.914-.187,2.742-.551,3.667-.738q.017,137,0,274a119.414,119.414,0,0,1-13.557,1.586c2.709-.529,5.385-1.255,8.073-1.916q.2-128.921.055-257.842C276,195.414,274.849,189.907,277.58,185.348Z" transform="translate(-267.69 -75.535)" fill="#e2910d"/>
|
||||||
|
<path id="Path_12" data-name="Path 12" d="M269.01,451.011c4-.727,8.062-.958,12.1-1.311-.044,7.346.1,14.7-.1,22.06a73.568,73.568,0,0,1-8.965,2.1c1.211-.9,2.4-1.839,3.579-2.775.1-6.729.143-13.458-.077-20.187C273.371,450.922,271.191,450.944,269.01,451.011Z" transform="translate(-267.556 -48.675)" fill="#e2910d"/>
|
||||||
|
</g>
|
||||||
|
<g id="_f8a928ff" data-name="#f8a928ff" transform="translate(49.565 101.982)">
|
||||||
|
<path id="Path_13" data-name="Path 13" d="M47.408,111.539c-.088-5.573-.066-11.156.231-16.729,5.65,2.808,11.156,5.9,16.751,8.822-.43,46.685-.055,93.37-.176,140.055-.033,54.912.055,109.824-.066,164.736-5.716-2.863-11.388-5.859-16.938-9.031.485-18.844.077-37.709.209-56.564Q47.4,227.189,47.408,111.539Z" transform="translate(-47.21 -94.81)" fill="#f8a928"/>
|
||||||
|
<path id="Path_14" data-name="Path 14" d="M468.456,105.151a127.8,127.8,0,0,1,14.394-7.291q-.05,147.109-.011,294.218c.121,1.982.408,3.965.7,5.936l.055.385q-93.833,49.873-187.577,99.945-.083-150.611-.022-301.2c57.379-30.859,115-61.278,172.456-91.993m-9.141,41.718q-70.242,37.071-140.419,74.3a9.162,9.162,0,0,0-5.253,8.932c-.011,17.7-.088,35.4.033,53.095a8.814,8.814,0,0,0,8.689,8.855c3.568-.121,6.564-2.313,9.659-3.855q67.318-35.65,134.659-71.322a9.319,9.319,0,0,0,5.507-8.756c-.022-18.018.154-36.046-.088-54.064C472.058,147.926,464.58,143.741,459.315,146.869Z" transform="translate(-22.002 -94.501)" fill="#f8a928"/>
|
||||||
|
<path id="Path_15" data-name="Path 15" d="M68.09,105.61c47.2,25.253,94.5,50.3,141.707,75.54,7.048,3.722,14,7.643,21.2,11.057q.066,152.66,0,305.32-81.625-43.5-163.238-87.06c.231-64.02.022-128.04.1-192.06C68.024,180.808,67.573,143.2,68.09,105.61Z" transform="translate(-45.128 -93.716)" fill="#f8a928"/>
|
||||||
|
<path id="Path_16" data-name="Path 16" d="M232.045,184.73c2.214.385,4.427.76,6.652,1.079.22,91.134.055,182.269.1,273.4,1.861.132,3.711.253,5.573.374.363-90.98.044-181.971.176-272.963,12.236,1.244,24.5.308,36.652-1.289-2.731,4.559-1.575,10.066-1.762,15.088q-.033,128.921-.055,257.842c-2.687.661-5.363,1.388-8.073,1.916-13.073,1.3-26.278.452-39.251-1.355q-.149-137.049-.011-274.1m23.48,30.54a9.061,9.061,0,0,0-5.9,9.328c.033,54-.066,108,.055,161.994-.242,5.551,5.694,10.088,11,8.469,4.251-1.013,6.938-5.385,6.564-9.637-.033-53.965.077-107.93-.055-161.883.242-5.826-6.245-10.419-11.663-8.271m-.044,189.438a8.733,8.733,0,0,0-5.8,8.2q-.165,13.315.011,26.641c-.209,5.782,6.278,10.308,11.652,8.172a9.075,9.075,0,0,0,5.892-9.35c-.011-8.491.1-16.982-.044-25.463C267.4,407.1,260.889,402.539,255.481,404.708Z" transform="translate(-28.488 -85.699)" fill="#f8a928"/>
|
||||||
|
<path id="Path_17" data-name="Path 17" d="M232.011,449.63a214.472,214.472,0,0,0,40.749,1.388c2.181-.066,4.361-.088,6.542-.11.22,6.729.176,13.458.077,20.187-1.178.936-2.368,1.872-3.579,2.775-10.242,2.335-20.958,2.126-31.344.936.066-7.775.407-15.573-.363-23.326-1.652-.319-3.293-.65-4.934-.969-.4,6.09-.573,12.2-.374,18.3a12.568,12.568,0,0,0,1.388,5.33c-2.709-.815-5.441-1.553-8.15-2.346Q231.973,460.715,232.011,449.63Z" transform="translate(-28.487 -58.859)" fill="#f8a928"/>
|
||||||
|
</g>
|
||||||
|
<g id="_fabf5dff" data-name="#fabf5dff" transform="translate(66.504 110.804)">
|
||||||
|
<path id="Path_18" data-name="Path 18" d="M62.832,102.82q2.907,1.5,5.782,3.073c-.518,37.588-.066,75.2-.231,112.8-.077,64.02.132,128.04-.1,192.06-1.883-1-3.789-1.993-5.694-2.974v-.165c.121-54.912.033-109.824.066-164.736C62.777,196.19,62.4,149.505,62.832,102.82Z" transform="translate(-62.59 -102.82)" fill="#fabf5d"/>
|
||||||
|
</g>
|
||||||
|
<g id="_d9d9d9ff" data-name="#d9d9d9ff" transform="translate(358.794 176.74)">
|
||||||
|
<path id="Path_19" data-name="Path 19" d="M327.99,228.009q61.669-32.676,123.359-65.319c-.033,11.388.066,22.775-.055,34.163-1.674.881-3.337,1.773-4.978,2.72-.154-9.78.187-19.559-.2-29.328-39.515,20.441-78.557,41.8-118.106,62.181C328,230.95,328,229.485,327.99,228.009Z" transform="translate(-327.99 -162.69)" fill="#d9d9d9"/>
|
||||||
|
</g>
|
||||||
|
<g id="_ffffffff" data-name="#ffffffff" transform="translate(358.801 184.295)">
|
||||||
|
<path id="Path_20" data-name="Path 20" d="M328.011,231.731c39.548-20.385,78.59-41.74,118.106-62.181.385,9.769.044,19.548.2,29.328Q387.241,230.365,328,261.554C328.011,251.621,327.978,241.676,328.011,231.731Z" transform="translate(-327.996 -169.55)" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
<g id="_f8ba55ff" data-name="#f8ba55ff" transform="translate(259.775 202.092)">
|
||||||
|
<path id="Path_21" data-name="Path 21" d="M238.08,185.71c1.949.165,3.9.33,5.848.485v.33c-.132,90.991.187,181.983-.176,272.963-1.861-.121-3.711-.242-5.573-.374C238.135,367.979,238.3,276.844,238.08,185.71Z" transform="translate(-238.08 -185.71)" fill="#f8ba55"/>
|
||||||
|
<path id="Path_22" data-name="Path 22" d="M238.168,468.734c-.2-6.1-.022-12.214.374-18.3,1.641.319,3.282.65,4.934.969.771,7.753.43,15.551.363,23.326l-4.284-.661A12.568,12.568,0,0,1,238.168,468.734Z" transform="translate(-238.079 -158.888)" fill="#f8ba55"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 12 KiB |
@@ -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
+6
-5
@@ -333,18 +333,19 @@ reference = "localpypi"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "70.3.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-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"},
|
{file = "setuptools-71.0.3-py3-none-any.whl", hash = "sha256:f501b6e6db709818dc76882582d9c516bf3b67b948864c5fa1d1624c09a49207"},
|
||||||
{file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"},
|
{file = "setuptools-71.0.3.tar.gz", hash = "sha256:3d8531791a27056f4a38cd3e54084d8b1c4228ff9cf3f2d7dd075ec99f9fd70d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
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"]
|
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)"]
|
||||||
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.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 (<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"]
|
||||||
|
|
||||||
[package.source]
|
[package.source]
|
||||||
type = "legacy"
|
type = "legacy"
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "cbz"
|
name = "cbz"
|
||||||
version = "3.3.1"
|
version = "3.3.4"
|
||||||
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>"]
|
||||||
|
|||||||
Reference in New Issue
Block a user