【Python】使用cwebp、gif2webp、exiftool实现保留exif信息的WebP转换

此前写了个使用cwebp、gif2webp的脚本,但是由于cwebp目前在win的元数据提取存在问题,因此我们可以使用已经支持exif提取和写入的exiftool进行最后一步的转换,这样我们的图片压缩、转码都在官方库得以实现。

前置条件:

cwebp、gif2webp、exiftool三个组件都注册到系统环境变量。python则使用pil库用于分辨率获取。

实现效果:

使用pil库对分辨率进行获取,但是不介入压缩过程,因为cwebp目前没法获取图片分辨率,使用pil库进行是否执行resize的判断。

使用cwebp处理静态png、jpg,使用gif2webp处理gif图,启用mt多线程,压缩质量85,resize到2560最长、宽边,exiftool采用”-overwrite_original”来避免生成两个图片。

测试效果:

该图片原图7M多,压缩质量选择85,可能由于细节较为丰富,压缩到WebP大小仍为1M左右,还是比较大,不过细节保留充分,同时保留了EXIF信息。

import tkinter as tk
from tkinter import filedialog, messagebox
import os
import subprocess
from PIL import Image

def validate_file(input_path):
    input_path = os.path.abspath(input_path)
    if not os.path.exists(input_path):
        raise FileNotFoundError(f"文件 {input_path} 不存在,请检查路径。")
    return input_path

def get_resized_dimensions(width, height, max_size):
    if width > height:
        new_width = max_size
        new_height = int((new_width / width) * height)
    else:
        new_height = max_size
        new_width = int((new_height / height) * width)
    return new_width, new_height

def convert_image(input_path, output_path, new_width=None, new_height=None):
    try:
        file_extension = os.path.splitext(input_path)[1].lower()
        if file_extension == ".gif":
            command = ["gif2webp","mt", input_path, "-o", output_path]
        else:
            if new_width and new_height:
                command = ["cwebp","mt", "-q", "85", "-resize", str(new_width), str(new_height), input_path, "-o", output_path]
            else:
                command = ["cwebp","mt", "-q", "85", input_path, "-o", output_path]
        subprocess.run(command, check=True)
    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"转换工具运行出错: {e}")

def embed_exif(input_path, output_path):
    try:
        command = ["exiftool", "-overwrite_original", "-tagsfromfile", input_path, "-all:all", output_path]
        subprocess.run(command, check=True)
    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"EXIF 数据嵌入失败: {e}")

def convert_to_webp(input_path, max_size=2560):
    try:
        # 验证文件路径
        input_path = validate_file(input_path)
        output_path = os.path.splitext(input_path)[0] + ".webp"

        # 使用 PIL 获取图像分辨率
        with Image.open(input_path) as img:
            width, height = img.size
            if width <= max_size and height <= max_size:
                convert_image(input_path, output_path)
            else:
                new_width, new_height = get_resized_dimensions(width, height, max_size)
                convert_image(input_path, output_path, new_width, new_height)

        # 嵌入 EXIF 数据
        embed_exif(input_path, output_path)

        return f"图片已转换并保存为 {output_path}"

    except (subprocess.CalledProcessError, FileNotFoundError) as e:
        return str(e)
    except Exception as e:
        return f"处理文件时发生错误: {e}"

def select_files():
    file_paths = filedialog.askopenfilenames(
        title="选择图片文件",
        filetypes=[("*所有图片格式", "*.jpg;*.jpeg;*.png;*.gif"),
                   ("JPEG 图片", "*.jpg;*.jpeg"),
                   ("PNG 图片", "*.png"),
                   ("GIF 图片", "*.gif")]
    )
    if file_paths:
        for path in file_paths:
            file_listbox.insert(tk.END, path)

def convert_and_save_batch():
    files = file_listbox.get(0, tk.END)
    if not files:
        messagebox.showerror("错误", "请选择至少一个图片文件!")
        return

    results = [convert_to_webp(file_path) for file_path in files]
    messagebox.showinfo("完成", "\n".join(results))

def clear_list():
    file_listbox.delete(0, tk.END)

root = tk.Tk()
root.title("批量图片转换为 WebP 工具")
root.geometry("600x400")

frame = tk.Frame(root)
frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)

scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL)
file_listbox = tk.Listbox(frame, selectmode=tk.EXTENDED, yscrollcommand=scrollbar.set)
scrollbar.config(command=file_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

button_frame = tk.Frame(root)
button_frame.pack(pady=10)

select_button = tk.Button(button_frame, text="选择文件", command=select_files, width=15)
select_button.grid(row=0, column=0, padx=5)

clear_button = tk.Button(button_frame, text="清空列表", command=clear_list, width=15)
clear_button.grid(row=0, column=1, padx=5)

convert_button = tk.Button(button_frame, text="批量转换", command=convert_and_save_batch, width=15)
convert_button.grid(row=0, column=2, padx=5)

root.mainloop()

评论

《“【Python】使用cwebp、gif2webp、exiftool实现保留exif信息的WebP转换”》 有 1 条评论

  1. […] 【Python】使用WebP官方库进行WebP转换【Python】使用cwebp、gif2webp、exiftool实现保留exif信息的WebP转换 […]

回复 【Python】使用PIL库进行多格式批量转换WebP并压缩分辨率 – 万雪飞扬 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注