#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ JPG照片压缩脚本 自动压缩超过3MB的JPG格式照片到小于3MB """ import os from PIL import Image import argparse from pathlib import Path def compress_jpg_to_size(file_path, max_size_mb=1, quality=85, step=5): """ 压缩JPG图片到指定大小以下 Args: file_path: 图片文件路径 max_size_mb: 最大文件大小(MB) quality: 初始压缩质量(1-95) step: 每次降低质量的步长 Returns: bool: 是否成功压缩 str: 结果信息 """ try: # 获取文件大小 original_size = os.path.getsize(file_path) original_size_mb = original_size / (1024 * 1024) print(f"原始文件: {os.path.basename(file_path)}") print(f"原始大小: {original_size_mb:.2f}MB") # 如果文件已经小于目标大小,直接返回 if original_size_mb <= max_size_mb: return True, f"文件已小于{max_size_mb}MB,无需压缩" # 打开图片 with Image.open(file_path) as img: # 确保是RGB模式 if img.mode != 'RGB': img = img.convert('RGB') # 获取图片尺寸 width, height = img.size print(f"图片尺寸: {width}x{height}") # 如果图片尺寸很大,先调整尺寸 max_dimension = 4000 # 最大边长限制 if width > max_dimension or height > max_dimension: if width > height: new_width = max_dimension new_height = int(height * max_dimension / width) else: new_height = max_dimension new_width = int(width * max_dimension / height) img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) print(f"调整尺寸至: {new_width}x{new_height}") # 尝试不同质量等级进行压缩 current_quality = quality while current_quality >= 20: # 最低质量限制 # 保存为临时文件来检查大小 temp_output = f"{file_path}.temp.jpg" img.save(temp_output, format='JPEG', quality=current_quality, optimize=True) # 检查压缩后的大小 compressed_size = os.path.getsize(temp_output) compressed_size_mb = compressed_size / (1024 * 1024) print(f"尝试质量{current_quality}%: {compressed_size_mb:.2f}MB") if compressed_size_mb <= max_size_mb: # 压缩成功,替换原文件 os.replace(temp_output, file_path) compression_ratio = (original_size - compressed_size) / original_size * 100 return True, f"压缩成功!质量:{current_quality}%, 大小:{compressed_size_mb:.2f}MB, 压缩率:{compression_ratio:.1f}%" else: # 删除临时文件,继续尝试更低质量 os.remove(temp_output) current_quality -= step # 如果还是太大,尝试进一步调整尺寸 print("质量压缩不足,尝试调整尺寸...") # 逐步减小尺寸 scale_factor = 0.9 current_quality = quality # 重置质量 while scale_factor >= 0.5: # 最小尺寸限制 new_width = int(width * scale_factor) new_height = int(height * scale_factor) resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) temp_output = f"{file_path}.temp.jpg" resized_img.save(temp_output, format='JPEG', quality=current_quality, optimize=True) compressed_size = os.path.getsize(temp_output) compressed_size_mb = compressed_size / (1024 * 1024) print(f"尝试尺寸{new_width}x{new_height} + 质量{current_quality}%: {compressed_size_mb:.2f}MB") if compressed_size_mb <= max_size_mb: os.replace(temp_output, file_path) compression_ratio = (original_size - compressed_size) / original_size * 100 return True, f"压缩成功!尺寸:{new_width}x{new_height}, 质量:{current_quality}%, 大小:{compressed_size_mb:.2f}MB, 压缩率:{compression_ratio:.1f}%" else: os.remove(temp_output) scale_factor -= 0.1 return False, "无法压缩到目标大小,图片可能过于复杂" except Exception as e: return False, f"压缩失败: {str(e)}" def compress_single_file(file_path, max_size_mb=1): """ 压缩单个文件 """ # 检查文件是否存在 if not os.path.exists(file_path): print(f"文件不存在: {file_path}") return False # 检查文件扩展名 if not file_path.lower().endswith(('.jpg', '.jpeg')): print(f"不是JPG格式文件: {file_path}") return False success, message = compress_jpg_to_size(file_path, max_size_mb) print(message) return success def compress_directory(directory_path, max_size_mb=1, recursive=True): """ 压缩目录中的所有JPG文件 """ if not os.path.exists(directory_path): print(f"目录不存在: {directory_path}") return success_count = 0 failed_count = 0 skipped_count = 0 # 获取所有JPG文件 jpg_files = [] if recursive: for root, dirs, files in os.walk(directory_path): for file in files: if file.lower().endswith(('.jpg', '.jpeg')): jpg_files.append(os.path.join(root, file)) else: for file in os.listdir(directory_path): if file.lower().endswith(('.jpg', '.jpeg')): full_path = os.path.join(directory_path, file) if os.path.isfile(full_path): jpg_files.append(full_path) print(f"找到 {len(jpg_files)} 个JPG文件") print("-" * 50) # 处理每个文件 for i, file_path in enumerate(jpg_files, 1): print(f"\\n[{i}/{len(jpg_files)}] 处理: {os.path.basename(file_path)}") # 检查文件大小 file_size_mb = os.path.getsize(file_path) / (1024 * 1024) if file_size_mb <= max_size_mb: print(f"文件已小于{max_size_mb}MB,跳过") skipped_count += 1 continue success, message = compress_jpg_to_size(file_path, max_size_mb) if success: success_count += 1 else: failed_count += 1 print(f"失败: {message}") # 打印统计结果 print("\\n" + "=" * 50) print(f"处理完成!") print(f"成功: {success_count}") print(f"失败: {failed_count}") print(f"跳过: {skipped_count}") print(f"总计: {len(jpg_files)}") def main(): """ 主函数 """ parser = argparse.ArgumentParser(description='JPG照片压缩工具') parser.add_argument('path', help='文件或目录路径') parser.add_argument('-s', '--size', type=float, default=3, help='目标文件大小(MB),默认3MB') parser.add_argument('-r', '--recursive', action='store_true', help='递归处理子目录') parser.add_argument('-q', '--quality', type=int, default=60, help='初始压缩质量(1-95),默认85') args = parser.parse_args() if os.path.isfile(args.path): compress_single_file(args.path, args.size) elif os.path.isdir(args.path): compress_directory(args.path, args.size, args.recursive) else: print(f"路径不存在: {args.path}") if __name__ == "__main__": # 如果没有命令行参数,使用交互模式 if len(os.sys.argv) == 1: print("JPG照片压缩工具") print("=" * 30) path = input("请输入文件或目录路径: ").strip() if not path: print("未输入路径,退出程序") exit() if os.path.isfile(path): compress_single_file(path) elif os.path.isdir(path): recursive = input("是否递归处理子目录?(y/n): ").lower().strip() == 'y' compress_directory(path, recursive=recursive) else: print(f"路径不存在: {path}") else: main()