python 无损批量压缩图片(支持保留图片信息)的示例


Posted in Python onSeptember 22, 2020

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

1、批量压缩照片

新建 photo_compress.py 代码如下

# -*- coding: utf-8 -*-

"""脚本功能说明:使用 tinypng api,一键批量压缩指定文件(夹)所有文件"""

import os
import sys
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor # 线程池,进程池
import json
import random
import requests
from you_get import common
from shutil import copyfile


def get_file_dir(file):
 """获取文件目录通用函数"""
 fullpath = os.path.abspath(os.path.realpath(file))
 return os.path.dirname(fullpath)


def check_suffix(file_path):
 """检查指定文件的后缀是否符合要求"""
 file_path_lower = file_path.lower()
 return (file_path_lower.endswith('.png')
   or file_path_lower.endswith('.jpg')
   or file_path_lower.endswith('.jpeg'))


def download_tinypng(input_file, url, output_file):
 file_name = os.path.basename(input_file)
 arr = file_name.split('.')
 new_file_name = arr[len(arr) - 2] + '_compress'
 new_output_file = os.path.join(os.path.dirname(output_file), arr[len(arr) - 2] + '_compress.' + arr[len(arr) - 1])
 print(u'开始下载文件 :%s' % new_output_file)
 # print(os.path.splitext(os.path.basename(output_file))[0])
 sys.argv = ['you-get', '-o', os.path.dirname(
  output_file), '-O', new_file_name, url]
 common.main()
 old_size = os.path.getsize(input_file)
 new_size = os.path.getsize(new_output_file)
 print(u'文件保存地址:%s' % new_output_file)
 print(u'压缩后文件大小:%d KB' % (new_size / 1024))
 print(u'压缩比: %d%%' % ((old_size - new_size) * 100 / old_size))


def compress_by_tinypng(input_file):
 if not check_suffix(input_file):
  print(u'只支持png\\jpg\\jepg格式文件:' + input_file)
  return

 file_name = os.path.basename(input_file)
 arr = file_name.split('.')
 new_file_name = arr[len(arr) - 2] + '_compress.' + arr[len(arr) - 1]
 output_path = os.path.join(get_file_dir(input_file), 'compress_output')
 output_file = os.path.join(output_path, new_file_name)
 if not os.path.isdir(output_path):
  os.makedirs(output_path)

 if (os.path.exists(output_file)):
  print("已存在,跳过压缩")
  return

 try:
  old_size = os.path.getsize(input_file)
  print(u'压缩前文件名:%s文件大小:%d KB' % (input_file, old_size / 1024))
  if (old_size < 1024 * 1024):
   print("已跳过压缩,并直接拷贝文件")
   try:
    copyfile(input_file, output_file)
   except IOError as e:
    print("Unable to copy file. %s" % e)
   return
  print("开始压缩")
  shrink_image(input_file)
  print(u'文件压缩成功:%s' % input_file)
  # download_thread_pool.submit(download_tinypng, source, input_file, output_file)
 except Exception as e:
  print(u'报错了:%s' % e)


def check_path(input_path):
 """如果输入的是文件则直接压缩,如果是文件夹则先遍历"""
 if os.path.isfile(input_path):
  compress_by_tinypng(input_path)
 elif os.path.isdir(input_path):
  dirlist = os.walk(input_path)
  for root, dirs, files in dirlist:
   if (not (root.endswith("\\compress_output") or root.endswith("/compress_output"))):
    i = 0
    for filename in files:
     i = i + 1
     process_pool.submit(compress_by_tinypng, os.path.join(
      root, filename))
     # compress_by_tinypng(os.path.join(root, filename))
 else:
  print(u'目标文件(夹)不存在,请确认后重试。')


def list_images(path):
 images = None
 try:
  if path:
   os.chdir(path)
  full_path = os.getcwd()
  files = os.listdir(full_path)
  images = []
  for file in files:
   ext = os.path.splitext(file)[1].lower()
   if ext in ('.jpg', '.jpeg', '.png'):
    images.append(os.path.join(full_path, file))
 except:
  pass
 return images


def shrink_image(file_path):
 print(u'源文件地址:%s' % file_path)
 result = shrink(file_path)
 if result:
  output_path = generate_output_path(file_path)
  url = result['output']['url']
  print(u'下载地址:%s' % url)
  download_tinypng(file_path, url, output_path)
  # download_thread_pool.submit(download_tinypng, file_path, url, output_path)
  # response = requests.get(url)
  # with open(output_path, 'wb') as file:
  #  file.write(response.content)
  # print(u'文件保存地址:%s' % output_path)
  # print('%s %d=>%d(%f)' % (
  #  result['input']['type'],
  #  result['input']['size'],
  #  result['output']['size'],
  #  result['output']['ratio']
  #  ))
 else:
  print('压缩失败')


def shrink(file_path):
 url = 'https://tinypng.com/web/shrink'
 headers = {
  'Cache-Control': 'no-cache',
  'Content-Type': 'application/x-www-form-urlencoded',
  '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',
  'X-Forwarded-For': get_random_ip()
 }
 result = None
 try:
  file = open(file_path, 'rb')
  response = requests.post(url, headers=headers, data=file)
  result = json.loads(response.text)
 except Exception as e:
  print(u'报错了:%s' % e)
  if file:
   file.close()
 if result and result['input'] and result['output']:
  return result
 else:
  return None


def generate_output_path(file_path):
 parent_path = os.path.abspath(os.path.dirname(file_path))
 output_path = os.path.join(parent_path, 'compress_output')
 if not os.path.isdir(output_path):
  os.mkdir(output_path)
 return os.path.join(output_path, os.path.basename(file_path))


def get_random_ip():
 ip = []
 for i in range(4):
  ip.append(str(random.randint(0 if i in (2, 3) else 1, 254)))
 return '.'.join(ip)


if __name__ == '__main__':
 thread_pool = ThreadPoolExecutor(5) # 定义5个线程执行此任务
 download_thread_pool = ThreadPoolExecutor(10) # 定义5个线程执行此任务
 process_pool = ProcessPoolExecutor(8) # 定义5个进程
 len_param = len(sys.argv)
 if len_param != 2 and len_param != 3:
  print('请使用: %s [filepath]' % os.path.basename(sys.argv[0]))
 else:
  check_path(sys.argv[1])
  input("Press <enter> 请耐心等待\n")

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

python 无损批量压缩图片(支持保留图片信息)的示例

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

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

2、批量拷贝照片信息

使用pyexiv2进行文件信息拷贝

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

新建 copy_fileinfo.py 代码如下

# -*- coding: utf-8 -*-

"""脚本功能说明:使用 pyexiv2 api,一键批量拷贝指定文件(夹)所有文件信息"""

import os
import sys
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor # 线程池,进程池
from pyexiv2 import Image


def get_file_dir(file):
 """获取文件目录通用函数"""
 fullpath = os.path.abspath(os.path.realpath(file))
 return os.path.dirname(fullpath)


def check_suffix(file_path):
 """检查指定文件的后缀是否符合要求"""
 file_path_lower = file_path.lower()
 return (file_path_lower.endswith('.png')
   or file_path_lower.endswith('.jpg')
   or file_path_lower.endswith('.jpeg'))


def copyinfo_by_pyexiv2(input_file):
 file_name = os.path.basename(input_file)
 arr = file_name.split('.')
 new_file_name = arr[len(arr) - 2] + '_compress.' + arr[len(arr) - 1]
 output_path = os.path.join(get_file_dir(input_file), 'compress_output')
 output_file = os.path.join(output_path, new_file_name)
 if not (check_suffix(input_file) or check_suffix(output_file)):
  print(u'只支持png\\jpg\\jepg格式文件:' + input_file)
  return
 if not (os.path.exists(output_file)):
  print(u'文件不存在:' + output_file)
  return
 old_size = os.path.getsize(input_file)
 if (old_size < 1024 * 1024):
  print(u"已跳过拷贝文件信息:", input_file)
  return

 # if not os.path.isdir(output_path):
 #  os.makedirs(output_path)
 try:
  i = Image(input_file) # 源图片路径
 except Exception:
  i = Image(input_file, "GB18030")

 try:
  _exif_info = i.read_exif()
 except Exception:
  _exif_info = i.read_exif("GB18030")

 # print(_exif_info)
 # _iptc_info = i.read_iptc()
 # print(_iptc_info)
 # _xmp_info = i.read_xmp()
 # print(_xmp_info)
 i.close()

 try:
  i2 = Image(output_file) # 拷贝信息图片路径
 except Exception:
  i2 = Image(output_file, "GB18030")

 try:
  _exif_info2 = i2.read_exif()
 except Exception:
  _exif_info2 = i2.read_exif("GB18030")

 # 方向不拷贝,防止图片旋转
 for item in _exif_info:
  if("Exif.Image.Orientation" != item):
   if (_exif_info2.get(item) != _exif_info.get(item)):
    try:
     i2.modify_exif({item: _exif_info[item]})
    except Exception as e:
     print(e)
     try:
      i2.modify_exif({item: _exif_info[item]}, "GB18030")
     except Exception as e:
      print(e)

 i2.close()

 print(u"拷贝信息完成:" + input_file)


def check_path(input_path):
 """如果输入的是文件则直接压缩,如果是文件夹则先遍历"""
 if os.path.isfile(input_path):
  copyinfo_by_pyexiv2(input_path)
 elif os.path.isdir(input_path):
  dirlist = os.walk(input_path)
  for root, dirs, files in dirlist:
   if (not (root.endswith("\\compress_output") or root.endswith("/compress_output"))):
    i = 0
    for filename in files:
     i = i + 1
     process_pool.submit(copyinfo_by_pyexiv2, os.path.join(
      root, filename))
 else:
  print(u'目标文件(夹)不存在,请确认后重试。')


if __name__ == '__main__':
 # thread_pool = ThreadPoolExecutor(10) # 定义5个线程执行此任务
 process_pool = ProcessPoolExecutor(8) # 定义5个进程
 len_param = len(sys.argv)
 if len_param != 2:
  print('请使用: %s [filepath]' % os.path.basename(sys.argv[0]))
 else:
  check_path(sys.argv[1])
  input("Press <enter> 请耐心等待\n")

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

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

以上就是python 无损批量压缩图片(支持保留图片信息)的示例的详细内容,更多关于python 无损批量压缩图片的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
精确查找PHP WEBSHELL木马的方法(1)
Apr 12 Python
Python深入学习之闭包
Aug 31 Python
python中随机函数random用法实例
Apr 30 Python
python用装饰器自动注册Tornado路由详解
Feb 14 Python
python实现旋转和水平翻转的方法
Oct 25 Python
使用50行Python代码从零开始实现一个AI平衡小游戏
Nov 21 Python
python实现在遍历列表时,直接对dict元素增加字段的方法
Jan 15 Python
利用python实现对web服务器的目录探测的方法
Feb 26 Python
浅谈python已知元素,获取元素索引(numpy,pandas)
Nov 26 Python
Python列表操作方法详解
Feb 09 Python
python实现word文档批量转成自定义格式的excel文档的思路及实例代码
Feb 21 Python
python3爬虫中多线程进行解锁操作实例
Nov 25 Python
Pytho爬虫中Requests设置请求头Headers的方法
Sep 22 #Python
python 线程的五个状态
Sep 22 #Python
python 如何实现遗传算法
Sep 22 #Python
利用python汇总统计多张Excel
Sep 22 #Python
爬虫代理的cookie如何生成运行
Sep 22 #Python
python 如何将office文件转换为PDF
Sep 22 #Python
Python制作一个仿QQ办公版的图形登录界面
Sep 22 #Python
You might like
暴雪前总裁遗憾:没尽早追赶Dota 取消星际争霸幽灵
2020/03/08 星际争霸
第十二节 类的自动加载 [12]
2006/10/09 PHP
PHP仿盗链代码
2012/06/03 PHP
PHP检查网站是否宕机的方法示例
2017/07/24 PHP
根据地区不同显示时间的javascript代码
2007/08/13 Javascript
javascript YUI 读码日记之 YAHOO.util.Dom - Part.4
2008/03/22 Javascript
extJs 文本框后面加上说明文字+下拉列表选中值后触发事件
2009/11/27 Javascript
javascript中负数算术右移、逻辑右移的奥秘探索
2013/10/17 Javascript
node.js中的fs.statSync方法使用说明
2014/12/16 Javascript
JS组件Bootstrap实现图片轮播效果
2016/05/16 Javascript
微信小程序 页面传参实例详解
2016/11/16 Javascript
基于JavaScript实现Tab选项卡切换效果
2016/11/24 Javascript
如何解决vue与传统jquery插件冲突
2017/03/20 Javascript
详解nuxt sass全局变量(公共scss解决方案)
2018/06/27 Javascript
json字符串传到前台input的方法
2018/08/06 Javascript
解决layer弹出层msg的文字不显示的问题
2019/09/11 Javascript
countup.js实现数字动态叠加效果
2019/10/17 Javascript
JS三级联动代码格式实例详解
2019/12/30 Javascript
如何区分vue中的v-show 与 v-if
2020/09/08 Javascript
vue中使用腾讯云Im的示例
2020/10/23 Javascript
[03:26]回顾2015国际邀请赛中国区预选赛
2015/06/09 DOTA
python解析json实例方法
2013/11/19 Python
python遍历文件夹并删除特定格式文件的示例
2014/03/05 Python
Python的Django框架中settings文件的部署建议
2015/05/30 Python
Python爬虫使用脚本登录Github并查看信息
2018/07/16 Python
pandas中的数据去重处理的实现方法
2020/02/10 Python
python with语句的原理与用法详解
2020/03/30 Python
pip install 使用国内镜像的方法示例
2020/04/03 Python
Python SQLAlchemy库的使用方法
2020/10/13 Python
html如何对span设置宽度
2019/10/30 HTML / CSS
解决方案设计综合面试题
2015/08/31 面试题
技校学生个人职业生涯规划范文
2014/03/03 职场文书
心理咨询专业自荐信
2014/07/07 职场文书
2015年反洗钱工作总结
2015/04/25 职场文书
证劵公司反洗钱宣传活动总结
2015/05/08 职场文书
Python可视化学习之seaborn绘制矩阵图详解
2022/02/24 Python