批量图片压缩 & 替换

背景

最近产品提了个需求,要求把包压缩一下,而项目是OC&Swift混编,这期还加上了RN,还要包不能增大。脑壳疼。。。。他则不上天呢。但需求出来了,还是要做的。所以就想了下面几个方法:

  1. 先用LSUnusedResources分析项目中无用的图片和类,删除;
  2. 然后对项目中的图片进行压缩替换;
  3. 再接着分析linkMap文件,找出大的文件进行优化。
  4. 基于clang插件的一种iOS包大小瘦身方案

实现

这篇就是关于第二步的,项目里大约有1600多张图片,之前几次压缩都是按大小排序,然后把大于10kb的图片一个个上传到tinypng上压缩,再下载替换。tinypng web最多支持一次20张,每次上传压缩,然后等,就问问烦不烦。。。。

图片批量压缩

so,这次我终于受不了,我要找批量压缩的,还真给我搜到了图片批量压缩脚本(Python),这种使用方式GitHub上已经写得很清楚了,每月可以500张批量压缩,然后有一个输出文件夹:

使用这个脚本的时候,要注意:

  1. 安装Python
  2. 安装click和tinify
  3. 到此处申请 API key: https://tinypng.com/developers ,一个 key 每个月可以免费压缩500张图片,可以申请多个 key。
1
2
pip install click // 安装click库
pip install --upgrade tinify // 安装tinify库

然后使用脚本,GitHub里那位大佬脚本print函数没更新,贴一下我更新后的:

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
77
78
79
80
81
82
83
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import os
import sys
import os.path
import click
import tinify

tinify.key = "Your API Key" # API KEY
version = "1.0.1" # 版本

# 压缩的核心
def compress_core(inputFile, outputFile, img_width):
source = tinify.from_file(inputFile)
if img_width is not -1:
resized = source.resize(method = "scale", width = img_width)
resized.to_file(outputFile)
else:
source.to_file(outputFile)

# 压缩一个文件夹下的图片
def compress_path(path, width):
print ("compress_path-------------------------------------")
if not os.path.isdir(path):
print ("这不是一个文件夹,请输入文件夹的正确路径!")
return
else:
fromFilePath = path # 源路径
toFilePath = path+"/tiny" # 输出路径
print ("fromFilePath=%s" %fromFilePath)
print ("toFilePath=%s" %toFilePath)

for root, dirs, files in os.walk(fromFilePath):
print ("root = %s" %root)
print ("dirs = %s" %dirs)
print ("files= %s" %files)
for name in files:
fileName, fileSuffix = os.path.splitext(name)
if fileSuffix == '.png' or fileSuffix == '.jpg' or fileSuffix == '.jpeg':
toFullPath = toFilePath + root[len(fromFilePath):]
toFullName = toFullPath + '/' + name
if os.path.isdir(toFullPath):
pass
else:
os.mkdir(toFullPath)
compress_core(root + '/' + name, toFullName, width)
break # 仅遍历当前目录

# 仅压缩指定文件
def compress_file(inputFile, width):
print ("compress_file-------------------------------------")
if not os.path.isfile(inputFile):
print ("这不是一个文件,请输入文件的正确路径!")
return
print ("file = %s" %inputFile)
dirname = os.path.dirname(inputFile)
basename = os.path.basename(inputFile)
fileName, fileSuffix = os.path.splitext(basename)
if fileSuffix == '.png' or fileSuffix == '.jpg' or fileSuffix == '.jpeg':
compress_core(inputFile, dirname+"/tiny_"+basename, width)
else:
print ("不支持该文件类型!")

@click.command()
@click.option('-f', "--file", type=str, default=None, help="单个文件压缩")
@click.option('-d', "--dir", type=str, default=None, help="被压缩的文件夹")
@click.option('-w', "--width", type=int, default=-1, help="图片宽度,默认不变")
def run(file, dir, width):
print ("GcsSloop TinyPng V%s" %(version))
if file is not None:
compress_file(file, width) # 仅压缩一个文件
pass
elif dir is not None:
compress_path(dir, width) # 压缩指定目录下的文件
pass
else:
compress_path(os.getcwd(), width) # 压缩当前目录下的文件
print ("结束!")

if __name__ == "__main__":
run()

图片批量替换

Yeah,使用了这个脚本之后,图片可以批量压缩了,但是压缩之后的图片是生成在一个独立文件夹,我需要批量替换,but,我图片的目录不确定,换句话说,我不知道这些文件具体在哪个目录下面。。。。oh no。

so,这是你逼我的,开动脑壳,我就想能不能做到我在读取图片压缩的之后直接替换;又或者,写一个单独的批量替换的脚本,因为大的目录确定,压缩前后图片名字没有变化,这么做应该可行,说干就干
使用的时候,把Python文件里TargetPath改成要替换的总目录,SourcePath改成上个脚本执行后压缩后图片的目录,然后运行,binggo,done
原理:

  1. 读取指定目录&子目录下所有文件
  2. 判断是不是图片,是就存到数组里
  3. 读取Target目录和Source目录,然后遍历用’/‘分割,取最后一个,判断是否相等,相等就写入
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

import os
import shutil

# 判断是否是图片
def is_img(ext):
ext = ext.lower()
if ext in ['.jpg', '.png', '.jpeg', '.bmp']:
return True
else:
return False

TargetPath = 'Your Target Path' # 要拷贝到哪个目录
SourcePath = 'Your Source Path' # 从哪个目录拷贝

# 获取指定目录下所有图片
def get_img_files(dir):
py_files = []
for root, dirs, files in os.walk(dir):
for file in files:
pre, suf = os.path.splitext(file)
if is_img(suf):
py_files.append(os.path.join(root, file))
return py_files

TargetFiles = get_img_files(TargetPath)
SourceFiles = get_img_files(SourcePath)

for target in TargetFiles:
for source in SourceFiles:
targetName = target.split('/')[-1]
sourceName = source.split('/')[-1]
if targetName == sourceName:
shutil.copyfile(source, target)
图片批量压缩 & 替换,二合一
这样通过两个脚本就可以实现批量压缩、替换,but,我要跑两个脚本,好麻烦,能不能合二为一,就问你能不能?
小样,这怎么可能难倒机智的我,

压缩脚本的输出目录是替换脚本的源目录,压缩脚本的源目录是替换脚本的输出目录
所以改一下压缩脚本的实现,读取目录改成是固定的,再改一下压缩脚本的输出目录,注意跟读取目录不要再是读取的子目录,要不然会有问题
然后在压缩脚本执行成功后,执行批量替换脚本,done
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import os
import sys
import os.path
import click
import tinify
import shutil

tinify.key = "Your API KEY" # API KEY
version = "1.0.1" # 版本

TargetPath = 'Your Target Path' # 要拷贝到哪个目录
SourcePath = 'Your Source Path' # 从哪个目录拷贝

# 压缩的核心
def compress_core(inputFile, outputFile, img_width):
source = tinify.from_file(inputFile)
if img_width is not -1:
resized = source.resize(method = "scale", width = img_width)
resized.to_file(outputFile)
else:
source.to_file(outputFile)

# 压缩一个文件夹下的图片
def compress_path(path, width):
print ("compress_path-------------------------------------")
if not os.path.isdir(path):
print ("这不是一个文件夹,请输入文件夹的正确路径!")
return
else:
fromFilePath = path # 源路径
toFilePath = SourcePath # 输出路径
print ("fromFilePath=%s" %fromFilePath)
print ("toFilePath=%s" %toFilePath)

for root, dirs, files in os.walk(fromFilePath):
print ("root = %s" %root)
print ("dirs = %s" %dirs)
print ("files= %s" %files)
for name in files:
fileName, fileSuffix = os.path.splitext(name)
if fileSuffix == '.png' or fileSuffix == '.jpg' or fileSuffix == '.jpeg':
toFullPath = toFilePath + root[len(fromFilePath):]
toFullName = toFullPath + '/' + name
if os.path.isdir(toFullPath):
pass
else:
os.mkdir(toFullPath)
compress_core(root + '/' + name, toFullName, width)
# break # 仅遍历当前目录

# 仅压缩指定文件
def compress_file(inputFile, width):
print ("compress_file-------------------------------------")
if not os.path.isfile(inputFile):
print ("这不是一个文件,请输入文件的正确路径!")
return
print ("file = %s" %inputFile)
dirname = os.path.dirname(inputFile)
basename = os.path.basename(inputFile)
fileName, fileSuffix = os.path.splitext(basename)
if fileSuffix == '.png' or fileSuffix == '.jpg' or fileSuffix == '.jpeg':
compress_core(inputFile, dirname+"/tiny_"+basename, width)
else:
print ("不支持该文件类型!")

# 判断是否是图片
def is_img(ext):
ext = ext.lower()
if ext in ['.jpg', '.png', '.jpeg', '.bmp']:
return True
else:
return False

# 获取指定目录下所有图片
def get_img_files(dir):
py_files = []
for root, dirs, files in os.walk(dir):
for file in files:
pre, suf = os.path.splitext(file)
if is_img(suf):
py_files.append(os.path.join(root, file))
return py_files

# 批处理替换
def batch_replace_img():
TargetFiles = get_img_files(TargetPath)
SourceFiles = get_img_files(SourcePath)

for target in TargetFiles:
for source in SourceFiles:
targetName = target.split('/')[-1]
sourceName = source.split('/')[-1]
if targetName == sourceName:
shutil.copyfile(source, target)

@click.command()
@click.option('-f', "--file", type=str, default=None, help="单个文件压缩")
@click.option('-d', "--dir", type=str, default=None, help="被压缩的文件夹")
@click.option('-w', "--width", type=int, default=-1, help="图片宽度,默认不变")
def run(file, dir, width):
print ("GcsSloop TinyPng V%s" %(version))
if file is not None:
compress_file(file, width) # 仅压缩一个文件
pass
elif dir is not None:
compress_path(dir, width) # 压缩指定目录下的文件
pass
else:
compress_path(os.getcwd(), width) # 压缩当前目录下的文件
print ("压缩结束!")

if __name__ == "__main__":
# run(None, TargetPath, None)
compress_path(TargetPath, -1)
batch_replace_img()
print("替换结束")

待续

but,因为我的工程目录下有1600多张的图片,而批量压缩脚本每月最多执行500张,而且我读取图片又不固定,所以没有办法一次性压缩。so,我还要想我这个能不能一步到位

有没有批量压缩不限数量的API
如果没有的话,怎么保证我多次执行这个脚本的连续性,即:我执行了一次之后,下一次换个key,怎么接着执行
。。。想想就脑壳疼
….to be continued