Blobstore 中的图像:获取元数据效率低下?

Images in Blobstore: inefficient to get metadata?

总结: 我正在使用 Blobstore 让用户上传要提供的图片。我想阻止用户上传不是有效图像或尺寸过大的文件。我正在使用 App Engine 的图像服务来获取相关的元数据。但是,为了从图像服务中获取有关图像类型或尺寸的任何信息,您必须首先执行转换,将转换后的图像提取到 App Engine 服务器。我让它进行无操作裁剪并编码为质量非常低的 JPEG 图像,但它仍在获取实际图像,而我想要的只是尺寸和文件类型。这是我能做的最好的吗?图像数据的内部传输(从 Blobstore 到 App Engine 服务器)会花费我吗?

详情:

Blobstore 似乎经过精心设计,可以高效地提供来自 App Engine 的图像。另一方面,某些操作似乎会让你跳过低效的圈套。我希望有人能告诉我有更有效的方法,或者让我相信我正在做的事情并不像我想的那样浪费。

我允许用户上传图片作为其他用户生成内容的一部分。 Blobstore 使上传和服务变得非常容易。不幸的是,它允许用户上传他们想要的任何文件,我想施加限制。

(旁注:Blobstore 确实允许您限制上传的文件大小,但此功能的文档很少。事实证明,如果用户试图超过限制,Blobstore 将 return 一个 413 "Entity too large",App Engine 处理程序根本没有被调用。)

我只想允许有效的 JPEG、GIF 和 PNG 文件,并且我想限制尺寸。这样做的方法似乎是上传后检查文件,如果不允许则删除它。这是我得到的:

class ImageUploadHandler(blobstore_handlers.BlobstoreUploadHandler):
  def post(self):
    try:
      # TODO: Check that user is logged in and has quota; xsrfToken.
      uploads = self.get_uploads()
      if len(uploads) != 1:
        logging.error('{} files uploaded'.format(len(uploads)))
        raise ServerError('Must be exactly 1 image per upload')
      image = images.Image(blob_key=uploads[0].key())
      # Do a no-op transformation; otherwise execute_transforms()
      # doesn't work and you can't get any image metadata.
      image.crop(0.0, 0.0, 1.0, 1.0)
      image.execute_transforms(output_encoding=images.JPEG, quality=1)
      if image.width > 640 or image.height > 640:
        raise ServerError('Image must be 640x640 or smaller')
      resultUrl = images.get_serving_url(uploads[0].key())
      self.response.headers['Content-Type'] = 'application/json'
      self.response.body = jsonEncode({'status': 0, 'imageUrl': resultUrl})
    except Exception as e:
      for upload in uploads:
        blobstore.delete(upload.key()) # TODO: delete in parallel with delete_async
      self.response.headers['Content-Type'] = 'text/plain'
      self.response.status = 403
      self.response.body = e.args[0]

代码中的注释突出了问题。

我知道图像可以在服务时动态调整大小(使用 get_serving_url),但我宁愿强制用户首先上传较小的图像,以避免用完存储空间。后来,我可能不想限制原始图像的尺寸,而是希望它在上传时自动缩小,但在缩小之前我仍然需要找出它的尺寸和类型。

我是否缺少更简单或更有效的方法?

实际上,Blobstore 并未针对提供图像进行优化,它可以对任何类型的数据进行操作。 The BlobReader class 可用于管理原始 blob 数据。

The GAE Images service 可用于管理图像(包括在 BlobStore 中存储为 blob 的图像)。从这个意义上说,您是对的,因为此服务仅在 对其执行转换后才提供有关上传图像的信息,这无助于在处理之前删除不需要的 blob 图像。

您可以做的是使用 the Image module from the PIL library (available between the GAE's Runtime-Provided Libraries) 覆盖在 BlobReader class.

之上

PIL 图像 formatsize 方法获取您查找的信息并在读取整个图像之前清理图像数据:

>>> image = Image.open('Spain-rail-map.jpg')
>>> image.format
'JPEG'
>>> image.size
(410, 317)

这些方法应该非常有效,因为它们只需要来自 open 方法加载的 blob 的图像 header 信息:

Opens and identifies the given image file. This is a lazy operation; the function reads the file header, but the actual image data is not read from the file until you try to process the data (call the load method to force loading).

这是在您的 ImageUploadHandler:

中完成叠加的方式
  from PIL import Image
  with blobstore.BlobReader(uploads[0].key()) as fd:
      image = Image.open(fd)
      logging.error('format=%s' % image.format)
      logging.error('size=%dx%d' % image.size)

当您上传到 Google 云存储 (GCS) 而不是 blobstore 时,您可以更好地控制对象上传条件,例如名称、类型和大小。策略文档控制用户条件。如果用户不满足这些上传条件,该对象将被拒绝。

文档 here.

示例:

{"expiration": "2010-06-16T11:11:11Z",
 "conditions": [
  ["starts-with", "$key", "" ],
  {"acl": "bucket-owner-read" },
  {"bucket": "travel-maps"},
  {"success_action_redirect":"http://www.example.com/success_notification.html" },
  ["eq", "$Content-Type", "image/jpeg" ],
  ["content-length-range", 0, 1000000]
  ]
}

如果超出内容长度,POST 响应:

<Error>
    <Code>EntityTooLarge</Code>
    <Message>
        Your proposed upload exceeds the maximum allowed object size.
    </Message>
    <Details>Content-length exceeds upper bound on range</Details>
</Error>

发送 PDF 后的 POST 响应:

<Error>
    <Code>InvalidPolicyDocument</Code>
    <Message>
        The content of the form does not meet the conditions specified in the policy document.
    </Message>
    <Details>Policy did not reference these fields: filename</Details>
</Error>

here 你可以找到我的 Python 直接上传到 GCS 的代码。