2015-02-25 2 views
1

Я пытаюсь сжать изображения, не касаясь диска, используя версию STDIN различных библиотек (jpegoptim в этом примере).Python: оптимизация изображений в памяти (StringIO & POpen с jpegoptim)

Этот код не возвращает оптимизированное (сжатое jpegoptim) изображение.

Может кто-нибудь помочь или объяснить, почему это использование Popen() с объектом StringIO.StringIO() не возвращает оптимизированную версию изображения? Если я сохраню файл на диск, он работает нормально.

import sys 
import urllib2 as urllib 
import StringIO 

from subprocess import Popen, PIPE, STDOUT 
fp = urllib.urlopen('http://www.path.to/unoptimized.jpg') 
out_im2 = StringIO.StringIO(fp.read()) # StringIO Image 
print "Image Size: %s" % format(sys.getsizeof(out_im2.getvalue())) 
subp = Popen(["/usr/bin/jpegoptim", "-"], shell=True, stdout=PIPE, stdin=PIPE, stderr=STDOUT) 
image_str = subp.communicate(input=out_im2.getvalue())[0] 
out_im2.write(image_str) 

##This should be a different size if it worked! It's not 
print "Compressed JPG: %s" % format(sys.getsizeof(out_im2.getvalue())) 
+0

FYI, jpegoptim 1.4+ требуется для функциональности stdin. – ryan83

ответ

2

Это потому, что вы пишете в тот же буфер ввода. Создайте новый StringIO().

Буфер StringIO вначале расширяется до размера первого несжатого jpeg. Затем вы пишете над этим буфером, начиная с позиции 0, с новым коротким буфером строк, но он не автоматически урезает ваш буфер или что-то еще. Буфер StringIO по-прежнему имеет тот же размер, и на самом деле все конечные данные будут оставлены по сравнению с исходным изображением.

In [1]: import StringIO 

In [2]: out = StringIO.StringIO("abcdefg") 

In [3]: out.getvalue() 
Out[3]: 'abcdefg' 

In [4]: out.write("123") 

In [5]: out.getvalue() 
Out[5]: '123defg' 
0

Есть несколько вопросов:

  1. Проблемы с неправильной перезаписью буфера StringIO()pointed out by @doog abides
  2. Использованием len вместо sys.getsizeof(). Последние возвращает размер внутреннего представления в памяти, не равно числу байт в байтовой строке

  3. Don't use a list argument and shell=True together

Вы можете передать сокет как стандартный ввод подпроцессу на некоторых системах:

import socket 
from urllib2 import urlopen 
from subprocess import check_output 

saved = socket._fileobject.default_bufsize 
socket._fileobject.default_bufsize = 0 # hack to disable buffering 
try: 
    fp = urlopen('http://www.path.to/unoptimized.jpg') 
finally: 
    socket._fileobject.default_bufsize = saved # restore back 

# urlopen() has read http headers; subprocess can read the body now 
image_bytes = check_output(["/usr/bin/jpegoptim", "-"], stdin=fp) 
fp.close() 

# use `image_bytes` bytestring here.. 

stderr не установлен, чтобы избежать скрытых ошибок.

Смежные вопросы