完善图片压缩功能
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
venv/
|
||||||
|
input/
|
||||||
|
output/
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#### v260717补丁
|
#### v260717补丁
|
||||||
1. 修改图片压缩文件名
|
1. 修改图片压缩文件名
|
||||||
|
2. 完善图片压缩功能
|
||||||
|
|
||||||
#### v260717
|
#### v260717
|
||||||
1. 新增JS音乐播放器(基于Meting-API)
|
1. 新增JS音乐播放器(基于Meting-API)
|
||||||
|
|||||||
+85
-84
@@ -3,118 +3,119 @@ from PIL import Image
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
def get_file_kb(file_path: str) -> float:
|
SUPPORTED_OUTPUT_FORMATS = ("jpg", "jpeg", "png", "webp")
|
||||||
"""获取文件大小 KB"""
|
|
||||||
size_byte = os.path.getsize(file_path)
|
|
||||||
return size_byte / 1024
|
|
||||||
|
|
||||||
def compress_to_jpg_loop(
|
|
||||||
input_path: str,
|
def get_file_kb(file_path: str) -> float:
|
||||||
output_path: str,
|
return os.path.getsize(file_path) / 1024
|
||||||
min_size_kb: float,
|
|
||||||
max_size_kb: float,
|
|
||||||
init_quality: int,
|
def _save_image(img: Image.Image, output_path: str, output_format: str, quality: int):
|
||||||
quality_step: int,
|
if output_format == "png":
|
||||||
min_quality: int,
|
img.save(output_path, format="PNG", optimize=True, compress_level=9)
|
||||||
max_width: int,
|
elif output_format == "webp":
|
||||||
max_height: int
|
img.save(output_path, format="WEBP", quality=quality, method=6)
|
||||||
):
|
else:
|
||||||
|
img.save(output_path, format="JPEG", quality=quality, optimize=True)
|
||||||
|
|
||||||
|
|
||||||
|
def compress_to_format_loop(input_path, output_path, min_size_kb, max_size_kb,
|
||||||
|
init_quality, quality_step, min_quality,
|
||||||
|
max_width, max_height, output_format):
|
||||||
try:
|
try:
|
||||||
|
# None 表示保持输入图片格式。
|
||||||
|
if output_format is None:
|
||||||
|
output_format = os.path.splitext(input_path)[1].lower().lstrip(".")
|
||||||
|
output_format = output_format.lower().lstrip(".")
|
||||||
|
if output_format not in SUPPORTED_OUTPUT_FORMATS:
|
||||||
|
raise ValueError(f"不支持的输出格式: {output_format}")
|
||||||
|
|
||||||
ori_size = get_file_kb(input_path)
|
ori_size = get_file_kb(input_path)
|
||||||
print(f"\n原图: {os.path.basename(input_path)} | 大小: {ori_size:.2f} KB")
|
print(f"\n原图: {os.path.basename(input_path)} | 大小: {ori_size:.2f} KB")
|
||||||
|
|
||||||
# 小于阈值,直接复制原图,不压缩
|
# INIT_QUALITY=None:不压缩、不缩放;目标格式不同则只做格式转换。
|
||||||
if ori_size < min_size_kb:
|
# 小于阈值:同样不压缩、不缩放;目标格式不同则只做格式转换。
|
||||||
shutil.copy2(input_path, output_path)
|
if init_quality is None or ori_size < min_size_kb:
|
||||||
print(f"✅ 小于{min_size_kb}KB,无需压缩,直接复制")
|
if os.path.splitext(input_path)[1].lower().lstrip(".") == output_format:
|
||||||
|
shutil.copy2(input_path, output_path)
|
||||||
|
print(f"✅ 小于{min_size_kb}KB,无需压缩,直接复制")
|
||||||
|
else:
|
||||||
|
img = Image.open(input_path)
|
||||||
|
img = _prepare_image(img, output_format)
|
||||||
|
_save_image(img, output_path, output_format, init_quality)
|
||||||
|
reason = "INIT_QUALITY=None" if init_quality is None else f"小于{min_size_kb}KB"
|
||||||
|
print(f"✅ {reason},不压缩,仅转换为 {output_format}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 打开图片,处理透明通道
|
|
||||||
img = Image.open(input_path)
|
img = Image.open(input_path)
|
||||||
if img.mode in ("RGBA", "P"):
|
img = _prepare_image(img, output_format)
|
||||||
new_img = Image.new("RGB", img.size, (255, 255, 255))
|
|
||||||
mask = img.split()[-1] if img.mode == "RGBA" else None
|
|
||||||
new_img.paste(img, mask=mask)
|
|
||||||
img = new_img
|
|
||||||
|
|
||||||
# 等比例缩放
|
|
||||||
if max_width and max_height:
|
if max_width and max_height:
|
||||||
img.thumbnail((max_width, max_height))
|
img.thumbnail((max_width, max_height))
|
||||||
|
|
||||||
current_quality = init_quality
|
# PNG 没有 quality 参数,直接用最高无损压缩保存。
|
||||||
|
if output_format == "png":
|
||||||
|
_save_image(img, output_path, output_format, init_quality)
|
||||||
|
print(f"✅ PNG 已保存 | 输出大小: {get_file_kb(output_path):.2f} KB")
|
||||||
|
return
|
||||||
|
|
||||||
|
quality = init_quality
|
||||||
while True:
|
while True:
|
||||||
img.save(output_path, format="JPEG", quality=current_quality, optimize=True)
|
_save_image(img, output_path, output_format, quality)
|
||||||
out_size = get_file_kb(output_path)
|
out_size = get_file_kb(output_path)
|
||||||
|
if out_size <= max_size_kb or quality <= min_quality:
|
||||||
if out_size <= max_size_kb or current_quality <= min_quality:
|
print(f"✅ 最终画质 {quality} | 输出大小: {out_size:.2f} KB")
|
||||||
print(f"✅ 最终画质 {current_quality} | 输出大小: {out_size:.2f} KB")
|
|
||||||
break
|
break
|
||||||
|
quality = max(min_quality, quality - quality_step)
|
||||||
current_quality -= quality_step
|
print(f"⚠️ {out_size:.2f}KB > {max_size_kb}KB,降低画质至 {quality} 重新压缩")
|
||||||
print(f"⚠️ {out_size:.2f}KB > {max_size_kb}KB,降低画质至 {current_quality} 重新压缩")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 处理失败 {input_path}:{str(e)}")
|
print(f"❌ 处理失败 {input_path}:{str(e)}")
|
||||||
|
|
||||||
|
|
||||||
def batch_compress_folder(
|
def _prepare_image(img, output_format):
|
||||||
input_dir: str,
|
has_alpha = "A" in img.getbands() or (img.mode == "P" and "transparency" in img.info)
|
||||||
output_dir: str,
|
if output_format in ("jpg", "jpeg"):
|
||||||
min_kb,
|
if has_alpha:
|
||||||
max_kb,
|
rgba = img.convert("RGBA")
|
||||||
init_q,
|
result = Image.new("RGB", rgba.size, (255, 255, 255))
|
||||||
q_step,
|
result.paste(rgba, mask=rgba.getchannel("A"))
|
||||||
min_q,
|
return result
|
||||||
max_w,
|
return img.convert("RGB")
|
||||||
max_h
|
return img.convert("RGBA" if has_alpha else "RGB")
|
||||||
):
|
|
||||||
|
|
||||||
|
def batch_compress_folder(input_dir, output_dir, min_kb, max_kb, init_q,
|
||||||
|
q_step, min_q, max_w, max_h, output_format):
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
support_ext = (".png", ".jpg", ".jpeg", ".webp", ".bmp")
|
support_ext = (".png", ".jpg", ".jpeg", ".webp", ".bmp")
|
||||||
|
|
||||||
for filename in os.listdir(input_dir):
|
for filename in os.listdir(input_dir):
|
||||||
file_path = os.path.join(input_dir, filename)
|
file_path = os.path.join(input_dir, filename)
|
||||||
if not filename.lower().endswith(support_ext):
|
if os.path.isdir(file_path) or not filename.lower().endswith(support_ext):
|
||||||
continue
|
continue
|
||||||
if os.path.isdir(file_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
name_no_ext = os.path.splitext(filename)[0]
|
name_no_ext = os.path.splitext(filename)[0]
|
||||||
out_file = os.path.join(output_dir, f"{name_no_ext}.jpg")
|
suffix = output_format or os.path.splitext(filename)[1].lstrip(".")
|
||||||
compress_to_jpg_loop(
|
out_file = os.path.join(output_dir, f"{name_no_ext}.{suffix}")
|
||||||
input_path=file_path,
|
compress_to_format_loop(file_path, out_file, min_kb, max_kb, init_q,
|
||||||
output_path=out_file,
|
q_step, min_q, max_w, max_h, output_format)
|
||||||
min_size_kb=min_kb,
|
|
||||||
max_size_kb=max_kb,
|
|
||||||
init_quality=init_q,
|
|
||||||
quality_step=q_step,
|
|
||||||
min_quality=min_q,
|
|
||||||
max_width=max_w,
|
|
||||||
max_height=max_h
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# ==================== 批量配置区(仅需修改这里) ====================
|
# 输入输出文件夹
|
||||||
INPUT_FOLDER = "./input"
|
INPUT_FOLDER = "./input"
|
||||||
OUTPUT_FOLDER = "./output"
|
OUTPUT_FOLDER = "./output"
|
||||||
MIN_SIZE_KB = 200 # 小于该KB不压缩
|
# 首次压缩质量,不压缩填 None
|
||||||
MAX_SIZE_KB = 800 # 超过该KB循环再压
|
INIT_QUALITY = 75
|
||||||
INIT_QUALITY = 75 # 初始画质
|
# 过小不压缩,过大重复压缩
|
||||||
QUALITY_STEP = 8 # 每次降低画质数值
|
MIN_SIZE_KB = 100
|
||||||
MIN_QUALITY = 20 # 最低画质底线
|
MAX_SIZE_KB = 500
|
||||||
LIMIT_W = 2560 # 不需要缩放填 None
|
# 重复压缩质量递减及最小值
|
||||||
|
QUALITY_STEP = 8
|
||||||
|
MIN_QUALITY = 20
|
||||||
|
# 更换格式,不转换格式填 None
|
||||||
|
OUTPUT_FORMAT = "webp"
|
||||||
|
# 尺寸压缩,不缩放填 None
|
||||||
|
LIMIT_W = 2560
|
||||||
LIMIT_H = 1440
|
LIMIT_H = 1440
|
||||||
# ==================================================================
|
|
||||||
|
|
||||||
# 仅批量执行,无任何单张代码
|
batch_compress_folder(INPUT_FOLDER, OUTPUT_FOLDER, MIN_SIZE_KB, MAX_SIZE_KB,
|
||||||
batch_compress_folder(
|
INIT_QUALITY, QUALITY_STEP, MIN_QUALITY, LIMIT_W,
|
||||||
input_dir=INPUT_FOLDER,
|
LIMIT_H, OUTPUT_FORMAT)
|
||||||
output_dir=OUTPUT_FOLDER,
|
|
||||||
min_kb=MIN_SIZE_KB,
|
|
||||||
max_kb=MAX_SIZE_KB,
|
|
||||||
init_q=INIT_QUALITY,
|
|
||||||
q_step=QUALITY_STEP,
|
|
||||||
min_q=MIN_QUALITY,
|
|
||||||
max_w=LIMIT_W,
|
|
||||||
max_h=LIMIT_H
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user