added support for multiple image types
This commit is contained in:
parent
c91fc096b3
commit
bee1d717c5
@ -1,8 +1,11 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
def rescale_image(image_object, base_width=720):
|
def rescale_image_jpeg(image_object, base_width=720):
|
||||||
img = Image.open(image_object)
|
img = Image.open(image_object)
|
||||||
icc_profile = img.info.get('icc_profile') # remember color profile to not mess up colors
|
icc_profile = img.info.get('icc_profile') # remember color profile to not mess up colors
|
||||||
width_percent = (base_width / float(img.size[0]))
|
width_percent = (base_width / float(img.size[0]))
|
||||||
@ -13,3 +16,30 @@ def rescale_image(image_object, base_width=720):
|
|||||||
img.save(img_bytes, 'JPEG', quality=75, optimize=True, icc_profile=icc_profile)
|
img.save(img_bytes, 'JPEG', quality=75, optimize=True, icc_profile=icc_profile)
|
||||||
|
|
||||||
return img_bytes
|
return img_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def rescale_image_png(image_object, base_width=720):
|
||||||
|
basewidth = 720
|
||||||
|
wpercent = (basewidth / float(image_object.size[0]))
|
||||||
|
hsize = int((float(image_object.size[1]) * float(wpercent)))
|
||||||
|
img = image_object.resize((basewidth, hsize), Image.ANTIALIAS)
|
||||||
|
|
||||||
|
im_io = BytesIO()
|
||||||
|
img.save(im_io, 'PNG', quality=70)
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def get_filetype(name):
|
||||||
|
try:
|
||||||
|
return os.path.splitext(name)[1]
|
||||||
|
except:
|
||||||
|
return '.jpeg'
|
||||||
|
|
||||||
|
|
||||||
|
def handle_image(request, image_object, filetype='.jpeg'):
|
||||||
|
if sys.getsizeof(image_object) / 8 > 500:
|
||||||
|
if filetype == '.jpeg':
|
||||||
|
return rescale_image_jpeg(image_object), filetype
|
||||||
|
if filetype == '.png':
|
||||||
|
return rescale_image_png(image_object), filetype
|
||||||
|
return image_object, filetype
|
||||||
|
@ -3,6 +3,7 @@ import re
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||||
@ -71,7 +72,7 @@ class Chowdown(Integration):
|
|||||||
import_zip = ZipFile(f['file'])
|
import_zip = ZipFile(f['file'])
|
||||||
for z in import_zip.filelist:
|
for z in import_zip.filelist:
|
||||||
if re.match(f'^images/{image}$', z.filename):
|
if re.match(f'^images/{image}$', z.filename):
|
||||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)), filetype=get_filetype(z.filename))
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from re import match
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.serializer import RecipeExportSerializer
|
from cookbook.serializer import RecipeExportSerializer
|
||||||
|
|
||||||
@ -15,8 +17,9 @@ class Default(Integration):
|
|||||||
|
|
||||||
recipe_string = recipe_zip.read('recipe.json').decode("utf-8")
|
recipe_string = recipe_zip.read('recipe.json').decode("utf-8")
|
||||||
recipe = self.decode_recipe(recipe_string)
|
recipe = self.decode_recipe(recipe_string)
|
||||||
if 'image.png' in recipe_zip.namelist():
|
images = list(filter(lambda v: match('image.*', v), recipe_zip.namelist()))
|
||||||
self.import_recipe_image(recipe, BytesIO(recipe_zip.read('image.png')))
|
if images:
|
||||||
|
self.import_recipe_image(recipe, BytesIO(recipe_zip.read(images[0])), filetype=get_filetype(images[0]))
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
def decode_recipe(self, string):
|
def decode_recipe(self, string):
|
||||||
|
@ -45,7 +45,7 @@ class Domestica(Integration):
|
|||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
if file['image'] != '':
|
if file['image'] != '':
|
||||||
self.import_recipe_image(recipe, BytesIO(base64.b64decode(file['image'].replace('data:image/jpeg;base64,', ''))))
|
self.import_recipe_image(recipe, BytesIO(base64.b64decode(file['image'].replace('data:image/jpeg;base64,', ''))), filetype='.jpeg')
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
@ -12,6 +13,7 @@ from django.utils.translation import gettext as _
|
|||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
|
|
||||||
from cookbook.forms import ImportExportBase
|
from cookbook.forms import ImportExportBase
|
||||||
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.models import Keyword, Recipe
|
from cookbook.models import Keyword, Recipe
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ class Integration:
|
|||||||
recipe_zip_obj.writestr(filename, recipe_stream.getvalue())
|
recipe_zip_obj.writestr(filename, recipe_stream.getvalue())
|
||||||
recipe_stream.close()
|
recipe_stream.close()
|
||||||
try:
|
try:
|
||||||
recipe_zip_obj.writestr('image.png', r.image.file.read())
|
recipe_zip_obj.writestr(f'image{get_filetype(r.image.file.name)}', r.image.file.read())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -185,13 +187,14 @@ class Integration:
|
|||||||
self.ignored_recipes.append(recipe.name)
|
self.ignored_recipes.append(recipe.name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def import_recipe_image(recipe, image_file):
|
def import_recipe_image(recipe, image_file, filetype='.jpeg'):
|
||||||
"""
|
"""
|
||||||
Adds an image to a recipe naming it correctly
|
Adds an image to a recipe naming it correctly
|
||||||
:param recipe: Recipe object
|
:param recipe: Recipe object
|
||||||
:param image_file: ByteIO stream containing the image
|
:param image_file: ByteIO stream containing the image
|
||||||
|
:param filetype: type of file to write bytes to, default to .jpeg if unknown
|
||||||
"""
|
"""
|
||||||
recipe.image = File(image_file, name=f'{uuid.uuid4()}_{recipe.pk}.png')
|
recipe.image = File(image_file, name=f'{uuid.uuid4()}_{recipe.pk}{filetype}')
|
||||||
recipe.save()
|
recipe.save()
|
||||||
|
|
||||||
def get_recipe_from_file(self, file):
|
def get_recipe_from_file(self, file):
|
||||||
|
@ -3,6 +3,7 @@ import re
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||||
@ -49,7 +50,7 @@ class Mealie(Integration):
|
|||||||
import_zip = ZipFile(f['file'])
|
import_zip = ZipFile(f['file'])
|
||||||
for z in import_zip.filelist:
|
for z in import_zip.filelist:
|
||||||
if re.match(f'^images/{recipe_json["slug"]}.jpg$', z.filename):
|
if re.match(f'^images/{recipe_json["slug"]}.jpg$', z.filename):
|
||||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)), filetype=get_filetype(z.filename))
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import re
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||||
@ -51,7 +52,7 @@ class NextcloudCookbook(Integration):
|
|||||||
import_zip = ZipFile(f['file'])
|
import_zip = ZipFile(f['file'])
|
||||||
for z in import_zip.filelist:
|
for z in import_zip.filelist:
|
||||||
if re.match(f'^Recipes/{recipe.name}/full.jpg$', z.filename):
|
if re.match(f'^Recipes/{recipe.name}/full.jpg$', z.filename):
|
||||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)), filetype=get_filetype(z.filename))
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
|
@ -81,6 +81,6 @@ class Paprika(Integration):
|
|||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
if recipe_json.get("photo_data", None):
|
if recipe_json.get("photo_data", None):
|
||||||
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])))
|
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])), filetype='.jpeg')
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
@ -7,6 +7,7 @@ from zipfile import ZipFile
|
|||||||
import imghdr
|
import imghdr
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||||
@ -113,17 +114,17 @@ class RecetteTek(Integration):
|
|||||||
|
|
||||||
# Import the original image from the zip file, if we cannot do that, attempt to download it again.
|
# Import the original image from the zip file, if we cannot do that, attempt to download it again.
|
||||||
try:
|
try:
|
||||||
if file['pictures'][0] !='':
|
if file['pictures'][0] != '':
|
||||||
image_file_name = file['pictures'][0].split('/')[-1]
|
image_file_name = file['pictures'][0].split('/')[-1]
|
||||||
for f in self.files:
|
for f in self.files:
|
||||||
if '.rtk' in f['name']:
|
if '.rtk' in f['name']:
|
||||||
import_zip = ZipFile(f['file'])
|
import_zip = ZipFile(f['file'])
|
||||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name)))
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name)), filetype=get_filetype(image_file_name))
|
||||||
else:
|
else:
|
||||||
if file['originalPicture'] != '':
|
if file['originalPicture'] != '':
|
||||||
response=requests.get(file['originalPicture'])
|
response = requests.get(file['originalPicture'])
|
||||||
if imghdr.what(BytesIO(response.content)) != None:
|
if imghdr.what(BytesIO(response.content)) != None:
|
||||||
self.import_recipe_image(recipe, BytesIO(response.content))
|
self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture']))
|
||||||
else:
|
else:
|
||||||
raise Exception("Original image failed to download.")
|
raise Exception("Original image failed to download.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -70,7 +70,7 @@ class RecipeKeeper(Integration):
|
|||||||
for f in self.files:
|
for f in self.files:
|
||||||
if '.zip' in f['name']:
|
if '.zip' in f['name']:
|
||||||
import_zip = ZipFile(f['file'])
|
import_zip = ZipFile(f['file'])
|
||||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipe-photo").get("src"))))
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipe-photo").get("src"))), filetype='.jpeg')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ from rest_framework.schemas.openapi import AutoSchema
|
|||||||
from rest_framework.schemas.utils import is_list_view
|
from rest_framework.schemas.utils import is_list_view
|
||||||
from rest_framework.viewsets import ViewSetMixin
|
from rest_framework.viewsets import ViewSetMixin
|
||||||
|
|
||||||
|
from cookbook.helper.image_processing import handle_image
|
||||||
from cookbook.helper.ingredient_parser import parse
|
from cookbook.helper.ingredient_parser import parse
|
||||||
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
|
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
|
||||||
CustomIsOwner, CustomIsShare,
|
CustomIsOwner, CustomIsShare,
|
||||||
@ -394,16 +395,9 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
img = Image.open(obj.image)
|
|
||||||
|
|
||||||
basewidth = 720
|
img, filetype = handle_image(request, obj.image)
|
||||||
wpercent = (basewidth / float(img.size[0]))
|
obj.image = File(img, name=f'{uuid.uuid4()}_{obj.pk}{filetype}')
|
||||||
hsize = int((float(img.size[1]) * float(wpercent)))
|
|
||||||
img = img.resize((basewidth, hsize), Image.ANTIALIAS)
|
|
||||||
|
|
||||||
im_io = io.BytesIO()
|
|
||||||
img.save(im_io, 'PNG', quality=70)
|
|
||||||
obj.image = File(im_io, name=f'{uuid.uuid4()}_{obj.pk}.png')
|
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
@ -495,7 +489,6 @@ class BookmarkletImportViewSet(viewsets.ModelViewSet):
|
|||||||
return self.queryset.filter(space=self.request.space).all()
|
return self.queryset.filter(space=self.request.space).all()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UserFileViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class UserFileViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||||
queryset = UserFile.objects
|
queryset = UserFile.objects
|
||||||
serializer_class = UserFileSerializer
|
serializer_class = UserFileSerializer
|
||||||
|
@ -17,6 +17,7 @@ from PIL import Image, UnidentifiedImageError
|
|||||||
from requests.exceptions import MissingSchema
|
from requests.exceptions import MissingSchema
|
||||||
|
|
||||||
from cookbook.forms import BatchEditForm, SyncForm
|
from cookbook.forms import BatchEditForm, SyncForm
|
||||||
|
from cookbook.helper.image_processing import handle_image
|
||||||
from cookbook.helper.permission_helper import group_required, has_group_permission
|
from cookbook.helper.permission_helper import group_required, has_group_permission
|
||||||
from cookbook.helper.recipe_url_import import parse_cooktime
|
from cookbook.helper.recipe_url_import import parse_cooktime
|
||||||
from cookbook.models import (Comment, Food, Ingredient, Keyword, Recipe,
|
from cookbook.models import (Comment, Food, Ingredient, Keyword, Recipe,
|
||||||
@ -186,18 +187,10 @@ def import_url(request):
|
|||||||
if 'image' in data and data['image'] != '' and data['image'] is not None:
|
if 'image' in data and data['image'] != '' and data['image'] is not None:
|
||||||
try:
|
try:
|
||||||
response = requests.get(data['image'])
|
response = requests.get(data['image'])
|
||||||
img = Image.open(BytesIO(response.content))
|
|
||||||
|
|
||||||
# todo move image processing to dedicated function
|
img, filetype = handle_image(request, response.content)
|
||||||
basewidth = 720
|
|
||||||
wpercent = (basewidth / float(img.size[0]))
|
|
||||||
hsize = int((float(img.size[1]) * float(wpercent)))
|
|
||||||
img = img.resize((basewidth, hsize), Image.ANTIALIAS)
|
|
||||||
|
|
||||||
im_io = BytesIO()
|
|
||||||
img.save(im_io, 'PNG', quality=70)
|
|
||||||
recipe.image = File(
|
recipe.image = File(
|
||||||
im_io, name=f'{uuid.uuid4()}_{recipe.pk}.png'
|
img, name=f'{uuid.uuid4()}_{recipe.pk}{filetype}'
|
||||||
)
|
)
|
||||||
recipe.save()
|
recipe.save()
|
||||||
except UnidentifiedImageError:
|
except UnidentifiedImageError:
|
||||||
|
Loading…
Reference in New Issue
Block a user