OpenCode 实践课:4 分钟用 Python 写出一个命令行图片批量格式转换工具

项目介绍

在日常开发中,经常需要把一批图片统一转成 WebP 格式以优化网站加载速度,或者从 PNG 转成 JPG 以减小文件体积。手动一张张转换太麻烦,今天就用 OpenCode 写一个命令行图片批量格式转换工具,支持 JPG、PNG、WebP、BMP、GIF 五种格式互转,还带 resize 和质量调节功能。

  • 技术栈:Python 3 + Pillow
  • 代码量:约 90 行
  • 核心功能:批量格式转换、按比例缩放、输出质量调节

准备工作

  • Python 3.7+
  • 安装 OpenCode:npm install -g @anthropic/opencode(或参考 opencode.ai
  • 安装 Pillow:pip install Pillow

实践过程

第一步:启动项目

打开终端,进入空项目目录,启动 OpenCode:

mkdir imgconv && cd imgconv
opencode

在 OpenCode 的交互界面中输入第一句话:

> 我对 OpenCode 说:帮我创建一个 Python 图片批量格式转换工具的项目文件,只需要一个 imgconv.py 单文件脚本,支持 argparse 命令行参数,先搭好骨架。

OpenCode 立刻生成了带有 argparse 的脚本骨架,包含 --input--output-dir--format 三个基础参数:

import argparse
import sys
from pathlib import Path
from PIL import Image


def main():
    parser = argparse.ArgumentParser(description="批量图片格式转换工具")
    parser.add_argument("input", help="输入目录路径")
    parser.add_argument("format", choices=["jpg", "png", "webp", "bmp", "gif"],
                        help="目标格式")
    parser.add_argument("-o", "--output-dir", help="输出目录(默认同目录)")
    args = parser.parse_args()

    # TODO: 转换逻辑

要点:让 OpenCode 一次只做一个层次的搭建。先有骨架,再填逻辑,比一次性要求生成全部代码效果好得多——生成质量更高,也更容易理解每一步做了什么。

第二步:填充核心转换逻辑

骨架有了,接下来让它写核心逻辑:

> 我对 OpenCode 说:继续完善 convert_images 函数,要求:
> 1. 用 Path.rglob 遍历 input 目录下所有图片
> 2. 支持 jpg/png/webp/bmp/gif 输入格式
> 3. 转换后保存到 output-dir,保持原文件名只改扩展名
> 4. 打印每个文件的转换进度
> 5. 最后统计成功/失败数量

OpenCode 一次性给出了完整的 convert_images 函数,包含异常处理和彩色进度输出:

SUPPORTED_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"}

def convert_images(input_dir, output_dir, target_fmt):
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    success, fail = 0, 0
    for img_file in input_path.rglob("*"):
        if img_file.suffix.lower() not in SUPPORTED_EXTS:
            continue
        try:
            img = Image.open(img_file)
            new_name = img_file.stem + "." + target_fmt
            new_path = output_path / new_name
            img.save(new_path, format=target_fmt.upper())
            print(f"  ✓ {img_file.name} → {new_name}")
            success += 1
        except Exception as e:
            print(f"  ✗ {img_file.name}: {e}")
            fail += 1
    print(f"\n转换完成:成功 {success} 张,失败 {fail} 张")

到这里就能跑起来了。python imgconv.py ./photos webp -o ./output 已经可以把 photos 目录里的图片全部转成 WebP。

第三步:添加 resize 和质量参数

工具能用了,但还不够——转换时常常需要控制输出图片的尺寸和质量:

> 我对 OpenCode 说:添加两个可选参数:--quality 控制输出质量(1-100,默认 85),--resize 控制缩放比例(如 0.5 表示缩小一半)。注意 GIF 格式不支持 quality 参数,需要特殊处理。

OpenCode 在 save 调用前插入了条件判断,并添加了 resize 逻辑:

parser.add_argument("-q", "--quality", type=int, default=85,
                    help="输出质量 1-100(默认 85)")
parser.add_argument("-r", "--resize", type=float, default=1.0,
                    help="缩放比例(默认 1.0)")

# 在 save 之前:
if args.resize != 1.0:
    w, h = img.size
    img = img.resize((int(w * args.resize), int(h * args.resize)), Image.LANCZOS)

save_kwargs = {}
if target_fmt.upper() != "GIF":
    save_kwargs["quality"] = args.quality
img.save(new_path, format=target_fmt.upper(), **save_kwargs)

要点:边界条件(如 GIF 不支持 quality)一定要在提示词中明确告诉 OpenCode,否则它可能忽略这些细节。越是具体的约束,生成结果越正确。

第四步:运行测试

代码写好了,让 OpenCode 来帮忙测试:

> 我对 OpenCode 说:帮我写一个 bash 脚本创建测试图片,然后用 imgconv.py 测试转换功能,验证 resize 和 quality 参数是否生效。

OpenCode 生成了测试脚本,创建几张彩色纯色图片,然后运行转换命令验证输出文件是否存在、尺寸是否正确。

# 创建测试图片
python -c "
from PIL import Image
for i, color in enumerate(['red','green','blue']):
    img = Image.new('RGB', (200, 100), color)
    img.save(f'test_input/img{i}.png')
"

# 测试转换
python imgconv.py test_input webp -o test_output -r 0.5 -q 60
ls -la test_output/

测试通过,3 张 PNG 成功转为 WebP,且尺寸缩小为原来的 50%。

完整代码

#!/usr/bin/env python3
"""图片批量格式转换工具 - 支持 JPG/PNG/WebP/BMP/GIF 互转"""
import argparse
from pathlib import Path
from PIL import Image

SUPPORTED_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif"}
VALID_FORMATS = {"jpg", "png", "webp", "bmp", "gif"}


def convert_images(input_dir, output_dir, target_fmt, quality=85, resize=1.0):
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    success, fail = 0, 0
    for img_file in input_path.rglob("*"):
        if img_file.suffix.lower() not in SUPPORTED_EXTS:
            continue
        try:
            img = Image.open(img_file)
            if img.mode in ("RGBA", "P") and target_fmt == "jpg":
                img = img.convert("RGB")

            if resize != 1.0:
                w, h = img.size
                img = img.resize(
                    (int(w * resize), int(h * resize)), Image.LANCZOS
                )

            new_name = img_file.stem + "." + target_fmt
            new_path = output_path / new_name

            save_kwargs = {}
            if target_fmt.upper() != "GIF":
                save_kwargs["quality"] = quality

            img.save(new_path, format=target_fmt.upper(), **save_kwargs)
            print(f"  \u2713 {img_file.name} \u2192 {new_name}")
            success += 1
        except Exception as e:
            print(f"  \u2717 {img_file.name}: {e}")
            fail += 1

    print(f"\n\u8f6c\u6362\u5b8c\u6210\uff1a\u6210\u529f {success} \u5f60\uff0c\u5931\u8d25 {fail} \u5f60")
    return success, fail


def main():
    parser = argparse.ArgumentParser(description="\u6279\u91cf\u56fe\u7247\u683c\u5f0f\u8f6c\u6362\u5de5\u5177")
    parser.add_argument("input", help="\u8f93\u5165\u76ee\u5f55\u8def\u5f84")
    parser.add_argument("format", choices=sorted(VALID_FORMATS), help="\u76ee\u6807\u683c\u5f0f")
    parser.add_argument("-o", "--output-dir", default="output", help="\u8f93\u51fa\u76ee\u5f55\uff08\u9ed8\u8ba4 output\uff09")
    parser.add_argument("-q", "--quality", type=int, default=85, help="\u8f93\u51fa\u8d28\u91cf 1-100\uff08\u9ed8\u8ba4 85\uff09")
    parser.add_argument("-r", "--resize", type=float, default=1.0, help="\u7f29\u653e\u6bd4\u4f8b\uff08\u9ed8\u8ba4 1.0\uff09")
    args = parser.parse_args()

    convert_images(args.input, args.output_dir, args.format, args.quality, args.resize)


if __name__ == "__main__":
    main()

小结

从零到完整可用的图片批量转换工具,整个过程不到 4 分钟。OpenCode 最大的价值不是"替你写代码",而是把思考—编码—验证的循环压缩到极致——你说需求,它出代码,你验证,不行就追加一句修正,几轮对话就搞定了。

对比传统开发方式,这种工具的差异体现在三个方面:一是省去了查 Pillow 文档和参数的时间,OpenCode 直接给出正确的 API 调用;二是减少了调试低级错误的心智负担,比如路径拼接、异常处理这些模板代码;三是让你把注意力集中在"要做什么"而非"怎么写"。

如果你还没试过 AI 编程助手,建议从类似这样的小工具开始——不需要任何上下文,也不需要复杂的项目结构,打开终端说一句话就能跑起来。