现代的智能手机拍出来的照片文件默认的命名方式是类似:“IMG_20191201_150800.jpg”这样的结构,但是我发现我手机里好多老照片没有遵循这一命名规则。
不仅前缀五花八门,而且很多老的手机默认不是以日期时间而是以一个递增的序号来命名。强迫症的我无法接受这些乱七八糟的命名,于是我写了一个Python程序来批量对这些图片来重命名。
读取一个图片文件的修改时间有两种方式,一是直接读取文件本身的修改时间,二是读取图像Exif信息中的时间。
第一种方式读取方法最简单,但是有一个很严重的问题:文件的修改时间并没有储存在文件本身,而是储存在磁盘文件系统的文件表里面,当你将一个文件从一个文件系统复制到另一个文件系统的时候,原来的时间戳会丢失:比如说当你把一个图片文件从手机拷贝到电脑或者当你把照片从电脑拷贝到手机的时候,文件的修改时间就会变成文件复制的时间。
所以要对图片文件按时间重命名只能读取Exif里面的信息,因为Exif信息是储存在文件内部的,不会因为拷贝或者通过网络传输而发生变化。
Python读取图片的Exif信息可以使用PIL这个库,读取方法很简单:
defget_exif_time(file):
img=Image.open(file)
date_time=''
try:
exif={
ExifTags.TAGS[k]: v
fork, vinimg._getexif().items()
ifkinExifTags.TAGS
}
date_time=exif['DateTime']
exceptExceptionaserr:
print(err)
print(traceback.format_exc())
date_time=date_time.replace(':','').replace(' ','_').strip()
returndate_time
好了,还是使用上一篇文章中的遍历方法:
defrename_jpg(file,date_time):
new_name='IMG_'+date_time+'.jpg'
new_name=os.path.join(WORK_DIR, new_name)
i=1
whileos.path.exists(new_name):
iffile.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)
exceptExceptionaserr:
print(err)
print(traceback.format_exc())
return'ERROR'
returnos.path.split(new_name)[1]
defrename_jpgs():
filecount=0
filetotal=len(glob.glob(JPG_FILTER))
forfileinglob.glob(JPG_FILTER):
filecount+=1
date_time=get_exif_time(file)
ifdate_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:
importdatetime
importglob
importos
importre
importtraceback
importpiexif
WORK_DIR='D:\\手机相册\\Sony Z1'
JPG_FILTER=os.path.join(WORK_DIR,'*.[jJ][pP][gG]')
deffix_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)
if41729inexif_dict['Exif']:
exif_dict['Exif'][41729]=b'1'
exceptExceptionaserr:
print(err)
print(traceback.format_exc())
return'LOAD ERROR'
date_time=''
if'0th'inexif_dict:
if306inexif_dict['0th']:
date_time=exif_dict['0th'][306].decode()
print('Read From 0th', date_time)
ifre.search(pattern, date_time)anddate_timenotinBAD_TIME:
return'Unmodified'
else:
exif_dict['0th'][306]=b''
else:
exif_dict['0th']={}
exif_dict['0th'][306]=b''
ifre.search(pattern, date_time)isNoneordate_timeinBAD_TIME:
if'GPS'inexif_dictand29inexif_dict['GPS']and7inexif_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])
if0<=gps_h<=24and0<=gps_m<=60and0<=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)
ifre.search(pattern, date_time)isNoneordate_timeinBAD_TIME:
if'Exif'inexif_dictand36867inexif_dict["Exif"]:
date_time=exif_dict["Exif"][36867].decode()
print('Read From Exif', date_time)
ifre.search(pattern, date_time)isNoneordate_timeinBAD_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])
iffind:
date_time='%s:%s:%s%s:%s:%s'%find.groups()
print('Read From FileName', date_time)
ifre.search(pattern, date_time)anddate_timenotinBAD_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)
exceptExceptionaserr:
print(os.path.split(file)[1],'Save ERROR!', err)
print(traceback.format_exc())
ifstr(err)=="Given data isn't JPEG.":
print('Del thumbnail')
delexif_dict['thumbnail']
exif_bytes=piexif.dump(exif_dict)
piexif.insert(exif_bytes,file)
returnstr(os.path.getsize(file))
deffix_exifs():
filecount=0
filetotal=len(glob.glob(JPG_FILTER))
forfileinglob.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的链接: