[ Python subprocess: pipe an image blob to imagemagick shell command ]

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())

Answer 1


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())