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 相关文章推荐
零基础写python爬虫之HTTP异常处理
Nov 05 Python
Python文件操作基本流程代码实例
Dec 11 Python
python实现word 2007文档转换为pdf文件
Mar 15 Python
解读python logging模块的使用方法
Apr 17 Python
完美解决在oj中Python的循环输入问题
Jun 25 Python
python 实现视频流下载保存MP4的方法
Jan 09 Python
python实现维吉尼亚算法
Mar 20 Python
如何使用python代码操作git代码
Feb 29 Python
python 使用cx-freeze打包程序的实现
Mar 14 Python
Python系统公网私网流量监控实现流程
Nov 23 Python
Pytorch模型迁移和迁移学习,导入部分模型参数的操作
Mar 03 Python
Python实现生活常识解答机器人
Jun 28 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
php中将图片gif,jpg或mysql longblob或blob字段值转换成16进制字符串
2011/08/23 PHP
解析isset与is_null的区别
2013/08/09 PHP
PHP实现根据时间戳获取周几的方法
2016/02/26 PHP
分享5个非常有用的Laravel Blade指令
2018/05/30 PHP
使用jQuery.Validate进行客户端验证(初级篇) 不使用微软验证控件的理由
2010/06/28 Javascript
JavaScript和ActionScript的交互实现代码
2010/08/01 Javascript
基于jQuery的合并表格中相同文本的相邻单元格的代码
2011/04/06 Javascript
js操作css属性实现div层展开关闭效果的方法
2015/05/11 Javascript
jquery简单实现外部链接用新窗口打开的方法
2015/05/30 Javascript
AngularJs中route的使用方法和配置
2016/02/04 Javascript
关于动态执行代码(js的Eval)实例详解
2016/08/15 Javascript
js判断请求的url是否可访问,支持跨域判断的实现方法
2016/09/17 Javascript
js实现漫天星星效果
2017/01/19 Javascript
Node.js操作redis实现添加查询功能
2017/05/25 Javascript
详解webpack + react + react-router 如何实现懒加载
2017/11/20 Javascript
浅谈Webpack打包优化技巧
2018/06/12 Javascript
vue项目中引入vue-datepicker插件的详解
2019/05/14 Javascript
js中火星坐标、百度坐标、WGS84坐标转换实现方法示例
2020/03/02 Javascript
antd-mobile ListView长列表的数据更新遇到的坑
2020/04/08 Javascript
Node.js 中判断一个文件是否存在
2020/08/24 Javascript
python 线程的暂停, 恢复, 退出详解及实例
2016/12/06 Python
Python编程实现输入某年某月某日计算出这一天是该年第几天的方法
2017/04/18 Python
Python分析学校四六级过关情况
2017/11/22 Python
20行python代码的入门级小游戏的详解
2019/05/05 Python
PyQt5 加载图片和文本文件的实例
2019/06/14 Python
win10安装tesserocr配置 Python使用tesserocr识别字母数字验证码
2020/01/16 Python
执行Python程序时模块报错问题
2020/03/26 Python
Currentbody西班牙:美容仪专家
2019/09/28 全球购物
《一件运动衫》教学反思
2014/02/19 职场文书
教育技术学专业职业规划书
2014/03/03 职场文书
建党伟业电影观后感
2015/06/01 职场文书
宝葫芦的秘密观后感
2015/06/11 职场文书
病假证明模板
2015/06/19 职场文书
2016年三八节红领巾广播稿
2015/12/17 职场文书
导游词之永泰公主墓
2019/12/04 职场文书
vue+elementui 实现新增和修改共用一个弹框的完整代码
2021/06/08 Vue.js