# ! /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)