253 lines
8.8 KiB
Python
253 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
JPG转PNG格式转换器
|
||
功能:
|
||
1. 单个文件转换
|
||
2. 批量文件夹转换
|
||
3. 保持图片质量
|
||
4. 可选择是否删除原文件
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
from PIL import Image
|
||
import argparse
|
||
|
||
class ImageConverter:
|
||
def __init__(self):
|
||
"""初始化图像转换器"""
|
||
self.supported_formats = ['.jpg', '.jpeg', '.JPG', '.JPEG']
|
||
self.converted_count = 0
|
||
self.failed_count = 0
|
||
self.failed_files = []
|
||
|
||
def convert_single_image(self, input_path: Path, output_path: Path = None, quality: int = 95) -> bool:
|
||
"""
|
||
转换单个JPG图片为PNG
|
||
|
||
Args:
|
||
input_path: 输入文件路径
|
||
output_path: 输出文件路径(可选)
|
||
quality: 图片质量(对PNG无效,但保留参数)
|
||
|
||
Returns:
|
||
bool: 转换是否成功
|
||
"""
|
||
try:
|
||
# 检查输入文件是否存在
|
||
if not input_path.exists():
|
||
print(f"❌ 文件不存在: {input_path}")
|
||
return False
|
||
|
||
# 检查文件格式
|
||
if input_path.suffix.lower() not in [ext.lower() for ext in self.supported_formats]:
|
||
print(f"❌ 不支持的格式: {input_path.suffix}")
|
||
return False
|
||
|
||
# 设置输出路径
|
||
if output_path is None:
|
||
output_path = input_path.with_suffix('.png')
|
||
|
||
# 确保输出目录存在
|
||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
print(f"🔄 转换中: {input_path.name} -> {output_path.name}")
|
||
|
||
# 打开并转换图片
|
||
with Image.open(input_path) as img:
|
||
# 如果是RGBA模式,直接保存
|
||
# 如果是RGB模式,转换为RGBA以支持透明度
|
||
if img.mode in ('RGBA', 'LA'):
|
||
# 已经有透明通道
|
||
img.save(output_path, 'PNG', optimize=True)
|
||
else:
|
||
# 转换为RGBA模式
|
||
if img.mode == 'P':
|
||
# 调色板模式,转换为RGBA
|
||
img = img.convert('RGBA')
|
||
elif img.mode in ('RGB', 'L'):
|
||
# RGB或灰度模式,转换为RGBA
|
||
img = img.convert('RGBA')
|
||
|
||
img.save(output_path, 'PNG', optimize=True)
|
||
|
||
print(f"✅ 转换成功: {output_path}")
|
||
self.converted_count += 1
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 转换失败 {input_path}: {e}")
|
||
self.failed_count += 1
|
||
self.failed_files.append(str(input_path))
|
||
return False
|
||
|
||
def convert_folder(self, folder_path: Path, output_folder: Path = None,
|
||
delete_original: bool = False, recursive: bool = True) -> None:
|
||
"""
|
||
批量转换文件夹中的JPG图片
|
||
|
||
Args:
|
||
folder_path: 输入文件夹路径
|
||
output_folder: 输出文件夹路径(可选)
|
||
delete_original: 是否删除原文件
|
||
recursive: 是否递归处理子文件夹
|
||
"""
|
||
if not folder_path.exists():
|
||
print(f"❌ 文件夹不存在: {folder_path}")
|
||
return
|
||
|
||
if not folder_path.is_dir():
|
||
print(f"❌ 不是有效的文件夹: {folder_path}")
|
||
return
|
||
|
||
print(f"📁 开始处理文件夹: {folder_path}")
|
||
|
||
# 获取所有JPG文件
|
||
if recursive:
|
||
jpg_files = []
|
||
for ext in self.supported_formats:
|
||
jpg_files.extend(folder_path.rglob(f"*{ext}"))
|
||
else:
|
||
jpg_files = []
|
||
for ext in self.supported_formats:
|
||
jpg_files.extend(folder_path.glob(f"*{ext}"))
|
||
|
||
if not jpg_files:
|
||
print("📷 未找到JPG文件")
|
||
return
|
||
|
||
print(f"📷 找到 {len(jpg_files)} 个JPG文件")
|
||
|
||
# 转换每个文件
|
||
for jpg_file in jpg_files:
|
||
# 计算输出路径
|
||
if output_folder:
|
||
# 保持相对目录结构
|
||
relative_path = jpg_file.relative_to(folder_path)
|
||
output_path = output_folder / relative_path.with_suffix('.png')
|
||
else:
|
||
# 在原目录生成PNG文件
|
||
output_path = jpg_file.with_suffix('.png')
|
||
|
||
# 转换文件
|
||
success = self.convert_single_image(jpg_file, output_path)
|
||
|
||
# 如果转换成功且需要删除原文件
|
||
if success and delete_original:
|
||
try:
|
||
jpg_file.unlink()
|
||
print(f"🗑️ 已删除原文件: {jpg_file}")
|
||
except Exception as e:
|
||
print(f"⚠️ 删除原文件失败 {jpg_file}: {e}")
|
||
|
||
def print_summary(self) -> None:
|
||
"""打印转换摘要"""
|
||
total = self.converted_count + self.failed_count
|
||
print("\n" + "="*50)
|
||
print("🎯 转换摘要")
|
||
print(f"📊 总文件数: {total}")
|
||
print(f"✅ 成功转换: {self.converted_count}")
|
||
print(f"❌ 转换失败: {self.failed_count}")
|
||
|
||
if self.failed_files:
|
||
print("\n❌ 失败的文件:")
|
||
for file in self.failed_files:
|
||
print(f" - {file}")
|
||
|
||
print("="*50)
|
||
|
||
def main():
|
||
"""主函数"""
|
||
parser = argparse.ArgumentParser(description='JPG转PNG格式转换器')
|
||
parser.add_argument('input', help='输入文件或文件夹路径')
|
||
parser.add_argument('-o', '--output', help='输出文件或文件夹路径(可选)')
|
||
parser.add_argument('-d', '--delete', action='store_true', help='转换后删除原文件')
|
||
parser.add_argument('-r', '--recursive', action='store_true', default=True, help='递归处理子文件夹(默认启用)')
|
||
parser.add_argument('--no-recursive', action='store_true', help='不递归处理子文件夹')
|
||
|
||
# 如果没有命令行参数,使用交互模式
|
||
if len(sys.argv) == 1:
|
||
print("🎨 JPG转PNG格式转换器")
|
||
print("="*40)
|
||
|
||
# 输入路径
|
||
input_path = input("请输入JPG文件或文件夹路径: ").strip().strip('"')
|
||
if not input_path:
|
||
print("❌ 未输入路径,退出程序")
|
||
return
|
||
|
||
input_path = Path(input_path)
|
||
|
||
# 输出路径(可选)
|
||
output_input = input("输出路径(留空为原位置转换): ").strip().strip('"')
|
||
output_path = Path(output_input) if output_input else None
|
||
|
||
# 是否删除原文件
|
||
delete_original = input("转换后删除原JPG文件吗?(y/N): ").strip().lower() == 'y'
|
||
|
||
# 是否递归处理
|
||
if input_path.is_dir():
|
||
recursive = input("递归处理子文件夹吗?(Y/n): ").strip().lower() != 'n'
|
||
else:
|
||
recursive = False
|
||
|
||
converter = ImageConverter()
|
||
|
||
if input_path.is_file():
|
||
# 单文件转换
|
||
converter.convert_single_image(input_path, output_path)
|
||
elif input_path.is_dir():
|
||
# 文件夹批量转换
|
||
converter.convert_folder(input_path, output_path, delete_original, recursive)
|
||
else:
|
||
print(f"❌ 路径不存在: {input_path}")
|
||
return
|
||
|
||
converter.print_summary()
|
||
|
||
else:
|
||
# 命令行模式
|
||
args = parser.parse_args()
|
||
|
||
input_path = Path(args.input)
|
||
output_path = Path(args.output) if args.output else None
|
||
delete_original = args.delete
|
||
recursive = args.recursive and not args.no_recursive
|
||
|
||
converter = ImageConverter()
|
||
|
||
if input_path.is_file():
|
||
converter.convert_single_image(input_path, output_path)
|
||
elif input_path.is_dir():
|
||
converter.convert_folder(input_path, output_path, delete_original, recursive)
|
||
else:
|
||
print(f"❌ 路径不存在: {input_path}")
|
||
return
|
||
|
||
converter.print_summary()
|
||
|
||
def quick_convert_demo():
|
||
"""快速转换演示函数"""
|
||
print("🚀 快速转换模式")
|
||
|
||
# 示例用法
|
||
converter = ImageConverter()
|
||
|
||
# 转换单个文件
|
||
# converter.convert_single_image(Path("example.jpg"))
|
||
|
||
# 批量转换文件夹
|
||
# converter.convert_folder(Path("./images"))
|
||
|
||
print("请修改代码中的路径后使用")
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
main()
|
||
except KeyboardInterrupt:
|
||
print("\n❌ 用户中断操作")
|
||
except Exception as e:
|
||
print(f"❌ 程序运行错误: {e}")
|