使用boto将Pillow文件保存到S3

3
我使用s3boto将Amazon S3作为我的存储后端。我有一个带有ImageField的Image模型。当通过管理员上传图像时,它成功地上传到S3。现在我正在尝试使用Pillow在保存后创建缩略图。我已经验证了缩略图是否被创建,方法是调用show()方法,但由于某种原因它没有被上传到S3。我认为我保存的方式可能是错误的 - 任何建议将不胜感激。 tasks.py
from celery import shared_task
from .models import Image
import os
from django.core.files.storage import default_storage as storage
from PIL import Image as PillowImage

@shared_task
def create_thumbnails(pk):
    try:
        image = Image.objects.get(pk=pk)
    except Image.ObjectDoesNotExist:
        pass
    thumbnail_size = (450,200)
    filename, ext = os.path.splitext(image.image.name)
    try:
        fh = storage.open(image.image.name, 'r')
        im = PillowImage.open(fh)
        im.thumbnail(thumbnail_size)
        im.show() # TEST - This opens the resized image in Preview on my mac
        filename = filename +'_thumbnail' +ext
        new_file = storage.open(filename, 'w')
        im.save(new_file, "PNG")
        new_file.close()
    except IOError as error:
        print("cannot create thumbnail for ", filename, 'error ', error)

堆栈

Django 1.85

Python 2.7.10

5个回答

10

这非常有帮助,我用它找到了一种在Django中将图片写入S3而不直接使用Boto的方法。

基本上,PIL的save()方法不适合在S3上工作,但是default_storage.write()方法适用。关键是要使用default_storage.write()方法,直接从StringIO内存文件中写入二进制数据,像这样:

file_to_write.write(memory_file.getvalue())

这是我在 Django shell 中运行的代码(python manage.py shell)来测试这个问题:

>>> from django.core.files.storage import default_storage as storage
>>> from PIL import Image
>>> import StringIO
>>> i = storage.open('ImageToCreate.jpg','w+')
>>> m = storage.open('ImageAlreadyOnS3.jpg','r')
>>> im = Image.open(m)
>>> im = im.resize((640,360),3)
>>> sfile = StringIO.StringIO() #cStringIO works too
>>> im.save(sfile, format="JPEG")
>>> i.write(sfile.getvalue())
>>> i.close()
>>> m.close()

它在我的views.py中也起作用。

我发现它非常有用,因为它既适用于远程s3存储,也适用于我的本地开发环境(我的笔记本电脑上的一个文件夹)。


1
这是我在这里看到的最有用的答案之一,真不敢相信我是第一个点赞它的人。 - Reveclair

4
请注意,对于Python3,您可能需要使用BytesIO:
from io import BytesIO

# 'image' is a PIL image object.

imageBuffer = BytesIO()
image.save(imageBuffer, format=imageType)

imageFile = default_storage.open(imageFileName, 'wb')
imageFile.write(imageBuffer.getvalue())
imageFile.flush()
imageFile.close()

2
请注意,以下解决方案适用于Python 2。
models.py
class Image(models.Model):
    caption = models.CharField(max_length=100, null=True, blank=True)
    width = models.IntegerField(default=0, blank=True)
    height = models.IntegerField(default=0, blank=True)
    image = models.ImageField(width_field='width', height_field='height',blank=True)

tasks.py

from celery import shared_task
import os
from django.core.files.storage import default_storage as storage
from django.conf import settings
import mimetypes
import cStringIO
from PIL import Image as PillowImage
import boto
from .models import Image

@shared_task
def create_thumbnails(pk):
    try:
        image = Image.objects.get(pk=pk)
    except Image.ObjectDoesNotExist:
        pass
    try:
        thumbnail_size = (450,200)
        filename, ext = os.path.splitext(image.image.name)
        filename = filename +'_thumbnail' +ext
        existing_file = storage.open(image.image.name, 'r')
        im = PillowImage.open(existing_file)
        im = im.resize(thumbnail_size, PillowImage.ANTIALIAS)
        memory_file = cStringIO.StringIO()
        mime = mimetypes.guess_type(filename)[0]
        plain_ext = mime.split('/')[1]
        im.save(memory_file, plain_ext)
        
        conn  = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
        bucket = conn.get_bucket( 'yourbucketname', validate=False)
        k = bucket.new_key('media/' +filename)
        k.set_metadata('Content-Type', mime)
        k.set_contents_from_string(memory_file.getvalue())
        k.set_acl("public-read")
        memory_file.close()
    except Exception as error:
        print("cannot create thumbnail for ", filename, 'error ', error)

博客文章已经不存在了,因此您的解决方案中缺少重要部分。.models.Image是什么? - ricoms

2

我有同样的问题。

default_storage.write() # It didn't work. it was not saving anything to s3 

关于@RunLoop的回答比较复杂,与我在Django中想要做的不同,所以我做了这个并且它起作用了。

    import StringIO
    from PIL import Image

首先,阅读上传的文件。

    image = request.FILES['image'].read()  #atleast in my case 

创建一个类似文件的对象,以便我们可以使用Image来读取它。
    image_file = StringIO.StringIO(image)
    thumbnail_image = Image.open(image_file)

将图像调整为所需大小

    resized_thumbnail_image = thumbnail_image.resize((200, 200), Image.ANTIALIAS)

创建另一个类似于文件的对象或内存文件,这样我们就可以将图像实例写入其中并获得字符串值,该字符串值应传递给默认存储。
    resized_thumbnail_image_file = StringIO.StringIO()
    resized_thumbnail_image.save(resized_thumbnail_image_file, 'JPEG',quality=90)

    default_storage.save(save_path, ContentFile(resized_thumbnail_image_file.getvalue()))

希望我的第一个详细回答能够帮到你。

技术栈: python 2.7 Django 1.8


这应该是被接受的答案。要将其更新为Python 3,请使用from io import BytesIO而不是StringIO。否则,您将遇到此问题 - https://dev59.com/fVUL5IYBdhLWcg3wI1Ep - ricoms

0

这是一个比rotten的回答更易懂、可行的例子

from io import BytesIO
from PIL import Image
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.contrib.staticfiles.storage import staticfiles_storage

img = Image.new('RGB', (1920, 1080), color = 'red') # <-- use this
#img = Image.open(r'C:\Users\you\Pictures\profile.jpg') # <-- or a file on your computer

buffer = BytesIO()
img.save(buffer, format='JPEG')

fp = '{path}{name}'.format(path='adminz/profile/', name='default.jpg')
f = default_storage.open(fp, 'wb')
f.write(buffer.getvalue())
f.close()

我正在使用 Django Storages,这就是我需要弄清楚这个问题的原因。我包含了额外未使用的导入,因为在处理这个主题时,你经常会发现需要了解它们的原因。

我在项目中使用的另一个例子:

image = Image.open(photo) # <- open
cropped_image = image.crop((x, y, w + x, h + y)) # <- do something
with BytesIO() as f:
    cropped_image.save(f, format=settings.THUMBNAIL_FORMAT, quality=95) # <- save to buffer
    img = f.getvalue()

post.featured_image.save(photo.name,
                         content=ContentFile(img))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接