我用 Python 写了个命令行版 TinyPNG,再也不打开网页了

0 阅读6分钟

一个命令行图片处理工具,支持批量压缩、格式转换、尺寸调整、EXIF 清除。安装只需一行命令。

前言

先问你几个问题:

  • 写博客截图要压缩,是不是每次都打开 TinyPNG → 拖文件 → 等 → 下载?
  • 网站性能优化要转 WebP,是不是一张一张手动来?
  • 发自拍/截图前要去掉 GPS 和相机信息,是不是还得找个在线工具?

我全都中过。 尤其是 TinyPNG 网页版,批量超过 20 张就弹窗让你注册,免费用户每个月还有额度限制。于是上周我怒了,花了一个周末,写了个命令行工具。

名字叫 PixCraft,已经发布到 PyPI 了。一行安装:

pip install pixcraft

五个命令,覆盖 90% 的图片处理场景

不多废话,直接看能干什么:

# 整个目录批量压缩,目标 500KB 以内
pixcraft compress ./photos/ -s 500k

# 全部转 WebP,网站加载速度直接拉满
pixcraft convert ./images/ -t webp

# 等比缩放到 1200 宽
pixcraft resize hero.jpg -w 1200

# 清除 GPS 和相机信息
pixcraft strip ./selfies/

# 查看图片详细信息(分辨率 / 格式 / EXIF / 文件大小)
pixcraft info unknown.avif

五个动词:compressconvertresizestripinfo。你就算一个月不碰,打开终端也能猜出来。

压缩是最有意思的设计

传统图片压缩的做法是让你自己选质量值——85%、70%、50%——然后反复试,直到文件大小差不多合适。说实话这很蠢,你变成了一个人肉二分搜索器。

PixCraft 的做法完全不同:你告诉它目标大小,它自己想办法。

算法思路

整个压缩逻辑分两层:

  1. 二分搜索质量值:在 1-100 之间找到最接近目标大小的质量参数
  2. 逐步缩小尺寸:如果质量降到最低还是超了,等比缩小分辨率,再重新二分搜索
┌─────────────────────────────────────────┐
│           输入:目标大小 500KB            │
├─────────────────────────────────────────┤
│  Layer 1: 二分搜索质量值                  │
│  ┌───────────────────────────────────┐  │
│  │ lo=1, hi=100                      │  │
│  │ 试 mid=50350KB < 500KB ✓       │  │
│  │ 试 mid=75620KB > 500KB ✗       │  │
│  │ 试 mid=62480KB ≈ 500KB ✓       │  │
│  │ → 找到最佳质量值 62                 │  │
│  └───────────────────────────────────┘  │
│                                          │
│  Layer 2: 质量不够?缩尺寸!              │
│  ┌───────────────────────────────────┐  │
│  │ 质量=1 还是 > 500KB?              │  │
│  │ → 等比缩到 2000px,重新二分搜索     │  │
│  │ → 还不行?缩到 1000px               │  │
│  │ → 还不行?缩到 500px                │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

核心代码实现

import io
import os
from PIL import Image


def compress_to_target(image_path: str, target_bytes: int) -> tuple[bytes, int, int]:
    """
    将图片压缩到目标大小以内。

    策略:
    1. 先用二分搜索找最佳 quality(1-100)
    2. 如果 quality=1 仍然超限,逐步缩小尺寸
    3. 每缩一档重新二分搜索 quality

    返回:(压缩后字节数据, 最终quality, 最终宽度)
    """
    img = Image.open(image_path)
    original_width = img.width

    # 缩尺寸的档位:原图 → 2000 → 1000 → 500
    width_steps = [w for w in [original_width, 2000, 1000, 500]
                   if w <= original_width]

    for target_width in width_steps:
        # 等比缩放
        if target_width != original_width:
            ratio = target_width / img.width
            new_height = int(img.height * ratio)
            resized = img.resize((target_width, new_height), Image.LANCZOS)
        else:
            resized = img.copy()

        # === 二分搜索最佳质量值 ===
        lo, hi = 1, 100
        best_data = None
        best_quality = 1

        while lo <= hi:
            mid = (lo + hi) // 2

            # 用当前质量值编码
            buf = io.BytesIO()
            resized.save(buf, format='JPEG', quality=mid, optimize=True)
            size = buf.tell()

            if size <= target_bytes:
                # 合格!记录并尝试更高的质量
                best_data = buf.getvalue()
                best_quality = mid
                lo = mid + 1
            else:
                # 太大了,降低质量
                hi = mid - 1

        # 如果在这个尺寸下找到了满足要求的质量值,直接返回
        if best_data is not None:
            return best_data, best_quality, target_width

        # 否则缩小尺寸,进入下一轮
        # quality=1 都超限 → 继续缩尺寸

    # 最小尺寸 + 最低质量还是超了,返回最小能做的
    return best_data, best_quality, target_width

为什么二分搜索比固定质量好?

假设你要把一张 5MB 的照片压到 500KB:

方式操作步骤耗时精确度
手动试试 80 → 600KB 超了 → 试 60 → 450KB → 试 70 → 520KB → 取 603-4 次凑合
固定质量只试一次,但结果可能超标或浪费画质1 次❌ 差
二分搜索50 → 75 → 62 → 68 → 65 → 63,6 次自动完成0 次(自动)✅ 精准

二分搜索每次都逼近最优解,而且你只需要敲一行命令,剩下的交给算法。

输出很简洁

$ pixcraft compress ./photos/ -s 500k

  ✓ sunset.jpg    (4.2MB → 0.3MB)  quality=68, 2400px→2000px
  ✓ banner.png    (1.8MB → 0.4MB)  quality=45, 1200px→1200px
  ✓ hero.jpg      (6.1MB → 0.5MB)  quality=22, 4000px→2000px
  - tiny-icon.png (12KB  → 跳过,已满足 500KB 限制)

  处理完成:3/4 个文件,共节省 11.1MB

一个小优化:跳过「已达标」文件

如果原文件已经小于目标大小,直接跳过不处理。没必要把一张 12KB 的图标再压一遍:

if os.path.getsize(filepath) <= target_bytes:
    print(f"  - {filename} (→ 跳过,已满足 {target_str} 限制)")
    continue

整个压缩逻辑不到 60 行代码,核心就是二分搜索 + 阶梯缩尺寸。有兴趣可以看完整源码,注释写得很清楚。

性能对比

工具处理方式批量支持离线可用免费额度
TinyPNG在线 API限制 20 张/次500 张/月
Squoosh浏览器本地✅ 完全
PixCraft本地 CLI✅ 无限制✅ 完全免费

格式转换也有小心思

支持四种格式互转:JPG、PNG、WebP、AVIF。

但你有没有想过——JPG 转 PNG 有意义吗? JPG 已经是有损压缩了,转成 PNG 只会让文件变大几十倍,画质零提升。PixCraft 直接拒绝这种操作:

  ✗ photo.jpg (JPG → PNG 无意义,已跳过)

反过来 PNG 转 WebP 就很实用。无损模式保留透明通道,文件还能小一截:

pixcraft convert logo.png -t webp --lossless
源格式 → 目标格式是否支持说明
PNG → WebP推荐,保留透明通道
PNG → AVIF压缩率更高
JPG → WebP体积可缩小 30-50%
JPG → PNG无意义转换,主动拦截
WebP → JPG兼容性转换

为什么要命令行而不是 GUI?

有人问为什么不做一个界面。三个理由:

  1. 批量处理天生命令行。pixcraft compress ./images/*.jpg — 一百张图,一行命令。换成 GUI?拖到手酸。

  2. 管道和脚本集成。你可以把它嵌到 CI/CD 里——部署前自动压缩所有图片:

    # 放在构建脚本里
    pixcraft compress ./public/images/ -s 200k
    pixcraft convert ./public/images/ -t webp
    
  3. 简单即快。打开终端 → 敲命令 → 回车。比打开软件 → 点菜单 → 选文件快一个数量级。

安装 & 使用

# 方式一:pip 安装
pip install pixcraft

# 方式二:Windows 单文件 exe,下载即用
# https://github.com/ilikeskyfire/pixcraft/releases

Python 3.10+,依赖自动安装。源码 MIT 协议开源:

👉 github.com/ilikeskyfir…

后续规划(TODO)

  • AVIF 编码性能优化(Pillow 的 AVIF 编码比较慢)
  • 水印功能(文字 + 图片)
  • 批量重命名(序号、日期等模式)

不过说实话,目前五个功能已经够 90% 的场景了。 工具就应该小而精,不搞功能大杂烩。


写在最后

如果你也是那个「每次打开 TinyPNG 都烦」的人,试试 PixCraft。

Star 是对开源作者最好的鼓励github.com/ilikeskyfir…

🐛 有 Bug 或需求?提 Issue,两天内回复。

🛠️ 想参与开发?PR 随时欢迎,代码结构很清晰。

如果这篇文章对你有帮助,点个赞 👍 让更多人看到吧。