本文转载自微信公众号「arige」,作者arige。转载本文请联系arige公众号。
一、背景
前天在给微信公众号上传文章的时候,文章里面有一个图片是gif的,在上传的过程中报错了,说是图片超大了。搜索之后发现图片需要小于5m。
那么问题就转化为怎么把当前的gif给缩减到5m以内本着有轮子用轮子,没有轮子造轮子的精神,网上搜索一番。
发现一些现象
1、压缩要不就是需要会员才能下载;
2、要不免费的只能压缩5m以下的。
考虑到能动手不花钱的本性,我觉得要自己搞一下。
知识背景:
众所周知,gif图就是由若干组图片组成的一种文件格式,有多张有一定差异的图片连续播放,间隔时间较小,欺骗了我们的眼睛和大脑,然后我们以为是一个完全连续的。其实就是一个类似快速翻书的操作。
二、方案选型
方案一
因为gif是有多种图片做的,那我们就考虑把图片减少一些,比如说原来是100张是10m,我给缩减到10张,那体积可不就要缩小到1m左右了吗?当然,为了用户看起来不是那么卡顿,我就拍脑袋给缩减到20张吧,即只有原来的1/5。
方案二
如果缩减的图片太多导致gif看起来卡顿的话,我们可以考虑不缩减图片的张数,但是我们可以压缩图片。
方案三
最后的都是重要的,如果前面两个都无法满足的话,那就可以考虑把他们进行叠加。先减张数,再压缩拆分的图片。
三、项目落实
整体流程如下:
if __name__ == "__main__":
# 设置源gif的地址
sourceGifPath = "/Users/user/test/f79a3e2c2e864863a6b1a66791cb0950_tplv-k3u1fbpfcp-watermark.gif"
# 将gif拆分成多个图片,并保存在本地
SplitGif(sourceGifPath)
# 将指定位置的文件下的图片按照文件名索引排序,做成gif
Combine2Gif(sourceGifPath[:-4], sourceGifPath[:-4] + "_result.gif")
print("== finished ==")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
1、将源gif读入内存
2、将gif拆分成png,并保存
def SplitGif(gifPath):
# 获取png存储的文件夹的地址
pngDir = gifPath[:-4]
# 要存储的文件夹下清理干净,避免影响当前操作
rmPngDir(pngDir)
# 创建存储的文件夹
os.mkdir(pngDir)
# 把指定gif拆分后存储到指定文件夹
savePngToDir(gifPath, pngDir)
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
2.1、获取要存储的地址
2.2、清空并移除存储png的文件夹
def rmPngDir(pngDir):
if os.path.exists(pngDir):
files = os.listdir(pngDir)
# 如果不一个一个的移除文件夹下的文件的话,无法移除文件夹
for file in files:
file = pngDir + "/" + file
os.remove(file)
os.rmdir(pngDir)
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
2.3、创建存储png的文件夹
2.4、将gif拆分成png,并保存
def savePngToDir(gifPath, pngDir):
# 通过路径传教image对象
image = Image.open(gifPath)
try:
# 循环,通过异常方案退出循环
while True:
# 获取当前的索引的位置
current = image.tell()
# 创建文件路径
pngPath = pngDir + '/' + str(current) + '.png'
image.save(pngPath, quality=100)
# 索引后移,越界后异常,退出当前循环
image.seek(current + 1)
except EOFError as e:
print(e)
pass
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
3、按照一定的间隔读取2中的png,并生成gif
def Combine2Gif(folderPath, gifFilePath):
GenerateGif(0.1, gifFilePath, getPngArray(folderPath))
- 1.
- 2.
3.1、获取所有的png
def getPngArray(folderPath):
files = os.listdir(folderPath)
pngFiles = []
# 通过设置step,将文件的大小修改为原来的体积的1/step
for i in range(0, len(files), 5):
pngFiles.append(folderPath + "/" + ('%d.png' % i))
return pngFiles
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
3.2、将png合并成gif
def GenerateGif(step, gifPath, filterPngs):
images = []
for filePath in filterPngs:
images.append(imageio.imread(filePath))
# 生成gif,duration 是播放两个图片之间的间隔时间
imageio.mimsave(gifPath, images, duration=step)
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
四、全部的代码
#! /usr/local/bin/python3
# -*- coding: utf-8 -*-
from PIL import Image
import os
import imageio
def SplitGif(gifPath):
# 获取png存储的文件夹的地址
pngDir = gifPath[:-4]
# 要存储的文件夹下清理干净,避免影响当前操作
rmPngDir(pngDir)
# 创建存储的文件夹
os.mkdir(pngDir)
# 把指定gif拆分后存储到指定文件夹
savePngToDir(gifPath, pngDir)
def rmPngDir(pngDir):
if os.path.exists(pngDir):
files = os.listdir(pngDir)
# 如果不一个一个的移除文件夹下的文件的话,无法移除文件夹
for file in files:
file = pngDir + "/" + file
os.remove(file)
os.rmdir(pngDir)
def savePngToDir(gifPath, pngDir):
# 通过路径传教image对象
image = Image.open(gifPath)
try:
# 循环,通过异常方案退出循环
while True:
# 获取当前的索引的位置
current = image.tell()
# 创建文件路径
pngPath = pngDir + '/' + str(current) + '.png'
image.save(pngPath, quality=100)
# 索引后移,越界后异常,退出当前循环
image.seek(current + 1)
except EOFError as e:
print(e)
pass
def Combine2Gif(folderPath, gifFilePath):
GenerateGif(0.1, gifFilePath, getPngArray(folderPath))
# 获取文件的数组
def getPngArray(folderPath):
files = os.listdir(folderPath)
pngFiles = []
# 通过设置step,将文件的大小修改为原来的体积的1/step
for i in range(0, len(files), 5):
pngFiles.append(folderPath + "/" + ('%d.png' % i))
return pngFiles
def GenerateGif(step, gifPath, filterPngs):
images = []
for filePath in filterPngs:
images.append(imageio.imread(filePath))
# 生成gif,duration 是播放两个图片之间的间隔时间
imageio.mimsave(gifPath, images, duration=step)
if __name__ == "__main__":
# 设置源gif的地址
sourceGifPath = "/Users/user/test/f79a3e2c2e864863a6b1a66791cb0950_tplv-k3u1fbpfcp-watermark.gif"
# 将gif拆分成多个图片,并保存在本地
SplitGif(sourceGifPath)
# 将指定位置的文件下的图片按照文件名索引排序,做成gif
Combine2Gif(sourceGifPath[:-4], sourceGifPath[:-4] + "_result.gif")
print("== finished ==")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
五、结尾
作为一个追求高效的程序员,我就做一个能满足我需求的方案,即方案一。至于方案二和方案三,有兴趣的朋友可以举一反三。