# standard imports from functools import partial import contextlib import io import os import shutil import tempfile import typing # external imports from preview_generator.manager import PreviewManager import PIL.Image # bsie imports from bsie.utils import errors # inner-module imports from .. import base # exports __all__: typing.Sequence[str] = ( 'PreviewGeneratorReader', ) ## code ## class PreviewGeneratorReader(base.Reader): """Uses preview_generator to create previews for various data formats. See `https://github.com/algoo/preview-generator`_ for details. """ # PreviewManager instance. _mngr: PreviewManager # Set of mime types supported by PreviewManager. _supported_mimetypes: typing.Set[str] # PreviewManager cache. _cache: str # Determines whether the cache directory should be deleted after use. _cleanup: bool def __init__(self, cache: typing.Optional[str] = None): # initialize cache directory # TODO: initialize in memory, e.g., via PyFilesystem if cache is None: self._cache = tempfile.mkdtemp(prefix='bsie-preview-cache-') self._cleanup = True else: self._cache = cache self._cleanup = False # create preview generator with contextlib.redirect_stderr(io.StringIO()): self._mngr = PreviewManager(self._cache, create_folder=True) self._supported_mimetypes = set(self._mngr.get_supported_mimetypes()) def __del__(self): if self._cleanup: shutil.rmtree(self._cache, ignore_errors=True) def __call__(self, path: str) -> typing.Callable[[int], PIL.Image.Image]: if not os.path.exists(path): raise errors.ReaderError(path) if self._mngr.get_mimetype(path) not in self._supported_mimetypes: raise errors.UnsupportedFileFormatError(path) return partial(self._preview_callback, path) def _preview_callback(self, path: str, max_side: int) -> PIL.Image.Image: """Produce a jpeg preview of *path* with at most *max_side* side length.""" try: # generate the preview preview_path = self._mngr.get_jpeg_preview(path, width=max_side, height=max_side) # open the preview and return return PIL.Image.open(preview_path) except Exception as err: # FIXME: less generic exception! raise errors.ReaderError(path) from err ## EOF ##