2
# ! /usr/bin/env python
# -*- coding: utf-8 -*-
# image_upload.py

""" Python        2.7.3
    Cherrypy      3.2.2
    PostgreSQL    9.1
    psycopy2      2.4.5
    SQLAlchemy    0.7.10
    PIL           1.1.7
"""

我正在尝试从客户端的本地文件中将图像及其缩略图保存在 SQLAlchemy 数据库中。上传通过 HTML 表单到 CherryPy 服务器进行。然后使用 Python Imaging Library (PIL) 处理图像以获得缩略图。最后结果应该保存在 SQLAlchemy 数据库中,该数据库失败了,可能是因为缩略图。

注意:我尝试在不将缩略图临时保存在文件中的情况下执行此操作。由于数据已经在 RAM 中可用,我不喜欢将其保存在文件夹中,然后将其添加到数据库中,最后从文件夹中删除它的想法。做这样的事情感觉不对。

EDIT 4 中最后的解决方案

一点关于 ObjectImage 类的表,
我必须照原样使用它!
这是一个要求!与 Python 2.7 一样

column       type            attributes
----------------------------------------
id           int             PRIMARY KEY
object_id    int             REFERENCES Obeject(id) ON DELETE CASCADE
filename     varchar(252)    
image        bytea           
thumbnail    bytea           
preview      boolean         

以下是与 PostgreSQL 数据库的 SQLAlchemy 连接。
它作为“会话”存储在 CherryPy 会话中,并作为 s1 检索。
就这样没有人将 s1 混淆为 CherryPy 对象。

pg = sqlalchemy.create_engine(
        'postgresql://{}:{}@{}:{}/{}'.format(
            user, password, server, port, data))
Session = sessionmaker(bind=pg)
cherrypy.session['session'] = Session

极简 Python 代码:

""" 
    The variable "image_file"
    comes directly from the POSTed dictionary.
    image_file = kwargs['image_file']
"""

s1 = cherrypy.session.get('session')
image_entry = {}

img = StringIO.StringIO(image_file.file.read())
image = Image.open(img)
image_entry['image'] = image.copy()

thumb = image.copy()
thumb.thumbnail((30000, 300,), Image.ANTIALIAS)
image_entry['thumbnail'] = thumb.copy()

image_entry['object_id'] = chosen_one
image_entry['filename'] = image_file.filename
image_entry['preview'] = 't'

s1.add(ObjecteImage(**image_entry))
s1.commit()                          #line 1621

CherryPy 回溯:

  File "image_upload.py", line 1621, in store_image
    s1.commit()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 710, in commit
    self.transaction.commit()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 368, in commit
    self._prepare_impl()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 347, in _prepare_impl
    self.session.flush()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 1734, in flush
    self._flush(objects)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 1805, in _flush
    flush_context.execute()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/unitofwork.py", line 331, in execute
    rec.execute(self)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/unitofwork.py", line 475, in execute
    uow
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/persistence.py", line 64, in save_obj
    table, insert)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/persistence.py", line 558, in _emit_insert_statements
    execute(statement, params)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1449, in execute
    params)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1584, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/engine/base.py", line 1691, in _execute_context
    context)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/engine/default.py", line 331, in do_execute
    cursor.execute(statement, parameters)
TypeError: can't escape instance to binary

在第二次尝试中,我尝试至少直接插入最初上传的文件,以检查是否有任何更改。

极简 Python 代码:

s1 = cherrypy.session.get('session')
image_entry = {}

img = StringIO.StringIO(image_file.file.read())
image = Image.open(img)
image_entry['image'] = image_file

[ ... the same as above ... ]

s1.add(ObjecteImage(**image_entry))
s1.commit()                          #line 1621

CherryPy 回溯:

  File "image_upload.py", line 1621, in store_image
    s1.commit()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 710, in commit
    self.transaction.commit()

  [ ... the same as above ... ]

  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-0.7.10-py2.7-linux-x86_64.egg/sqlalchemy/engine/default.py", line 331, in do_execute
    cursor.execute(statement, parameters)
TypeError: can't escape Part to binary

如何将 PIL Image 中的实例或 PIL Image 缩略图中的 None 转换为二进制文件?
或者可能是一个缓冲区,bytearray ......我什至不知道我真正需要什么。

字节数组转换导致:

  File "image_upload.py", line 1611, in store_image
    image_entry['thumbnail'] = bytearray(thumb.copy())
  TypeError: iteration over non-sequence

缓冲区强制转换导致:

  File "image_upload.py", line 1611, in store_image
    image_entry['thumbnail'] = buffer(thumb.copy())
  TypeError: buffer object expected

在这里没有找到答案:
- Python Imaging Library Handbook以前的链接
- CherryPy 文件处理

是否有更好的工具/库来解决这个问题?
一种只创建缩略图?

编辑1:

我对如何将 Image 对象保存到流中做了一些研究。
但首先我尝试了 PIL.Image.tostring() 函数:

thumb = image.copy()
thumb.thumbnail((30000, 300,), Image.ANTIALIAS)
thumb.tostring()
image_entry['thumbnail'] = thumb.copy()

然后我尝试了 BytesIO() 模块。
它导致了 UnsupportedOperation: fileno PIL
由于它是 PIL 中的一个已知错误,我用它的 fork Pillow 替换了 PIL 并再次尝试:

thumb = image.copy()
thumb.thumbnail((30000, 300,), Image.ANTIALIAS)
stream = BytesIO()
thumb.save(stream, "JPEG")
image_entry['thumbnail'] = stream.getvalue()

两者都给了我 SQLAlchemy TypeError: can't escape instance to binary
当然,就像在错误被追溯到之前一样:

s1.add(ObjectImage(**image_entry))
s1.commit()                          #line 1621

最后我用 StringIO.StringIO() 替换了 BytesIO(),但它并没有改变任何事情。
我想这更像是一个特殊的 SQLAlchemy 问题。

编辑2:

在我错误地提到未知的 SQLAlchemy TypeError: can't escape Part to binary
Corrected in EDIT 1 之前,它是 SQLAlchemy TypeError: can't escape instance to binary
这只是因为我试图将 POSTed 值保存在数据库中:

""" 
    The variable "image_file"
    comes directly from the POSTed dictionary.
    image_file = kwargs['image_file']

    Alternative first line:
    img = StringIO.StringIO(image_file.file.read())
"""

img = BytesIO(image_file.file.read())
image = Image.open(img)
image_entry['image'] = image_file    # the dict value was meant be image.copy()

s1.add(ObjectImage(**image_entry))
s1.commit()                          #line 1621

编辑 3:

似乎我犯了一个错误,仍然试图插入全尺寸图像作为实例,
而缩略图已经以正确的方式“格式化”。

"""
    old version:
"""

img = BytesIO(image_file.file.read())
image = Image.open(img)
image_entry['image'] = image.copy()


"""
    new version:
"""

img = BytesIO(image_file.file.read())
image = Image.open(img)

fullsize = image.copy()
stream = BytesIO()
fullsize.save(stream, "JPEG")
image_entry['image'] =  stream.getvalue()


"""
    from EDIT 1:
"""

thumb = image.copy()
thumb.thumbnail((30000, 300,), Image.ANTIALIAS)
stream = BytesIO()
thumb.save(stream, "JPEG")
image_entry['thumbnail'] = stream.getvalue()

现在至少在表格中插入了一个很长的 HEX 代码,也许我可以使用它。
不过,图像和缩略图列似乎包含相同的十六进制代码。

编辑4:

图像和缩略图不包含相同的十六进制代码,正如稍后确认的 SQL 查询。
只有前 1179 和后 4 个字符相同,我刚开始检查。
两者之间的内容不同,每个条目的长度也不同。
最后将整个代码片段作为一个。

首先是必要的导入:

from io import BytesIO
import base64
import cherrypy
import sqlalchemy
from sqlalchemy.orm import sessionmaker
from PIL import Image

其次是引擎和会话:

pg = sqlalchemy.create_engine(
        'postgresql://{}:{}@{}:{}/{}'.format(
            user, password, server, port, data))
Session = sessionmaker(bind=pg)
cherrypy.session['session'] = Session

三、图片和缩略图上传代码:

s1 = cherrypy.session.get('session')
image_file = kwargs['image_file']

img = BytesIO(image_file.file.read())
image = Image.open(img)

fullsize = image.copy()
stream = BytesIO()
fullsize.save(stream, "JPEG")
image_entry['image'] =  stream.getvalue()

thumb = image.copy()
thumb.thumbnail((30000, 300,), Image.ANTIALIAS)
stream = BytesIO()
thumb.save(stream, "JPEG")
image_entry['thumbnail'] = stream.getvalue()

image_entry['sample'] = chosen_one
image_entry['filename'] = image_file.filename
image_entry['preview'] = 't'

s1.add(ObjectImage(**image_entry))
s1.commit()                          #line 1621

最后一个简短的缩略图检索 HTML 代码:

s1 = cherrypy.session.get('session')
qry = (s1.query(ObjectImage.id, ObjectImage.filename, ObjectImage.thumbnail).
    filter(ObjectImage.preview == 't'))

for rowX in qry:
    yield (u'<img src="data:image/jpeg; base64, {}" alt="thumbnail">'.
        format(base64.b64encode(rowX.thumbnail)))

出于性能原因,我考虑编写一个额外的函数来替换数据 URI 方案。
但现在感谢 Neaţu Ovidiu Gabriel,他提到了“保存到流”选项,
并感谢提供这些资源的人:

- Python 变量和文件(简单的 io.BytesIO() 示例)
- python Image PIL 到二进制 Hex(提到 UnsupportedOperation: fileno)

4

1 回答 1

1

我不知道 Cherrypy,但我的猜测是您将 PIL.Image 对象作为参数发送给 s1,而 s1 不能做它必须做的事情,因为他不认识那种对象。

于 2013-08-02T19:09:29.283 回答