Python: оптимизация изображений в памяти (StringIO & POpen с jpegoptim)

Я пытаюсь сжать изображения, не касаясь диска, используя STDIN-версию различных библиотек (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()))

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'

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

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

  3. Не используйте аргумент списка и shell=True все вместе

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

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 не установлен, чтобы избежать сокрытия ошибок.

Другие вопросы по тегам