Heppoko Binbo Yarou no Newbie Nikki

旧題: へっぽこびんぼう野郎のnewbie日記 #vZkt8fc6J

Resolving "OSError: cannot write mode P as JPEG" Completely.

In this article, I'm going to talk about the following error. I assume this error is mainly related to Python but maybe it can be applied to other cases.

OSError: cannot write mode P as JPEG

This error originally comes from Pillow, a python package and PIL fork. In my case, this error was reported on Sentry, an error monitoring and reporting tool.

Reproduction

I could replay causing the error according to the following step.

1. You should prepare an 8-bit PNG file. I think it's not in trouble if you download from a website, unless you make it public.
2. Just change the extension name of the filename. It means if you download the PNG file, possibly it has ".png" So you should change the extension to ".jpg" intentionally.
3. Execute im.save() using this file.
4. "OSError: cannot write mode P as JPEG" happened.

Causation

An 8-bit PNG file is referred as P mode in Concepts — Pillow (PIL Fork) 3.1.2 documentation

However, JPEG format doesn't support this P mode, using 256 color palette. Neither Pillow nor PIL does. This is probably a specification of JPEG (I didn't search this in detail.)

That is why, the error occurred.

So I think the main reason this happened is a lack of some implementation which checks incoming data is valid or not. (Technically, it's just our case.)

Resolution

You can find codes easily and apply it if you only want to avoid this error.

python - Getting "cannot write mode P as JPEG" while operating on JPG image - Stack Overflow

OR

Caught IOError while rendering: cannot write mode P as JPEG · Issue #95 · SmileyChris/easy-thumbnails · GitHub

are convenient references.

This might work. However this way causes another trouble such as all files become RGB formatted files.

I belive the Image.format in the reference satisfies for this problem. Once you get an Image instance by doing Image.open() or whatever, you easily get the format.

Image Module — Pillow (PIL Fork) 3.1.2 documentation

This is like the following.

>> im.format
"PNG"

In my case, I thought this is the best solution, though it still has a problem that its format and extension don't match.

def im_save_cleanly(im, filepath):
    if im.format == 'PNG':
        im.save(filepath, 'PNG')
    else:
        im.save(filepath)

im_save_cleanly(im, 'foo/bar.jpg')

Now, that's all!!