Skip to content

Instantly share code, notes, and snippets.

@erickacevedor
Created March 17, 2026 16:45
Show Gist options
  • Select an option

  • Save erickacevedor/0645307fa10e576a898333becf2c65fd to your computer and use it in GitHub Desktop.

Select an option

Save erickacevedor/0645307fa10e576a898333becf2c65fd to your computer and use it in GitHub Desktop.
Convert images to several formats and with an specific width
import argparse
import sys
from pathlib import Path
from PIL import Image, ImageOps
try:
import pillow_heif
pillow_heif.register_heif_opener()
HEIC_SUPPORTED = True
except ImportError:
HEIC_SUPPORTED = False
SUPPORTED_INPUTS = {".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif"}
SUPPORTED_OUTPUTS = {"jpg", "webp"}
def ensure_rgb(image: Image.Image, background=(255, 255, 255)) -> Image.Image:
if image.mode in ("RGBA", "LA") or (image.mode == "P" and "transparency" in image.info):
bg = Image.new("RGB", image.size, background)
alpha = image.convert("RGBA")
bg.paste(alpha, mask=alpha.getchannel("A"))
return bg
if image.mode != "RGB":
return image.convert("RGB")
return image
def resize_if_needed(image: Image.Image, max_width: int) -> Image.Image:
width, height = image.size
if width <= max_width:
return image
new_height = int((max_width / width) * height)
return image.resize((max_width, new_height), Image.LANCZOS)
def process_image(
input_path: Path,
output_dir: Path,
output_format: str,
max_width: int,
quality: int,
overwrite: bool = False,
) -> tuple[bool, str]:
try:
with Image.open(input_path) as img:
img = ImageOps.exif_transpose(img)
img = resize_if_needed(img, max_width)
if output_format == "jpg":
img = ensure_rgb(img)
output_ext = ".jpg"
elif output_format == "webp":
img = ensure_rgb(img)
output_ext = ".webp"
else:
return False, f"Formato no soportado: {output_format}"
output_path = output_dir / f"{input_path.stem}{output_ext}"
if output_path.exists() and not overwrite:
return False, f"Ya existe: {output_path.name}"
save_kwargs = {"quality": quality}
if output_format == "jpg":
save_kwargs.update({
"format": "JPEG",
"optimize": True,
"progressive": True
})
elif output_format == "webp":
save_kwargs.update({
"format": "WEBP",
"method": 6
})
img.save(output_path, **save_kwargs)
return True, f"OK -> {output_path.name}"
except Exception as e:
return False, f"ERROR -> {input_path.name}: {e}"
def collect_images(input_dir: Path, recursive: bool) -> list[Path]:
if recursive:
files = [p for p in input_dir.rglob("*") if p.is_file()]
else:
files = [p for p in input_dir.iterdir() if p.is_file()]
images = [p for p in files if p.suffix.lower() in SUPPORTED_INPUTS]
return sorted(images)
def main():
parser = argparse.ArgumentParser(
description="Resize y conversión masiva de imágenes a JPG o WEBP."
)
parser.add_argument("input_dir", help="Carpeta de entrada con las imágenes")
parser.add_argument(
"-o",
"--output-dir",
default="output_images",
help="Carpeta de salida"
)
parser.add_argument(
"-f",
"--format",
default="jpg",
choices=SUPPORTED_OUTPUTS,
help="Formato de salida: jpg o webp"
)
parser.add_argument(
"-w",
"--max-width",
type=int,
default=1200,
help="Ancho máximo en px"
)
parser.add_argument(
"-q",
"--quality",
type=int,
default=85,
help="Calidad de salida (1-100)"
)
parser.add_argument(
"-r",
"--recursive",
action="store_true",
help="Buscar imágenes en subcarpetas"
)
parser.add_argument(
"--overwrite",
action="store_true",
help="Sobrescribir archivos existentes"
)
args = parser.parse_args()
input_dir = Path(args.input_dir)
output_dir = Path(args.output_dir)
output_format = args.format.lower()
if not input_dir.exists() or not input_dir.is_dir():
print("La carpeta de entrada no existe o no es válida.")
sys.exit(1)
if output_format not in SUPPORTED_OUTPUTS:
print("Formato de salida no válido.")
sys.exit(1)
if not HEIC_SUPPORTED:
print("Aviso: no se detectó soporte HEIC. Instala 'pillow-heif' para convertir .heic/.heif.")
output_dir.mkdir(parents=True, exist_ok=True)
images = collect_images(input_dir, args.recursive)
if not images:
print("No se encontraron imágenes compatibles.")
sys.exit(0)
print(f"Se encontraron {len(images)} imágenes.")
print(f"Salida: {output_format.upper()} | Max width: {args.max_width}px\n")
success_count = 0
error_count = 0
skipped_count = 0
for image_path in images:
ok, message = process_image(
input_path=image_path,
output_dir=output_dir,
output_format=output_format,
max_width=args.max_width,
quality=args.quality,
overwrite=args.overwrite,
)
print(message)
if ok:
success_count += 1
elif message.startswith("Ya existe:"):
skipped_count += 1
else:
error_count += 1
print("\nResumen:")
print(f"Convertidas: {success_count}")
print(f"Omitidas: {skipped_count}")
print(f"Errores: {error_count}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment