Image Format Converter (python, written by Codex)
envgap__codex__python-t1-18
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
requirements.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/python-t1 #18 · read the task the agent was given
Codex wrote this python project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Image Format Converter Write a program that converts images between common formats (PNG, JPEG, BMP, TIFF, WebP) with configurable quality settings, preserving metadata where possible. FUNCTIONAL REQUIREMENTS: - Accept an image file path and target format as command-line arguments - Support conversions between: PNG, JPEG, BMP, TIFF, and WebP formats - Support a --quality flag for lossy formats (JPEG, WebP) with a value from 1-100 (default: 85) - Support a --resize flag to resize during conversion using WxH syntax (e.g., --resize 800x600), preserving aspect ratio by default - Support --maintain-aspect flag (default true) to control whether resizing preserves the original aspect ratio by fitting within the specified dimensions - Preserve EXIF metadata during conversion where the target format supports it, with a --strip-metadata flag to optionally remove it - Support alpha channel handling: when converting from formats with transparency (PNG, WebP) to formats without (JPEG, BMP), use a configurable background color via --background flag (default: white) - Support batch conversion of all images in a directory via --batch flag, optionally filtering by input format with --filter flag - Print conversion details to console: input format, output format, original dimensions, new dimensions, file size before and after, compression ratio - Save the converted image to a path specified by --output flag (default: same name with new extension) - If no input is given, generate a sample 800x600 PNG image with gradients, text, and transparency, then convert it to all other supported formats and display a comparison table - Handle errors: unsupported formats, corrupted images, insufficient disk space, invalid resize dimensions Create a complete Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include: - Source code - requirements.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
README.md
# Image Format Converter (Python) Converts images among PNG/JPEG/BMP/TIFF/WebP with quality, resize, metadata options, alpha handling, batch mode, and demo generation. ## Requirements - Ubuntu 22.04 - Python 3.10+ ## Dependencies (Pinned) - `Pillow==10.4.0` ## Setup ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Run ```bash python src/main.py input.png webp --quality 90 python src/main.py input.png jpeg --background "#ffffff" --resize 800x600 --maintain-aspect true python src/main.py input.webp png --strip-metadata python src/main.py ./images jpeg --batch --filter png --quality 80 python src/main.py ``` ## Features - Formats: PNG, JPEG, BMP, TIFF, WebP - `--quality` for lossy output (JPEG/WebP) - `--resize WxH` + `--maintain-aspect` (default true) - Metadata keep/strip (`--strip-metadata`) - Alpha flattening with `--background` for non-alpha targets - Batch conversion (`--batch`) + optional `--filter` - Conversion details printed per file - `--output` destination override - No-args demo generates sample PNG and converts to all other formats
requirements.txt
Pillow==10.4.0
src/main.py
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import os
from pathlib import Path
from typing import Any
from PIL import Image, ImageDraw, ImageFont
SUPPORTED = {"png", "jpeg", "jpg", "bmp", "tiff", "webp"}
def normalize_format(fmt: str) -> str:
f = fmt.lower()
if f == "jpg":
return "jpeg"
if f not in SUPPORTED:
raise ValueError(f"Unsupported format: {fmt}")
return f
def parse_resize(value: str | None) -> tuple[int, int] | None:
if not value:
return None
try:
w, h = value.lower().split("x", 1)
return int(w), int(h)
except Exception as exc:
raise ValueError("Invalid --resize. Use WxH, e.g., 800x600.") from exc
def output_path(input_path: Path, target: str, explicit: str | None) -> Path:
if explicit:
return Path(explicit).resolve()
return input_path.with_suffix(f".{target}")
def conversion_details(
in_path: Path,
out_path: Path,
in_fmt: str,
out_fmt: str,
orig_size: tuple[int, int],
new_size: tuple[int, int],
) -> dict[str, Any]:
before = in_path.stat().st_size
after = out_path.stat().st_size
ratio = (after / before) if before else 1.0
return {
"input": str(in_path),
"output": str(out_path),
"input_format": in_fmt,
"output_format": out_fmt,
"original_dimensions": f"{orig_size[0]}x{orig_size[1]}",
"new_dimensions": f"{new_size[0]}x{new_size[1]}",
"size_before": before,
"size_after": after,
"compression_ratio": round(ratio, 4),
}
def print_details(d: dict[str, Any]) -> None:
print(f"Input: {d['input']}")
print(f"Output: {d['output']}")
print(f"Format: {d['input_format']} -> {d['output_format']}")
print(f"Dimensions: {d['original_dimensions']} -> {d['new_dimensions']}")
print(f"Size: {d['size_before']} -> {d['size_after']} bytes")
print(f"Compression ratio: {d['compression_ratio']}")
print()
def convert_one(input_path: Path, target: str, args: argparse.Namespace) -> dict[str, Any]:
target = normalize_format(target)
quality = int(args.quality)
if quality < 1 or quality > 100:
raise ValueError("Quality must be in range 1..100.")
resize = parse_resize(args.resize)
maintain_aspect = str(args.maintain_aspect).lower() not in {"false", "0", "no"}
input_abs = input_path.resolve()
output_abs = output_path(input_abs, target, args.output)
with Image.open(input_abs) as img:
in_fmt = (img.format or input_abs.suffix.lstrip(".")).lower()
orig_size = img.size
exif = img.info.get("exif")
image = img.copy()
if resize:
if maintain_aspect:
image.thumbnail(resize, Image.Resampling.LANCZOS)
else:
image = image.resize(resize, Image.Resampling.LANCZOS)
if target in {"jpeg", "bmp"} and image.mode in {"RGBA", "LA"}:
bg = Image.new("RGBA", image.size, args.background)
bg.alpha_composite(image.convert("RGBA"))
image = bg.convert("RGB")
elif target in {"jpeg", "bmp"} and image.mode not in {"RGB", "L"}:
image = image.convert("RGB")
save_kwargs: dict[str, Any] = {}
if target in {"jpeg", "webp"}:
save_kwargs["quality"] = quality
if not args.strip_metadata and exif and target in {"jpeg", "tiff", "webp"}:
save_kwargs["exif"] = exif
output_abs.parent.mkdir(parents=True, exist_ok=True)
image.save(output_abs, format=target.upper(), **save_kwargs)
new_size = image.size
return conversion_details(input_abs, output_abs, in_fmt, target, orig_size, new_size)
def batch_convert(directory: Path, target: str, args: argparse.Namespace) -> list[dict[str, Any]]:
filter_fmt = normalize_format(args.filter) if args.filter else None
files = [p for p in directory.iterdir() if p.is_file() and p.suffix.lstrip(".").lower() in SUPPORTED]
if filter_fmt:
files = [p for p in files if normalize_format(p.suffix.lstrip(".").lower()) == filter_fmt]
results = []
for p in files:
per_file_args = argparse.Namespace(**vars(args))
per_file_args.output = None
results.append(convert_one(p, target, per_file_args))
return results
def generate_sample_png() -> Path:
out = Path("sample_800x600.png").resolve()
w, h = 800, 600
img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
for y in range(h):
r = int(255 * (y / h))
b = 255 - r
draw.line((0, y, w, y), fill=(r, 120, b, 255))
draw.ellipse((80, 80, 380, 380), fill=(255, 255, 255, 100))
draw.rectangle((430, 140, 700, 380), fill=(0, 0, 0, 100))
font = ImageFont.load_default()
draw.text((220, 540), "Image Converter Demo", fill=(255, 255, 255, 255), font=font)
img.save(out, format="PNG")
return out
def run_demo() -> int:
sample = generate_sample_png()
targets = ["jpeg", "bmp", "tiff", "webp"]
rows = []
for t in targets:
args = argparse.Namespace(
quality=85, resize=None, maintain_aspect="true", strip_metadata=False, background="white", output=None
)
rows.append(convert_one(sample, t, args))
print("format,orig_dim,new_dim,size_before,size_after,ratio")
for r in rows:
print(f"{r['output_format']},{r['original_dimensions']},{r['new_dimensions']},{r['size_before']},{r['size_after']},{r['compression_ratio']}")
return 0
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description="Image Format Converter")
p.add_argument("input", nargs="?")
p.add_argument("target_format", nargs="?")
p.add_argument("--quality", type=int, default=85)
p.add_argument("--resize")
p.add_argument("--maintain-aspect", default="true")
p.add_argument("--strip-metadata", action="store_true")
p.add_argument("--background", default="white")
p.add_argument("--batch", action="store_true")
p.add_argument("--filter")
p.add_argument("--output")
return p
def main() -> int:
args = build_parser().parse_args()
if not args.input and not args.batch:
return run_demo()
if args.batch:
if not args.input or not args.target_format:
raise ValueError("Batch mode usage: --batch <directory> <target-format>")
results = batch_convert(Path(args.input).resolve(), args.target_format, args)
for r in results:
print_details(r)
return 0
if not args.input or not args.target_format:
raise ValueError("Usage: <input-image> <target-format> [flags]")
details = convert_one(Path(args.input).resolve(), args.target_format, args)
print_details(details)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(f"Error: {exc}")
raise SystemExit(1)