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.

196 lines
8.3 KiB

6 years ago
  1. ###################################################################################
  2. #
  3. # Copyright (C) 2018 MuK IT GmbH
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as
  7. # published by the Free Software Foundation, either version 3 of the
  8. # License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. ###################################################################################
  19. import os
  20. import io
  21. import PyPDF2
  22. import base64
  23. import shutil
  24. import urllib
  25. import logging
  26. import tempfile
  27. import mimetypes
  28. from contextlib import closing
  29. from odoo.tools import config
  30. from odoo.tools.mimetypes import guess_mimetype
  31. from odoo.addons.muk_utils.tools import utils_os
  32. from odoo.addons.muk_converter.service import unoconv
  33. _logger = logging.getLogger(__name__)
  34. try:
  35. from wand.image import Image
  36. except ImportError:
  37. Image = False
  38. _logger.warn('Cannot `import wand`.')
  39. try:
  40. import imageio
  41. except ImportError:
  42. imageio = False
  43. _logger.warn('Cannot `import imageio`.')
  44. try:
  45. from moviepy.editor import VideoFileClip
  46. except ImportError:
  47. VideoFileClip = False
  48. _logger.warn('Cannot `import moviepy`.')
  49. FORMATS = [
  50. "png", "jpg"
  51. ]
  52. VIDEO_IMPORTS = [
  53. "mp4", "ogv", "webm"
  54. ]
  55. PDF_IMPORTS = [
  56. "pdf"
  57. ]
  58. WAND_IMPORTS = [
  59. "aai", "art", "arw", "avi", "avs", "bpg", "brf", "cals", "cgm", "cin", "cip", "cmyk", "cmyka",
  60. "cr2", "crw", "cur", "cut", "dcm", "dcr", "dcx", "dds", "dib", "djvu", "dng", "dot", "dpx", "tim",
  61. "emf", "epdf", "epi", "eps", "eps2", "eps3", "epsf", "epsi", "ept", "exr", "fax", "fig", "fits",
  62. "fpx", "gplt", "gray", "graya", "hdr", "hdr", "heic", "hpgl", "hrz", "html", "ico", "info", "ttf",
  63. "inline", "isobrl", "isobrl6", "jbig", "jng", "jp2", "jpt", "j2c", "j2k", "jxr", "json", "man", "bmp",
  64. "mat", "miff", "mono", "mng", "m2v", "mpeg", "mpc", "mpr", "mrw", "msl", "mtv", "mvg", "nef", "yuv",
  65. "orf", "otb", "p7", "palm", "pam", "clipboard", "pbm", "pcd", "pcds", "pcl", "pcx", "pdb", "pdf", "jpe",
  66. "pef", "pes", "pfa", "pfb", "pfm", "pgm", "picon", "pict", "pix", "png8", "png00", "png24", "tiff",
  67. "png32", "png48", "png64", "pnm", "ppm", "ps", "ps2", "ps3", "psb", "psd", "ptif", "pwp", "rad", "bmp2",
  68. "raf", "rgb", "rgba", "rgf", "rla", "rle", "sct", "sfw", "sgi", "shtml", "sid", " mrsid", "jpeg", "bmp3",
  69. "sparse-color", "sun", "text", "tga", "txt", "ubrl", "ubrl6", "uyvy", "vicar", "viff", "wbmp", "jpg",
  70. "wdp", "webp", "wmf", "wpg", "x", "xbm", "xcf", "xpm", "xwd", "x3f", "ycbcr", "ycbcra", "png", "uil"
  71. ]
  72. def formats():
  73. return FORMATS
  74. def imports():
  75. return VIDEO_IMPORTS + PDF_IMPORTS + WAND_IMPORTS + unoconv.IMPORTS
  76. def create_thumbnail(binary, mimetype=None, filename=None, export="binary", format="png", page=0, frame=0,
  77. animation=False, video_resize={'width': 256}, image_resize='256x256>', image_crop=None):
  78. """
  79. Converts a thumbnail for a given file.
  80. :param binary: The binary value.
  81. :param mimetype: The mimetype of the binary value.
  82. :param filename: The filename of the binary value.
  83. :param export: The output format (binary, file, base64).
  84. :param format: Specify the output format for the document.
  85. :param page: Specifies the page if the file has several pages, e.g. if it is a PDF file.
  86. :param frame: Specifies the frame if the file has several frames, e.g. if it is a video file.
  87. :param animation: In this case, the parameter frame specifies the number of frames.
  88. :param video_resize: Specify to resize the output image.
  89. :param image_resize: Specify to resize the output image.
  90. :param image_crop: Specify to crop the output image.
  91. :return: Returns the output depending on the given format.
  92. :raises ValueError: The file extension could not be determined or the format is invalid.
  93. """
  94. extension = utils_os.get_extension(binary, filename, mimetype)
  95. if not extension:
  96. raise ValueError("The file extension could not be determined.")
  97. if format not in FORMATS:
  98. raise ValueError("Invalid export format.")
  99. if extension not in (VIDEO_IMPORTS + PDF_IMPORTS + WAND_IMPORTS + unoconv.IMPORTS):
  100. raise ValueError("Invalid import format.")
  101. if not imageio or not Image or not VideoFileClip:
  102. raise ValueError("Some libraries couldn't be imported.")
  103. image_data = None
  104. image_extension = extension
  105. if extension in WAND_IMPORTS:
  106. image_data = binary
  107. elif not image_data and extension in PDF_IMPORTS:
  108. reader = PyPDF2.PdfFileReader(io.BytesIO(binary))
  109. writer = PyPDF2.PdfFileWriter()
  110. writer.addPage(reader.getPage(page))
  111. thumbnail_bytes = io.BytesIO()
  112. thumbnail_pdf.write(thumbnail_bytes)
  113. image_data = thumbnail_bytes.seek(0)
  114. image_extension = "pdf"
  115. elif not image_data and extension in unoconv.IMPORTS:
  116. image_data = unoconv.convert_binary(binary, mimetype, filename)
  117. image_extension = "pdf"
  118. if image_data:
  119. with Image(blob=image_data, format=image_extension) as thumbnail:
  120. thumbnail.format = format
  121. if image_resize:
  122. thumbnail.transform(resize=image_resize)
  123. if image_crop:
  124. thumbnail.transform(crop=image_crop)
  125. if export == 'file':
  126. return io.BytesIO(thumbnail.make_blob())
  127. elif export == 'base64':
  128. return base64.b64encode(thumbnail.make_blob())
  129. else:
  130. return thumbnail.make_blob()
  131. elif extension in VIDEO_IMPORTS:
  132. tmp_dir = tempfile.mkdtemp()
  133. try:
  134. tmp_wpath = os.path.join(tmp_dir, "tmpfile.%s" % extension)
  135. if os.name == 'nt':
  136. tmp_wpath = tmp_wpath.replace("\\", "/")
  137. with closing(open(tmp_wpath, 'wb')) as file:
  138. file.write(binary)
  139. clip = VideoFileClip(tmp_wpath)
  140. try:
  141. tmp_opath = os.path.join(tmp_dir, "output.%s" % format)
  142. clip.resize(**video_resize)
  143. if animation:
  144. files = []
  145. current_frame = 0
  146. while clip.duration > current_frame and current_frame < frame:
  147. filename = os.path.join(tmp_dir, "output_%s.png" % frame)
  148. clip.save_frame(filename, t=frame)
  149. files.append(filename)
  150. frame += 0.25
  151. tmp_opath = os.path.join(tmp_dir, "output.gif")
  152. with imageio.get_writer(tmp_opath, fps=5, mode='I') as writer:
  153. for filename in files:
  154. image = imageio.imread(filename)
  155. writer.append_data(image)
  156. elif clip.duration > int(frame):
  157. clip.save_frame(tmp_opath, t=int(frame))
  158. if os.path.isfile(tmp_opath):
  159. with open(tmp_opath, 'rb') as file:
  160. if export == 'file':
  161. return io.BytesIO(file.read())
  162. elif export == 'base64':
  163. return base64.b64encode(file.read())
  164. else:
  165. return file.read()
  166. else:
  167. raise ValueError("No output could be created from the video.")
  168. finally:
  169. try:
  170. clip.reader.close()
  171. del clip.reader
  172. if clip.audio != None:
  173. clip.audio.reader.close_proc()
  174. del clip.audio
  175. del clip
  176. except Exception as e:
  177. sys.exc_clear()
  178. finally:
  179. shutil.rmtree(tmp_dir)
  180. else:
  181. raise ValueError("No output could be generated.")