I'm trying to upload resized image to S3:
fp = urllib.urlopen('http:/example.com/test.png')
img = cStringIO.StringIO(fp.read())
im = Image.open(img)
im2 = im.resize((500, 100), Image.NEAREST)
AK = 'xx' # Access Key ID
SK = 'xx' # Secret Access Key
conn = S3Connection(AK,SK)
b = conn.get_bucket('example')
k = Key(b)
k.key = 'example.png'
k.set_contents_from_filename(im2)
but I get an error:
in set_contents_from_filename
fp = open(filename, 'rb')
TypeError: coercing to Unicode: need string or buffer, instance found
You need to convert your output image into a set of bytes before you can upload to s3. You can either write the image to a file then upload the file, or you can use a cStringIO object to avoid writing to disk as I've done here:
import boto
import cStringIO
import urllib
import Image
#Retrieve our source image from a URL
fp = urllib.urlopen('http://example.com/test.png')
#Load the URL data into an image
img = cStringIO.StringIO(fp.read())
im = Image.open(img)
#Resize the image
im2 = im.resize((500, 100), Image.NEAREST)
#NOTE, we're saving the image into a cStringIO object to avoid writing to disk
out_im2 = cStringIO.StringIO()
#You MUST specify the file type because there is no file name to discern it from
im2.save(out_im2, 'PNG')
#Now we connect to our s3 bucket and upload from memory
#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
conn = boto.connect_s3()
#Connect to bucket and create key
b = conn.get_bucket('example')
k = b.new_key('example.png')
#Note we're setting contents from the in-memory string provided by cStringIO
k.set_contents_from_string(out_im2.getvalue())
My guess is that Key.set_contents_from_filename expects a single string argument, but you are passing in im2, which is some other object type as returned by Image.resize. I think you will need to write your resized image out to the filesystem as a name file and then pass that file name to k.set_contents_from_filename. Otherwise find another method in the Key class that can get the image contents from an in-memory construct (StringIO or some object instance).
print ("loading object", input_bucket, input_key)
response = s3client.get_object(Bucket=input_bucket, Key=input_key)
print("s3 get object response", response)
body = response['Body']
image = Image.open(body)
print ("generating thumbnail", output_width, output_height)
thumbnail = resizeimage.resize_thumbnail(
image, [output_width, output_height])
body.close()
print ("saving thumbnail", output_format)
with io.BytesIO() as output:
thumbnail.save(output, output_format)
print ("uploading thumbnail", output_bucket, output_key)
output.seek(0)
s3client.put_object(Bucket=output_bucket, Key=output_key,
Body=output, ContentType=output_content_type)
Related
I'm trying to write an AWS Lambda function that takes a TIFF, converts it to JPEG, then outputs it in base64 so that lambda can serve it. But I keep running into malformed response, or issues with reshape_as_image saying axes doesn't match array.
My understanding was that the return of memfile.read() would allow me to use reshape_as_image, however my logic seems faulty.
Without saving to disk, how can I get from memfile to a base64 jpeg representation so that lambda can serve it? I've also tried pillow but I think the necessary step is where I'm failing.
def get_image(self, driver="jpeg"):
data = self.get_image()
with MemoryFile() as memfile:
# Change the driver for output
data[1]['driver'] = driver
with MemoryFile() as memfile:
# Change the driver for output
data[1]['driver'] = driver
with memfile.open(**data[1]) as dataset:
dataset.write(data[0])
image = memfile.read()
image = reshape_as_image(image)
im = Image.open(io.BytesIO(image))
b64data = base64.b64encode(im.tobytes()).decode('utf-8')
return b64data
It seems this isn't necessary for some reason, assuming because memfile.read() gives the actual bytes of the image.
def get_image(self, store=False, driver="GTiff"):
data = self.crop_ortho(store)
with MemoryFile() as memfile:
# Change the driver for output
data[1]['driver'] = driver
with memfile.open(**data[1]) as dataset:
dataset.write(data[0])
image = memfile.read()
im = Image.open(io.BytesIO(image))
im = im.convert('RGB')
# Save bytes to a byte array
imgByteArr = io.BytesIO()
im.save(imgByteArr, format='jpeg')
b64data = base64.b64encode(imgByteArr.getvalue())
return b64data
I am trying to create a zip file in memory with a list of PIL image objects.
import io
from PIL import Image
def get_images(path):
'''This returns a list of PIL images'''
pass
def file_process_im_memory():
images = get_images('mypath')
file_object = io.BytesIO()
file_object2 = io.BytesIO()
images[0].save(file_object, 'PNG')
images[1].save(file_object2, 'PNG')
file_object.seek(0)
file_object2.seek(0)
The image are written in memory, now I want to create an zip file from the in memory image files, I tried below but it didn't work.
zip_file = ZipFile(zip_file_bytes_io, 'w')
for image in images:
file_object = io.BytesIO()
image.save(file_object, 'PNG')
file_object.seek(0)
zip_file.writestr(file_object.getvlaue())
zip_file_bytes_io.seek(0)
I believe this will do what you want. As I said in a now deleted comment, one issue was that the first argument to zip_file.writestr() should be the filename / member name it will be given in the archive with the second one being the data to be written.
In order to be able to do that the image filenames must be preserved. In the code below get_images() now returns a list of [<image file name>, <PIL image object>] pairs of values so the name will be available when writing the in-memory zip file.
import io
import os
from PIL import Image
from pprint import pprint
from zipfile import ZipFile
def get_images(path):
""" Returns a list of image file base name & PIL image object pairs. """
# Harcoded with two images for testing purposes.
IMAGES = (r"C:\vols\Files\PythonLib\Stack Overflow\cookie_cutter_background.png",
r"C:\vols\Files\PythonLib\Stack Overflow\Flying-Eagle.png")
images = []
for image_path in IMAGES:
# Get image file name without extension.
image_name = os.path.splitext(os.path.os.path.basename(image_path))[0]
pil_image = Image.open(image_path)
images.append([image_name, pil_image])
return images
def file_process_in_memory():
""" Converts PIL image objects into BytesIO in-memory bytes buffers. """
images = get_images('mypath')
for i, (image_name, pil_image) in enumerate(images):
file_object = io.BytesIO()
pil_image.save(file_object, "PNG")
pil_image.close()
images[i][1] = file_object # Replace PIL image object with BytesIO memory buffer.
return images # Return modified list.
images = file_process_in_memory()
# Create an in-memory zip file from the in-memory image file data.
zip_file_bytes_io = io.BytesIO()
with ZipFile(zip_file_bytes_io, 'w') as zip_file:
for image_name, bytes_stream in images:
zip_file.writestr(image_name+".png", bytes_stream.getvalue())
pprint(zip_file.infolist()) # Print final contents of in memory zip file.
print('done')
Sample output:
[<ZipInfo filename='cookie_cutter_background.png' filemode='?rw-------' file_size=727857>,
<ZipInfo filename='Flying-Eagle.png' filemode='?rw-------' file_size=462286>]
done
This is what I need: when a user upload an image, verify if that image exceeds some size, if so change the size. This code works with no errors, but the image saved has the size without changes. The image is in a Google Cloud Storage Bucket, it is uploaded just before, but it works fine.
Any idea is welcome. Thanks in advance.
from PIL import Image
from django.core.files.storage import default_storage
from google.cloud import storage
from google.cloud.storage import Blob
import io
if default_storage.exists(image_path):
client = storage.Client()
bucket = client.get_bucket('mybucket.appspot.com')
blob = Blob(image_path, bucket)
contenido = blob.download_as_string()
fp = io.BytesIO(contenido)
im = Image.open(fp)
x, y = im.size
if x>450 or y>450:
im.thumbnail((450,450))
im.save(fp, "JPEG")
# im.show() here it shows the image thumbnail (thumbnail works)
blob.upload_from_string(fp.getvalue(), content_type="image/jpeg")
blob_dest = Blob('new_image.jpg', bucket)
blob.download_as_string()
blob_dest.rewrite(blob)
You've got a lot of extra stuff happening here, including saving the image to the local filesystem, which is unnecessary. This minimal example should work:
import io
from PIL import Image
from django.core.files.storage import default_storage
from google.cloud import storage
if default_storage.exists(image_path):
client = storage.Client()
bucket = client.get_bucket('mybucket.appspot.com')
# Download the image
blob = bucket.get_blob(data['name']).download_as_string()
bytes = io.BytesIO(blob)
im = Image.open(bytes)
x, y = im.size
if x>450 or y>450:
# Upload the new image
thumbnail_blob = bucket.blob('new_image.jpg')
thumbnail_blob.upload_from_string(im.resize(450, 450).tobytes())
I've tried the solution from #dustin-ingram and it happened to me that the file ended up corrupted when downloading it again. Using the code from this answer I reached a solution.
import io
from PIL import Image
from google.cloud import storage
__max_size = 450, 450
image_name = 'my_images/adasdasadas7c2a7367cf1f.jpg'
client = storage.Client()
bucket = client.bucket('my-bucket')
# Download the image
blob = bucket.blob(image_name).download_as_string()
blob_in_bytes = io.BytesIO(blob)
# Translating into PIL Image object and transform
pil_image = Image.open(blob_in_bytes)
pil_image.thumbnail(__max_size, Image.ANTIALIAS)
# Creating the "string" object to use upload_from_string
img_byte_array = io.BytesIO()
pil_image.save(img_byte_array, format='JPEG')
# Create the propper blob using the same bucket and upload it with it's content type
thumbnail_blob = bucket.blob(image_name)
thumbnail_blob.upload_from_string( img_byte_array.getvalue(), content_type="image/jpeg")
Regardless of what Cloud Storage you are using, you can use this method to resize the uploaded image in memory, then you can upload the image or manipulate it as you wish:
from io import BytesIO
from PIL import Image as PilImage
import os
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
def resize_uploaded_image(image, max_width, max_height):
size = (max_width, max_height)
# Uploaded file is in memory
if isinstance(image, InMemoryUploadedFile):
memory_image = BytesIO(image.read())
pil_image = PilImage.open(memory_image)
img_format = os.path.splitext(image.name)[1][1:].upper()
img_format = 'JPEG' if img_format == 'JPG' else img_format
if pil_image.width > max_width or pil_image.height > max_height:
pil_image.thumbnail(size)
new_image = BytesIO()
pil_image.save(new_image, format=img_format)
new_image = ContentFile(new_image.getvalue())
return InMemoryUploadedFile(new_image, None, image.name, image.content_type, None, None)
# Uploaded file is in disk
elif isinstance(image, TemporaryUploadedFile):
path = image.temporary_file_path()
pil_image = PilImage.open(path)
if pil_image.width > max_width or pil_image.height > max_height:
pil_image.thumbnail(size)
pil_image.save(path)
image.size = os.stat(path).st_size
return image
In case you are taking the image from a post form, you can do this:
image = request.FILES['image']
...
image = resize_uploaded_image(image, 450, 450)
...
thumbnail_blob.upload_from_string(image.read(), image.content_type)
A better way is to use it in the clean method of the image field in your form:
class ImageForm(forms.Form):
IMAGE_WIDTH = 450
IMAGE_HEIGHT = 450
image = forms.ImageField()
def clean_image(self):
image = self.cleaned_data.get('image')
image = resize_uploaded_image(image, self.IMAGE_WIDTH, self.IMAGE_HEIGHT)
return image
I achieved some results when combining your question with the answer of Dustin:
bucket = client.get_bucket('mybucket.appspot.com')
blob = Blob(image_path, bucket)
contenido = blob.download_as_string()
fp = io.BytesIO(contenido)
im = Image.open(fp)
x, y = im.size
if x > 128 or y > 128:
thumbnail_blob = bucket.blob('new_image.jpg')
thumbnail_blob.upload_from_string(im.resize((128, 128), 2).tobytes())
The 2 in resize is for resampling filter:
Use Image.NEAREST (0), Image.LANCZOS (1), Image.BILINEAR (2), Image.BICUBIC (3), Image.BOX (4) or Image.HAMMING (5)
I'm getting URL through POST via DAJAX.
The URL is then passed into the function below. A TypeError is thrown.
I do not want to save the 'img' to disk and then reopen it to do the conversion.
I'm not sure what else to try so I figured I as the world. Thanks for the help in advance.
def getqrcode(link):
bsettings = Bitcoinsettings.objects.get(pk=1)
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_H, box_size=bsettings.qrcodesize , border=5,)
qr.add_data(link)
qr.make(fit=True)
img = qr.make_image()
output = StringIO.StringIO()
img.save(output, 'GIF')
contents = output.getvalue()
data = base64.b64encode(open(contents,'rb').read())
data = "data:image/png;base64," + data
output.close()
img = []
return data
TypeError: file() argument 1 must be encoded string without NULL bytes, not str
Here is the ajax.py code.
from torgap.bitcoin.bitcoin import getqrcode
from dajaxice.decorators import dajaxice_register
from dajax.core import Dajax
#dajaxice_register
def getimage(request, image):
try:
dajax = Dajax()
link = image
image = getqrcode(link)
dajax.assign('#qrcode', 'src', image)
return dajax.json()
except Exception as e:
print e
I'm not sure you understand what returns ouput.getvalue() since you are trying to read the file again with
data = base64.b64encode(open(contents,'rb').read())
but in the line above contents already contains a string representation of the image file. And it is almost sure that here is where are hidden the annoying NULL bytes that file() complains about.
Try change the line above by:
data = base64.b64encode(contents)
Also you can give a look at StringIO reference.
I am trying to convert a pdf to jpegs using wand, but when I iterate over the SingleImages in image.sequence and save each image separately. I am saving each image on AWS, with database references, using Django.
image_pdf = Image(blob=pdf_blob)
image_jpeg = image_pdf.convert('jpeg')
for img in image_jpeg.sequence:
memory_file = SimpleUploadedFile(
"{}.jpeg".format(img.page_number),
page.container.make_blob())
spam = Spam.objects.create(
page_image=memory_file,
caption="Spam")
This doesn't work, the page.container is calling the parent Image class, and the first page is written over and over again. How do I get the second frame/page for saveing?
Actually, you can get per-file blobs:
for img in image_jpeg.sequence:
img_page = Image(image=img)
Then you can work with each img_page variable like a full-fledged image: change format, resize, save, etc.
It seems you cannot get per file blobs without messing with c_types. So this is my solution
from path import path # wrapper for os.path
import re
import tempfile
image_pdf = Image(blob=pdf_blob)
image_jpeg = image_pdf.convert('jpeg')
temp_dir = path(tempfile.mkdtemp())
# set base file name (join)
image_jpeg.save(temp_dir / 'pdf_title.jpeg')
images = temp_dir.files()
sorted_images = sorted(
images,
key=lambda img_path: int(re.search(r'\d+', img_path.name).group())
)
for img in sorted_images:
with open(img, 'rb') as img_fd:
memory_file = SimpleUploadedFile(
img.name,
img_fd.read()
)
spam = Spam.objects.create(
page_image=memory_file,
caption="Spam Spam",
)
tempfile.rmtree(tempdir)
Not as clean as doing it all in memory, but it gets it done.