Python脚本:GIF压缩工具

Published on 2025-03-31 17:11 in 分类: 博客 with 狂盗一枝梅
分类: 博客

我经常使用ScreenToGif工具生成GIF动图,有的时候GIF生成的比较大,互联网上有很多在线GIF压缩工具,但是有些不能用,有些速度慢。。。有没有本地工具压缩GIF呢?其实是有的,那就是Gifsicle。

一、Gifsicle

Gifsicle是一个命令行工具,能够创建、编辑GIF动图。

官网地址:https://www.lcdf.org/gifsicle/

下载地址:https://eternallybored.org/misc/gifsicle/

完整的使用手册:https://www.lcdf.org/gifsicle/man.html

1、安装

安装比较简单,下载下来之后将命令行工具目录添加到Path就可以了,运行gifsicle --version命令,出现如下界面就表示安装成功了:

image-20250331161647115

2、使用

使用手册上的内容比较多,我们只关心关于能压缩GIF体积的参数。

--optimize:最重要的优化参数,能有效压缩GIF大小,使用方式为-O{level},level为1、2、3其中一种,数字越大,压缩效果越好,但是耗时也更长,一般想要更好的压缩效果,使用-O3即可。

--colors:减小GIF中使用到的颜色数量到指定数量,通过减小改值,可以有效减小GIF体积。使用方式:--colors={num},num必须是2到256之间的值。

--lossy:有损度,默认值大小为20,该值越大,生成的GIF体积越小,使用方式:--lossy={num}

这样,如果我们想压缩一张GIF动图,可以使用如下命令:

gifsicle -O3 --lossy=100 --colors=64 input.gif -o output.gif

二、python脚本

为了更方便的使用gifsicle工具,我写了一个python脚本。

需要先安装依赖:pip install tkinter rich

完整代码如下所示:

import sys
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import subprocess
from pathlib import Path
import time
from rich.progress import Progress

"""
这是一个压缩gif图像的python脚本
"""

#默认存放GIF动图的目录,需要修改成自己的
default_work_dir = "E:\\动图"
run_mode = 1


def choose_gif_files():
    # 创建Tkinter根窗口并隐藏
    root = tk.Tk()
    root.withdraw()

    # 配置文件对话框参数
    file_path = filedialog.askopenfilename(
        title="请选择动图文件",  # 对话框标题
        initialdir=os.path.expanduser(default_work_dir),  # 初始目录为用户主目录
        filetypes=[  # 文件类型过滤
            ("动图文件", "*.gif")
        ],
        # multiple=True
    )
    # 销毁根窗口
    root.destroy()
    return file_path


def do_process_file(file):
    path = Path(file)
    parent_path = path.parent
    origin_file_name = path.name
    split_parts = origin_file_name.split(".")
    new_file_path = parent_path.joinpath(f"{split_parts[0]}_resize.{split_parts[1]}")
    lossy = input("--lossy(默认300,越大压缩效果越好):")
    colors = input("--colors(默认64,越小压缩效果越好,但是图片可能会失真):")
    print(f"正在处理文件{file}的转换......")
    result = subprocess.Popen(
        [
            "gifsicle",
            "-O3",
            f"--lossy={300 if not lossy else lossy}",
            f"--colors={64 if not colors else colors}",
            "--no-extensions",
            str(file).replace("/", "\\"),
            "-o",
            new_file_path.absolute()
        ],
        stdout=subprocess.PIPE,
        text=True
    )
    # 逐行读取输出
    while True:
        output = result.stdout.readline()
        if output == "" and result.poll() is not None:
            break
        if output:
            print(output.strip())

    # 获取返回码
    return_code = result.poll()
    if return_code == 0:
        return path, new_file_path
    else:
        return path, None


def open_dir(file_path):
    try:
        subprocess.Popen(f'explorer /select, "{file_path.absolute()}"', shell=True)
    except Exception as e:
        messagebox.showerror("错误", f"无法打开资源管理器:{e}")


def format_size(bytes_num):
    """
    将字节数转换为自适应单位(KB/MB/GB等)
    :param bytes_num: 文件字节数,支持负数
    :return: 带单位的格式化字符串
    """
    units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
    sign = '-' if bytes_num < 0 else ''
    size = abs(bytes_num)
    index = 0

    while size >= 1024 and index < len(units) - 1:
        size /= 1024.0
        index += 1

    return f"{sign}{size:.2f} {units[index]}"


def print_origin_and_new_file_diff_info(origin_file_path: Path, new_file_path: Path):
    origin_file_size = origin_file_path.stat().st_size
    new_file_size = new_file_path.stat().st_size
    print(
        f"源文件大小:{format_size(origin_file_size)},新文件大小:{format_size(new_file_size)},"
        f"节约{(((origin_file_size - new_file_size) / origin_file_size) * 100):.2f}%的空间"
    )


def process():
    file_path = choose_gif_files()
    # 处理用户选择结果
    if not file_path:
        print("用户取消选择")
        return
    origin_file_path, new_file_path = do_process_file(file_path)
    if new_file_path:
        print_origin_and_new_file_diff_info(origin_file_path, new_file_path)
        open_result_dir_flag = input(f"转换后文件:{new_file_path.absolute()},是否打开转换后目标文件夹(y/n)?")
        if open_result_dir_flag == 'n' or open_result_dir_flag == 'N':
            return
        else:
            open_dir(new_file_path)
    else:
        print("转换失败")


def exit_program():
    with Progress() as progress:
        # 创建一个隐藏进度条的任务(总时间为3秒)
        task = progress.add_task(
            description="[bold red]即将退出程序... (5)",  # 初始描述
            total=5,
            visible=True, 
        )

        for remaining in range(5, 0, -1):
            # 更新描述中的倒计时数字
            progress.update(
                task,
                description=f"[bold red]即将退出程序... ({remaining})",
                advance=1,
                refresh=True
            )
            time.sleep(1)  # 等待1秒
    sys.exit(0)


if __name__ == '__main__':
    while True:
        process()
        if run_mode == 1:
            exit_program()
            break
        continue_flag = input("是否继续转换(y/n)?")
        if continue_flag == 'n' or continue_flag == 'N':
            exit_program()
        continue

运行结果如下:

动画22_resize
#python
复制 复制成功