愚蠢的地球人

用Python读取图片的Exif时间给相册图片批量重命名

现代的智能手机拍出来的照片文件默认的命名方式是类似:“IMG_20191201_150800.jpg”这样的结构,但是我发现我手机里好多老照片没有遵循这一命名规则。

不仅前缀五花八门,而且很多老的手机默认不是以日期时间而是以一个递增的序号来命名。强迫症的我无法接受这些乱七八糟的命名,于是我写了一个Python程序来批量对这些图片来重命名。

读取一个图片文件的修改时间有两种方式,一是直接读取文件本身的修改时间,二是读取图像Exif信息中的时间。

第一种方式读取方法最简单,但是有一个很严重的问题:文件的修改时间并没有储存在文件本身,而是储存在磁盘文件系统的文件表里面,当你将一个文件从一个文件系统复制到另一个文件系统的时候,原来的时间戳会丢失:比如说当你把一个图片文件从手机拷贝到电脑或者当你把照片从电脑拷贝到手机的时候,文件的修改时间就会变成文件复制的时间。

所以要对图片文件按时间重命名只能读取Exif里面的信息,因为Exif信息是储存在文件内部的,不会因为拷贝或者通过网络传输而发生变化。


Python读取图片的Exif信息可以使用PIL这个库,读取方法很简单:

def get_exif_time(file):
    img = Image.open(file)
    date_time = ''
    try:
        exif = {
            ExifTags.TAGS[k]: v
            for k, v in img._getexif().items()
            if k in ExifTags.TAGS
        }
        date_time = exif['DateTime']
    except Exception as err:
        print(err)
        print(traceback.format_exc())
    date_time = date_time.replace(':''').replace(' ''_').strip()
    return date_time


好了,还是使用上一篇文章中的遍历方法:

def rename_jpg(filedate_time):
    new_name = 'IMG_' + date_time + '.jpg'
    new_name = os.path.join(WORK_DIR, new_name)
    i = 1
    while os.path.exists(new_name):
        if file.lower() == new_name.lower():
            return 'Not Renamed'
        new_name = 'IMG_' + date_time + '_' + str(i) + '.jpg'
        new_name = os.path.join(WORK_DIR, new_name)
        i += 1
    try:
        os.rename(file, new_name)
    except Exception as err:
        print(err)
        print(traceback.format_exc())
        return 'ERROR'
    return os.path.split(new_name)[1]


def rename_jpgs():
    filecount = 0
    filetotal = len(glob.glob(JPG_FILTER))
    for file in glob.glob(JPG_FILTER):
        filecount += 1
        date_time = get_exif_time(file)
        if date_time == '':
            print(os.path.split(file)[1], 'No Exif Date''(%d/%d)' % (filecount, filetotal))
        else:
            new_name = rename_jpg(file, date_time)

            print(os.path.split(file)[1], '-->', new_name, '(%d/%d)' % (filecount, filetotal))


重命名比压缩快多了,一杯咖啡没喝完,程序就跑完了。当我点开图片文件夹的时候,我差点把一口咖啡喷到键盘上。

目录里除了有一些无法读取exif信息的图片没有重命名之外,还有大量的图片变成了如下的样子:

IMG_20021208_120000_1.jpg

IMG_20021208_120000_2.jpg

IMG_20021208_120000_3.jpg

IMG_20021208_120000_4.jpg

IMG_20021208_120000_5.jpg

……

……


打开图片一看,WTF! 这些图片的exif信息里的DateTime全都一样:“2002:12:08 12:00:00”

想必这是当年这个手机相机软件的一个BUG,怎么办?


我还得再写一个修复Exif时间的程序!

其实Exif信息里记录时间的Tag不止DateTime这一个,除了我们刚才读取的那个DateTime标签之外还有三个Tag也记录了图片的拍摄时间信息,分别是图像的拍摄时间(DateTimeOriginal)、数字化时间(DateTimeDigitized)和GPS时间。大多数情况下拍摄时间和数字化时间是一样的,这两个选择一个就可以,除此之外,有些文件名也能体现图片的拍摄时间。

我先依次读取图片的“修改时间”、“GPS时间”、“拍摄时间”、“文件名时间”这四个时间,然后用BAD_TIME列表和正则表达式来对读取的时间做校验,如果有效则采用,如果无效则尝试下一个。

思路有了,但是在写代码的时候突然发现PIL库居然不提供编辑EXIF的功能。晕!读都读出来了,为什么不加一个编辑的功能呢?

只好再次求助于度娘,终于找到一个能编辑exif信息的库:piexif。

pip install piexif

……


修复Exif时间的代码如下fixExifTime.py:

import datetime
import glob
import os
import re
import traceback
import piexif



WORK_DIR = 'D:\\手机相册\\Sony Z1'
JPG_FILTER = os.path.join(WORK_DIR, '*.[jJ][pP][gG]')



def fix_exif_time(file): 
    BAD_TIME = ['2002:12:08 12:00:00']
    pattern = r'\d{4}:\d{2}:\d{2}\s\d{2}:\d{2}:\d{2}'
    exif_dict = {}
    try:
        exif_dict = piexif.load(file)
        if 41729 in exif_dict['Exif']:
            exif_dict['Exif'][41729= b'1'
    except Exception as err:
        print(err)
        print(traceback.format_exc())
        return 'LOAD ERROR'
                
    date_time = ''
    if '0th' in exif_dict:
        if 306 in exif_dict['0th']:
            date_time = exif_dict['0th'][306].decode()
            print('Read From 0th', date_time)
            if re.search(pattern, date_time) and date_time not in BAD_TIME:
                return 'Unmodified'
        else:
            exif_dict['0th'][306= b''
    else:
        exif_dict['0th'= {}
        exif_dict['0th'][306= b''

    if re.search(pattern, date_time) is None or date_time in BAD_TIME:
        if 'GPS' in exif_dict and 29 in exif_dict['GPS'and 7 in exif_dict['GPS']:
            gps_h = exif_dict['GPS'][7][0][0]
            gps_m = exif_dict['GPS'][7][1][0]
            gps_s = int(exif_dict['GPS'][7][2][0]/exif_dict['GPS'][7][2][1])
            if 0 <= gps_h <= 24 and 0<= gps_m <= 60 and 0 <= gps_s <= 60:
                gps_time = str(str(gps_h) + ':' + str(gps_m) + ':' + str(gps_s))
            else:
                gps_time = '00:00:00'
            date_time = exif_dict['GPS'][29].decode() + ' ' + gps_time
            utc_date = datetime.datetime.strptime(date_time, "%Y:%m:%d %H:%M:%S")
            local_date = utc_date + datetime.timedelta(hours=8)
            date_time = datetime.datetime.strftime(local_date, '%Y:%m:%d %H:%M:%S')
            print('Read From GPS', date_time)

    if re.search(pattern, date_time) is None or date_time in BAD_TIME:
        if 'Exif' in exif_dict and 36867 in exif_dict["Exif"]:
            date_time = exif_dict["Exif"][36867].decode()
            print('Read From Exif', date_time)
            
    if re.search(pattern, date_time) is None or date_time in BAD_TIME:
        pattern2 = r'(\d{4})[\-\:\s\_\/]?(1[0-2]|0[1-9])[\-\:\s\_\/]?([1-2]\d|3[0-1]|0[1-9])'
        pattern2 += r'[\-\:\s\_\/]?([0-1]\d|2[0-4])[\-\:\s\_\/]?([0-5]\d)[\-\:\s\_\/]?([0-5]\d)'
        find = re.search(pattern2, os.path.split(file)[1])
        if find:
            date_time = '%s:%s:%s %s:%s:%s' % find.groups()
            print('Read From FileName', date_time)

    if re.search(pattern, date_time) and date_time not in BAD_TIME:
        date_time = bytes(date_time, encoding="ascii")
        exif_dict["Exif"][36867= date_time
        exif_dict["Exif"][36868= date_time
        exif_dict["0th"][306= date_time
        try:
            exif_bytes = piexif.dump(exif_dict)
            piexif.insert(exif_bytes, file)
        except Exception as err:
            print(os.path.split(file)[1], 'Save ERROR!', err)
            print(traceback.format_exc())
            if str(err) == "Given data isn't JPEG.":
                print('Del thumbnail')
                del exif_dict['thumbnail']
                exif_bytes = piexif.dump(exif_dict)
                piexif.insert(exif_bytes, file)
    
    return str(os.path.getsize(file))
    

def fix_exifs():
    filecount = 0
    filetotal = len(glob.glob(JPG_FILTER))
    for file in glob.glob(JPG_FILTER):
        filesize = os.path.getsize(file)
        filecount += 1
        new_size = fix_exif_time(file)

        print(os.path.split(file)[1], filesize, '-->', new_size, '(%d/%d)' % (filecount, filetotal))


if __name__ == '__main__':
    fix_exifs()


再跑一遍,OK了!


安卓手机Pydroid 3也能完美运行


强迫症患者终于得到了救赎。


最后附上GitHub的链接:

https://github.com/laukeng/jpeg

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

相关推荐

友情链接

网站分类

最新留言

最近发表