202 lines
6.6 KiB
Python
202 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
图片尺寸处理工具 - 固定宽度,智能高度
|
||
目标尺寸: 宽1620px,高≤2880px(9:16比例)
|
||
处理逻辑:
|
||
1. 先按宽度1620px等比例压缩
|
||
2. 压缩后高度>2880px: 从中间裁剪多余部分
|
||
3. 压缩后高度<2880px: 保持实际高度
|
||
"""
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
# 配置
|
||
TARGET_WIDTH = 1620
|
||
MAX_HEIGHT = 2880
|
||
SOURCE_DIR = Path('/home/ubuntu/images')
|
||
OUTPUT_DIR = SOURCE_DIR / 'resized_output'
|
||
|
||
def get_image_size(filepath):
|
||
"""获取图片尺寸"""
|
||
try:
|
||
result = subprocess.run(
|
||
['identify', '-format', '%w %h', str(filepath)],
|
||
capture_output=True, text=True, check=True
|
||
)
|
||
width, height = map(int, result.stdout.strip().split())
|
||
return width, height
|
||
except Exception as e:
|
||
print(f" ❌ 无法获取图片尺寸: {e}")
|
||
return None, None
|
||
|
||
def process_image(input_path, output_path):
|
||
"""
|
||
处理图片:
|
||
1. 先按宽度1620px等比例压缩
|
||
2. 如果高度>2880,从中间裁剪
|
||
3. 如果高度<2880,保持原样
|
||
"""
|
||
try:
|
||
# 第一步: 按宽度1620px等比例压缩
|
||
# 使用 ^ 表示最小尺寸,确保宽度至少1620,然后裁剪
|
||
temp_path = str(output_path) + '.tmp.jpg'
|
||
|
||
cmd_resize = [
|
||
'convert', str(input_path),
|
||
'-resize', f'{TARGET_WIDTH}x', # 宽度固定1620,高度自适应
|
||
'-quality', '95',
|
||
temp_path
|
||
]
|
||
subprocess.run(cmd_resize, check=True, capture_output=True)
|
||
|
||
# 获取压缩后的尺寸
|
||
new_width, new_height = get_image_size(temp_path)
|
||
if not new_width:
|
||
os.remove(temp_path)
|
||
return False, "无法获取压缩后尺寸"
|
||
|
||
print(f" 等比压缩后: {new_width}x{new_height}")
|
||
|
||
# 第二步: 处理高度
|
||
if new_height > MAX_HEIGHT:
|
||
# 高度超过2880,从中间裁剪
|
||
offset_y = (new_height - MAX_HEIGHT) // 2
|
||
cmd_crop = [
|
||
'convert', temp_path,
|
||
'-crop', f'{TARGET_WIDTH}x{MAX_HEIGHT}+0+{offset_y}',
|
||
'+repage', # 重置页面信息
|
||
'-quality', '95',
|
||
str(output_path)
|
||
]
|
||
subprocess.run(cmd_crop, check=True, capture_output=True)
|
||
final_height = MAX_HEIGHT
|
||
action = f"居中裁剪至{MAX_HEIGHT}px"
|
||
|
||
else:
|
||
# 高度小于等于2880,保持原样,只复制
|
||
os.rename(temp_path, output_path)
|
||
final_height = new_height
|
||
action = f"保持实际高度{new_height}px"
|
||
|
||
# 清理临时文件
|
||
if os.path.exists(temp_path):
|
||
os.remove(temp_path)
|
||
|
||
return True, action
|
||
|
||
except Exception as e:
|
||
print(f" ❌ 处理失败: {e}")
|
||
return False, str(e)
|
||
|
||
def format_size(size_bytes):
|
||
"""格式化文件大小"""
|
||
if size_bytes < 1024:
|
||
return f"{size_bytes}B"
|
||
elif size_bytes < 1024 * 1024:
|
||
return f"{size_bytes/1024:.1f}KB"
|
||
else:
|
||
return f"{size_bytes/(1024*1024):.1f}MB"
|
||
|
||
def main():
|
||
print("=" * 70)
|
||
print(f"🖼️ 图片尺寸处理工具")
|
||
print(f" 目标宽度: {TARGET_WIDTH}px")
|
||
print(f" 最大高度: {MAX_HEIGHT}px (超出则居中裁剪)")
|
||
print(f" 输出目录: {OUTPUT_DIR}")
|
||
print("=" * 70)
|
||
|
||
# 检查ImageMagick
|
||
try:
|
||
subprocess.run(['identify', '--version'], capture_output=True, check=True)
|
||
except:
|
||
print("❌ 错误: 未安装 ImageMagick")
|
||
print(" 请安装: sudo apt install imagemagick")
|
||
sys.exit(1)
|
||
|
||
if not SOURCE_DIR.exists():
|
||
print(f"❌ 目录不存在: {SOURCE_DIR}")
|
||
sys.exit(1)
|
||
|
||
# 创建输出目录
|
||
OUTPUT_DIR.mkdir(exist_ok=True)
|
||
|
||
# 支持的图片格式
|
||
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp', '.tiff'}
|
||
|
||
# 收集需要处理的图片
|
||
to_process = []
|
||
for file in SOURCE_DIR.iterdir():
|
||
if file.is_file() and file.suffix.lower() in extensions:
|
||
# 跳过输出目录和备份目录
|
||
if file.parent.name in ['resized_output', 'original_backup']:
|
||
continue
|
||
width, height = get_image_size(file)
|
||
if width:
|
||
to_process.append({
|
||
'file': file,
|
||
'orig_width': width,
|
||
'orig_height': height
|
||
})
|
||
|
||
if not to_process:
|
||
print(f"\n❌ 未找到需要处理的图片")
|
||
return
|
||
|
||
print(f"\n📋 找到 {len(to_process)} 张图片:\n")
|
||
|
||
for item in to_process:
|
||
ratio = item['orig_width'] / item['orig_height']
|
||
estimated_height = int(TARGET_WIDTH / ratio)
|
||
action = "需裁剪" if estimated_height > MAX_HEIGHT else "保持"
|
||
print(f" {item['file'].name}: {item['orig_width']}x{item['orig_height']} → {TARGET_WIDTH}x{estimated_height} ({action})")
|
||
|
||
# 确认
|
||
response = input(f"\n是否开始处理? [y/N]: ").strip().lower()
|
||
|
||
if response != 'y':
|
||
print("\n❌ 已取消")
|
||
return
|
||
|
||
# 处理图片
|
||
print("\n" + "=" * 70)
|
||
success_count = 0
|
||
|
||
for i, item in enumerate(to_process, 1):
|
||
file = item['file']
|
||
output_path = OUTPUT_DIR / f"{file.stem}_1620x{MAX_HEIGHT}{file.suffix}"
|
||
|
||
print(f"\n[{i}/{len(to_process)}] {file.name}")
|
||
print(f" 原图: {item['orig_width']}x{item['orig_height']}")
|
||
|
||
# 处理
|
||
success, action = process_image(file, output_path)
|
||
|
||
if success:
|
||
final_width, final_height = get_image_size(output_path)
|
||
orig_size = file.stat().st_size
|
||
new_size = output_path.stat().st_size
|
||
|
||
print(f" 结果: {final_width}x{final_height}")
|
||
print(f" 动作: {action}")
|
||
print(f" 大小: {format_size(orig_size)} → {format_size(new_size)}")
|
||
print(f" 保存: {output_path.name}")
|
||
success_count += 1
|
||
else:
|
||
print(f" ❌ 失败: {action}")
|
||
|
||
# 汇总
|
||
print("\n" + "=" * 70)
|
||
print(f"✅ 完成: {success_count}/{len(to_process)} 张图片已处理")
|
||
print(f"📁 输出目录: {OUTPUT_DIR}")
|
||
|
||
if success_count > 0:
|
||
print(f"\n💡 提示:")
|
||
print(f" - 处理后的图片在: {OUTPUT_DIR}/")
|
||
print(f" - 原图未改动,保留在: {SOURCE_DIR}/")
|
||
print(f" - 文件名格式: 原名_1620x2880.扩展名")
|
||
|
||
if __name__ == '__main__':
|
||
main()
|