def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: mimetypes_mapping = [MimetypeMapping("image/webp", ".webp") ] # type: typing.List[MimetypeMapping] mimetypes_mapping = (mimetypes_mapping + cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING + cls.SUPPORTED_HEIC_MIMETYPE_MAPPING) return mimetypes_mapping
class ImagePreviewBuilderSketch(PreviewBuilder): SKETCH_MIMETYPES_MAPPING = [ MimetypeMapping("application/sketch", ".sketch") ] @classmethod def get_label(cls) -> str: return "Images generator from sketch files" @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: mimetypes = [] for mimetype_mapping in cls.get_mimetypes_mapping(): mimetypes.append(mimetype_mapping.mimetype) return mimetypes @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return cls.SKETCH_MIMETYPES_MAPPING def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size with tempfile.TemporaryDirectory( prefix="preview-generator-") as tmp_dir: with open(file_path, "rb") as filestream: zip = zipfile.ZipFile(filestream) zip.extract("previews/preview.png", tmp_dir) zip.close() ImagePreviewBuilderPillow().build_jpeg_preview( tmp_dir + "/previews/preview.png", preview_name, cache_path, page_id, extension, size, mimetype, ) def has_jpeg_preview(self) -> bool: return True def get_page_number(self, file_path: str, preview_name: str, cache_path: str, mimetype: str = "") -> int: return 1
class ImagePreviewBuilderVtk(PreviewBuilder): PLY_MIMETYPES_MAPPING = [MimetypeMapping("application/ply", ".ply")] OBJ_MIMETYPES_MAPPING = [ MimetypeMapping("application/wobj", ".obj"), MimetypeMapping("application/object", ".obj"), MimetypeMapping("model/obj", ".obj"), ] STL_MIMETYPES_MAPPING = [ MimetypeMapping("application/sla", ".stl"), MimetypeMapping("application/vnd.ms-pki.stl", ".stl"), MimetypeMapping("application/x-navistyle", ".stl"), MimetypeMapping("model/stl", ".stl"), ] @classmethod def get_label(cls) -> str: return "Images generator from 3d file - based on Vtk" @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: mimetypes = [] for mimetype_mapping in cls.get_mimetypes_mapping(): mimetypes.append(mimetype_mapping.mimetype) return mimetypes @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return cls.STL_MIMETYPES_MAPPING + cls.OBJ_MIMETYPES_MAPPING + cls.PLY_MIMETYPES_MAPPING @classmethod def check_dependencies(cls) -> None: if not vtk_installed: raise BuilderDependencyNotFound( "this builder requires vtk to be available") @classmethod def dependencies_versions(cls) -> typing.Optional[str]: vtk_version = vtkVersion() return "VTK version :{}".format(vtk_version.GetVTKVersion()) @classmethod def _get_vtk_reader(cls, mimetype: str) -> "vtkAbstractPolyDataReader": if mimetype in [ mapping.mimetype for mapping in cls.STL_MIMETYPES_MAPPING ]: return vtkSTLReader() elif mimetype in [ mapping.mimetype for mapping in cls.OBJ_MIMETYPES_MAPPING ]: return vtkOBJReader() elif mimetype in [ mapping.mimetype for mapping in cls.PLY_MIMETYPES_MAPPING ]: return vtkPLYReader() else: raise UnsupportedMimeType( "Unsupported mimetype: {}".format(mimetype)) def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size colors = vtkNamedColors() if not mimetype: guessed_mimetype, _ = mimetypes_storage.guess_type(file_path, strict=False) # INFO - G.M - 2019-11-22 - guessed_mimetype can be None mimetype = guessed_mimetype or "" reader = self._get_vtk_reader(mimetype) reader.SetFileName(file_path) mapper = vtkPolyDataMapper() mapper.SetInputConnection(reader.GetOutputPort()) actor = vtkActor() actor.SetMapper(mapper) rotation = (-70, 0, 45) R_x, R_y, R_z = rotation # TODO set a good looking default orientation actor.RotateX(R_x) actor.RotateY(R_y) actor.RotateZ(R_z) # Create a rendering window and renderer ren = vtkRenderer() renWin = vtkRenderWindow() renWin.OffScreenRenderingOn() renWin.AddRenderer(ren) renWin.SetSize(size.width, size.height) ren.SetBackground(colors.GetColor3d("white")) # Assign actor to the renderer ren.AddActor(actor) renWin.Render() # Write image windowto_image_filter = vtkWindowToImageFilter() windowto_image_filter.SetInput(renWin) # windowto_image_filter.SetScale(scale) # image scale windowto_image_filter.SetInputBufferTypeToRGBA() with tempfile.NamedTemporaryFile("w+b", prefix="preview-generator-", suffix=".png") as tmp_png: writer = vtkPNGWriter() writer.SetFileName(tmp_png.name) writer.SetInputConnection(windowto_image_filter.GetOutputPort()) writer.Write() return ImagePreviewBuilderPillow().build_jpeg_preview( tmp_png.name, preview_name, cache_path, page_id, extension, size, mimetype) def has_jpeg_preview(self) -> bool: return True def get_page_number(self, file_path: str, preview_name: str, cache_path: str, mimetype: str = "") -> int: return 1
class ImagePreviewBuilderRawpy(ImagePreviewBuilder): weight = 150 SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING = [ MimetypeMapping("image/x-sony-arw", ".arw"), MimetypeMapping("image/x-adobe-dng", ".dng"), MimetypeMapping("image/x-sony-sr2", ".sr2"), MimetypeMapping("image/x-sony-srf", ".srf"), MimetypeMapping("image/x-sigma-x3f", ".x3f"), MimetypeMapping("image/x-canon-crw", ".crw"), MimetypeMapping("image/x-canon-cr2", ".cr2"), MimetypeMapping("image/x-epson-erf", ".erf"), MimetypeMapping("image/x-fuji-raf", ".raf"), MimetypeMapping("image/x-nikon-nef", ".nef"), MimetypeMapping("image/x-olympus-orf", ".orf"), MimetypeMapping("image/x-panasonic-raw", ".raw"), MimetypeMapping("image/x-panasonic-rw2", ".rw2"), MimetypeMapping("image/x-pentax-pef", ".pef"), MimetypeMapping("image/x-kodak-dcr", ".dcr"), MimetypeMapping("image/x-kodak-k25", ".k25"), MimetypeMapping("image/x-kodak-kdc", ".kdc"), MimetypeMapping("image/x-minolta-mrw", ".mrw"), MimetypeMapping("image/x-samsung-srw", ".srw"), ] @classmethod def get_label(cls) -> str: return "Rawpy Preview Builder" @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: mimes = [] for mimetype_mapping in cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING: mimes.append(mimetype_mapping.mimetype) return mimes @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING @classmethod def check_dependencies(cls) -> None: if not rawpy_installed: raise BuilderDependencyNotFound("this builder requires rawpy to be available") def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpeg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size with tempfile.NamedTemporaryFile( "w+b", prefix="preview-generator", suffix=".tiff" ) as tmp_tiff: with rawpy.imread(file_path) as raw: processed_image = raw.postprocess(use_auto_wb=True) with Image.from_array(processed_image) as img: img.save(filename=tmp_tiff.name) return ImagePreviewBuilderWand().build_jpeg_preview( tmp_tiff.name, preview_name, cache_path, page_id, extension, size, mimetype )
class ImagePreviewBuilderIMConvert(ImagePreviewBuilder): MIMETYPES = [] # type: typing.List[str] # TODO - G.M - 2019-11-21 - find better storage solution for mimetype mapping # dict and/or list. # see https://github.com/algoo/preview-generator/pull/148#discussion_r346381508 SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING = [ MimetypeMapping("image/x-sony-arw", ".arw"), MimetypeMapping("image/x-adobe-dng", ".dng"), MimetypeMapping("image/x-sony-sr2", ".sr2"), MimetypeMapping("image/x-sony-srf", ".srf"), MimetypeMapping("image/x-sigma-x3f", ".x3f"), MimetypeMapping("image/x-canon-crw", ".crw"), MimetypeMapping("image/x-canon-cr2", ".cr2"), MimetypeMapping("image/x-epson-erf", ".erf"), MimetypeMapping("image/x-fuji-raf", ".raf"), MimetypeMapping("image/x-nikon-nef", ".nef"), MimetypeMapping("image/x-olympus-orf", ".orf"), MimetypeMapping("image/x-panasonic-raw", ".raw"), MimetypeMapping("image/x-panasonic-rw2", ".rw2"), MimetypeMapping("image/x-pentax-pef", ".pef"), MimetypeMapping("image/x-kodak-dcr", ".dcr"), MimetypeMapping("image/x-kodak-k25", ".k25"), MimetypeMapping("image/x-kodak-kdc", ".kdc"), MimetypeMapping("image/x-minolta-mrw", ".mrw"), ] SUPPORTED_HEIC_MIMETYPE_MAPPING = [ MimetypeMapping("image/heic", ".heic"), MimetypeMapping("image/heic", ".heif"), ] """ IM means Image Magick""" @classmethod def get_label(cls) -> str: return "Images - based on convert command (Image magick)" @classmethod def __load_mimetypes(cls) -> typing.List[str]: """ Load supported mimetypes from WAND library :return: list of supported mime types """ mimes = imagemagick_supported_mimes() # type: typing.List[str] # HACK - G.M - 2019-10-31 - Handle raw format only if ufraw-batch is installed as most common # default imagemagick configuration delegate raw format to ufraw-batch. if executable_is_available("ufraw-batch"): for mimetype_mapping in cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING: mimes.append(mimetype_mapping.mimetype) # HACK - G.M - 2019-11-14 - disable support for postscript file in imagemagick to use # pillow instead mimes.remove("application/postscript") mimes.append("application/x-xcf") mimes.append("image/x-xcf") return mimes @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: """ :return: list of supported mime types """ if len(ImagePreviewBuilderIMConvert.MIMETYPES) == 0: ImagePreviewBuilderIMConvert.MIMETYPES = cls.__load_mimetypes() return ImagePreviewBuilderIMConvert.MIMETYPES @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: mimetypes_mapping = [] # type: typing.List[MimetypeMapping] mimetypes_mapping = (mimetypes_mapping + cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING + cls.SUPPORTED_HEIC_MIMETYPE_MAPPING) return mimetypes_mapping @classmethod def check_dependencies(cls) -> None: if not executable_is_available("convert"): raise BuilderDependencyNotFound( "this builder requires convert to be available") @classmethod def dependencies_versions(cls) -> typing.Optional[str]: return "{} from {}".format( check_output(["convert", "--version"], universal_newlines=True).split("\n")[0], which("convert"), ) def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size # inkscape tesselation-P3.svg -e with tempfile.NamedTemporaryFile("w+b", prefix="preview-generator-", suffix=".png") as tmp_png: build_png_result_code = self._imagemagick_convert( source_path=file_path, dest_path=tmp_png.name, mimetype=mimetype) if build_png_result_code != 0: raise IntermediateFileBuildingFailed( "Building PNG intermediate file using convert " "failed with status {}".format(build_png_result_code)) return ImagePreviewBuilderPillow().build_jpeg_preview( tmp_png.name, preview_name, cache_path, page_id, extension, size) def _imagemagick_convert(self, source_path: str, dest_path: str, mimetype: typing.Optional[str] = None) -> int: """ Try convert using both explicit or implicit input type convert. """ assert mimetype != "" # INFO - G.M - 2019-11-14 - use explicit input type to clarify conversion for imagemagick do_an_explicit_convert = False input_file_extension = "" # type: str if mimetype is not None: input_file_extension = mimetypes_storage.guess_extension( mimetype, strict=False) or "" if input_file_extension: do_an_explicit_convert = True if do_an_explicit_convert: explicit_source_path = "{}:{}".format( input_file_extension.lstrip("."), source_path) build_image_result_code = check_call( [ "convert", explicit_source_path, "-layers", "merge", dest_path ], stdout=DEVNULL, stderr=STDOUT, ) # INFO - G.M - 2019-11-14 - if explicit convert failed, fallback to # implicit input type convert if build_image_result_code != 0: build_image_result_code = check_call( ["convert", source_path, "-layers", "merge", dest_path], stdout=DEVNULL, stderr=STDOUT, ) else: build_image_result_code = check_call( ["convert", source_path, "-layers", "merge", dest_path], stdout=DEVNULL, stderr=STDOUT, ) return build_image_result_code
def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return [ MimetypeMapping( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx" ) ]
class ImagePreviewBuilderWand(ImagePreviewBuilder): weight = 30 MIMETYPES = [] # type: typing.List[str] # TODO - G.M - 2019-11-21 - find better storage solution for mimetype mapping # dict and/or list. # see https://github.com/algoo/preview-generator/pull/148#discussion_r346381508 SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING = [ MimetypeMapping("image/x-sony-arw", ".arw"), MimetypeMapping("image/x-adobe-dng", ".dng"), MimetypeMapping("image/x-sony-sr2", ".sr2"), MimetypeMapping("image/x-sony-srf", ".srf"), MimetypeMapping("image/x-sigma-x3f", ".x3f"), MimetypeMapping("image/x-canon-crw", ".crw"), MimetypeMapping("image/x-canon-cr2", ".cr2"), MimetypeMapping("image/x-epson-erf", ".erf"), MimetypeMapping("image/x-fuji-raf", ".raf"), MimetypeMapping("image/x-nikon-nef", ".nef"), MimetypeMapping("image/x-olympus-orf", ".orf"), MimetypeMapping("image/x-panasonic-raw", ".raw"), MimetypeMapping("image/x-panasonic-rw2", ".rw2"), MimetypeMapping("image/x-pentax-pef", ".pef"), MimetypeMapping("image/x-kodak-dcr", ".dcr"), MimetypeMapping("image/x-kodak-k25", ".k25"), MimetypeMapping("image/x-kodak-kdc", ".kdc"), MimetypeMapping("image/x-minolta-mrw", ".mrw"), ] SUPPORTED_HEIC_MIMETYPE_MAPPING = [ MimetypeMapping("image/heic", ".heic"), MimetypeMapping("image/heic", ".heif"), ] def __init__( self, quality: int = DEFAULT_JPEG_QUALITY, progressive: bool = DEFAULT_JPEG_PROGRESSIVE, ): super().__init__() self.quality = quality self.progressive = progressive @classmethod def get_label(cls) -> str: return "Images - based on WAND (image magick)" @classmethod def dependencies_versions(cls) -> typing.Optional[str]: return "wand {} from {}".format(wand.version.VERSION, ", ".join(wand.__path__)) @classmethod def __load_mimetypes(cls) -> typing.List[str]: """ Load supported mimetypes from WAND library :return: list of supported mime types """ mimes = imagemagick_supported_mimes() # type: typing.List[str] # HACK - G.M - 2019-10-31 - Handle raw format only if ufraw-batch is installed as most common # default imagemagick configuration delegate raw format to ufraw-batch. if executable_is_available("ufraw-batch"): for mimetype_mapping in cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING: mimes.append(mimetype_mapping.mimetype) if executable_is_available("dwebp"): mimes.append("image/webp") return mimes @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: mimetypes_mapping = [MimetypeMapping("image/webp", ".webp") ] # type: typing.List[MimetypeMapping] mimetypes_mapping = (mimetypes_mapping + cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING + cls.SUPPORTED_HEIC_MIMETYPE_MAPPING) return mimetypes_mapping @classmethod def check_dependencies(cls) -> None: if not executable_is_available("convert"): raise BuilderDependencyNotFound( "this builder requires convert to be available") @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: """ :return: list of supported mime types """ if len(ImagePreviewBuilderWand.MIMETYPES) == 0: ImagePreviewBuilderWand.MIMETYPES = cls.__load_mimetypes() mimetypes = ImagePreviewBuilderWand.MIMETYPES extra_mimetypes = ["application/x-xcf", "image/x-xcf"] mimetypes.extend(extra_mimetypes) return mimetypes def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpeg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size preview_name = preview_name + extension dest_path = os.path.join(cache_path, preview_name) self.image_to_jpeg_wand(file_path, size, dest_path, mimetype=mimetype) def image_to_jpeg_wand(self, file_path: str, preview_dims: ImgDims, dest_path: str, mimetype: typing.Optional[str]) -> None: try: with self._convert_image(file_path, preview_dims) as img: img.save(filename=dest_path) except (CoderError, CoderFatalError, CoderWarning) as e: assert mimetype file_ext = mimetypes_storage.guess_extension(mimetype, strict=False) or "" if file_ext: file_path = file_ext.lstrip(".") + ":" + file_path with self._convert_image(file_path, preview_dims) as img: img.save(filename=dest_path) else: raise e def _convert_image(self, file_path: str, preview_dims: ImgDims) -> Image: """ refer: https://legacy.imagemagick.org/Usage/thumbnails/ like cmd: convert -layers merge -background white -thumbnail widthxheight \ -auto-orient -quality 85 -interlace plane input.jpeg output.jpeg """ img = Image(filename=file_path) resize_dim = compute_resize_dims(dims_in=ImgDims(width=img.width, height=img.height), dims_out=preview_dims) img.auto_orient() img.iterator_reset() img.background_color = Color("white") img.merge_layers("merge") if self.progressive: img.interlace_scheme = "plane" img.compression_quality = self.quality img.thumbnail(resize_dim.width, resize_dim.height) return img
class ImagePreviewBuilderDrawio(PreviewBuilder): DRAWIO_MIMETYPES_MAPPING = [ MimetypeMapping("application/drawio", ".drawio") ] @classmethod def check_dependencies(cls) -> None: if not executable_is_available("xvfb-run"): raise BuilderDependencyNotFound( "this builder requires xvfb-run to be available") if not executable_is_available("/usr/bin/drawio"): raise BuilderDependencyNotFound( "this builder requires drawio to be available") @classmethod def get_label(cls) -> str: return "Images generator from Drawio files" @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: mimetypes = [] for mimetype_mapping in cls.get_mimetypes_mapping(): mimetypes.append(mimetype_mapping.mimetype) return mimetypes @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return cls.DRAWIO_MIMETYPES_MAPPING def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size with tempfile.NamedTemporaryFile("w+b", prefix="preview-generator-", suffix=".jpg") as tmp_jpg: with Xvfb(): build_jpg_result_code = check_call( [ "/usr/bin/drawio", "--no-sandbox", "-x", "-f", "jpg", "-o", tmp_jpg.name, file_path, ], stdout=DEVNULL, stderr=STDOUT, timeout=30, ) if build_jpg_result_code != 0: raise IntermediateFileBuildingFailed( "Building JPG intermediate file using drawio " "failed with status {}".format(build_jpg_result_code)) ImagePreviewBuilderPillow().build_jpeg_preview( tmp_jpg.name, preview_name, cache_path, page_id, extension, size, mimetype, ) def has_jpeg_preview(self) -> bool: return True def get_page_number(self, file_path: str, preview_name: str, cache_path: str, mimetype: str = "") -> int: return 1
class ImagePreviewBuilderDrawio(PreviewBuilder): DRAWIO_MIMETYPES_MAPPING = [ MimetypeMapping("application/drawio", ".drawio") ] weight = 120 @classmethod def check_dependencies(cls) -> None: if not xvfbwrapper_installed: raise BuilderDependencyNotFound( "this builder requires xvfbwrapper") if not executable_is_available("xvfb-run"): raise BuilderDependencyNotFound( "this builder requires xvfb-run to be available") if not executable_is_available("drawio"): raise BuilderDependencyNotFound( "this builder requires drawio to be available") @classmethod def get_label(cls) -> str: return "Images generator from Drawio files" @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: mimetypes = [] for mimetype_mapping in cls.get_mimetypes_mapping(): mimetypes.append(mimetype_mapping.mimetype) return mimetypes @classmethod def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return cls.DRAWIO_MIMETYPES_MAPPING def build_jpeg_preview( self, file_path: str, preview_name: str, cache_path: str, page_id: int, extension: str = ".jpg", size: ImgDims = None, mimetype: str = "", ) -> None: if not size: size = self.default_size with tempfile.NamedTemporaryFile("w+b", prefix="preview-generator-", suffix=".jpg") as tmp_jpg: with Xvfb(): build_jpg_result_code = check_call( [ "drawio", "-x", "-f", "jpg", "-o", tmp_jpg.name, file_path, # INFO - G.M - 12/11/2021 - Add no-sandbox at the end as putting it before # doesn't work, see: # https://github.com/jgraph/drawio-desktop/issues/249#issuecomment-695179747 "--no-sandbox", ], stdout=DEVNULL, stderr=STDOUT, timeout=30, ) if build_jpg_result_code != 0: raise IntermediateFileBuildingFailed( "Building JPG intermediate file using drawio " "failed with status {}".format(build_jpg_result_code)) ImagePreviewBuilderWand().build_jpeg_preview( tmp_jpg.name, preview_name, cache_path, page_id, extension, size, mimetype) def has_jpeg_preview(self) -> bool: return True def get_page_number(self, file_path: str, preview_name: str, cache_path: str, mimetype: str = "") -> int: return 1
def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: return [ MimetypeMapping("application/x-preview-generator-test", ".runpy") ]
def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: mimetypes_mapping = [] for mimetype, extension in LO_MIMETYPES.items(): mimetypes_mapping.append( MimetypeMapping(mimetype, ".{}".format(extension))) return mimetypes_mapping