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.

211 lines
8.7 KiB

  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 sys
  22. import PyPDF2
  23. import base64
  24. import shutil
  25. import urllib
  26. import logging
  27. import tempfile
  28. import mimetypes
  29. from contextlib import closing
  30. from odoo.tools import config
  31. from odoo.tools.mimetypes import guess_mimetype
  32. from odoo.addons.muk_utils.tools import utils_os
  33. from odoo.addons.muk_converter.service import unoconv
  34. _logger = logging.getLogger(__name__)
  35. try:
  36. from wand.image import Image
  37. from wand.color import Color
  38. except ImportError:
  39. Image = False
  40. Color = False
  41. _logger.warn('Cannot `import wand`.')
  42. try:
  43. import imageio
  44. except ImportError:
  45. imageio = False
  46. _logger.warn('Cannot `import imageio`.')
  47. try:
  48. from moviepy.editor import VideoFileClip
  49. except ImportError:
  50. VideoFileClip = False
  51. _logger.warn('Cannot `import moviepy`.')
  52. FORMATS = [
  53. "png", "jpg", "jepg"
  54. ]
  55. VIDEO_IMPORTS = [
  56. "mp4", "mov", "wav", "avi", "mpg", "flv", "wmv", "webm"
  57. ]
  58. PDF_IMPORTS = [
  59. "pdf"
  60. ]
  61. WAND_IMPORTS = [
  62. "aai", "art", "arw", "avi", "avs", "bpg", "brf", "cals", "cgm", "cin", "cip", "cmyk", "cmyka", "svg",
  63. "cr2", "crw", "cur", "cut", "dcm", "dcr", "dcx", "dds", "dib", "djvu", "dng", "dot", "dpx", "tim",
  64. "emf", "epdf", "epi", "eps", "eps2", "eps3", "epsf", "epsi", "ept", "exr", "fax", "fig", "fits",
  65. "fpx", "gplt", "gray", "graya", "hdr", "hdr", "heic", "hpgl", "hrz", "html", "ico", "info", "ttf",
  66. "inline", "isobrl", "isobrl6", "jbig", "jng", "jp2", "jpt", "j2c", "j2k", "jxr", "json", "man", "bmp",
  67. "mat", "miff", "mono", "mng", "m2v", "mpeg", "mpc", "mpr", "mrw", "msl", "mtv", "mvg", "nef", "yuv",
  68. "orf", "otb", "p7", "palm", "pam", "clipboard", "pbm", "pcd", "pcds", "pcl", "pcx", "pdb", "jpe",
  69. "pef", "pes", "pfa", "pfb", "pfm", "pgm", "picon", "pict", "pix", "png8", "png00", "png24", "tiff",
  70. "png32", "png48", "png64", "pnm", "ppm", "ps", "ps2", "ps3", "psb", "psd", "ptif", "pwp", "rad",
  71. "raf", "rgb", "rgba", "rgf", "rla", "rle", "sct", "sfw", "sgi", "shtml", "sid", " mrsid", "jpeg",
  72. "sparse-color", "sun", "tga", "ubrl", "ubrl6", "uyvy", "vicar", "viff", "wbmp", "jpg", "png", "uil",
  73. "wdp", "webp", "wmf", "wpg", "x", "xbm", "xcf", "xpm", "xwd", "x3f", "ycbcr", "ycbcra", "bmp3", "bmp2",
  74. ]
  75. def formats():
  76. return FORMATS
  77. def imports():
  78. return VIDEO_IMPORTS + PDF_IMPORTS + WAND_IMPORTS + unoconv.UNOCONV_IMPORTS
  79. def create_thumbnail(binary, mimetype=None, filename=None, export="binary", format="png", page=0, frame=0,
  80. animation=False, video_resize={'width': 256}, image_resize='256x256>', image_crop=None):
  81. """
  82. Converts a thumbnail for a given file.
  83. :param binary: The binary value.
  84. :param mimetype: The mimetype of the binary value.
  85. :param filename: The filename of the binary value.
  86. :param export: The output format (binary, file, base64).
  87. :param format: Specify the output format for the document.
  88. :param page: Specifies the page if the file has several pages, e.g. if it is a PDF file.
  89. :param frame: Specifies the frame if the file has several frames, e.g. if it is a video file.
  90. :param animation: In this case, the parameter frame specifies the number of frames.
  91. :param video_resize: Specify to resize the output image.
  92. :param image_resize: Specify to resize the output image.
  93. :param image_crop: Specify to crop the output image.
  94. :return: Returns the output depending on the given format.
  95. :raises ValueError: The file extension could not be determined or the format is invalid.
  96. """
  97. extension = utils_os.get_extension(binary, filename, mimetype)
  98. if not extension:
  99. raise ValueError("The file extension could not be determined.")
  100. if format not in FORMATS:
  101. raise ValueError("Invalid export format.")
  102. if extension not in (VIDEO_IMPORTS + PDF_IMPORTS + WAND_IMPORTS + unoconv.UNOCONV_IMPORTS):
  103. raise ValueError("Invalid import format.")
  104. if not imageio or not Image or not VideoFileClip:
  105. raise ValueError("Some libraries couldn't be imported.")
  106. image_data = None
  107. image_extension = extension
  108. if extension in WAND_IMPORTS:
  109. image_data = binary
  110. elif not image_data and (extension in PDF_IMPORTS or extension in unoconv.UNOCONV_IMPORTS):
  111. pdf_data = binary if extension in PDF_IMPORTS else None
  112. if not pdf_data:
  113. image_extension = "pdf"
  114. pdf_data = unoconv.unoconv.convert(binary, mimetype, filename)
  115. reader = PyPDF2.PdfFileReader(io.BytesIO(pdf_data))
  116. writer = PyPDF2.PdfFileWriter()
  117. if reader.getNumPages() >= page:
  118. writer.addPage(reader.getPage(page))
  119. else:
  120. writer.addPage(reader.getPage(0))
  121. pdf_bytes = io.BytesIO()
  122. writer.write(pdf_bytes)
  123. image_data = pdf_bytes.getvalue()
  124. if image_data:
  125. with Image(blob=image_data, format=image_extension) as thumbnail:
  126. thumbnail.format = format
  127. if image_extension == "pdf":
  128. thumbnail.background_color = Color('white')
  129. thumbnail.alpha_channel = 'remove'
  130. if image_resize:
  131. thumbnail.transform(resize=image_resize)
  132. if image_crop:
  133. thumbnail.transform(crop=image_crop)
  134. if export == 'file':
  135. return io.BytesIO(thumbnail.make_blob())
  136. elif export == 'base64':
  137. return base64.b64encode(thumbnail.make_blob())
  138. else:
  139. return thumbnail.make_blob()
  140. elif extension in VIDEO_IMPORTS:
  141. tmp_dir = tempfile.mkdtemp()
  142. try:
  143. tmp_wpath = os.path.join(tmp_dir, "tmpfile.%s" % extension)
  144. if os.name == 'nt':
  145. tmp_wpath = tmp_wpath.replace("\\", "/")
  146. with closing(open(tmp_wpath, 'wb')) as file:
  147. file.write(binary)
  148. clip = VideoFileClip(tmp_wpath)
  149. try:
  150. tmp_opath = os.path.join(tmp_dir, "output.%s" % format)
  151. clip.resize(**video_resize)
  152. if animation:
  153. files = []
  154. current_frame = 0
  155. while clip.duration > current_frame and current_frame < frame:
  156. filename = os.path.join(tmp_dir, "output_%s.png" % frame)
  157. clip.save_frame(filename, t=frame)
  158. files.append(filename)
  159. frame += 0.25
  160. tmp_opath = os.path.join(tmp_dir, "output.gif")
  161. with imageio.get_writer(tmp_opath, fps=5, mode='I') as writer:
  162. for filename in files:
  163. image = imageio.imread(filename)
  164. writer.append_data(image)
  165. elif clip.duration > int(frame):
  166. clip.save_frame(tmp_opath, t=int(frame))
  167. else:
  168. clip.save_frame(tmp_opath, t=int(0))
  169. if os.path.isfile(tmp_opath):
  170. with open(tmp_opath, 'rb') as file:
  171. if export == 'file':
  172. return io.BytesIO(file.read())
  173. elif export == 'base64':
  174. return base64.b64encode(file.read())
  175. else:
  176. return file.read()
  177. else:
  178. raise ValueError("No output could be created from the video.")
  179. finally:
  180. try:
  181. clip.reader.close()
  182. del clip.reader
  183. if clip.audio != None:
  184. clip.audio.reader.close_proc()
  185. del clip.audio
  186. del clip
  187. except Exception as e:
  188. sys.exc_clear()
  189. finally:
  190. try:
  191. shutil.rmtree(tmp_dir)
  192. except PermissionError:
  193. _logger.warn("Temporary directory could not be deleted.")
  194. else:
  195. raise ValueError("No output could be generated.")