PixConvertPixConvertBlog

PixConvertBlog

PythonPillowBatch5 min read

Convert PNG and JPG to WebP with Python

Using Pillow — the most popular Python imaging library. Single file, batch, lossless, and transparency all covered.

Install Pillow

pip install Pillow

Basic conversion (single file)

from PIL import Image

# Open and convert to WebP
img = Image.open("photo.jpg")
img.save("photo.webp", "WEBP", quality=85)

# PNG with transparency → WebP (preserves alpha channel)
img = Image.open("logo.png")  # RGBA mode
img.save("logo.webp", "WEBP", quality=90)

print("Done!")

Lossless WebP (pixel-perfect, best for logos)

from PIL import Image

img = Image.open("logo.png")
img.save(
    "logo.webp",
    "WEBP",
    lossless=True,   # Lossless mode
    quality=100,     # Ignored when lossless=True, but good practice
    method=6         # Compression effort 0-6 (6 = smallest, slower)
)

Batch convert a folder

from PIL import Image
from pathlib import Path

def batch_to_webp(
    src_dir: str,
    dst_dir: str = None,
    quality: int = 85,
    lossless: bool = False,
    extensions: tuple = (".jpg", ".jpeg", ".png"),
):
    src = Path(src_dir)
    dst = Path(dst_dir) if dst_dir else src
    dst.mkdir(parents=True, exist_ok=True)

    for path in src.iterdir():
        if path.suffix.lower() not in extensions:
            continue
        out = dst / path.with_suffix(".webp").name
        try:
            img = Image.open(path)
            # Preserve RGBA for PNG transparency
            if img.mode not in ("RGB", "RGBA"):
                img = img.convert("RGBA" if "A" in img.getbands() else "RGB")
            img.save(out, "WEBP", quality=quality, lossless=lossless)
            print(f"✓ {path.name} → {out.name}")
        except Exception as e:
            print(f"✗ {path.name}: {e}")

# Usage
batch_to_webp("./images", "./images/webp", quality=85)
# Lossless for logos
batch_to_webp("./logos", "./logos/webp", lossless=True)

Handling transparency correctly

Pillow preserves the alpha channel automatically when saving PNG to WebP. The only gotcha is when you have a palette-mode (mode='P') PNG:

from PIL import Image

img = Image.open("palette.png")  # mode='P' (palette with transparency)

# WRONG — saves without transparency
img.save("output.webp", "WEBP")

# CORRECT — convert to RGBA first
if img.mode == "P":
    img = img.convert("RGBA")
img.save("output.webp", "WEBP", quality=90)

Resize and convert in one step

from PIL import Image

img = Image.open("large-photo.jpg")

# Resize to max 1200px wide while keeping aspect ratio
img.thumbnail((1200, 1200), Image.LANCZOS)

img.save("resized.webp", "WEBP", quality=85)
print(f"Output size: {img.size}")

Command-line script

Save as convert_to_webp.py and run from terminal:

#!/usr/bin/env python3
"""
Convert image files to WebP.
Usage:
  python convert_to_webp.py input.jpg            # → input.webp
  python convert_to_webp.py *.jpg -q 90          # batch with quality
  python convert_to_webp.py logo.png --lossless  # lossless mode
"""
import argparse
from pathlib import Path
from PIL import Image


def convert(src: Path, quality: int, lossless: bool) -> None:
    dst = src.with_suffix(".webp")
    img = Image.open(src)
    if img.mode not in ("RGB", "RGBA"):
        img = img.convert("RGBA" if "transparency" in img.info else "RGB")
    img.save(dst, "WEBP", quality=quality, lossless=lossless)
    orig_kb = src.stat().st_size / 1024
    out_kb = dst.stat().st_size / 1024
    savings = (1 - out_kb / orig_kb) * 100
    print(f"✓ {src.name} → {dst.name}  ({orig_kb:.0f}KB → {out_kb:.0f}KB, -{savings:.0f}%)")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("files", nargs="+", help="Input image file(s)")
    parser.add_argument("-q", "--quality", type=int, default=85)
    parser.add_argument("--lossless", action="store_true")
    args = parser.parse_args()

    for pattern in args.files:
        for path in Path(".").glob(pattern) if "*" in pattern else [Path(pattern)]:
            try:
                convert(path, args.quality, args.lossless)
            except Exception as e:
                print(f"✗ {path}: {e}")