首页 > 解决方案 > Flask Web 服务器在处理和上传到 S3 时损坏图像

问题描述

我正在尝试将图像从 Flask 网络服务器存储在 S3 上。服务器接收图像并对其进行处理以创建两个副本(压缩 + 缩略图),然后上传所有三个。

映像的两个进程都可以正常接收,但原始进程已损坏。该代码不会引发任何错误。

这都是使用 Python 3.6、Flask 1.0.2、Boto3 1.9.88

以下是上传页面代码的摘录:

form = UploadForm()
if form.validate_on_submit():
    photo = form.photo.data

    name, ext = os.path.splitext(form.photo.data.filename)

    photo_comp, photo_thum = get_compressions(photo, 'JPEG')

    pic_set = {}

    pic_set['original'] = photo
    pic_set['compressed'] = photo_comp
    pic_set['thumbs'] = photo_thum

    for pic in pic_set:
        output = upload_to_s3(file=pic_set[pic], username=current_user.username, \
                         filetype = pic, \
                         bucket_name = current_app.config['S3_BUCKET'])

函数 'get_compressions()' 产生一个缩小的 .jpeg 文件加上一个缩略图(很抱歉缩进格式出现错误):

def get_compressions(file, filetype):
#Creates new compressed and thumbnail copies. Checks for alpha
#channel, removes if present, resaves as compressed .jpeg, then
#wraps into a Werkzeug FileStorage type.

name, ext = os.path.splitext(file.filename)

temp_compress = BytesIO()
temp_thumb = BytesIO()

image = Image.open(file)

if image.mode in ['RGBA', 'LA', 'RGBa']:
    image2 = Image.new('RGB', image.size, '#ffffff')
    image2.paste(image, None, image)
    image = image2.copy()

image.save(temp_compress, format=filetype, quality=85, optimize=True)
image.thumbnail((400,400), Image.ANTIALIAS)
image.save(temp_thumb, format=filetype, optimize=True)  

temp_thumb.seek(0)
temp_compress.seek(0)

file_comp = FileStorage(stream=temp_compress,
                        filename=name + '.' + filetype,
                        content_type='image/jpg',
                        name=file.name,
                        )

file_thum = FileStorage(stream=temp_thumb,
                        filename=name + '.' + filetype,
                        content_type='image/jpg',
                        name=file.name,
                        )

return file_comp, file_thum

最后,'upload_to_s3()' 函数在 AWS S3 上是一个相当简单的保存:

def upload_to_s3(file, username, filetype, bucket_name, acl= os.environ.get('AWS_DEFAULT_ACL')):
s3.upload_fileobj(
    Fileobj=file
    , Bucket=bucket_name
    , Key = "{x}/{y}/{z}".format(x=username,y=filetype,z=file.filename)
    , ExtraArgs = {'ContentType': file.content_type}
)
print('Upload successful: ', file.filename)
return file.filename

我的信念是压缩正在影响原始文件对象的上传 - 虽然 PIL image.save() 返回一个新对象,但压缩行为似乎以某种方式影响了原始对象。

在尝试对此进行研究时,我注意到 Flask 作为标准是多线程的,并且 Python GIL 不适用于 I/O 操作或图像处理 - 不确定这是否相关。

我试图解决这个问题的两个选项是:

  1. 更改代码顺序执行,使其进入原始上传 - 压缩 - 压缩上传,但这导致错误'ValueError: I/O operation on a closed file'

  2. 在使用 get_compressions() 之前使用 copy.deepcopy() 创建一个新对象,但这会导致'TypeError: cannot serialize '_io.BufferedRandom' object'.

我真的不知道如何进行!可能会上传原始文件,然后让服务器在后台处理压缩(基于上传的文件),但这会给想要立即检索压缩版本以加载页面的客户端带来问题。

标签: pythonamazon-s3flaskiopython-imaging-library

解决方案


在您的get_compressions函数中,您正在读取作为 FileStorage 对象的原始file文件,因此您的文件指针最终位于文件末尾,并且您最终将零字节文件写入 S3。因此,您需要seek回到文件的开头,就像您对压缩版本所做的那样:

file.seek(0)                                                                
temp_thumb.seek(0)                                                          
temp_compress.seek(0)

推荐阅读