diff --git a/pyproject.toml b/pyproject.toml index a6bb69aa7..e5e812eda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "pillow-avif-plugin~=1.5", "pillow-heif~=0.22", "pillow-jxl-plugin~=1.3", + "py7zr==1.0.0", "pydantic~=2.10", "pydub~=0.25", "PySide6==6.8.0.*", diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index 77a65c9b1..fe60d14d0 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -20,6 +20,7 @@ import cv2 import numpy as np import pillow_avif # noqa: F401 # pyright: ignore[reportUnusedImport] +import py7zr import rarfile import rawpy import srctools @@ -93,6 +94,21 @@ logger.exception('[ThumbRenderer] Could not import the "pillow_jxl" module') +class _SevenZipFile(py7zr.SevenZipFile): + """Wrapper around py7zr.SevenZipFile to mimic zipfile.ZipFile's API.""" + + def __init__(self, filepath: Path, mode: Literal["r"]) -> None: + super().__init__(filepath, mode) + + def read(self, name: str) -> bytes: + # SevenZipFile must be reset after every extraction + # See https://py7zr.readthedocs.io/en/stable/api.html#py7zr.SevenZipFile.extract + self.reset() + factory = py7zr.io.BytesIOFactory(limit=10485760) # 10 MiB + self.extract(targets=[name], factory=factory) + return factory.get(name).read() + + class _TarFile(tarfile.TarFile): """Wrapper around tarfile.TarFile to mimic zipfile.ZipFile's API.""" @@ -106,8 +122,10 @@ def read(self, name: str) -> bytes: return self.extractfile(name).read() -type _Archive_T = type[zipfile.ZipFile] | type[rarfile.RarFile] | type[_TarFile] -type _Archive = zipfile.ZipFile | rarfile.RarFile | _TarFile +type _Archive_T = ( + type[zipfile.ZipFile] | type[rarfile.RarFile] | type[_SevenZipFile] | type[_TarFile] +) +type _Archive = zipfile.ZipFile | rarfile.RarFile | _SevenZipFile | _TarFile class ThumbRenderer(QObject): @@ -890,7 +908,9 @@ def _epub_cover(filepath: Path, ext: str) -> Image.Image | None: im: Image.Image | None = None try: archiver: _Archive_T = zipfile.ZipFile - if ext == ".cbr": + if ext == ".cb7": + archiver = _SevenZipFile + elif ext == ".cbr": archiver = rarfile.RarFile elif ext == ".cbt": archiver = _TarFile