python 无损压缩照片,支持批量压缩,支持保留照片信息

由于云盘空间有限,照片尺寸也是很大,所以写个Python程序压缩一下照片,腾出一些云盘空间  

1、批量压缩照片

 新建 photo_compress.py 代码如下 

  1 # -*- coding: utf-8 -*-
  2 
  3 """脚本功能说明:使用 tinypng api,一键批量压缩指定文件(夹)所有文件"""
  4 
  5 import os
  6 import sys
  7 from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor  # 线程池,进程池
  8 import json
  9 import random
 10 import requests
 11 from you_get import common
 12 from shutil import copyfile
 13 
 14 
 15 def get_file_dir(file):
 16     """获取文件目录通用函数"""
 17     fullpath = os.path.abspath(os.path.realpath(file))
 18     return os.path.dirname(fullpath)
 19 
 20 
 21 def check_suffix(file_path):
 22     """检查指定文件的后缀是否符合要求"""
 23     file_path_lower = file_path.lower()
 24     return (file_path_lower.endswith('.png')
 25             or file_path_lower.endswith('.jpg')
 26             or file_path_lower.endswith('.jpeg'))
 27 
 28 
 29 def download_tinypng(input_file, url, output_file):
 30     file_name = os.path.basename(input_file)
 31     arr = file_name.split('.')
 32     new_file_name = arr[len(arr) - 2] + '_compress'
 33     new_output_file = os.path.join(os.path.dirname(output_file), arr[len(arr) - 2] + '_compress.' + arr[len(arr) - 1])
 34     print(u'开始下载文件 :%s' % new_output_file)
 35     # print(os.path.splitext(os.path.basename(output_file))[0])
 36     sys.argv = ['you-get', '-o', os.path.dirname(
 37         output_file), '-O', new_file_name, url]
 38     common.main()
 39     old_size = os.path.getsize(input_file)
 40     new_size = os.path.getsize(new_output_file)
 41     print(u'文件保存地址:%s' % new_output_file)
 42     print(u'压缩后文件大小:%d KB' % (new_size / 1024))
 43     print(u'压缩比: %d%%' % ((old_size - new_size) * 100 / old_size))
 44 
 45 
 46 def compress_by_tinypng(input_file):
 47     if not check_suffix(input_file):
 48         print(u'只支持png\\jpg\\jepg格式文件:' + input_file)
 49         return
 50 
 51     file_name = os.path.basename(input_file)
 52     arr = file_name.split('.')
 53     new_file_name = arr[len(arr) - 2] + '_compress.' + arr[len(arr) - 1]
 54     output_path = os.path.join(get_file_dir(input_file), 'compress_output')
 55     output_file = os.path.join(output_path, new_file_name)
 56     if not os.path.isdir(output_path):
 57         os.makedirs(output_path)
 58 
 59     if (os.path.exists(output_file)):
 60         print("已存在,跳过压缩")
 61         return
 62 
 63     try:
 64         old_size = os.path.getsize(input_file)
 65         print(u'压缩前文件名:%s文件大小:%d KB' % (input_file, old_size / 1024))
 66         if (old_size < 1024 * 1024):
 67             print("已跳过压缩,并直接拷贝文件")
 68             try:
 69                 copyfile(input_file, output_file)
 70             except IOError as e:
 71                 print("Unable to copy file. %s" % e)
 72             return
 73         print("开始压缩")
 74         shrink_image(input_file)
 75         print(u'文件压缩成功:%s' % input_file)
 76         # download_thread_pool.submit(download_tinypng, source, input_file, output_file)
 77     except Exception as e:
 78         print(u'报错了:%s' % e)
 79 
 80 
 81 def check_path(input_path):
 82     """如果输入的是文件则直接压缩,如果是文件夹则先遍历"""
 83     if os.path.isfile(input_path):
 84         compress_by_tinypng(input_path)
 85     elif os.path.isdir(input_path):
 86         dirlist = os.walk(input_path)
 87         for root, dirs, files in dirlist:
 88             if (not (root.endswith("\\compress_output") or root.endswith("/compress_output"))):
 89                 i = 0
 90                 for filename in files:
 91                     i = i + 1
 92                     process_pool.submit(compress_by_tinypng, os.path.join(
 93                         root, filename))
 94                     # compress_by_tinypng(os.path.join(root, filename))
 95     else:
 96         print(u'目标文件(夹)不存在,请确认后重试。')
 97 
 98 
 99 def list_images(path):
100     images = None
101     try:
102         if path:
103             os.chdir(path)
104         full_path = os.getcwd()
105         files = os.listdir(full_path)
106         images = []
107         for file in files:
108             ext = os.path.splitext(file)[1].lower()
109             if ext in ('.jpg', '.jpeg', '.png'):
110                 images.append(os.path.join(full_path, file))
111     except:
112         pass
113     return images
114 
115 
116 def shrink_image(file_path):
117     print(u'源文件地址:%s' % file_path)
118     result = shrink(file_path)
119     if result:
120         output_path = generate_output_path(file_path)
121         url = result['output']['url']
122         print(u'下载地址:%s' % url)
123         download_tinypng(file_path, url, output_path)
124         # download_thread_pool.submit(download_tinypng, file_path, url,  output_path)
125         # response = requests.get(url)
126         # with open(output_path, 'wb') as file:
127         #     file.write(response.content)
128         # print(u'文件保存地址:%s' % output_path)
129         # print('%s %d=>%d(%f)' % (
130         #     result['input']['type'],
131         #     result['input']['size'],
132         #     result['output']['size'],
133         #     result['output']['ratio']
134         #     ))
135     else:
136         print('压缩失败')
137 
138 
139 def shrink(file_path):
140     url = 'https://tinypng.com/web/shrink'
141     headers = {
142         'Cache-Control': 'no-cache',
143         'Content-Type': 'application/x-www-form-urlencoded',
144         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44',
145         'X-Forwarded-For': get_random_ip()
146     }
147     result = None
148     try:
149         file = open(file_path, 'rb')
150         response = requests.post(url, headers=headers, data=file)
151         result = json.loads(response.text)
152     except Exception as e:
153         print(u'报错了:%s' % e)
154         if file:
155             file.close()
156     if result and result['input'] and result['output']:
157         return result
158     else:
159         return None
160 
161 
162 def generate_output_path(file_path):
163     parent_path = os.path.abspath(os.path.dirname(file_path))
164     output_path = os.path.join(parent_path, 'compress_output')
165     if not os.path.isdir(output_path):
166         os.mkdir(output_path)
167     return os.path.join(output_path, os.path.basename(file_path))
168 
169 
170 def get_random_ip():
171     ip = []
172     for i in range(4):
173         ip.append(str(random.randint(0 if i in (2, 3) else 1, 254)))
174     return '.'.join(ip)
175 
176 
177 if __name__ == '__main__':
178     thread_pool = ThreadPoolExecutor(5)  # 定义5个线程执行此任务
179     download_thread_pool = ThreadPoolExecutor(10)  # 定义5个线程执行此任务
180     process_pool = ProcessPoolExecutor(8)  # 定义5个进程
181     len_param = len(sys.argv)
182     if len_param != 2 and len_param != 3:
183         print('请使用: %s [filepath]' % os.path.basename(sys.argv[0]))
184     else:
185         check_path(sys.argv[1])
186         input("Press <enter> 请耐心等待\n")

 

 

 执行python .\photo_compress.py F:\\test

 生成compress_output文件夹,里面就是压缩的文件,但此时的照片没有,拍摄时的时间、位置的信息,所以下面要复制文件信息

 若要压缩的文件不全,可以再执行一次压缩(会自动过滤已压缩的照片)

2、批量拷贝照片信息

  使用pyexiv2进行文件信息拷贝

pip install pyexiv2 -i https://pypi.tuna.tsinghua.edu.cn/simple

  新建 copy_fileinfo.py 代码如下

  1 # -*- coding: utf-8 -*-
  2 
  3 """脚本功能说明:使用 pyexiv2 api,一键批量拷贝指定文件(夹)所有文件信息"""
  4 
  5 import os
  6 import sys
  7 from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor  # 线程池,进程池
  8 from pyexiv2 import Image
  9 
 10 
 11 def get_file_dir(file):
 12     """获取文件目录通用函数"""
 13     fullpath = os.path.abspath(os.path.realpath(file))
 14     return os.path.dirname(fullpath)
 15 
 16 
 17 def check_suffix(file_path):
 18     """检查指定文件的后缀是否符合要求"""
 19     file_path_lower = file_path.lower()
 20     return (file_path_lower.endswith('.png')
 21             or file_path_lower.endswith('.jpg')
 22             or file_path_lower.endswith('.jpeg'))
 23 
 24 
 25 def copyinfo_by_pyexiv2(input_file):
 26     file_name = os.path.basename(input_file)
 27     arr = file_name.split('.')
 28     new_file_name = arr[len(arr) - 2] + '_compress.' + arr[len(arr) - 1]
 29     output_path = os.path.join(get_file_dir(input_file), 'compress_output')
 30     output_file = os.path.join(output_path, new_file_name)
 31     if not (check_suffix(input_file) or check_suffix(output_file)):
 32         print(u'只支持png\\jpg\\jepg格式文件:' + input_file)
 33         return
 34     if not (os.path.exists(output_file)):
 35         print(u'文件不存在:' + output_file)
 36         return
 37     old_size = os.path.getsize(input_file)
 38     if (old_size < 1024 * 1024):
 39         print(u"已跳过拷贝文件信息:", input_file)
 40         return
 41 
 42     # if not os.path.isdir(output_path):
 43     #     os.makedirs(output_path)
 44     try:
 45         i = Image(input_file)    # 源图片路径
 46     except Exception:
 47         i = Image(input_file, "GB18030")
 48 
 49     try:
 50         _exif_info = i.read_exif()
 51     except Exception:
 52         _exif_info = i.read_exif("GB18030")
 53 
 54     # print(_exif_info)
 55     # _iptc_info = i.read_iptc()
 56     # print(_iptc_info)
 57     # _xmp_info = i.read_xmp()
 58     # print(_xmp_info)
 59     i.close()
 60 
 61     try:
 62         i2 = Image(output_file)  # 拷贝信息图片路径
 63     except Exception:
 64         i2 = Image(output_file, "GB18030")
 65 
 66     try:
 67         _exif_info2 = i2.read_exif()
 68     except Exception:
 69         _exif_info2 = i2.read_exif("GB18030")
 70 
 71     # 方向不拷贝,防止图片旋转
 72     for item in _exif_info:
 73         if("Exif.Image.Orientation" != item):
 74             if (_exif_info2.get(item) != _exif_info.get(item)):
 75                 try:
 76                     i2.modify_exif({item: _exif_info[item]})
 77                 except Exception as e:
 78                     print(e)
 79                     try:
 80                         i2.modify_exif({item: _exif_info[item]}, "GB18030")
 81                     except Exception as e:
 82                         print(e)
 83 
 84     i2.close()
 85 
 86     print(u"拷贝信息完成:" + input_file)
 87 
 88 
 89 def check_path(input_path):
 90     """如果输入的是文件则直接压缩,如果是文件夹则先遍历"""
 91     if os.path.isfile(input_path):
 92         copyinfo_by_pyexiv2(input_path)
 93     elif os.path.isdir(input_path):
 94         dirlist = os.walk(input_path)
 95         for root, dirs, files in dirlist:
 96             if (not (root.endswith("\\compress_output") or root.endswith("/compress_output"))):
 97                 i = 0
 98                 for filename in files:
 99                     i = i + 1
100                     process_pool.submit(copyinfo_by_pyexiv2, os.path.join(
101                         root, filename))
102     else:
103         print(u'目标文件(夹)不存在,请确认后重试。')
104 
105 
106 if __name__ == '__main__':
107     # thread_pool = ThreadPoolExecutor(10)  # 定义5个线程执行此任务
108     process_pool = ProcessPoolExecutor(8)  # 定义5个进程
109     len_param = len(sys.argv)
110     if len_param != 2:
111         print('请使用: %s [filepath]' % os.path.basename(sys.argv[0]))
112     else:
113         check_path(sys.argv[1])
114         input("Press <enter> 请耐心等待\n")

 

 执行python .\copy_fileinfo.py F:\\test

 大功告成!图片压缩完毕,信息还没有丢失

posted @ 2020-09-05 10:03  xiaocaidev  阅读(1145)  评论(0编辑  收藏  举报