I am try resize an image when this is saved.
Specifically, my images are stored on Amazon S3, then I use the django-storages and boto3 third party applications
When a image is saved, this is stored in my Amazon S3 bucket, having a acces url such as follow:
https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg
The code to save and resize the image is this:
class UploadStudyOffer(models.Model):
study_offer = models.ForeignKey(StudiesOffert, related_name='uploadsstudyoffer')
image = models.ImageField(upload_to=get_image_path)
# images folder per object
def save(self, *args, **kwargs):
super(UploadStudyOffer, self).save(*args, **kwargs)
# We first check to make sure an image exists
if self.image:
# Open image and check their size
image = Image.open(self.image)
i_width, i_height = image.size
max_size = (100,100)
# We resize the image if it's too large
if i_width > 1000:
image.thumbnail(max_size, Image.ANTIALIAS)
image.save(self.image.path)
When I upload an image, I get this message:
Exception Type: NotImplementedError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: This backend doesn't support absolute paths.
And I am not sure, if the error is manage at storages or boto backends or in Pillow.
Then at level of Pillow I found the following options in the moment of save the image, such as follow:
I change the section code:
image.save(self.image.path)
to:
image.save(self.image.name)
And I get this error:
File "/home/bgarcial/workspace/hostayni_platform/hosts/models.py" in save
542. image.save(self.image.name) #[Errno 2] No such file or directory: 'studyoffer_images/ingenieria-de-sistemas/15061122523583.jpg'
File "/home/bgarcial/.virtualenvs/hostayni/lib/python3.6/site-packages/PIL/Image.py" in save
1725. fp = builtins.open(filename, "w+b")
Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'studyoffer_images/algoritmos-para-ensenanza/1900-X-1080-Wallpapers-022.jpg'
Of course, my image is stored om Amazon S3 and not locally in my project or hard disk, then I use the url parameter of this way:
I change
image.save(self.image.name)
to
image.save(self.image.url)
And I get this error:
Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg'
Getting the amazon s3 image URL does not works, even though the url is a valid url https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg
Then I change
image.save(self.image.url)
to:
image.save(self.image.file)
And my image is uploaded without any errors, but is not resized and is uploaded as its original format.
How to can I process a image uploaded from my application and their result was saved on Amazon S3 to after use them?
Fairly new to Django/Python, but this is how I solved it. Save large file to AWS, open it with Pillow, then resize and save in memory. Then push to AWS using default_storage just as the django-storages help docs suggest. Note that img.thumbnail will retain aspect with just the longer edge of the image set to 1000 pixels. image is the Django model ImageField.
from django.core.files.storage import default_storage
from io import BytesIO
..
def save(self, *args, **kwargs):
#run save of parent class above to save original image to disk
super().save(*args, **kwargs)
memfile = BytesIO()
img = Image.open(self.image)
if img.height > 1000 or img.width > 1000:
output_size = (1000, 1000)
img.thumbnail(output_size, Image.ANTIALIAS)
img.save(memfile, 'JPEG', quality=95)
default_storage.save(self.image.name, memfile)
memfile.close()
img.close()
You can make it easier and use easy_thumbnails app.
If you want to crop the image on save then you can do it with:
from easy_thumbnails.fields import ThumbnailerImageField
CROP_SETTINGS = {'size': (1000, 500), 'crop': 'smart'}
class UploadStudyOffer(models.Model):
image =ThumbnailerImageField(upload_to=get_image_path,
resize_source=CROP_SETTINGS)
Or you can manually specify the size of the image in the template:
{% load thumbnail %}
<img src="{% thumbnail offer.image 1000x500 crop %}" alt="" />
Most of what I found online suggested removing the Pillow resize and writing an AWS Lambda function to handle the resize on upload. I initially tried that approach, but according to the AWS docs you shouldn’t use the same bucket for input and output, meaning I had to create a second S3 bucket just for resized images. I couldn’t figure out how to get that setup working with django-storages.
A second approach I found mentioned using a buffer to save the resized image into, and then saving that to AWS. The examples of this that I found were either incomplete or used old versions of python. Here is what actually worked for me.
user/models.py
from app.utils import image_resize
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default="profile-default.png",
upload_to="profile_pics")
def __str__(self):
return f"{self.user.username} Profile"
def save(self, *args, **kwargs):
image_resize(self.image, 512, 512)
super().save(*args, **kwargs)
app/utils.py
from django.core.files import File
from pathlib import Path
from PIL import Image
from io import BytesIO
image_types = {
"jpg": "JPEG",
"jpeg": "JPEG",
"png": "PNG",
"gif": "GIF",
"tif": "TIFF",
"tiff": "TIFF",
}
def image_resize(image, width, height):
# Open the image using Pillow
img = Image.open(image)
# check if either the width or height is greater than the max
if img.width > width or img.height > height:
output_size = (width, height)
# Create a new resized “thumbnail” version of the image with Pillow
img.thumbnail(output_size)
# Find the file name of the image
img_filename = Path(image.file.name).name
# Spilt the filename on “.” to get the file extension only
img_suffix = Path(image.file.name).name.split(".")[-1]
# Use the file extension to determine the file type from the image_types dictionary
img_format = image_types[img_suffix]
# Save the resized image into the buffer, noting the correct file type
buffer = BytesIO()
img.save(buffer, format=img_format)
# Wrap the buffer in File object
file_object = File(buffer)
# Save the new resized file as usual, which will save to S3 using django-storages
image.save(img_filename, file_object)
I’m overriding the save method still, and calling a function I’ve placed in utils.py of my main application. The following happens in the image_resize function:
The image_function checks if the image is too wide or tall and, if it is, saves a resized version first to a memory buffer and then to S3. Back in the save method we call super().save() to save the remaining fields. The super().save() needs to be called after the image.save() or both the original and the resized images will get uploaded to S3.
I hope that was helpful to someone.
You can Visit https://blog.soards.me/posts/resize-image-on-save-in-django-before-sending-to-amazon-s3/ for more details
Related
i'm making a site for uploading and processing of files (mostly images but also other stuff). sometimes, some files need some editing before saving.
i want edit the uploaded file before saving it to media dir (mostly to avoid IO interaction and only keep original file in memory and discard after saving edit version).
edit: so i thought i have to run the edit function in POST, and change information in request.FILES. but i failed, and don't know what to do anymore.
NOTE: i'm mostly looking for a way to do it via class views.
here's some code for reference:
The model:
class FilePatient(models.Model):
file_imag = models.FileField(upload_to='')
The View:
class FileAddView(LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, CreateView):
model = FilePatient
fields = ['file_imag']
def post(self, request, *args, **kwargs):
if request.FILES['file_imag'].name.endswith('.png'):
newFile=editFile(request.FILES['image_imag'])
# what to do, what not to do
return super().post(request, *args, **kwargs)
I hope the following will help you... this code "edits" the file by resizing the original file if it is too big. Based on working code, I substituted the vars you used in your question, not tested as below.
from pathlib import Path
import PIL
from six import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
img = request.FILES['fileInput']
if img.name.endswith('.png'):
fp = FilePatient.objects.get(...) # get the object for uploading file
img2 = PIL.Image.open(img)
exif = img2.info['exif'] if 'exif' in img2.info else None
max_hw = 800 # px width / height maximum
width_percent = (max_hw/float(img2.size[0]))
heigth_percent = (max_hw/float(img2.size[1]))
min_wh_percent = float(min(width_percent, heigth_percent))
if min_wh_percent <= 1:
width_size = int((float(img2.size[0])*min_wh_percent))
height_size = int((float(img2.size[1])*min_wh_percent))
img2 = img2.resize((width_size, height_size), PIL.Image.ANTIALIAS)
fp.file_imag = img2
buffer = BytesIO()
if exif:
img2.save(buffer, format='JPEG', exif=exif, quality=90)
else:
img2.save(buffer, format='JPEG', quality=90)
buffer.seek(0)
fp.file_imag.name = Path(img.name).stem
fp.file_imag = InMemoryUploadedFile(buffer,
'ImageField',
f"{fp.file_imag.name}.jpg",
'image/jpeg',
img2.size,
"utf-8")
fp.save()
Context
I have made a simple web app for uploading content to a blog. The front sends AJAX requests (using FormData) to the backend which is Bottle running on Python 3.7. Text content is saved to a MySQL database and images are saved to a folder on the server. Everything works fine.
Image processing and PIL/Pillow
Now, I want to enable processing of uploaded images to standardise them (I need them all resized and/or cropped to 700x400px).
I was hoping to use Pillow for this. My problem is creating a PIL Image object from the file object in Bottle. I cannot initialise a valid Image object.
Code
# AJAX sends request to this route
#post('/update')
def update():
# Form data
title = request.forms.get("title")
body = request.forms.get("body")
image = request.forms.get("image")
author = request.forms.get("author")
# Image upload
file = request.files.get("file")
if file:
extension = file.filename.split(".")[-1]
if extension not in ('png', 'jpg', 'jpeg'):
return {"result" : 0, "message": "File Format Error"}
save_path = "my/save/path"
file.save(save_path)
The problem
This all works as expected, but I cannot create a valid Image object with pillow for processing. I even tried reloading the saved image using the save path but this did not work either.
Other attempts
The code below did not work. It caused an internal server error, though I am having trouble setting up more detailed Python debugging.
path = save_path + "/" + file.filename
image_data = open(path, "rb")
image = Image.open(image_data)
When logged manually, the path is a valid relative URL ("../domain-folder/images") and I have checked that I am definitely importing PIL (Pillow) correctly using PIL.PILLOW_VERSION.
I tried adapting this answer:
image = Image.frombytes('RGBA', (128,128), image_data, 'raw')
However, I won’t know the size until I have created the Image object. I also tried using io:
image = Image.open(io.BytesIO(image_data))
This did not work either. In each case, it is only the line trying to initialise the Image object that causes problems.
Summary
The Bottle documentation says the uploaded file is a file-like object, but I am not having much success in creating an Image object that I can process.
How should I go about this? I do not have a preference about processing before or after saving. I am comfortable with the processing, it is initialising the Image object that is causing the problem.
Edit - Solution
I got this to work by adapting the answer from eatmeimadanish. I had to use a io.BytesIO object to save the file from Bottle, then load it with Pillow from there. After processing, it could be saved in the usual way.
obj = io.BytesIO()
file.save(obj) # This saves the file retrieved by Bottle to the BytesIO object
path = save_path + "/" + file.filename
# Image processing
im = Image.open(obj) # Reopen the object with PIL
im = im.resize((700,400))
im.save(path, optimize=True)
I found this from the Pillow documentation about a different function that may also be of use.
PIL.Image.frombuffer(mode, size, data, decoder_name='raw', *args)
Note that this function decodes pixel data only, not entire images.
If you have an entire image file in a string, wrap it in a BytesIO object, and use open() to load it.
Use StringIO instead.
From PIL import Image
try:
import cStringIO as StringIO
except ImportError:
import StringIO
s = StringIO.StringIO()
#save your in memory file to this instead of a regular file
file = request.files.get("file")
if file:
extension = file.filename.split(".")[-1]
if extension not in ('png', 'jpg', 'jpeg'):
return {"result" : 0, "message": "File Format Error"}
file.save(s)
im = Image.open(s)
im.resize((700,400))
im.save(s, 'png', optimize=True)
s64 = base64.b64encode(s.getvalue())
From what I understand, you're trying to resize the image after it has been saved locally (note that you could try to do the resize before it is saved). If this is what you want to achieve here, you can open the image directly using Pillow, it does the job for you (you do not have to open(path, "rb"):
image = Image.open(path)
image.resize((700,400)).save(path)
I want to build an image gallery using Django. Each image is a post, of course. Now, I don't want to be uploading each image independently. I want to zip them all and upload them in the Django admin page and maybe create some kind of a trigger to:
decompressed the zip
read all the images info
store the info in a database, each image in a row
Is this possible with Django? what would be your best way to accomplish this? I'll appreciate any kind of help, I'm very new to Django (like 5 hours new)
Yes, it's possible. Here is a broad outline inspired entirely by how Mezzanine implements this.
First you define a field for accepting the zip file:
class BaseGallery(models.Model):
zip_import = models.FileField(blank=True, upload_to=upload_to("galleries")
Then you have a separate model that is Foreign keyed to your parent model. In the example here the parent model is BaseGallery and the image model is GalleryImage:
class GalleryImage(Orderable):
gallery = models.ForeignKey(Gallery, related_name="images")
file = models.ImageField(upload_to="galleries")
Then in your model's save method, you can extract this zip file and save the individual images:
from django.core.files import ContentFile
from django.conf import settings
from zipfile import ZipFile
def save(self, delete_zip_import=True, *args, **kwargs):
"""
If a zip file is uploaded, extract any images from it and add
them to the gallery, before removing the zip file.
"""
super(BaseGallery, self).save(*args, **kwargs)
if self.zip_import:
zip_file = ZipFile(self.zip_import)
for name in zip_file.namelist():
data = zip_file.read(name)
try:
from PIL import Image
image = Image.open(BytesIO(data))
image.load()
image = Image.open(BytesIO(data))
image.verify()
except ImportError:
pass
except:
continue
name = os.path.split(name)[1]
# You now have an image which you can save
path = os.path.join(settings.MEDIA_ROOT, "galleries",
native(str(name, errors="ignore")))
saved_path = default_storage.save(path, ContentFile(data))
self.images.create(file=saved_path)
if delete_zip_import:
zip_file.close()
self.zip_import.delete(save=True)
Note, the bit where the image is actually saved has been simplified and if you look at the source I have linked to there is a bit more jiggery-pokery required to handle unicode file names etc.
Also note that Mezzanine uses its own FileField which isn't the same as Django's FileField. I have tried to refactor this in the example above.
Here's code to extract files from an uploaded ZIP file:
source
import zipfile
localFile = 'beer.zip'
with zipfile.ZipFile(localFile, 'r') as myzip:
myzip.extractall()
print 'got:', [info.filename for info in myzip.infolist()]
reference
Python zipfile module
Iam using django-ckeditor.
Iam uploading image files and .swf files in image and flash upload icons. But when i upload files other than these, Iam getting error as "cannot identify image file". For swf files , thumbnail is not creating and dummy image is showing without any url.
My views in ckeditor as :
#csrf_exempt
def upload(request):
upload = request.FILES['upload']
upload_ext = os.path.splitext(upload.name)[1]
upload_filename = get_upload_filename(upload.name, request.user)
out = open(upload_filename, 'wb+')
for chunk in upload.chunks():
out.write(chunk)
out.close()
if upload_ext != ".swf":
create_thumbnail(upload_filename)
url = get_media_url(upload_filename)
url = url.replace("\\", "/")
return HttpResponse("""
<script type='text/javascript'>
window.parent.CKEDITOR.tools.callFunction(%s, '%s');
</script>""" % (request.GET['CKEditorFuncNum'], url))
thumbnail,
def create_thumbnail(filename):
image = Image.open(filename)
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
imagefit = ImageOps.fit(image, THUMBNAIL_SIZE, Image.ANTIALIAS)
imagefit.save(get_thumb_filename(filename))
Anyone help me to solve this issue..
Technically, only image files can have thumbnails. All other types should have default thumbnails based on the their types(may be icon image). In your code, the logic should
if upload_ext in [".jpg", ".png",]:
create_thumbnail(filename)
else:
create_default_thumbnail(upload_ext) // for .swf, .doc etc
My website allows users to upload photos to their gallery via email and it works perfectly. However, photos taken on the iPhone in portrait mode do NOT rotate correctly. I would like to rotate the photo using PIL during the "mail filtering" process. Here is the code that I am using to successfully retrieve the image from the email and save to my Django model
image = ContentFile(b64decode(part.get_payload()))
img = Photo(user=user)
filename = part.get_filename().lower()
img.img.save(filename, image)
img.save()
*Updated code that successfully rotates temp image to local dir *
image = ContentFile(b64decode(part.get_payload()))
im = Image.open(image)
tempfile = im.rotate(90)
tempfile.save("/srv/www/mysite.com/public_html/media/images/rotate.jpg", "JPEG")
img = Photo(user=user)
img.img.save('rotate.jpg', tempfile)
img.save()
Now, I'm trying to take the "temp image" and save it to my model. Unfortunately, it is not saving. Any suggestions would be greatly appreciated.
http://effbot.org/imagingbook/image.htm
clearly states that rotate() returns an new image instance.
There is nothing in the documentation about in-place operations. Or?