Renaming files based on EXIF info

Digital cameras generally name their files DSC00001.JPG or something silly like that. I prefer that have them named according to the shooting data first, and possibly some additional information thereafter so that they sort by time taken. Fortunately the EXIF information contains the shooting data, so it is just a matter of extracting it from the file and renaming it. There is a slight complication: some cameras can take more than one shot per second, but they typically only provide the EXIF information with second-granularity, so we have to deal with this.


In [1]:
#!wget https://www.dropbox.com/s/-/DSC00005.JPG
#!cp DSC00005.JPG IMAGE.jpg
!cp IMAGE.jpg DSC00005.JPG
!ls -l *.JPG


-rw-r--r-- 1 root root 2981888 Sep 15 10:01 20140913_210715_DSC00005.JPG
-rw-r--r-- 1 root root 2981888 Sep 15 10:11 DSC00005.JPG

Reading out the EXIF info and dealing with datetime reformatting


In [2]:
from PIL import Image,ExifTags
from datetime import datetime as dt
import pytz

class exif:
    
    def __init__(self, fn, tz=None, fmt_str=None):
        
        self.fn = fn
        if tz == None: tz = 'Europe/London'
        self.timezone = pytz.timezone(tz)
        if fmt_str == None: fmt_str = '%Y:%m:%d %H:%M:%S'
        self.fmt_str = fmt_str
        
        img = img = Image.open(fn)
        self.data = {
            ExifTags.TAGS[k]: v
            for k, v in img._getexif().items()
            if k in ExifTags.TAGS
        }
        
        dto = dt.strptime(self.data['DateTime'], fmt_str)
        self.data['DateTime_o'] = self.timezone.localize(dto)
        
        dto = dt.strptime(self.data['DateTimeOriginal'], fmt_str)
        self.data['DateTimeOrignal_o'] = self.timezone.localize(dto)

        dto = dt.strptime(self.data['DateTimeDigitized'], fmt_str)
        self.data['DateTimeDigitized_o'] = self.timezone.localize(dto)

        
    def time (self, fmt=None, which_time=None, tz=None):
        
        if fmt == None: fmt = "%Y%m%d_%H%M%S"
        if which_time == None: which_time = 'DateTime'
        dto = self.data[which_time+"_o"];
        if tz:
            timezone = pytz.timezone(tz)
            dto = timezone.normalize(dto.astimezone(timezone))
        
        if fmt == False: return dto
        return dto.strftime(fmt)

In [2]:


In [3]:
e = exif('DSC00005.JPG')
e.data['MakerNote'] = ""
e.data['UserComment'] = ""
e.data


Out[3]:
{'YResolution': (350, 1),
 'ResolutionUnit': 2,
 'Software': 'ILCE-5000 v1.00',
 'DateTimeDigitized': '2014:09:13 21:07:15',
 'ColorSpace': 1,
 'Orientation': 1,
 'Flash': 24,
 'ExifImageHeight': 2576,
 'DateTimeDigitized_o': datetime.datetime(2014, 9, 13, 21, 7, 15, tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>),
 'ExposureTime': (1, 80),
 'FocalLength': (160, 10),
 'ExposureProgram': 7,
 'CustomRendered': 0,
 'XResolution': (350, 1),
 'UserComment': '',
 'ExifImageWidth': 3872,
 'Sharpness': 0,
 'ComponentsConfiguration': b'\x01\x02\x03\x00',
 'CompressedBitsPerPixel': (2, 1),
 'DateTime_o': datetime.datetime(2014, 9, 13, 21, 7, 15, tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>),
 'DateTimeOrignal_o': datetime.datetime(2014, 9, 13, 21, 7, 15, tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>),
 'YCbCrPositioning': 2,
 'FNumber': (35, 10),
 'MakerNote': '',
 'ImageDescription': '                               ',
 'Saturation': 0,
 'ExifInteroperabilityOffset': 38156,
 'WhiteBalance': 0,
 'SceneCaptureType': 2,
 'Make': 'SONY',
 'FocalLengthIn35mmFilm': 24,
 'ExifOffset': 364,
 'FlashPixVersion': b'0100',
 'LensModel': 'E PZ 16-50mm F3.5-5.6 OSS',
 'ISOSpeedRatings': 3200,
 'SceneType': 1,
 'ExposureMode': 0,
 'LensSpecification': ((160, 10), (500, 10), (35, 10), (56, 10)),
 'Model': 'ILCE-5000',
 'MaxApertureValue': (926, 256),
 'LightSource': 0,
 'ExifVersion': b'0230',
 'FileSource': 3,
 'DateTimeOriginal': '2014:09:13 21:07:15',
 'MeteringMode': 5,
 'DateTime': '2014:09:13 21:07:15',
 'Contrast': 0,
 'DigitalZoomRatio': (16, 16)}

In [4]:
type(e.time())


Out[4]:
str

In [5]:
e.time()


Out[5]:
'20140913_210715'

Renaming files


In [6]:
import glob
def exifRename (fmt1=None, pattern=None, fmt=None, dryrun=False):
    
    if fmt==None: fmt='%Y%m%d_%H%M%S_'
    if pattern==None: pattern='DSC*.JPG'

    files = glob.glob(pattern)
    flist = []
    for fn in files:
        fn2 = 'test'
        e = exif(fn)
        fn2a = e.time(fmt)
        if fmt1 == None: fn2 = fn2a + fn
        else: fn2 = fn2a + fmt1
        flist.append((fn, fn2))

    if dryrun==True: return flist
    
    for oldfn, newfn in flist:
        !mv $oldfn $newfn
    
    return flist

In [7]:
exifRename()


Out[7]:
[('DSC00005.JPG', '20140913_210715_DSC00005.JPG')]

In [8]:
!ls -l *.JPG


-rw-r--r-- 1 root root 2981888 Sep 15 10:11 20140913_210715_DSC00005.JPG

In [8]: