""" Classify photos by EXIF attributes. Before classified: DCIM ├── 100ANDRO │   ├── DSC_2828.JPG │   └── DSC_2830.JPG ├── 99ANDRO │   ├── DSC_2822.JPG │   └── DSC_2827.JPG ├── Camera │   ├── C1 │   │   ├── P_20180124_165745.jpg │   │   └── P_20180124_165756.jpg │   ├── C2 │   │   ├── P_20180124_172141.jpg │   │   └── P_20180124_172720.jpg │   └── P_20180124_165611.jpg └── DSC_2833.JPG After classified: DCIM ├── 100ANDRO ├── 99ANDRO ├── Camera │   ├── C1 │   └── C2 └── Classified ├── 2015-11 │   ├── 29-062447-Sony-D5503.jpg │   ├── 29-063154-Sony-D5503.jpg │   ├── 29-063503-Sony-D5503.jpg │   ├── 29-063617-Sony-D5503.jpg │   └── 29-063810-Sony-D5503.jpg └── 2018-01 ├── 24-165610-ASUS-Z00UD.jpg ├── 24-165745-ASUS-Z00UD.jpg ├── 24-165755-ASUS-Z00UD.jpg ├── 24-172141-ASUS-Z00UD.jpg └── 24-172719-ASUS-Z00UD.jpg """ import piexif import json import re import os import shutil import time from datetime import datetime class ExifReader: def __init__(self, photo): self.ifds = piexif.load(photo) def read_attribute(self, tag, ifd='0th'): if ifd in self.ifds: if tag in self.ifds[ifd]: if isinstance(self.ifds[ifd][tag], bytes): return self.ifds[ifd][tag].decode('ascii') else: return self.ifds[ifd][tag] return None def rearrange(photo_src, base_dest = 'rearranged'): # See: http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf p.79 exr = ExifReader(photo_src) brand = exr.read_attribute(271) model = exr.read_attribute(272) mtime = exr.read_attribute(306) m = re.match(r'(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})', mtime) if m is None: exit() if model.lower().startswith(brand.lower()): camera = model else: camera = (brand + '-' + model) camera = camera.replace('_', '-') photo_dir = '{}/{}-{}'.format(base_dest, m[1], m[2]) photo_dest = '{}/{}-{}{}{}-{}.jpg'.format(photo_dir, m[3], m[4], m[5], m[6], camera) if not os.path.isdir(photo_dir): os.makedirs(photo_dir) shutil.copy2(photo_src, photo_dest) ts_mtime = time.mktime(time.strptime(mtime, '%Y:%m:%d %H:%M:%S')) st = os.stat(photo_dest) os.chmod(photo_dest, 0o644) os.utime(photo_dest, (ts_mtime, ts_mtime)) os.remove(photo_src) def main(): source_dir = 'DCIM' target_dir = 'DCIM/Classified' for base, directories, files in os.walk(source_dir): if base == target_dir: continue for f in files: if re.match(r'.+\.jpg', f, re.I): photo_src = '{}/{}'.format(base, f) rearrange(photo_src, target_dir) if __name__ == '__main__': main()