a
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""自动压缩图片,无需交互"""
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
MAX_WIDTH = 2180
|
||||
SOURCE_DIR = Path('/home/ubuntu/images')
|
||||
BACKUP_DIR = SOURCE_DIR / 'original_backup'
|
||||
|
||||
def get_size(f):
|
||||
try:
|
||||
r = subprocess.run(['identify', '-format', '%w %h', str(f)], capture_output=True, text=True)
|
||||
return map(int, r.stdout.strip().split())
|
||||
except:
|
||||
return None, None
|
||||
|
||||
def resize(f, out):
|
||||
try:
|
||||
cmd = ['convert', str(f), '-resize', f'{MAX_WIDTH}x>', '-quality', '90', str(out)]
|
||||
subprocess.run(cmd, check=True, capture_output=True)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" 失败: {e}")
|
||||
return False
|
||||
|
||||
# 创建备份目录
|
||||
BACKUP_DIR.mkdir(exist_ok=True)
|
||||
|
||||
exts = {'.jpg', '.jpeg', '.png', '.webp'}
|
||||
images = []
|
||||
for f in SOURCE_DIR.iterdir():
|
||||
if f.is_file() and f.suffix.lower() in exts:
|
||||
w, h = get_size(f)
|
||||
if w and w > MAX_WIDTH:
|
||||
images.append({'file': f, 'w': w, 'h': h})
|
||||
|
||||
print(f"压缩 {len(images)} 张图片...\n")
|
||||
|
||||
success = 0
|
||||
for i, item in enumerate(images, 1):
|
||||
f = item['file']
|
||||
print(f"[{i}/{len(images)}] {f.name}")
|
||||
|
||||
# 备份
|
||||
backup = BACKUP_DIR / f.name
|
||||
os.rename(f, backup)
|
||||
|
||||
# 压缩
|
||||
if resize(backup, f):
|
||||
nw, nh = get_size(f)
|
||||
orig_kb = backup.stat().st_size / 1024
|
||||
new_kb = f.stat().st_size / 1024
|
||||
print(f" {item['w']}x{item['h']} → {nw}x{nh}")
|
||||
print(f" {orig_kb:.1f}KB → {new_kb:.1f}KB\n")
|
||||
success += 1
|
||||
else:
|
||||
os.rename(backup, f)
|
||||
|
||||
print(f"✅ 完成: {success}/{len(images)} 张")
|
||||
print(f"📁 备份: {BACKUP_DIR}")
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
图片尺寸压缩工具
|
||||
将宽度超过 2180 像素的图片等比例压缩
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# 配置
|
||||
MAX_WIDTH = 2180
|
||||
SOURCE_DIR = Path('/home/ubuntu/images')
|
||||
BACKUP_DIR = SOURCE_DIR / 'original_backup'
|
||||
|
||||
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:
|
||||
return None, None
|
||||
|
||||
def resize_image(filepath, output_path):
|
||||
"""压缩图片,保持比例,宽度限制为 MAX_WIDTH"""
|
||||
try:
|
||||
# 使用 ImageMagick convert
|
||||
# -resize 2180x> 表示宽度最大2180,高度自适应,>表示只缩小不放大
|
||||
cmd = [
|
||||
'convert', str(filepath),
|
||||
'-resize', f'{MAX_WIDTH}x>',
|
||||
'-quality', '90', # 保持较好质量
|
||||
str(output_path)
|
||||
]
|
||||
subprocess.run(cmd, check=True, capture_output=True)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ❌ 压缩失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print(f"🖼️ 图片尺寸压缩工具")
|
||||
print(f" 最大宽度: {MAX_WIDTH}px")
|
||||
print(f" 源目录: {SOURCE_DIR}")
|
||||
print("=" * 60)
|
||||
|
||||
if not SOURCE_DIR.exists():
|
||||
print(f"❌ 目录不存在: {SOURCE_DIR}")
|
||||
sys.exit(1)
|
||||
|
||||
# 支持的图片格式
|
||||
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp'}
|
||||
|
||||
# 收集需要处理的图片
|
||||
to_process = []
|
||||
for file in SOURCE_DIR.iterdir():
|
||||
if file.is_file() and file.suffix.lower() in extensions:
|
||||
width, height = get_image_size(file)
|
||||
if width and width > MAX_WIDTH:
|
||||
to_process.append({
|
||||
'file': file,
|
||||
'width': width,
|
||||
'height': height
|
||||
})
|
||||
|
||||
if not to_process:
|
||||
print("\n✅ 所有图片尺寸都符合要求,无需压缩")
|
||||
return
|
||||
|
||||
print(f"\n📋 发现 {len(to_process)} 张需要压缩的图片:\n")
|
||||
|
||||
for item in to_process:
|
||||
print(f" {item['file'].name}: {item['width']}x{item['height']}")
|
||||
|
||||
# 确认
|
||||
print(f"\n⚠️ 将备份原图到: {BACKUP_DIR}")
|
||||
response = input("\n是否开始压缩? [y/N]: ").strip().lower()
|
||||
|
||||
if response != 'y':
|
||||
print("\n❌ 已取消")
|
||||
return
|
||||
|
||||
# 创建备份目录
|
||||
BACKUP_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 处理图片
|
||||
print("\n" + "=" * 60)
|
||||
success_count = 0
|
||||
|
||||
for i, item in enumerate(to_process, 1):
|
||||
file = item['file']
|
||||
print(f"\n[{i}/{len(to_process)}] {file.name}")
|
||||
print(f" 原尺寸: {item['width']}x{item['height']}")
|
||||
|
||||
# 备份原图
|
||||
backup_path = BACKUP_DIR / file.name
|
||||
try:
|
||||
os.rename(file, backup_path)
|
||||
except Exception as e:
|
||||
print(f" ❌ 备份失败: {e}")
|
||||
continue
|
||||
|
||||
# 压缩
|
||||
if resize_image(backup_path, file):
|
||||
new_width, new_height = get_image_size(file)
|
||||
orig_size = backup_path.stat().st_size / 1024
|
||||
new_size = file.stat().st_size / 1024
|
||||
|
||||
print(f" 新尺寸: {new_width}x{new_height}")
|
||||
print(f" 大小: {orig_size:.1f}KB → {new_size:.1f}KB")
|
||||
success_count += 1
|
||||
else:
|
||||
# 恢复备份
|
||||
os.rename(backup_path, file)
|
||||
print(f" ❌ 已恢复原图")
|
||||
|
||||
# 汇总
|
||||
print("\n" + "=" * 60)
|
||||
print(f"✅ 完成: {success_count}/{len(to_process)} 张图片已压缩")
|
||||
print(f"📁 备份位置: {BACKUP_DIR}")
|
||||
|
||||
if success_count > 0:
|
||||
print(f"\n💡 如需恢复,执行:")
|
||||
print(f" cp {BACKUP_DIR}/* {SOURCE_DIR}/")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user