-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcompress_images.py
More file actions
226 lines (186 loc) · 7.67 KB
/
compress_images.py
File metadata and controls
226 lines (186 loc) · 7.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#!/usr/bin/env python3
"""
图片批量压缩脚本
将 images 文件夹中的所有图片压缩到最大 2048px
保持纵横比,使用高质量 JPEG 压缩
"""
import os
import sys
from pathlib import Path
from PIL import Image
import shutil
# 压缩配置(与网页配置保持一致)
MAX_WIDTH = 2048
MAX_HEIGHT = 2048
JPEG_QUALITY = 90
OUTPUT_FORMAT = 'JPEG'
def compress_image(input_path, output_path, backup=True):
"""
压缩单张图片
Args:
input_path: 输入图片路径
output_path: 输出图片路径
backup: 是否备份原图
Returns:
dict: 压缩结果信息
"""
try:
# 打开图片
img = Image.open(input_path)
# 转换RGBA到RGB(处理PNG透明通道)
if img.mode in ('RGBA', 'LA', 'P'):
# 创建白色背景
background = Image.new('RGB', img.size, (255, 255, 255))
if img.mode == 'P':
img = img.convert('RGBA')
background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
img = background
elif img.mode != 'RGB':
img = img.convert('RGB')
original_size = os.path.getsize(input_path)
original_width, original_height = img.size
# 检查是否需要缩放
needs_resize = original_width > MAX_WIDTH or original_height > MAX_HEIGHT
if needs_resize:
# 计算新尺寸(保持纵横比)
aspect_ratio = original_width / original_height
if aspect_ratio > 1: # 宽图
new_width = min(original_width, MAX_WIDTH)
new_height = round(new_width / aspect_ratio)
else: # 高图
new_height = min(original_height, MAX_HEIGHT)
new_width = round(new_height * aspect_ratio)
# 高质量缩放
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
else:
new_width, new_height = original_width, original_height
# 备份原图
if backup and input_path == output_path and (needs_resize or img.format != OUTPUT_FORMAT):
backup_path = input_path.parent / f"{input_path.stem}_original{input_path.suffix}"
if not backup_path.exists():
shutil.copy2(input_path, backup_path)
# 保存压缩后的图片
img.save(output_path, OUTPUT_FORMAT, quality=JPEG_QUALITY, optimize=True)
compressed_size = os.path.getsize(output_path)
compression_ratio = ((original_size - compressed_size) / original_size * 100) if original_size > 0 else 0
return {
'success': True,
'original_size': original_size,
'compressed_size': compressed_size,
'compression_ratio': compression_ratio,
'original_dimensions': (original_width, original_height),
'new_dimensions': (new_width, new_height),
'resized': needs_resize
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
def format_size(bytes_size):
"""格式化文件大小"""
for unit in ['B', 'KB', 'MB', 'GB']:
if bytes_size < 1024.0:
return f"{bytes_size:.2f} {unit}"
bytes_size /= 1024.0
return f"{bytes_size:.2f} TB"
def compress_folder(folder_path='./images', backup=True, output_folder=None):
"""
批量压缩文件夹中的所有图片
Args:
folder_path: 输入文件夹路径
backup: 是否备份原图
output_folder: 输出文件夹(None表示覆盖原图)
"""
folder = Path(folder_path)
if not folder.exists():
print(f"错误: 文件夹 '{folder_path}' 不存在")
return
# 支持的图片格式
image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.gif'}
# 收集所有图片文件
images = [f for f in folder.iterdir()
if f.is_file() and f.suffix.lower() in image_extensions
and not f.stem.endswith('_original')] # 跳过备份文件
if not images:
print(f"错误: 在 '{folder_path}' 中没有找到图片文件")
return
images.sort()
print(f"找到 {len(images)} 张图片")
print(f"压缩配置: 最大尺寸 {MAX_WIDTH}x{MAX_HEIGHT}px, JPEG质量 {JPEG_QUALITY}%")
print(f"备份模式: {'启用' if backup else '禁用'}")
if output_folder:
output_path = Path(output_folder)
output_path.mkdir(exist_ok=True)
print(f"输出目录: {output_folder}")
else:
print("模式: 覆盖原图")
print("=" * 70)
# 统计信息
success_count = 0
failed_count = 0
total_original_size = 0
total_compressed_size = 0
resized_count = 0
# 逐个处理图片
for i, img_path in enumerate(images, 1):
# 确定输出路径
if output_folder:
# 保存到指定文件夹,统一为.jpg扩展名
out_path = Path(output_folder) / f"{img_path.stem}.jpg"
else:
# 覆盖原图,保持原扩展名或改为.jpg
out_path = img_path.parent / f"{img_path.stem}.jpg"
print(f"[{i}/{len(images)}] {img_path.name} ... ", end='', flush=True)
result = compress_image(img_path, out_path, backup=backup)
if result['success']:
success_count += 1
total_original_size += result['original_size']
total_compressed_size += result['compressed_size']
if result['resized']:
resized_count += 1
print(f"[OK] {result['original_dimensions'][0]}x{result['original_dimensions'][1]} -> "
f"{result['new_dimensions'][0]}x{result['new_dimensions'][1]} "
f"({format_size(result['original_size'])} -> {format_size(result['compressed_size'])}, "
f"压缩 {result['compression_ratio']:.1f}%)")
else:
if result['compression_ratio'] > 0:
print(f"[OK] 仅压缩 ({format_size(result['original_size'])} -> "
f"{format_size(result['compressed_size'])}, 压缩 {result['compression_ratio']:.1f}%)")
else:
print(f"[OK] 无需处理")
else:
failed_count += 1
print(f"[FAIL] 失败: {result['error']}")
# 打印总结
print("=" * 70)
print(f"完成! 成功: {success_count}, 失败: {failed_count}")
print(f"调整尺寸: {resized_count} 张")
print(f"总大小: {format_size(total_original_size)} → {format_size(total_compressed_size)}")
if total_original_size > 0:
total_ratio = (total_original_size - total_compressed_size) / total_original_size * 100
print(f"总压缩率: {total_ratio:.1f}%")
print(f"节省空间: {format_size(total_original_size - total_compressed_size)}")
if __name__ == '__main__':
print("=" * 70)
print(" 图片批量压缩工具")
print("=" * 70)
# 检查PIL库
try:
from PIL import Image
except ImportError:
print("\n错误: 缺少 Pillow 库")
print("请运行: pip install Pillow")
sys.exit(1)
# 解析命令行参数
backup = '--no-backup' not in sys.argv
output_folder = None
# 检查是否指定输出文件夹
for i, arg in enumerate(sys.argv):
if arg == '--output' or arg == '-o':
if i + 1 < len(sys.argv):
output_folder = sys.argv[i + 1]
compress_folder(folder_path='./images', backup=backup, output_folder=output_folder)
print("\n提示: 压缩完成后,可以刷新网页查看效果")
if backup and not output_folder:
print("原图已备份为 *_original.jpg,确认无误后可删除备份文件")