一个命令行图片处理工具,支持批量压缩、格式转换、尺寸调整、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
五个动词:compress、convert、resize、strip、info。你就算一个月不碰,打开终端也能猜出来。
压缩是最有意思的设计
传统图片压缩的做法是让你自己选质量值——85%、70%、50%——然后反复试,直到文件大小差不多合适。说实话这很蠢,你变成了一个人肉二分搜索器。
PixCraft 的做法完全不同:你告诉它目标大小,它自己想办法。
算法思路
整个压缩逻辑分两层:
- 二分搜索质量值:在 1-100 之间找到最接近目标大小的质量参数
- 逐步缩小尺寸:如果质量降到最低还是超了,等比缩小分辨率,再重新二分搜索
┌─────────────────────────────────────────┐
│ 输入:目标大小 500KB │
├─────────────────────────────────────────┤
│ Layer 1: 二分搜索质量值 │
│ ┌───────────────────────────────────┐ │
│ │ lo=1, hi=100 │ │
│ │ 试 mid=50 → 350KB < 500KB ✓ │ │
│ │ 试 mid=75 → 620KB > 500KB ✗ │ │
│ │ 试 mid=62 → 480KB ≈ 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 → 取 60 | 3-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?
有人问为什么不做一个界面。三个理由:
-
批量处理天生命令行。
pixcraft compress ./images/*.jpg— 一百张图,一行命令。换成 GUI?拖到手酸。 -
管道和脚本集成。你可以把它嵌到 CI/CD 里——部署前自动压缩所有图片:
# 放在构建脚本里 pixcraft compress ./public/images/ -s 200k pixcraft convert ./public/images/ -t webp -
简单即快。打开终端 → 敲命令 → 回车。比打开软件 → 点菜单 → 选文件快一个数量级。
安装 & 使用
# 方式一:pip 安装
pip install pixcraft
# 方式二:Windows 单文件 exe,下载即用
# https://github.com/ilikeskyfire/pixcraft/releases
Python 3.10+,依赖自动安装。源码 MIT 协议开源:
后续规划(TODO)
- AVIF 编码性能优化(Pillow 的 AVIF 编码比较慢)
- 水印功能(文字 + 图片)
- 批量重命名(序号、日期等模式)
不过说实话,目前五个功能已经够 90% 的场景了。 工具就应该小而精,不搞功能大杂烩。
写在最后
如果你也是那个「每次打开 TinyPNG 都烦」的人,试试 PixCraft。
⭐ Star 是对开源作者最好的鼓励:github.com/ilikeskyfir…
🐛 有 Bug 或需求?提 Issue,两天内回复。
🛠️ 想参与开发?PR 随时欢迎,代码结构很清晰。
如果这篇文章对你有帮助,点个赞 👍 让更多人看到吧。