4

I have an image in-memory and I wish to execute the convert method of imagemagick using Python's subprocess. While this line works well using Ubuntu's terminal:

cat image.png | convert - new_image.jpg

This piece of code doesn't work using Python:

jpgfile = Image.open('image.png');
proc = Popen(['convert', '-', 'new_image.jpg'], stdin=PIPE, shell=True)
print proc.communicate(jpgfile.tostring())

I've also tried reading the image as a regular file without using PIL, I've tried switching between subprocess methods and different ways to write to stdin.

The best part is, nothing is happening but I'm not getting a real error. When printing stdout I can see imagemagick help on terminal, followed by the following:

By default, the image format of `file' is determined by its magic number. To specify a particular image format, precede the filename with an image format name and a colon (i.e. ps:image) or specify the image type as the filename suffix (i.e. image.ps). Specify 'file' as '-' for standard input or output. (None, None)

Maybe there's a hint in here I'm not getting. Please point me in the right direction, I'm new to Python but from my experience with PHP this should be an extremely easy task, or so I hope.

Edit:

This is the solution I eventually used to process PIL image object without saving a temporary file. Hope it helps someone. (in the example I'm reading the file from the local drive, but the idea is to read an image from a remote location)

out = StringIO()
jpgfile = Image.open('image.png')
jpgfile.save(out, 'png', quality=100);
out.seek(0);
proc = Popen(['convert', '-', 'image_new.jpg'], stdin=PIPE)
proc.communicate(out.read())
iMoses
  • 4,338
  • 1
  • 24
  • 39

1 Answers1

4

It is not subprocess that is causing any issue it is what you are passing to imagemagick that is incorrect,tostring() does get passed to imagemagick. if you actually wanted to replicate the linux command you can pipe from one process to another:

from subprocess import Popen,PIPE
proc = Popen(['cat', 'image.jpg'], stdout=PIPE)

p2 = Popen(['convert', '-', 'new_image.jpg'],stdin=proc.stdout)

proc.stdout.close()
out,err = proc.communicate()

print(out)

When you pass a list of args you don't need shell=True, if you wanted to use shell=True you would pass a single string:

from subprocess import check_call
check_call('cat image.jpg | convert - new_image.jpg',shell=True)

Generally I would avoid shell=True. This answer outlines what exactly shell=True does.

You can also pass a file object to stdin:

with open('image.jpg') as jpgfile:
    proc = Popen(['convert', "-", 'new_image.jpg'], stdin=jpgfile)

out, err = proc.communicate()

print(out)

But as there is no output when the code runs successfully you can use check_call which will raise a CalledProcessError if there is a non-zero exit status which you can catch and take the appropriate action:

from subprocess import check_call, CalledProcessError


with open('image.jpg') as jpgfile:
    try:
        check_call(['convert', "-", 'new_image.jpg'], stdin=jpgfile)
    except CalledProcessError as e:
        print(e.message)

If you wanted to write to stdin using communicate you could also pass the file contents using .read:

with  open('image.jpg') as jpgfile:
     proc = Popen(['convert', '-', 'new_image.jpg'], stdin=PIPE)
     proc.communicate(jpgfile.read())

If you want don't want to store the image on disk use a tempfile:

import tempfile
import requests

r = requests.get("http://www.reptileknowledge.com/images/ball-python.jpg")
out = tempfile.TemporaryFile()
out.write(r.content)
out.seek(0)

from subprocess import check_call
check_call(['convert',"-", 'new_image.jpg'], stdin=out)

Using a CStringIo.StringIO object:

import requests

r = requests.get("http://www.reptileknowledge.com/images/ball-python.jpg")
out = cStringIO.StringIO(r.content)

from subprocess import check_call,Popen,PIPE
p = Popen(['convert',"-", 'new_image.jpg'], stdin=PIPE)

p.communicate(out.read())
Community
  • 1
  • 1
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • It doesn't help me because I want to pass an image from memory *without* having to create a physical copy on the disk. All your examples are concerning reading an image file. Trying to pass an image object to stdin doesn't work, nothing happens as I described in the question. – iMoses May 08 '15 at 11:31
  • So where is the image coming from? – Padraic Cunningham May 08 '15 at 11:33
  • I use proxy to read the image via HTTP and I want to use the blob itself to create optimized versions of the image without having to save the original on disk. I know it shouldn't be an issue when using other programming languages, I have no idea how to implement it using Python. – iMoses May 08 '15 at 11:35
  • Thanks you! I used a slightly different solution, since I need to use PIL image object, but your answer definitely got me in the right direction. – iMoses May 08 '15 at 13:20
  • @iMoses, no worries, I was not totally sure where the data was coming from but I thought the logic would be round about the same. – Padraic Cunningham May 08 '15 at 13:23