• webp动图转gif


    目录

    前言

    解决过程

    遇到问题

    获取duration


    前言

    上一次我们实现了webp转jpg格式:

    https://blog.csdn.net/weixin_54143563/article/details/139758200

    那么对于含动图的webp文件我们如何将其转为gif文件呢?

    之所以会出现这个问题,是因为我很喜欢芙芙,逛b站的时候发现了一个芙芙的表情包

    https://i0.hdslb.com/bfs/new_dyn/e541e5ee95aaee7ce655f0074d50e5b8180207493.gif@1052w_!web-dynamic.avif

    打开链接下载后发现是webp格式的,尽管在“另存为”的时候,将保存类型选为“所有文件”,然后文件名加.gif后缀可以下载为gif格式

    但是打开这个文件却并不是动图。

    由此我想要思考怎么才能将webp文件转为gif呢?(虽然在线格式转换的工具有很多,但是为什么不尝试自己动手解决呢?)

    解决过程

    选择imageio库来完成格式转化。

    逻辑流程为:使用imageio.get_reader读取webp文件,然后读取每一帧图像,最后将这些图像写入gif文件中,代码如下:
     

    1. import imageio
    2. # 指定输入的webp文件路径
    3. input_path = '芙宁娜.webp'
    4. # 指定输出的gif文件路径
    5. output_path = 'output.gif'
    6. # 使用imageio读取webp文件
    7. webp_reader = imageio.get_reader(input_path)
    8. # 初始化一个空的gif列表,用于存储每一帧
    9. gif_frames = []
    10. # 读取每一帧图像
    11. for i, frame in enumerate(webp_reader):
    12. gif_frames.append(frame)
    13. # 关闭reader
    14. webp_reader.close()
    15. # 使用imageio写入gif文件,loop=0表示无限循环播放
    16. imageio.mimsave(output_path, gif_frames, format='GIF',loop=0)
    17. print(f"Converted {input_path} to {output_path}")

    遇到问题

    可以实现目标但是有一个问题:

    播放的速度不一样。

    在imageio.mimsave(output_path, gif_frames, format='GIF',loop=0)还有一个参数duration用来设置每帧持续时间,也就是播放速度。

    获取duration

    如果我们想要与原webp播放速度一致,那么就需要获取原图的duration信息。但是目前我还没有找到什么库能够解析出webp的duration。上网查了查给出的建议是用软件工具获取动图的各种信息。既然如此还不如直接下载格式转化的工具呢,毕竟我们没有必要为了这碟醋包饺子呀。

    最后没有办法,只能手动解析了。

    WebP 容器规范  |  Google for Developers

    进入这个网站查看webp的文件结构。

    webp的文件标题如下:

    那么我们可以先读取这些信息:

    1. with open(file_path, 'rb') as f:
    2. # 读取文件头部 RIFF 标志
    3. riff = f.read(4)
    4. if riff != b'RIFF':
    5. raise ValueError("File is not a valid WebP file.")
    6. # 读取文件大小
    7. file_size = struct.unpack(', f.read(4))[0]
    8. # 读取文件类型 WEBP 标志
    9. webp = f.read(4)
    10. if webp != b'WEBP':
    11. raise ValueError("File is not a valid WebP file.")
    12. frame_durations = []

    由于f.read()和f.seek()函数都能够更改文件指针的位置,所以经过上述操作指针f会指向webp文件标题的末尾。

    接下来是一些块(chunk),与动画有关的块分别为“ANIM”和“ANMF”,其中我们需要的duration信息则存储在“ANMF”中。

    “ANMF”区块的结果为:

    在chunkheader中前四位存储的信息为名称ANMF,后四位存储的是ANMF的大小。

    上面我们已经获取了整个webp文件的大小file_size,用f.tell()表示当前文件指针位置,在file_size范围内读取文件内容。

    1. while f.tell() < file_size:
    2. # 读取四字节的 chunk 标志
    3. chunk_id = f.read(4)
    4. if chunk_id == b'ANMF':
    5. # 读取 ANMF chunk 大小
    6. anmf_size = struct.unpack(', f.read(4))[0]
    7. # 读取 ANMF chunk 数据
    8. chunk_data = f.read(anmf_size)
    9. # 解析 Frame Duration,偏移量为 12,读取3个字节
    10. duration_bytes = chunk_data[12:15]
    11. duration = (duration_bytes[2] << 16) + (duration_bytes[1] << 8) + duration_bytes[0]
    12. frame_durations.append(duration)
    13. else:
    14. # 跳过未知的 chunk
    15. chunk_size = struct.unpack(', f.read(4))[0]
    16. f.seek(chunk_size, 1)
    17. return frame_durations

    首先读取4个字节的标志,用条件语句判断是否为'ANMF'区块,进入'ANMF'区块后,由于已经读取了4个字节的标志,用anmf_size = struct.unpack('

    简单来说例如123,1位于百位,那么我们用数组arr这样存储arr[0]=3,arr[1]=2,arr[2]=1.因此在后面按位读取是这样的:

    duration = (duration_bytes[2] << 16) + (duration_bytes[1] << 8) + duration_bytes[0]

    我们继续刚才的过程,获取anmf_size的过程也有f.read(4),所以之后就进入了:

    剩下的:

    所以帧时长的偏移量从3+3+3+3=12开始,读取3个字节。

    duration_bytes = chunk_data[12:15]

    然后读取duration = (duration_bytes[2] << 16) + (duration_bytes[1] << 8) + duration_bytes[0]

    其实,我们只用读取第一帧的duration就可以了。

  • 相关阅读:
    OpenGL 色阶
    java计算机毕业设计中小企业的在线工作日志管理系统源程序+mysql+系统+lw文档+远程调试
    索引(二)
    我的企业证书是正常的但是下载应用app到手机提示无法安装“app名字”无法安装此app,因为无法验证其完整性解决方案
    多态的定义 以及 虚函数重写(覆盖)
    警惕超范围采集隐私-移动APP违规十宗罪
    Kubernetes(k8s)最新版最完整版环境部署+master高可用实现(k8sV1.24.1+dashboard+harbor)
    【数据库原理及应用】——关系操作、关系完整性、关系代数(万字大总结)
    计算机网络第2章-DNS(3)
    Elastcisearch 读书笔记
  • 原文地址:https://blog.csdn.net/weixin_54143563/article/details/139816786