################################################################################### # # Copyright (C) 2018 MuK IT GmbH # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ################################################################################### import os import io import PyPDF2 import base64 import shutil import urllib import logging import tempfile import mimetypes from contextlib import closing from odoo.tools import config from odoo.tools.mimetypes import guess_mimetype from odoo.addons.muk_utils.tools import utils_os from odoo.addons.muk_converter.service import unoconv _logger = logging.getLogger(__name__) try: from wand.image import Image except ImportError: Image = False _logger.warn('Cannot `import wand`.') try: import imageio except ImportError: imageio = False _logger.warn('Cannot `import imageio`.') try: from moviepy.editor import VideoFileClip except ImportError: VideoFileClip = False _logger.warn('Cannot `import moviepy`.') FORMATS = [ "png", "jpg" ] VIDEO_IMPORTS = [ "mp4", "ogv", "webm" ] PDF_IMPORTS = [ "pdf" ] WAND_IMPORTS = [ "aai", "art", "arw", "avi", "avs", "bpg", "brf", "cals", "cgm", "cin", "cip", "cmyk", "cmyka", "cr2", "crw", "cur", "cut", "dcm", "dcr", "dcx", "dds", "dib", "djvu", "dng", "dot", "dpx", "tim", "emf", "epdf", "epi", "eps", "eps2", "eps3", "epsf", "epsi", "ept", "exr", "fax", "fig", "fits", "fpx", "gplt", "gray", "graya", "hdr", "hdr", "heic", "hpgl", "hrz", "html", "ico", "info", "ttf", "inline", "isobrl", "isobrl6", "jbig", "jng", "jp2", "jpt", "j2c", "j2k", "jxr", "json", "man", "bmp", "mat", "miff", "mono", "mng", "m2v", "mpeg", "mpc", "mpr", "mrw", "msl", "mtv", "mvg", "nef", "yuv", "orf", "otb", "p7", "palm", "pam", "clipboard", "pbm", "pcd", "pcds", "pcl", "pcx", "pdb", "pdf", "jpe", "pef", "pes", "pfa", "pfb", "pfm", "pgm", "picon", "pict", "pix", "png8", "png00", "png24", "tiff", "png32", "png48", "png64", "pnm", "ppm", "ps", "ps2", "ps3", "psb", "psd", "ptif", "pwp", "rad", "bmp2", "raf", "rgb", "rgba", "rgf", "rla", "rle", "sct", "sfw", "sgi", "shtml", "sid", " mrsid", "jpeg", "bmp3", "sparse-color", "sun", "text", "tga", "txt", "ubrl", "ubrl6", "uyvy", "vicar", "viff", "wbmp", "jpg", "wdp", "webp", "wmf", "wpg", "x", "xbm", "xcf", "xpm", "xwd", "x3f", "ycbcr", "ycbcra", "png", "uil" ] def formats(): return FORMATS def imports(): return VIDEO_IMPORTS + PDF_IMPORTS + WAND_IMPORTS + unoconv.IMPORTS def create_thumbnail(binary, mimetype=None, filename=None, export="binary", format="png", page=0, frame=0, animation=False, video_resize={'width': 256}, image_resize='256x256>', image_crop=None): """ Converts a thumbnail for a given file. :param binary: The binary value. :param mimetype: The mimetype of the binary value. :param filename: The filename of the binary value. :param export: The output format (binary, file, base64). :param format: Specify the output format for the document. :param page: Specifies the page if the file has several pages, e.g. if it is a PDF file. :param frame: Specifies the frame if the file has several frames, e.g. if it is a video file. :param animation: In this case, the parameter frame specifies the number of frames. :param video_resize: Specify to resize the output image. :param image_resize: Specify to resize the output image. :param image_crop: Specify to crop the output image. :return: Returns the output depending on the given format. :raises ValueError: The file extension could not be determined or the format is invalid. """ extension = utils_os.get_extension(binary, filename, mimetype) if not extension: raise ValueError("The file extension could not be determined.") if format not in FORMATS: raise ValueError("Invalid export format.") if extension not in (VIDEO_IMPORTS + PDF_IMPORTS + WAND_IMPORTS + unoconv.IMPORTS): raise ValueError("Invalid import format.") if not imageio or not Image or not VideoFileClip: raise ValueError("Some libraries couldn't be imported.") image_data = None image_extension = extension if extension in WAND_IMPORTS: image_data = binary elif not image_data and extension in PDF_IMPORTS: reader = PyPDF2.PdfFileReader(io.BytesIO(binary)) writer = PyPDF2.PdfFileWriter() writer.addPage(reader.getPage(page)) thumbnail_bytes = io.BytesIO() thumbnail_pdf.write(thumbnail_bytes) image_data = thumbnail_bytes.seek(0) image_extension = "pdf" elif not image_data and extension in unoconv.IMPORTS: image_data = unoconv.convert_binary(binary, mimetype, filename) image_extension = "pdf" if image_data: with Image(blob=image_data, format=image_extension) as thumbnail: thumbnail.format = format if image_resize: thumbnail.transform(resize=image_resize) if image_crop: thumbnail.transform(crop=image_crop) if export == 'file': return io.BytesIO(thumbnail.make_blob()) elif export == 'base64': return base64.b64encode(thumbnail.make_blob()) else: return thumbnail.make_blob() elif extension in VIDEO_IMPORTS: tmp_dir = tempfile.mkdtemp() try: tmp_wpath = os.path.join(tmp_dir, "tmpfile.%s" % extension) if os.name == 'nt': tmp_wpath = tmp_wpath.replace("\\", "/") with closing(open(tmp_wpath, 'wb')) as file: file.write(binary) clip = VideoFileClip(tmp_wpath) try: tmp_opath = os.path.join(tmp_dir, "output.%s" % format) clip.resize(**video_resize) if animation: files = [] current_frame = 0 while clip.duration > current_frame and current_frame < frame: filename = os.path.join(tmp_dir, "output_%s.png" % frame) clip.save_frame(filename, t=frame) files.append(filename) frame += 0.25 tmp_opath = os.path.join(tmp_dir, "output.gif") with imageio.get_writer(tmp_opath, fps=5, mode='I') as writer: for filename in files: image = imageio.imread(filename) writer.append_data(image) elif clip.duration > int(frame): clip.save_frame(tmp_opath, t=int(frame)) if os.path.isfile(tmp_opath): with open(tmp_opath, 'rb') as file: if export == 'file': return io.BytesIO(file.read()) elif export == 'base64': return base64.b64encode(file.read()) else: return file.read() else: raise ValueError("No output could be created from the video.") finally: try: clip.reader.close() del clip.reader if clip.audio != None: clip.audio.reader.close_proc() del clip.audio del clip except Exception as e: sys.exc_clear() finally: shutil.rmtree(tmp_dir) else: raise ValueError("No output could be generated.")