Using a PIL Image with flasks send_file without saving to disk? - flask

I'm saving the PIL image to a io.BytesIO() object.
imgByteArr = io.BytesIO()
img.save(imgByteArr, format=format)
Then trying to return the image to the user.
return send_file(img.getvalue(), mimetype="image/" + img_details["ext"].lower())
But I'm getting the error
TypeError: The view function did not return a valid response. The function either returned None or ended without a return statement.
I dont want to send as an attachment, i want the image to be displayed on the page.
Does any one know if it is possible without saving to disk first?

I was missing "seek"
imgByteArr = io.BytesIO()
img.save(imgByteArr, format=format)
imgByteArr.seek(0)
return send_file(imgByteArr, mimetype="image/" + img_details["ext"].lower())
This now works.

Related

Flask: change content size

I have a Flask route which returns a video feed. I would like to be able to change the video frame size. How can I do this?
def gen(stream):
while True:
try:
frame = stream.get_last()
if frame is not None:
yield (b'--frame\r\n'
b'Pragma-directive: no-cache\r\n'
b'Cache-directive: no-cache\r\n'
b'Cache-control: no-cache\r\n'
b'Pragma: no-cache\r\n'
b'Expires: 0\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
except Exception as exception:
# Output unexpected Exceptions.
logging.error("Error occurred", exc_info=True)
#app.route('/video')
def video_feed():
return Response(gen(RedisImageStream(conn, args)),
mimetype='multipart/x-mixed-replace; boundary=frame')
If you need to change the size of the image only on the viewport, you may be able to edit your code that displays the image. If this is a website, maybe you can use some CSS configuration.
If you really need to change the size of the images you send out from your server, you will need to load each image into memory, then apply the conversion you want, and then re-encode it as JPEG. This is computationally expensive, and this is one the main sources of latency in video streaming; in fact, the main reason the streaming service of YouTube and Twitch and the usual suspects is expensive to run is because they need to re-encode the incoming video into many resolutions and send it out in real time.
For your case of Python and JPEG images, you can use PIL / Pillow. Here's an example:
import io
import PIL
def downscale(image, size):
'''
Accept a JPEG binary representation of an image,
and return the JPEG of a smaller version of the image
that has the same aspect ratio and is not larger than size.
'''
fp = io.BytesIO(image) # create a file-like object from the supplied buffer
im = PIL.Image(fp)
im_downscale = im.thumbnail(size) # Image.thumbnail creates a smaller version of the image no larger than size.
# If this is not what you want, take a look at Image.transform
outp = io.BytesIO() # create empty buffer for output
im_downscale.save(outp, "JPEG")
bytestring = outp.getvalue()
return bytestring
Then, before your yield line, call:
frame = downscale(frame, (400, 300))

Getting an error while giving POST request

The error that I'm getting on Postman
My Code:
#app.route('/getcoordinates', methods=['GET', 'POST'])
def get_image():
if request.method == 'POST':
file = request.files['image'].read()
#use numpy to construct an array from the bytes
x = np.fromstring(file, dtype='uint8')
#decode the array into an image
img = cv2.imdecode(x, cv2.IMREAD_UNCHANGED)
#In output we get the x&y coordinates of the face bounding box
output = give_coordinates(img)
else:
return "Error: No image provided. Please specify a image."
return jsonify(output)
Or can anyone tell me how to give a picture in POST request because I think that's where it's going wrong.
Possible answered here: "Post Image data using POSTMAN"
That's not how you send file on postman. What you did is sending a string which is the path of your image, nothing more.
What you should do is;
After setting request method to POST, click to the 'body' tab.
Select form-data. At first line, you'll see text boxes named key and value. Write 'image' to the key. You'll see value type which is set to 'text' as default. Make it File and upload your file.
Then select 'raw' and paste your json file. Also just next to the binary choice, You'll see 'Text' is clicked. Make it JSON.

Programmatically saving image to Django ImageField returning 404 in production

I have a Django app where users can upload images and can have a processed version of the images if they want. and the processing function returns the path, so my approach was
model2.processed_image = processingfunction( model1.uploaded_image.path)
and as the processing function returns path here's how it looks in my admin view
not like the normally uploaded images
In my machine it worked correctly and I always get a 404 error for the processed ones while the normally uploaded is shown correctly when I try to change the url of the processed from
myurl.com/media/home/ubuntu/Eyelizer/media/path/to/the/image
to
myurl.com/media/path/to/the/image
so how can I fix this ? is there a better approach to saving the images manually to the database ?
I have the same function but returns a Pil.image.image object and I've tried many methods to save it in a model but I didn't know how so I've made the function return a file path.
I think the problem is from nginx where I define the media path.
should/can I override the url attribute of the processedimage?
making something like
model.processed_image.url = media/somefolder/filename
Instead of using the PIL Image directly, create a django.core.files.File.
Example:
from io import BytesIO
from django.core.files import File
img_io = BytesIO() # create a BytesIO object to temporarily save the file in memory
img = processingfunction( model1.uploaded_image.path)
img.save(img_io, 'PNG') # save the PIL image to the BytesIO object
img_file = File(thumb_io, name='some-name.png') # create the File object
# you can use the `name` from `model1.uploaded_image` and use
# that above
# finally, pass the image file to your model field
model2.processed_image = img_file
To avoid repetition of this code, it would be a good idea to keep this code in processingfunction and return the File object directly from there.
My approach is a bit different from #Xyres's, I thought xyres's would make a duplicate of the existing image and create a new one and when I tried overriding the URL attribute it returned an error of
can't set the attribute
but when I saw this question and this ticket I tried making this and it worked
model2.processed_image = processingfunction(model1.uploaded_image.path)
full_path = model2.processed_image.path
model2.processed_image.name = full_path.split('media')[1]
so that explicitly making the URL media/path/to/image and cut out all of the unneeded parts like home/ubuntu and stuff

Django ImageField validation (is it sufficient)?

I have a lot of user uploaded content and I want to validate that uploaded image files are not, in fact, malicious scripts. In the Django documentation, it states that ImageField:
"Inherits all attributes and methods from FileField, but also validates that the uploaded object is a valid image."
Is that totally accurate? I've read that compressing or otherwise manipulating an image file is a good validation test. I'm assuming that PIL does something like this....
Will ImageField go a long way toward covering my image upload security?
Django validates the image uploaded via form using PIL.
See https://code.djangoproject.com/browser/django/trunk/django/forms/fields.py#L519
try:
# load() is the only method that can spot a truncated JPEG,
# but it cannot be called sanely after verify()
trial_image = Image.open(file)
trial_image.load()
# Since we're about to use the file again we have to reset the
# file object if possible.
if hasattr(file, 'reset'):
file.reset()
# verify() is the only method that can spot a corrupt PNG,
# but it must be called immediately after the constructor
trial_image = Image.open(file)
trial_image.verify()
...
except Exception: # Python Imaging Library doesn't recognize it as an image
raise ValidationError(self.error_messages['invalid_image'])
PIL documentation states the following about verify():
Attempts to determine if the file is broken, without actually decoding
the image data. If this method finds any problems, it raises suitable
exceptions. This method only works on a newly opened image; if the
image has already been loaded, the result is undefined. Also, if you
need to load the image after using this method, you must reopen the
image file.
You should also note that ImageField is only validated when uploaded using form. If you save the model your self (e.g. using some kind of download script), the validation is not performed.
Another test is with the file command. It checks for the presence of "magic numbers" in the file to determine its type. On my system, the file package includes libmagic as well as a ctypes-based wrapper /usr/lib64/python2.7/site-packages/magic.py. It looks like you use it like:
import magic
ms = magic.open(magic.MAGIC_NONE)
ms.load()
type = ms.file("/path/to/some/file")
print type
f = file("/path/to/some/file", "r")
buffer = f.read(4096)
f.close()
type = ms.buffer(buffer)
print type
ms.close()
(Code from here.)
As to your original question: "Read the Source, Luke."
django/core/files/images.py:
"""
Utility functions for handling images.
Requires PIL, as you might imagine.
"""
from django.core.files import File
class ImageFile(File):
"""
A mixin for use alongside django.core.files.base.File, which provides
additional features for dealing with images.
"""
def _get_width(self):
return self._get_image_dimensions()[0]
width = property(_get_width)
def _get_height(self):
return self._get_image_dimensions()[1]
height = property(_get_height)
def _get_image_dimensions(self):
if not hasattr(self, '_dimensions_cache'):
close = self.closed
self.open()
self._dimensions_cache = get_image_dimensions(self, close=close)
return self._dimensions_cache
def get_image_dimensions(file_or_path, close=False):
"""
Returns the (width, height) of an image, given an open file or a path. Set
'close' to True to close the file at the end if it is initially in an open
state.
"""
# Try to import PIL in either of the two ways it can end up installed.
try:
from PIL import ImageFile as PILImageFile
except ImportError:
import ImageFile as PILImageFile
p = PILImageFile.Parser()
if hasattr(file_or_path, 'read'):
file = file_or_path
file_pos = file.tell()
file.seek(0)
else:
file = open(file_or_path, 'rb')
close = True
try:
while 1:
data = file.read(1024)
if not data:
break
p.feed(data)
if p.image:
return p.image.size
return None
finally:
if close:
file.close()
else:
file.seek(file_pos)
So it looks like it just reads the file 1024 bytes at a time until PIL says it's an image, then stops. This obviously does not integrity-check the entire file, so it really depends on what you mean by "covering my image upload security": illicit data could be appended to an image and passed through your site. Someone could DOS your site by uploading a lot of junk or a really big file. You could be vulnerable to an injection attack if you don't check any uploaded captions or make assumptions about the image's uploaded filename. And so on.

Saving animated GIFs using PIL (image saved does not animate)

I have Apache2 + PIL + Django + X-sendfile. My problem is that when I save an animated GIF, it won't "animate" when I output through the browser.
Here is my code to display the image located outside the public accessible directory.
def raw(request,uuid):
target = str(uuid).split('.')[:-1][0]
image = Uploads.objects.get(uuid=target)
path = image.path
filepath = os.path.join(path,"%s.%s" % (image.uuid,image.ext))
response = HttpResponse(mimetype=mimetypes.guess_type(filepath))
response['Content-Disposition']='filename="%s"'\
%smart_str(image.filename)
response["X-Sendfile"] = filepath
response['Content-length'] = os.stat(filepath).st_size
return response
UPDATE
It turns out that it works. My problem is when I try to upload an image via URL. It probably doesn't save the entire GIF?
def handle_url_file(request):
"""
Open a file from a URL.
Split the file to get the filename and extension.
Generate a random uuid using rand1()
Then save the file.
Return the UUID when successful.
"""
try:
file = urllib.urlopen(request.POST['url'])
randname = rand1(settings.RANDOM_ID_LENGTH)
newfilename = request.POST['url'].split('/')[-1]
ext = str(newfilename.split('.')[-1]).lower()
im = cStringIO.StringIO(file.read()) # constructs a StringIO holding the image
img = Image.open(im)
filehash = checkhash(im)
image = Uploads.objects.get(filehash=filehash)
uuid = image.uuid
return "%s" % (uuid)
except Uploads.DoesNotExist:
img.save(os.path.join(settings.UPLOAD_DIRECTORY,(("%s.%s")%(randname,ext))))
del img
filesize = os.stat(os.path.join(settings.UPLOAD_DIRECTORY,(("%s.%s")%(randname,ext)))).st_size
upload = Uploads(
ip = request.META['REMOTE_ADDR'],
filename = newfilename,
uuid = randname,
ext = ext,
path = settings.UPLOAD_DIRECTORY,
views = 1,
bandwidth = filesize,
source = request.POST['url'],
size = filesize,
filehash = filehash,
)
upload.save()
#return uuid
return "%s" % (upload.uuid)
except IOError, e:
raise e
Any ideas?
Thanks!
Wenbert
Where does that Image class come from and what does Image.open do?
My guess is that it does some sanitizing of the image data (which is a good thing), but does only save the first frame of the Gif.
Edit:
I'm convinced this is an issue with PIL. The PIL documentation on GIF says:
PIL reads GIF87a and GIF89a versions of the GIF file format. The library writes run-length encoded GIF87a files.
To verify, you can write the contents of im directly to disk and compare with the source image.
The problem is saving a PIL-opened version of the image. When you save it out via PIL, it will only save the first frame.
However, there's an easy workaround: Make a temp copy of the file, open that with PIL, and then if you detect that it's an animated GIF, then just save the original file, not the PIL-opened version.
If you save the original animated GIF file and then stream it back into your HTTP response, it will come through animated to the browser.
Example code to detect if your PIL object is an animated GIF:
def image_is_animated_gif(image):
# verify image format
if image.format.lower() != 'gif':
return False
# verify GIF is animated by attempting to seek beyond the initial frame
try:
image.seek(1)
except EOFError:
return False
else:
return True