From a3430ca2a92df610e2610340e19b85e95f9136a4 Mon Sep 17 00:00:00 2001 From: MitulDavid Date: Mon, 2 Aug 2021 18:23:26 +0530 Subject: [PATCH] feat: Image optimization --- frappe/core/doctype/file/file.py | 14 +++++++++++--- frappe/handler.py | 10 ++++++++-- .../js/frappe/file_uploader/FileUploader.vue | 4 ++++ frappe/utils/file_manager.py | 14 +++++++++++--- frappe/utils/image.py | 17 ++++++++++++++--- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index b4bfe1d21b..05be825bcc 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -28,7 +28,7 @@ import frappe from frappe import _, conf from frappe.model.document import Document from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip -from frappe.utils.image import strip_exif_data +from frappe.utils.image import strip_exif_data, optimize_image class MaxFileSizeReachedError(frappe.ValidationError): pass @@ -876,6 +876,15 @@ def extract_images_from_html(doc, content): data = match.group(1) data = data.split("data:")[1] headers, content = data.split(",") + mtype = headers.split(";")[0] + + if isinstance(content, str): + content = content.encode("utf-8") + if b"," in content: + content = content.split(b",")[1] + content = base64.b64decode(content) + + content = optimize_image(content, mtype) if "filename=" in headers: filename = headers.split("filename=")[-1] @@ -884,7 +893,6 @@ def extract_images_from_html(doc, content): if not isinstance(filename, str): filename = str(filename, 'utf-8') else: - mtype = headers.split(";")[0] filename = get_random_filename(content_type=mtype) doctype = doc.parenttype if doc.parent else doc.doctype @@ -896,7 +904,7 @@ def extract_images_from_html(doc, content): "attached_to_doctype": doctype, "attached_to_name": name, "content": content, - "decode": True + "decode": False }) _file.save(ignore_permissions=True) file_url = _file.file_url diff --git a/frappe/handler.py b/frappe/handler.py index 8d0c18a00b..abbcb02cde 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -10,6 +10,8 @@ from frappe.utils import cint from frappe import _, is_whitelisted from frappe.utils.response import build_response from frappe.utils.csvutils import build_csv_response +from frappe.utils.image import optimize_image +from mimetypes import guess_type from frappe.core.doctype.server_script.server_script_utils import run_server_script_api @@ -145,6 +147,7 @@ def upload_file(): folder = frappe.form_dict.folder or 'Home' method = frappe.form_dict.method filename = frappe.form_dict.file_name + optimize = frappe.form_dict.optimize content = None if 'file' in files: @@ -152,12 +155,15 @@ def upload_file(): content = file.stream.read() filename = file.filename + content_type = guess_type(filename)[0] + if optimize and content_type.startswith("image/"): + content = optimize_image(content, content_type) + frappe.local.uploaded_file = content frappe.local.uploaded_filename = filename if not file_url and (frappe.session.user == "Guest" or (user and not user.has_desk_access())): - import mimetypes - filetype = mimetypes.guess_type(filename)[0] + filetype = guess_type(filename)[0] if filetype not in ALLOWED_MIMETYPES: frappe.throw(_("You can only upload JPG, PNG, PDF, or Microsoft documents.")) diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue index 92c38dfe8e..6f6345e08d 100644 --- a/frappe/public/js/frappe/file_uploader/FileUploader.vue +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -478,6 +478,10 @@ export default { form_data.append('method', this.method); } + if (this.attach_doc_image) { + form_data.append('optimize', true); + } + xhr.send(form_data); }); }, diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 30b0d816bb..7efdff299b 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -11,7 +11,7 @@ from frappe import _ from frappe import conf from copy import copy from urllib.parse import unquote - +from frappe.utils.image import optimize_image class MaxFileSizeReachedError(frappe.ValidationError): pass @@ -386,6 +386,15 @@ def extract_images_from_html(doc, content): data = match.group(1) data = data.split("data:")[1] headers, content = data.split(",") + mtype = headers.split(";")[0] + + if isinstance(content, str): + content = content.encode("utf-8") + if b"," in content: + content = content.split(b",")[1] + content = base64.b64decode(content) + + content = optimize_image(content, mtype) if "filename=" in headers: filename = headers.split("filename=")[-1] @@ -394,7 +403,6 @@ def extract_images_from_html(doc, content): if not isinstance(filename, str): filename = str(filename, 'utf-8') else: - mtype = headers.split(";")[0] filename = get_random_filename(content_type=mtype) doctype = doc.parenttype if doc.parent else doc.doctype @@ -405,7 +413,7 @@ def extract_images_from_html(doc, content): name = doc.reference_name # TODO fix this - file_url = save_file(filename, content, doctype, name, decode=True).get("file_url") + file_url = save_file(filename, content, doctype, name, decode=False).get("file_url") if not frappe.flags.has_dataurl: frappe.flags.has_dataurl = True diff --git a/frappe/utils/image.py b/frappe/utils/image.py index b6f4c67c44..556c5e4a32 100644 --- a/frappe/utils/image.py +++ b/frappe/utils/image.py @@ -1,6 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt import os +from PIL import Image +import io def resize_images(path, maxdim=700): from PIL import Image @@ -26,9 +28,6 @@ def strip_exif_data(content, content_type): Bytes: Stripped image content """ - from PIL import Image - import io - original_image = Image.open(io.BytesIO(content)) output = io.BytesIO() @@ -38,4 +37,16 @@ def strip_exif_data(content, content_type): content = output.getvalue() + return content + +def optimize_image(content, content_type, max_width=1920, max_height=1080, optimize=True, quality=85): + image = Image.open(io.BytesIO(content)) + image_format = content_type.split('/')[1] + size = max_width, max_height + image.thumbnail(size, Image.LANCZOS) + + output = io.BytesIO() + image.save(output, format=image_format, optimize=optimize, quality=quality, save_all=True if image_format=='gif' else None) + + content = output.getvalue() return content \ No newline at end of file