Python/Django download Image from URL, modify, and save to ImageField - python

I've been looking for a way to download an image from a URL, preform some image manipulations (resize) actions on it, and then save it to a django ImageField. Using the two great posts (linked below), I have been able to download and save an image to an ImageField. However, I've been having some trouble manipulating the file once I have it.
Specifically, the model field save() method requires a File() object as the second parameter. So my data has to eventually be a File() object. The blog posts linked below show how to use urllib2 to save your an image URL into a File() object. This is great, however, I also want to manipulate the image using PIL as an Image() object. (or ImageFile object).
My preferred approach would be then to load the image URL directly into an Image() object, preform the resize, and convert it to a File() object and then save it in the model. However, my attempts to convert an Image() to a File() have failed. If at all possible, I want to limit the number of times I write to the disk, so I'd like to do this object transformation in Memory or using a NamedTemporaryFile(delete=True) object so I don't have to worry about extra files laying around. (Of course, I want the file to be written to disk once it is saved via the model).
import urllib2
from PIL import Image, ImageFile
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
inStream = urllib2.urlopen('http://www.google.com/intl/en_ALL/images/srpr/logo1w.png')
parser = ImageFile.Parser()
while True:
s = inStream.read(1024)
if not s:
break
parser.feed(s)
inImage = parser.close()
# convert to RGB to avoid error with png and tiffs
if inImage.mode != "RGB":
inImage = inImage.convert("RGB")
# resize could occur here
# START OF CODE THAT DOES NOT SEEM TO WORK
# I need to somehow convert an image .....
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(inImage.tostring())
img_temp.flush()
file_object = File(img_temp)
# .... into a file that the Django object will accept.
# END OF CODE THAT DOES NOT SEEM TO WORK
my_model_instance.image.save(
'some_filename',
file_object, # this must be a File() object
save=True,
)
With this approach, the file appears corrupt whenever I view it as an image. Does anyone have any approach that takes a file file from a URL, allows one to manipulate it as an Image and then save it to a Django ImageField?
Any help is much appreciated.
Programmatically saving image to Django ImageField
Django: add image in an ImageField from image url
Update 08/11/2010: I did end up going with StringIO, however, I was stringIO was throwing an unusual Exception when I tried to save it in a Django ImageField. Specifically, the stack trace showed a name error:
"AttribueError exception "StringIO instance has no attribute 'name'"
After digging through the Django source, it looks like this error was caused when the model save tries to access the size attribute of the StringIO "File". (Though the error above indicates a problem with the name, the root cause of this error appears to be the lack of a size property on the StringIO image). As soon as I assigned a value to the size attribute of the image file, it worked fine.

In an attempt to kill 2 birds with 1 stone. Why not use a (c)StringIO object instead of a NamedTemporaryFile? You won't have to store it on disk anymore and I know for a fact that something like this works (I use similar code myself).
from cStringIO import StringIO
img_temp = StringIO()
inImage.save(img_temp, 'PNG')
img_temp.seek(0)
file_object = File(img_temp, filename)

Related

Uploading Image with PIL+twython on twitter without saving it

After much searching I finally found it. Here's the response.
Step 1
code for the user to provide the link and for PIL to resize it according to some conditions (not relevant to the question).
PIL opens the image file link like this:
img=Image.open(io.BytesIO(requests.get(url).content))
where url is the link.
Step2
then PIL must save it AND seek(0) on the io.BytesIO class:
blob = io.BytesIO()
img.save(blob, 'JPEG')
blob.seek(0)
response = twitter.upload_media(media=blob);
Step 3
Proceed according to the documendation:
twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']])
Twython's documendation is outdated and StringIO() has changed package and also doesn't take bytes objects. I also can't get the logic behind making a bytes object to a string and then send it as a bytes object.
https://twython.readthedocs.io/en/latest/usage/advanced_usage.html
The media parameter also takes io.BytesIO class objects as shown above and in this simpler example below:
response = twitter.upload_media(media=io.BytesIO(requests.get(url).content));

How to get Pillow to make identical copies (edit EXIF in-line)

You would think it's quite simple, but the following code doesn't work as I would expect:
from hashlib import md5
from PIL import Image
im = Image.open("/tmp/original.jpg")
im.save("/tmp/new.jpg", quality="keep")
original = Image.open("/tmp/original.jpg")
new = Image.open("/tmp/new.jpg")
assert md5(original.tobytes()).hexdigest() == md5(new.tobytes()).hexdigest()
Why is it that when I'm simply saving an image as a new file, and keeping the quality settings the same, that the image data isn't identical? What am I missing?
Update (Explanation):
My problem is that I have a Pillow JpegImage object being handed to my code as part of a pipeline and I don't have control over the step at which the file is saved to disk:
<magic> → <my code> → <magic that saves to disk>
All I want my code to do is add/update/replace (any of these) the EXIF data for the to-be-saved jpeg image. As this info doesn't appear to be editable on an image object, the only way that I can figure to do this is to save the image to a temporary place (like BytesIO), save it and the re-open it with Image.open() before passing it to the next function in the chain.
Please tell me that there's a smarter, more efficient way to do this?

Python Pillow: Make image progressive before sending to 3rd party server

I have an image that I am uploading using Django Forms, and its available in the variable as InMemoryFile What I want to do is to make it progressive.
Code to make an image a progressive
img = Image.open(source)
img.save(destination, "JPEG", quality=80, optimize=True, progressive=True)
Forms.py
my_file = pic.pic_url.file
photo = uploader.upload_picture_to_album(title=title, file_obj=my_file)
The issue is, I have to save the file in case I want to make it progressive, and open it again to send it to the server. (It seems a redundant actions to make it progressive)
I just want to know if there is anyway to make an image progressive which does not save the image physically on disk but in memory, which I can use the existing code to upload it?
Idea
Looking for something similar.
my_file=pic.pic_url.file
progressive_file = (my_file)
photo = picasa_api.upload_picture_to_album(title=title, file_obj=progressive_file)
If all you want is not saving the intermediate file to disk, you can save it to a StringIO. Both PIL.open() and PIL.save() accept file-like objects as well as filenames.
img = Image.open(source)
progressive_img = StringIO()
img.save(progressive_img, "JPEG", quality=80, optimize=True, progressive=True)
photo = uploader.upload_picture_to_album(title=title, file_obj=progressive_img)
The uploader needs to support working with the StringIO but that is hopefully the case.
It's probably possible to directly stream the result from save() using suitable coroutines, but that is a little more work.

Get binary image data in Python

How do I take an image and turn that into binary image data?
This is what I have tried:
class get_binary_data(image_url):
#Get the image online online_image = http://www.myimg.com/test.jpg
online_image = requests.get(image_url).content
image_data = BytesIO(online_image)
However this does not seem to give me the binary image data, could someone help explain the process of getting the binary image data?
This is what I'm trying to do:
app = subprocess.Popen("externalApp",
in=subprocess.PIPE,
out=subprocess.PIPE)
app.in.write(image_data)
app.in.close()
which gives me the error:
IOError: [Errno 32] Broken pipe
You do not need to wrap the response data in a BytesIO object just to write it to a pipe. Use the response.content data directly:
class get_binary_data(image_url):
#Get the image online online_image = http://www.myimg.com/test.jpg
return requests.get(image_url).content
I used a BytesIO object in my answer to your previous question only because you wanted to load that data into a PIL Image object. It is a requirement for Image.open() that a file-like object with seek support is provided, and BytesIO does just that.
Here, however, you need a (byte)string, so just use the .contents value.

How can I created a PIL Image from an in-memory file?

More specifically, I want to change the filetype of an image uploaded through a Django ImageField.
My current thinking is to created a custom ImageField and overwrite the save method to manipulate the file.
I've having trouble getting an in memory file to because a PIL Image instance.
Thanks for the help.
Have you tried StringIO ?
see the docs http://effbot.org/imagingbook/introduction.htm#more-on-reading-images
#Reading from a string
import StringIO
im = Image.open(StringIO.StringIO(buffer))
Note that Django's ImageField inherits the open method from FieldFile. This returns a stream object that can be passed to PIL's Image.open (the standard factory method for creating Image objects from an image stream):
stream = imagefield.open()
image = Image.open(stream)
stream.close()
# ... and then save image with: image.save(outfile, format, options)
See PIL Image documentation.

Categories