apng2gif各种问题的解决

事情起因

追溯起来已经是两年多以前的事情了,当时有个表情包在Line上火了起来,之后血洗国内各大平台,这个表情包就是Menhera酱

NanaseKurumi

NanaseKonomi

谁会抗拒可爱的事物呢?所以当时我就立志要把她们的表情包都给要了,最开始就是通过网友们的搬运,在百度网盘等地方收集。

但是时间久了,Menhera酱的热度也下去了,没有多少人再收集了,有多少人的喜欢能像我一样始终如一呢?

这时候我强迫症就来了,收集不完整那多难受啊,所以我决定亲自上Line收集。

上Line是需要魔法的,当时用了点魔法,又在Chrome上找了个插件,成功地把所有她们的表情都下载了下来。

但是!

Line上的动图是PNG格式的,更准确地来说是APNG格式的,这样的动图在微信上是用不了的,放上去就是张不动的图片,甚至背景还是黑乎乎的。

微信只支持GIF的动图,这我可忍不了,必须得给安排了!

艰辛的转换历程

找工具阶段

最开始肯定是找别人做好的批量转换的工具,关键词搜apng2gif,当然有一堆博文和工具了。

尝试了一个据说很好用的工具isparta,下载了很久很久很久才下载好。转换时间很长,但结果转换出来很多都有问题,在电脑上可以正常播放,但发送到微信上一遍播放完了就会花屏

e38e75791d5535498e6c9c0b3463f07

感觉可能是版本bug,就有花了好久下载其他版本,结果都不好用,只能放弃了这个软件。

然后又找到了一个在线转换的网站,需要配置很多参数,生成了一张试试效果居然还不错。

但是这个网站毕竟在线的,自动化程度不高,网络连接也比较慢,批量处理肯定是个大工程,只好放弃了。

这是第一次尝试。

自己造轮子阶段

后来的某一天又想起了这件事,决定重新思考一下怎么弄。

APNG格式诞生这么长时间了,肯定有很多资料吧?

思路是这样的,APNG是动图,肯定是由很多帧组成的,把这些帧给提取出来,然后再合成GIF不就行了?

找了一些资料,写了一个python程序,导出了一张APNG的各帧。

结果发现问题很大,APNG的每一帧并不是完整的一张图片,而是和上一帧有变化的地方!

最后合成出来的东西就很鬼畜,参见下图:

69594924

心情就很不美丽了,费这么大劲你给我合成出了啥奇怪的东西?

又在Github上找到了一个湾湾同胞写的python程序,反复调试运行都没法得到正确结果。

后来也有构思,还是解析合成的思路,只要把每一帧放在相应的坐标就行了,但是想了想太复杂就放弃了。

这是第二次尝试

改造轮子阶段

这两天看到了七濑胡桃公众号推送的文章,其中有一张可爱的动图,就转发给了对象,结果她直呼好可爱!

虽然尝试了那么多次,也失败了那么多次,但是这次一定要成功。

发现新轮子

思路还是先看看有没有现成的轮子,当然是有的!

我尝试了pip中的apng2gif包,完全不好使,甚至正常播放都不行。于是去找到了源代码,结果……

这不就是我上次参考的那个Github上的源代码吗?

放弃!

但是,我在命令行中不小心输入了apng2gif,忘记了加后缀,结果提示我可以通过apt安装?

原来Linux上也是有现成轮子可以用的!赶紧apt了下来。

这个程序的使用很简单:

1603255890727

试了一张APNG动图,导出的GIF效果居然还挺好?

立刻发到微信上试试,结果还是播放一遍就花屏,果然还是不靠谱。

给轮子加一轮转换

但是这个播放一遍就花屏问题,似乎就是因为它只循环了一遍?那我只需要把GIF的循环设置为一直循环不就可以了?

可惜的是这个工具并没有这个选项。

不过现在又可以造轮子了呀,这次只需要解析GIF的每一帧,然后合成,保存的时候设置loop为0就行了!

遗憾的是这么做还是存在问题。

有一个保存选项是disposal,设置为3的时候不会出现重影,但是存在部分帧只显示一部分的情况。

new69594922

disposal设置为1或0的时候虽然会显示完整,但是会有重影。

newani

再然后就是关于duration的奇怪问题,即使设置新gif的duration(帧间时长)和旧的duration一样,最后导出的gif的duration会变成两倍,不知何故!设置为一半时反而可以一样。

1603256906973

总之还是有问题的。

换个方式给轮子加一轮转换

既然这个轮子和我的需求只相差一个loop的值的问题,那我直接修改那个值不就好了?

GIF文件中肯定有特定的位用来存放loop的值。去找了GIF文件格式,也不知怎么搞的,网上都是互相转载的,没有找到直接的资料,最后找到一份代码中倒是有文件的详细结构。

经过分析,对文件进行16进制搜索,找到“NETSCAPE2.0”,再往后数第三、四个字节就是了。

1603257569580

将荧光笔标记的01改成00,loop就从一次变成无限循环了(下面两张分别为循环一次和无数次)。

ani

ani2

这个方案确实有效!

那么编程思路也很简单,以16进制的方式读取文件,然后匹配并找到“NETSCAPE2.0”,确定loop值对应的位置,然后将值直接修改为0保存即可。

修改轮子

但是我没有按照上一部分的思路去写程序,别问,问就是懒。

我们换个思路,既然apng2gif这个工具最后合成了GIF,必然也写入了loop位,我们直接修改该工具的源代码不就可以了?

费了好大劲从SourceForge上下载了源码,因为下载各种失败,然后切换镜像。

找到源代码之后就简单了,在代码中合成GIF之前的位置,加入一句代码即可:

1
num_loops = 0;

重新make生成可执行文件,再尝试转换发现达到了想要的效果。

make过程中提示“-lpng”选项的问题,通过apt安装libpng-dev即可:

1
sudo apt-get install libpng-dev

编写批处理程序

那么接下来,把执行apng2gif的过程变成批处理即可,这一步用shell或python都行,我用的是python(因为这段时间写了个千余行的shell脚本,现在不想写shell了)。

程序很简单,扫描所有后缀为.png的文件,调用apng2gif程序转换到gif文件夹下。

1
2
3
4
5
6
7
8
9
import os

def trans(filename):
os.system("./apng2gif "+filename+" gif/"+filename[:-4]+".gif")

if __name__ == "__main__":
for filename in os.listdir('.'):
if filename.endswith('.png'):
trans(filename)

所以折腾了这么久,真正有效的代码量只有修改轮子1句c代码和批处理中的7句python代码(python代码甚至可以精简成三句!)?

其中解决问题的方式不一定难,但探索的过程一定艰难且有趣。

107433842

保持好奇是探索发现的最大动力源泉。

48859426