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}")