-
-
Notifications
You must be signed in to change notification settings - Fork 29
Add support for new illustrations APIs in libzim 9.4.0 #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: libzim_9.4.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| from __future__ import annotations | ||
|
|
||
| class IllustrationInfo: | ||
| """Information about an illustration in a ZIM archive.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| width: int = 0, | ||
| height: int = 0, | ||
| scale: float = 1.0, | ||
| extra_attributes: dict[str, str] | None = None, | ||
| ) -> None: ... | ||
| @staticmethod | ||
| def from_metadata_item_name(name: str) -> IllustrationInfo: ... | ||
| @property | ||
| def width(self) -> int: ... | ||
| @width.setter | ||
| def width(self, value: int) -> None: ... | ||
| @property | ||
| def height(self) -> int: ... | ||
| @height.setter | ||
| def height(self, value: int) -> None: ... | ||
| @property | ||
| def scale(self) -> float: ... | ||
| @scale.setter | ||
| def scale(self, value: float) -> None: ... | ||
| @property | ||
| def extra_attributes(self) -> dict[str, str]: ... | ||
| @extra_attributes.setter | ||
| def extra_attributes(self, value: dict[str, str]) -> None: ... | ||
| def as_metadata_item_name(self) -> str: ... | ||
| def __repr__(self) -> str: ... | ||
| def __eq__(self, other: object) -> bool: ... |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -479,20 +479,34 @@ cdef class _Creator: | |||||
| self.c_creator.setMainPath(mainPath.encode('UTF-8')) | ||||||
| return self | ||||||
|
|
||||||
| def add_illustration(self, int size: pyint, content: bytes): | ||||||
| def add_illustration(self, size_or_info, content: bytes): | ||||||
| """Add a PNG illustration to Archive. | ||||||
|
|
||||||
| Refer to https://wiki.openzim.org/wiki/Metadata for more details. | ||||||
|
|
||||||
| Args: | ||||||
| size (int): The width of the square PNG illustration in pixels. | ||||||
| size_or_info: Either an int (width of the square PNG illustration in pixels) | ||||||
| or an IllustrationInfo object with width, height, and scale. | ||||||
| content (bytes): The binary content of the PNG illustration. | ||||||
|
|
||||||
| Raises: | ||||||
| RuntimeError: If an illustration with the same width already exists. | ||||||
| RuntimeError: If an illustration with the same attributes already exists. | ||||||
|
|
||||||
| Examples: | ||||||
| # Old style (square illustration at scale 1) | ||||||
| creator.add_illustration(48, png_data) | ||||||
|
|
||||||
| # New style (with dimensions and scale) | ||||||
| info = IllustrationInfo(48, 48, 2.0) | ||||||
| creator.add_illustration(info, png_data) | ||||||
| """ | ||||||
| cdef string _content = content | ||||||
| self.c_creator.addIllustration(size, _content) | ||||||
| if isinstance(size_or_info, IllustrationInfo): | ||||||
| self.c_creator.addIllustration((<IllustrationInfo>size_or_info).c_info, _content) | ||||||
| elif isinstance(size_or_info, int): | ||||||
| self.c_creator.addIllustration(<int>size_or_info, _content) | ||||||
| else: | ||||||
| raise TypeError(f"First argument must be int or IllustrationInfo, not {type(size_or_info)}") | ||||||
|
|
||||||
| # def set_uuid(self, uuid) -> _Creator: | ||||||
| # self.c_creator.setUuid(uuid) | ||||||
|
|
@@ -762,6 +776,146 @@ writer_public_objects = [ | |||||
| writer = create_module(writer_module_name, writer_module_doc, writer_public_objects) | ||||||
|
|
||||||
|
|
||||||
| ############################################################################### | ||||||
| # Illustration module # | ||||||
| ############################################################################### | ||||||
|
|
||||||
| illustration_module_name = f"{__name__}.illustration" | ||||||
|
|
||||||
| cdef class IllustrationInfo: | ||||||
| """Information about an illustration in a ZIM archive. | ||||||
|
|
||||||
| Attributes: | ||||||
| width (int): Width of the illustration in CSS pixels. | ||||||
| height (int): Height of the illustration in CSS pixels. | ||||||
| scale (float): Device pixel ratio (scale) of the illustration. | ||||||
| extra_attributes (dict): Additional attributes as key-value pairs. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| """ | ||||||
| __module__ = illustration_module_name | ||||||
| cdef zim.IllustrationInfo c_info | ||||||
| def __cinit__(self, width: pyint = 0, height: pyint = 0, scale: float = 1.0, extra_attributes: Dict[str, str] = None): | ||||||
| """Create an IllustrationInfo. | ||||||
|
|
||||||
| Args: | ||||||
| width: Width of the illustration in CSS pixels. | ||||||
| height: Height of the illustration in CSS pixels. | ||||||
| scale: Device pixel ratio (default: 1.0). | ||||||
| extra_attributes: Additional attributes as key-value pairs (optional). | ||||||
| """ | ||||||
| # Initialize struct fields directly | ||||||
| self.c_info.width = width | ||||||
| self.c_info.height = height | ||||||
| self.c_info.scale = scale | ||||||
| self.c_info.extraAttributes = zim.Attributes({}) | ||||||
|
|
||||||
| # Set extra attributes if provided (need to encode strings to bytes) | ||||||
| if extra_attributes is not None: | ||||||
| for key, val in extra_attributes.items(): | ||||||
| self.c_info.extraAttributes[key.encode('UTF-8')] = val.encode('UTF-8') | ||||||
|
|
||||||
| @staticmethod | ||||||
| cdef from_illustration_info(zim.IllustrationInfo info): | ||||||
| """Creates a Python IllustrationInfo from a C++ IllustrationInfo. | ||||||
|
|
||||||
| Args: | ||||||
| info: A C++ IllustrationInfo | ||||||
|
|
||||||
| Returns: | ||||||
| IllustrationInfo: Casted illustration info | ||||||
| """ | ||||||
| cdef IllustrationInfo ii = IllustrationInfo() | ||||||
| ii.c_info = move(info) | ||||||
| return ii | ||||||
| @staticmethod | ||||||
| def from_metadata_item_name(name: str) -> IllustrationInfo: | ||||||
| """Parse an illustration metadata item name into IllustrationInfo. | ||||||
|
|
||||||
| Args: | ||||||
| name: The metadata item name (e.g., "Illustration_48x48@2"). | ||||||
|
|
||||||
| Returns: | ||||||
| The parsed IllustrationInfo. | ||||||
|
|
||||||
| Raises: | ||||||
| RuntimeError: If the name cannot be parsed. | ||||||
| """ | ||||||
| cdef string _name = name.encode('UTF-8') | ||||||
| cdef zim.IllustrationInfo info = zim.IllustrationInfo.fromMetadataItemName(_name) | ||||||
| return IllustrationInfo.from_illustration_info(move(info)) | ||||||
| @property | ||||||
| def width(self) -> pyint: | ||||||
| """Width of the illustration in CSS pixels.""" | ||||||
| return self.c_info.width | ||||||
| @width.setter | ||||||
| def width(self, value: pyint): | ||||||
| self.c_info.width = value | ||||||
| @property | ||||||
| def height(self) -> pyint: | ||||||
| """Height of the illustration in CSS pixels.""" | ||||||
| return self.c_info.height | ||||||
| @height.setter | ||||||
| def height(self, value: pyint): | ||||||
| self.c_info.height = value | ||||||
| @property | ||||||
| def scale(self) -> float: | ||||||
| """Device pixel ratio (scale) of the illustration.""" | ||||||
| return self.c_info.scale | ||||||
| @scale.setter | ||||||
| def scale(self, value: float): | ||||||
| self.c_info.scale = value | ||||||
| @property | ||||||
| def extra_attributes(self) -> Dict[str, str]: | ||||||
| """Additional attributes as key-value pairs.""" | ||||||
| result = {} | ||||||
| for item in self.c_info.extraAttributes: | ||||||
| result[item.first.decode('UTF-8')] = item.second.decode('UTF-8') | ||||||
| return result | ||||||
| @extra_attributes.setter | ||||||
| def extra_attributes(self, value: Dict[str, str]): | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like extra_attributes is mutable ( |
||||||
| """Set additional attributes.""" | ||||||
| self.c_info.extraAttributes.clear() | ||||||
| for key, val in value.items(): | ||||||
| self.c_info.extraAttributes[key.encode('UTF-8')] = val.encode('UTF-8') | ||||||
| def as_metadata_item_name(self) -> str: | ||||||
| """Convert this IllustrationInfo to a metadata item name. | ||||||
|
|
||||||
| Returns: | ||||||
| The metadata item name (e.g., "Illustration_48x48@2"). | ||||||
| """ | ||||||
| return self.c_info.asMetadataItemName().decode('UTF-8') | ||||||
| def __repr__(self) -> str: | ||||||
| return f"IllustrationInfo(width={self.width}, height={self.height}, scale={self.scale})" | ||||||
| def __eq__(self, other) -> pybool: | ||||||
| if not isinstance(other, IllustrationInfo): | ||||||
| return False | ||||||
| return (self.width == other.width and | ||||||
| self.height == other.height and | ||||||
| self.scale == other.scale and | ||||||
| self.extra_attributes == other.extra_attributes) | ||||||
|
|
||||||
|
|
||||||
| illustration_module_doc = """Illustration data structures for ZIM archives | ||||||
|
|
||||||
| This module provides classes for working with illustrations in ZIM archives. | ||||||
|
|
||||||
| Usage: | ||||||
|
|
||||||
| ```python | ||||||
| from libzim.illustration import IllustrationInfo | ||||||
|
|
||||||
| # Create an IllustrationInfo | ||||||
| info = IllustrationInfo(48, 48, 2.0) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Creator usage sample and README should be amended as well |
||||||
| print(f"Metadata name: {info.as_metadata_item_name()}") | ||||||
|
|
||||||
| # Parse from metadata name | ||||||
| parsed = IllustrationInfo.from_metadata_item_name("Illustration_48x48@2") | ||||||
| ```""" | ||||||
| illustration_public_objects = [ | ||||||
| IllustrationInfo, | ||||||
| ] | ||||||
| illustration = create_module(illustration_module_name, illustration_module_doc, illustration_public_objects) | ||||||
|
|
||||||
|
|
||||||
| ############################################################################### | ||||||
| # Reader module # | ||||||
| ############################################################################### | ||||||
|
|
@@ -1329,19 +1483,57 @@ cdef class Archive: | |||||
| return self.c_archive.hasIllustration(size) | ||||||
| return self.c_archive.hasIllustration() | ||||||
|
|
||||||
| def get_illustration_item(self, size: pyint = None) -> Item: | ||||||
| def get_illustration_item(self, size: pyint = None, info: IllustrationInfo = None) -> Item: | ||||||
| """Get the illustration Metadata item of the archive. | ||||||
|
|
||||||
| Args: | ||||||
| size: Optional size of the illustration (for backward compatibility). | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If optional, the default should be mentioned |
||||||
| info: Optional IllustrationInfo with width, height, and scale. | ||||||
|
|
||||||
| Returns: | ||||||
| The illustration item. | ||||||
|
|
||||||
| Note: | ||||||
| Either provide size (int) or info (IllustrationInfo), not both. | ||||||
| If neither is provided, returns the default illustration item. | ||||||
| """ | ||||||
| try: | ||||||
| if size is not None: | ||||||
| return Item.from_item(move(self.c_archive.getIllustrationItem(size))) | ||||||
| if info is not None: | ||||||
| return Item.from_item(move(self.c_archive.getIllustrationItem(info.c_info))) | ||||||
| elif size is not None: | ||||||
| return Item.from_item(move(self.c_archive.getIllustrationItem(<int>size))) | ||||||
| return Item.from_item(move(self.c_archive.getIllustrationItem())) | ||||||
| except RuntimeError as e: | ||||||
| raise KeyError(str(e)) | ||||||
|
|
||||||
| def get_illustration_infos(self, width: pyint = None, height: pyint = None, | ||||||
| min_scale: float = None) -> List[IllustrationInfo]: | ||||||
| """Get information about available illustrations. | ||||||
|
|
||||||
| Args: | ||||||
| width: Optional width to filter illustrations (must be provided with height). | ||||||
| height: Optional height to filter illustrations (must be provided with width). | ||||||
| min_scale: Optional minimum scale to filter illustrations (requires width and height). | ||||||
|
|
||||||
| Returns: | ||||||
| List of IllustrationInfo objects describing available illustrations. | ||||||
|
|
||||||
| Note: | ||||||
| - When called without arguments, returns all available illustrations. | ||||||
| - When called with width, height, and min_scale, filters illustrations. | ||||||
| """ | ||||||
| cdef zim.Archive.IllustrationInfos infos | ||||||
| if width is not None and height is not None and min_scale is not None: | ||||||
| infos = self.c_archive.getIllustrationInfos(width, height, min_scale) | ||||||
| elif width is None and height is None and min_scale is None: | ||||||
| infos = self.c_archive.getIllustrationInfos() | ||||||
| else: | ||||||
| raise ValueError("Either provide all of (width, height, min_scale) or none of them") | ||||||
| result = [] | ||||||
| for info in infos: | ||||||
| result.append(IllustrationInfo.from_illustration_info(info)) | ||||||
| return result | ||||||
|
|
||||||
| @property | ||||||
| def dirent_cache_max_size(self) -> pyint: | ||||||
| """Maximum size of the dirent cache. | ||||||
|
|
@@ -1380,7 +1572,7 @@ def get_cluster_cache_max_size() -> pyint: | |||||
| """Get the maximum size of the cluster cache. | ||||||
|
|
||||||
| Returns: | ||||||
| (int): the maximum memory size used by the cluster cache (in bytes). | ||||||
| (int): the maximum memory size used by the cluster cache (in bytes). | ||||||
| """ | ||||||
| return zim.getClusterCacheMaxSize() | ||||||
|
|
||||||
|
|
@@ -1400,7 +1592,7 @@ def get_cluster_cache_current_size() -> pyint: | |||||
| """Get the current size of the cluster cache. | ||||||
|
|
||||||
| Returns: | ||||||
| (int): the current memory size (in bytes) used by the cluster cache. | ||||||
| (int): the current memory size (in bytes) used by the cluster cache. | ||||||
| """ | ||||||
| return zim.getClusterCacheCurrentSize() | ||||||
|
|
||||||
|
|
@@ -1743,6 +1935,7 @@ class ModuleLoader(importlib.abc.Loader): | |||||
| @staticmethod | ||||||
| def create_module(spec): | ||||||
| return { | ||||||
| 'libzim.illustration': illustration, | ||||||
| 'libzim.writer': writer, | ||||||
| 'libzim.reader': reader, | ||||||
| 'libzim.search': search, | ||||||
|
|
@@ -1766,4 +1959,4 @@ class ModuleFinder(importlib.abc.MetaPathFinder): | |||||
| # register finder for our submodules | ||||||
| sys.meta_path.insert(0, ModuleFinder()) | ||||||
|
|
||||||
| __all__ = ["writer", "reader", "search", "suggestion", "version"] | ||||||
| __all__ = ["illustration", "writer", "reader", "search", "suggestion", "version"] | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size_or_infois not annotated