You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
8.3 KiB

###################################################################################
#
# 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 <http://www.gnu.org/licenses/>.
#
###################################################################################
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.")