使用 Pillow (PIL 友善分支) 實作批次等比縮圖,支援遞迴尋找、多執行緒並行、EXIF 方向修正、錯誤隔離與進度顯示。
pip install Pillow
from PIL import Image, ImageOps
import os
def resize_image(src_path, dst_path, scale=0.12, max_dim=None, quality=85):
"""
等比縮放單張圖片
:param scale: 縮放比例 (0-1)
:param max_dim: 最大邊長像素 (優先於 scale)
:param quality: JPEG 品質 (1-95)
"""
try:
with Image.open(src_path) as img:
# 修正 EXIF 方向 (避免手機照片旋轉)
img = ImageOps.exif_transpose(img)
orig_w, orig_h = img.size
if max_dim:
scale = min(max_dim / orig_w, max_dim / orig_h, 1.0)
if scale >= 1.0:
new_w, new_h = orig_w, orig_h
else:
new_w, new_h = int(orig_w * scale), int(orig_h * scale)
# 高品質重採樣
resized = img.resize((new_w, new_h), Image.LANCZOS)
# 保留 EXIF (若為 JPEG)
exif = img.info.get('exif')
save_kwargs = {'quality': quality, 'optimize': True}
if exif:
save_kwargs['exif'] = exif
# 確保輸出目錄存在
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
resized.save(dst_path, **save_kwargs)
return True, f'{orig_w}x{orig_h} -> {new_w}x{new_h}'
except Exception as e:
return False, str(e)
import glob
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from tqdm import tqdm # pip install tqdm
def batch_resize(src_dir, dst_dir, pattern='**/*.jpg', scale=0.12, max_workers=4):
# 遞迴尋找檔案
search_path = os.path.join(src_dir, pattern)
files = glob.glob(search_path, recursive=True)
if not files:
print('找不到符合條件的檔案')
return
print(f'找到 {len(files)} 個檔案,開始處理...')
success = 0
failed = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任務
future_to_file = {}
for src in files:
rel = os.path.relpath(src, src_dir)
dst = os.path.join(dst_dir, rel)
future = executor.submit(resize_image, src, dst, scale)
future_to_file[future] = src
# 進度條收集結果
for future in tqdm(as_completed(future_to_file), total=len(files), desc='縮圖中'):
src = future_to_file[future]
ok, msg = future.result()
if ok:
success += 1
else:
failed.append((src, msg))
print(f'\n完成:成功 {success},失敗 {len(failed)}')
for src, err in failed:
print(f' 失敗: {src} -> {err}')
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='批次等比縮圖')
parser.add_argument('src', help='來源目錄')
parser.add_argument('dst', help='輸出目錄')
parser.add_argument('--scale', type=float, default=0.12, help='縮放比例 (預設 0.12)')
parser.add_argument('--max-dim', type=int, help='最大邊長 (像素)')
parser.add_argument('--workers', type=int, default=4, help='執行緒數')
args = parser.parse_args()
batch_resize(args.src, args.dst, scale=args.scale, max_workers=args.workers)
# 基本用法
python batch_resize.py ./photos ./thumbnails
# 指定最大邊長 800px
python batch_resize.py ./photos ./thumbnails --max-dim 800
# 8 執行緒加速
python batch_resize.py ./photos ./thumbnails --workers 8
| 功能 | 實作要點 |
|---|---|
| 遞迴尋找 | glob.glob('**/*.jpg', recursive=True) |
| EXIF 方向修正 | ImageOps.exif_transpose(img) 自動旋轉 |
| 高品質重採樣 | Image.LANCZOS (Lanczos 濾波器) |
| EXIF 保留 | 讀取 img.info['exif'] 寫入 save(exif=...) |
| 多執行緒 | ThreadPoolExecutor 適合 IO 密集型 (讀寫磁碟) |
| 進度條 | tqdm 視覺化進度 |
| 錯誤隔離 | 單檔失敗不影響其他,統一收集錯誤報告 |
# 擴充支援格式
patterns = ['**/*.jpg', '**/*.jpeg', '**/*.png', '**/*.webp', '**/*.tiff']
for pattern in patterns:
batch_resize(src, dst, pattern=pattern, ...)
此腳本可直接用於生產環境縮圖需求,配合 cron 或 systemd timer 實現定期自動處理。