Post

Converting WebP using sips and Python PIL/Pillow

Converting WebP to JPEG and PNG on macOS

Converting WebP images to JPEG or PNG can be done using sips:

1
2
$ sips -s format jpeg 1.webp --out 1.webp.jpg
$ sips -s format png 1.webp --out 1.webp.png

You can add these functions to your shell profile for convenience:

1
2
function jpg() { sips -s format jpeg --out $1.jpg $1; }
function png() { sips -s format png  --out $1.png $1; }

And use them like that:

1
2
$ jpg 1.webp
$ png 1.webp

It does not work backwards though (yet):

1
2
3
4
5
6
7
$ sips -s format webp 1.webp.png --out 1.webp.png.webp
Error: Unsupported output format org.webmproject.webp
Error 13: an unknown error occurred
Try 'sips --help' for help using this tool

$ sips -v
sips-305

Pillow

Using a Python package Pillow you can convert PNG/JPEG to WebP using a line of code:

1
2
import PIL.Image
PIL.Image.open(path_in).convert('RGB').save(path_out, 'webp')

Let’s make a console util webp for that (for example, on macOS).

1
2
3
4
5
6
7
8
9
10
11
12
# start making a project directory
mkdir -p WebP && cd WebP
# create a virtual environment with your Python
python -m venv venv
# update pip and install Pillow
venv/bin/pip install -U pip Pillow
# create webp.py with the local Python path to be used as shebang
echo "#\!${PWD}/venv/bin/python" >> webp.py
# make it executable
chmod +x webp.py
# link it to your local bin directory so that it cab be used everywhere
sudo ln -s "${PWD}/webp.py" /usr/local/bin/webp

Now we add Python code to webp.py that can take in arguments and check for errors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/Users/andrei/Projects/WebP/venv/bin/python
import argparse
import os

import PIL.Image


def main(path, output, quality):
    if not os.path.exists(path):
        raise argparse.ArgumentTypeError(f"Error: image not found {path!r}")

    if not output:
        output = path + '.webp'

    kw = {'quality': quality} if quality else {}

    PIL.Image.open(path).convert('RGB').save(output, 'webp', optimize=True, **kw)
    print(f"  🪄💫 ✨  created file://{os.path.abspath(output)} ({os.stat(output).st_size} bytes) "
          f"using Pillow {PIL.__version__}  ✨ ")


def int_range(a, b):
    def inner(arg):
        try:
            value = int(arg)
        except ValueError:
            raise argparse.ArgumentTypeError(f"Error: not an integer: {arg!r}")
        if value < a:
            raise argparse.ArgumentTypeError(f"Error: provided integer {value} "
                                             f"must be greater than or equal to {a}")
        if value > b:
            raise argparse.ArgumentTypeError(f"Error: provided integer {value} "
                                             f"must be less than or equal to {b}")
        return value
    return inner


if __name__ == '__main__':
    ap = argparse.ArgumentParser(description='')
    ap.add_argument('path')
    ap.add_argument('-o', '--output', nargs='?')
    ap.add_argument('-q', '--quality', nargs='?', type=int_range(0, 100))
    try:
        main(**ap.parse_args().__dict__)
    except argparse.ArgumentTypeError as e:
        print(e)
        exit(1)

Let’s test

1
2
3
4
$ cd ~/Downloads
$ webp 1.jpg
  🪄💫 ✨  created file:///Users/andrei/Downloads/1.jpg.webp (13618 bytes) using Pillow 10.2.0  ✨ 
$

You can set quality of image with -q argument

1
2
$ webp 1.jpg -q 40
  🪄💫 ✨  created file:///Users/andrei/Downloads/1.jpg.webp (9160 bytes) using Pillow 10.2.0  ✨ 

and/or set output path with -o

1
2
$ webp 1.jpg -q 40 -o 1.webp
  🪄💫 ✨  created file:///Users/andrei/Downloads/1.webp (9160 bytes) using Pillow 10.2.0  ✨ 

It works for PNGs too

1
2
$ webp 2.png
  🪄💫 ✨  created file:///Users/andrei/Downloads/2.png.webp (13168 bytes) using Pillow 10.2.0  ✨ 

The link in the output should be clickable in modern terminals.

Enjoy!

Comment on

This post is licensed under CC BY 4.0 by the author.