forked from mirrors/cbz
146 lines
4.3 KiB
Python
146 lines
4.3 KiB
Python
import struct
|
|
from enum import Enum
|
|
from io import BytesIO
|
|
|
|
from PIL import Image
|
|
from path import Path
|
|
|
|
|
|
def extract_ico(path: Path) -> list[Image]:
|
|
"""
|
|
Extracts all the images from an ICO file.
|
|
|
|
Args:
|
|
path (Path): Path to the ICO file.
|
|
|
|
Returns:
|
|
list[Image]: List of PIL Image objects representing the icons in the ICO file.
|
|
"""
|
|
# https://stackoverflow.com/questions/74341558/how-do-i-read-a-different-size-of-an-icon-file
|
|
# https://en.wikipedia.org/wiki/ICO_(file_format)
|
|
icons = []
|
|
|
|
# Constants for ICO file structure
|
|
ICONDIRSIZE = 6
|
|
ICONDIRENTRYSIZE = 16
|
|
|
|
# Read the entire ICO file content
|
|
d = path.read_bytes()
|
|
|
|
# Extract ICONDIR header
|
|
hdrReserved, hdrImageType, hdrNumImages = struct.unpack('<HHH', d[:ICONDIRSIZE])
|
|
|
|
# Process each image entry in the ICO file
|
|
for i in range(hdrNumImages):
|
|
start = ICONDIRSIZE + i * ICONDIRENTRYSIZE
|
|
ICONDIRENTRY = d[start:start + ICONDIRENTRYSIZE]
|
|
|
|
# Unpack ICONDIRENTRY fields
|
|
width, height, palColours, reserved, planes, bpp, nBytes, offset = struct.unpack('<BBBBHHII', ICONDIRENTRY)
|
|
|
|
# Create a new in-memory ICO file with one icon in it
|
|
hdr = struct.pack('<HHH', hdrReserved, hdrImageType, 1)
|
|
dirent = struct.pack('<BBBBHHII', width, height, palColours, reserved, planes, bpp, nBytes, ICONDIRSIZE + ICONDIRENTRYSIZE)
|
|
pxData = d[offset:offset + nBytes]
|
|
inMemoryICO = BytesIO(hdr + dirent + pxData)
|
|
|
|
# Open the image using PIL
|
|
im = Image.open(inMemoryICO)
|
|
icons.append(im)
|
|
|
|
return icons
|
|
|
|
|
|
def default_attr(value: any) -> any:
|
|
"""
|
|
Provides a default value based on the expected type of an attribute.
|
|
|
|
Args:
|
|
value (any): Expected type or class of the attribute.
|
|
|
|
Returns:
|
|
any: Default value appropriate for the specified type or class.
|
|
- For Enum types: Returns the 'UNKNOWN' member if available, otherwise the first member.
|
|
- For int or float: Returns -1.
|
|
- For bool: Returns False.
|
|
- For str: Returns an empty string.
|
|
- For other types: Invokes the callable (assuming it's a function or callable object).
|
|
"""
|
|
if issubclass(value, Enum):
|
|
keys = [i.name for i in list(value)]
|
|
return value['UNKNOWN' if 'UNKNOWN' in keys else 'STORY']
|
|
elif value in (int, float):
|
|
return -1
|
|
elif value == bool:
|
|
return False
|
|
elif value == str:
|
|
return ''
|
|
else:
|
|
return value()
|
|
|
|
|
|
def verify_attr(expected_type: any, key: str, value: any) -> None:
|
|
"""
|
|
Verifies if the provided value matches the expected type.
|
|
|
|
Args:
|
|
expected_type (any): Expected type of the attribute.
|
|
key (str): Name of the attribute.
|
|
value (any): Value to be verified against the expected type.
|
|
|
|
Raises:
|
|
TypeError: If the provided value does not match the expected type.
|
|
"""
|
|
if not isinstance(value, expected_type):
|
|
raise TypeError(f'Expected type {expected_type} for attribute "{key}", but got {type(value)}')
|
|
|
|
|
|
def repr_attr(value: any) -> any:
|
|
"""
|
|
Provides a representation of the attribute's value.
|
|
|
|
Args:
|
|
value (any): Value of the attribute.
|
|
|
|
Returns:
|
|
any: Representation of the attribute's value.
|
|
- For Enum types: Returns the value of the Enum.
|
|
- For other types: Returns the value itself.
|
|
"""
|
|
if isinstance(value, Enum):
|
|
return value.value
|
|
return value
|
|
|
|
|
|
def readable_size(size: int, decimal: int = 2) -> str:
|
|
"""
|
|
Converts a file size in bytes to a human-readable string format.
|
|
|
|
Args:
|
|
size (int): The size in bytes.
|
|
decimal (int): Number of decimal places to display (default is 2).
|
|
|
|
Returns:
|
|
str: Human-readable string representation of the size.
|
|
"""
|
|
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
|
if size < 1024:
|
|
return f'{size:.{decimal}f} {unit}'
|
|
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.
|
|
"""
|
|
icons = extract_ico(path)
|
|
content = BytesIO()
|
|
icons[-1].save(content, format='PNG')
|
|
return content
|