!/usr/bin/env python
-- coding: utf-8 --
"""
图片极限压缩脚本
功能:扫描当前目录及子目录,极限压缩大于500KB的PNG和JPG图片
警告:此脚本会大幅降低图片质量以换取最小体积!
"""
import os
from pathlib import Path
from PIL import Image
import io
配置参数 - 极限压缩模式
SIZE_THRESHOLD = 100 * 1024 # 500KB
JPEG_QUALITY = 5 # JPEG质量极低 (1-100),越低体积越小
PNG_COLORS = 16 # PNG颜色数量,越少体积越小 (2-256)
MAX_DIMENSION = 1024 # 最大尺寸,超过会缩小
def get_file_size(file_path):
"""获取文件大小(字节)"""
return os.path.getsize(file_path)
def format_size(size_bytes):
"""格式化文件大小显示"""
if size_bytes < 1024:
return f"{size_bytes}B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.2f}KB"
else:
return f"{size_bytes / (1024 * 1024):.2f}MB"
def resize_if_large(img, max_dim):
"""如果图片尺寸过大则缩小"""
width, height = img.size
if width > max_dim or height > max_dim:
if width > height:
new_width = max_dim
new_height = int(height * (max_dim / width))
else:
new_height = max_dim
new_width = int(width * (max_dim / height))
return img.resize((new_width, new_height), Image.LANCZOS)
return img
def compress_jpeg_extreme(img, file_path, original_size):
"""JPEG极限压缩"""
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
# 缩小尺寸
img = resize_if_large(img, MAX_DIMENSION)
# 使用极低质量
buffer = io.BytesIO()
img.save(buffer, format='JPEG', quality=JPEG_QUALITY, optimize=True, subsampling=2)
new_size = buffer.tell()
if new_size < original_size:
img.save(file_path, format='JPEG', quality=JPEG_QUALITY, optimize=True, subsampling=2)
return True, new_size
return False, original_size
def compress_png_extreme(img, file_path, original_size):
"""PNG极限压缩 - 强制量化到极少颜色"""
# 缩小尺寸
img = resize_if_large(img, MAX_DIMENSION)
has_alpha = img.mode == 'RGBA'
best_size = original_size
best_buffer = None
# 尝试不同的颜色数量,找到最小的
for colors in [PNG_COLORS, 32, 64, 128]:
try:
buffer = io.BytesIO()
if has_alpha:
# 有透明通道 - 分离处理
alpha = img.split()[-1]
rgb = img.convert('RGB')
# 量化RGB
rgb_q = rgb.quantize(colors=colors, method=Image.MEDIANCUT)
rgb_q = rgb_q.convert('RGB')
# 重新合并alpha
img_final = rgb_q.convert('RGBA')
img_final.putalpha(alpha)
img_final.save(buffer, format='PNG', optimize=True, compress_level=9)
else:
# 无透明通道 - 直接量化
if img.mode != 'RGB':
img_temp = img.convert('RGB')
else:
img_temp = img
img_q = img_temp.quantize(colors=colors, method=Image.MEDIANCUT)
img_q.save(buffer, format='PNG', optimize=True, compress_level=9)
size = buffer.tell()
if size < best_size:
best_size = size
best_buffer = buffer
# 如果已经足够小,就用最少颜色的
if colors == PNG_COLORS:
break
except Exception:
continue
if best_buffer and best_size < original_size:
best_buffer.seek(0)
with open(file_path, 'wb') as f:
f.write(best_buffer.read())
return True, best_size
return False, original_size
def compress_image(file_path):
"""极限压缩单张图片"""
original_size = get_file_size(file_path)
if original_size <= SIZE_THRESHOLD:
return False, original_size, original_size
try:
img = Image.open(file_path)
file_ext = Path(file_path).suffix.lower()
if file_ext in ['.jpg', '.jpeg']:
success, new_size = compress_jpeg_extreme(img, file_path, original_size)
elif file_ext == '.png':
success, new_size = compress_png_extreme(img, file_path, original_size)
else:
return False, original_size, original_size
if success:
return True, original_size, new_size
return False, original_size, original_size
except Exception as e:
print(f" [错误] 处理失败: {e}")
return False, original_size, original_size
def find_images(root_dir):
"""查找所有PNG和JPG图片"""
images = []
extensions = {'.png', '.jpg', '.jpeg'}
for root, dirs, files in os.walk(root_dir):
for file in files:
if Path(file).suffix.lower() in extensions:
images.append(os.path.join(root, file))
return images
def main():
# 获取脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
print("=" * 60)
print("图片极限压缩工具 (不考虑画质)")
print("=" * 60)
print(f"扫描目录: {script_dir}")
print(f"压缩阈值: {format_size(SIZE_THRESHOLD)}")
print(f"JPEG质量: {JPEG_QUALITY} (极低)")
print(f"PNG颜色: {PNG_COLORS} 色")
print(f"最大尺寸: {MAX_DIMENSION}px")
print("-" * 60)
# 查找所有图片
images = find_images(script_dir)
print(f"找到 {len(images)} 张图片")
print("-" * 60)
# 统计信息
total_original = 0
total_compressed = 0
compressed_count = 0
skipped_count = 0
# 筛选大于阈值的图片
large_images = [(img, get_file_size(img)) for img in images if get_file_size(img) > SIZE_THRESHOLD]
print(f"大于 {format_size(SIZE_THRESHOLD)} 的图片: {len(large_images)} 张")
print("-" * 60)
for file_path, original_size in large_images:
relative_path = os.path.relpath(file_path, script_dir)
print(f"\n处理: {relative_path}")
print(f" 原始大小: {format_size(original_size)}")
success, orig_size, new_size = compress_image(file_path)
total_original += orig_size
if success:
total_compressed += new_size
compressed_count += 1
saved = orig_size - new_size
saved_percent = (saved / orig_size) * 100
print(f" 压缩后: {format_size(new_size)} (节省 {format_size(saved)}, {saved_percent:.1f}%)")
else:
total_compressed += orig_size
skipped_count += 1
print(f" [跳过] 无法进一步压缩")
# 输出汇总
print("\n" + "=" * 60)
print("压缩完成!")
print("=" * 60)
print(f"处理图片: {len(large_images)} 张")
print(f"成功压缩: {compressed_count} 张")
print(f"跳过: {skipped_count} 张")
if total_original > 0:
total_saved = total_original - total_compressed
print(f"原始总大小: {format_size(total_original)}")
print(f"压缩后总大小: {format_size(total_compressed)}")
print(f"总共节省: {format_size(total_saved)} ({(total_saved/total_original)*100:.1f}%)")
print("=" * 60)
if name == "__main__":
main()
评论已关闭