From 11d3deb66f87c0d71ec62b0f8b4c92fbcebbf351 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 16 May 2020 00:21:40 +0200 Subject: [PATCH] sorting mov files that are the iphone live photos. shitty code but comitting anyway --- movheaders.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ sort-photos.py | 48 ++++++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 movheaders.py mode change 100644 => 100755 sort-photos.py diff --git a/movheaders.py b/movheaders.py new file mode 100644 index 0000000..c5c9d9c --- /dev/null +++ b/movheaders.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + + +from datetime import datetime +import struct +import sys + +def get_mov_dates(filename): + # borrowed from stackoverflow + # https://stackoverflow.com/questions/21355316/getting-metadata-for-mov-video + ATOM_HEADER_SIZE = 8 + # difference between Unix epoch and QuickTime epoch, in seconds + EPOCH_ADJUSTER = 2082844800 + + # open file and search for moov item + f = open(filename, "rb") + while 1: + atom_header = f.read(ATOM_HEADER_SIZE) + if atom_header[4:8] == b'moov': + break + else: + try: + atom_size = struct.unpack(">I", atom_header[0:4])[0] + f.seek(atom_size - 8, 1) + except struct.error: + raise ValueError("no 'moov' header found in {}".format(filename)) + + # found 'moov', look for 'mvhd' and timestamps + atom_header = f.read(ATOM_HEADER_SIZE) + if atom_header[4:8] == b'cmov': + print("moov atom is compressed") + elif atom_header[4:8] != b'mvhd': + print("expected to find 'mvhd' header") + else: + f.seek(4, 1) + creation_date = struct.unpack(">I", f.read(4))[0] + modification_date = struct.unpack(">I", f.read(4))[0] + + created = datetime.utcfromtimestamp(creation_date - EPOCH_ADJUSTER) + #modified = datetime.utcfromtimestamp(modification_date - EPOCH_ADJUSTER) + # print(created) + #return modified + return created + +def get_date(path): + """Returns a tuple of strings (y, m, d) where m and d are zero-padded + + what sort-photos.py expects""" + + date = get_mov_dates(path) + y, m, d = [str(a).zfill(2) for a in [date.year, date.month, date.day]] + + return (y, m, d) + + +if __name__ == "__main__": + path = sys.argv[1] + date = get_date(path) + print(date) diff --git a/sort-photos.py b/sort-photos.py old mode 100644 new mode 100755 index 3fe65e6..1117a0d --- a/sort-photos.py +++ b/sort-photos.py @@ -7,6 +7,8 @@ import shutil import exifread +import movheaders + HOME = os.getenv("HOME") NEXTCLOUD_DIR = os.path.join(HOME, "Nextcloud") PHOTOS_DIR = os.path.join(NEXTCLOUD_DIR, "Photos") @@ -18,6 +20,7 @@ parser.add_argument("--src", required=True) parser.add_argument("--dst", default=PHOTOS_DIR, required=False) parser.add_argument("--debug", action="store_true") parser.add_argument("--dry-run", action="store_true") +parser.add_argument("--fileext") args = parser.parse_args() def debug(s): @@ -33,29 +36,56 @@ def mkdir(path): os.makedirs(path, exist_ok=True) def get_date(path): - with open(path, 'rb') as f: - tags = exifread.process_file(f, stop_tag="EXIF DateTimeOriginal") + if os.path.splitext(path)[1].lower() == ".mov": + return movheaders.get_date(path) + else: + with open(path, 'rb') as f: + tags = exifread.process_file(f, stop_tag="EXIF DateTimeOriginal") + y, m, d = tags["EXIF DateTimeOriginal"].values.split(" ")[0].split(":") + return (y, m, d) - y, m, d = tags["EXIF DateTimeOriginal"].values.split(" ")[0].split(":") - return (y, m, d) if __name__ == "__main__": - g = glob.glob(args.src) + #g = glob.glob(args.src) + # just one dir at a time + g = os.listdir(args.src) + # print(g) + # raise SystemExit for a in g: try: - y, m, _ = get_date(a) + # im gonna have fun with this next time i look at it + a_ext = os.path.splitext(a)[1].lower() + if args.fileext and a_ext != args.fileext: + continue + _path = os.path.join(args.src, a) + a_mb = os.path.getsize(_path) >> 20 + #print("{}, {} mb".format(a, a_mb)) + y, m, d = get_date(_path) + if args.fileext and a_ext == ".mov": + # big videos (actual videos ive taken) have already + # been uploaded by nextcloud. Trying to single out + # the live videos around photos + if a_mb > 5: + debug("Too big: {}, {} mb, {}/{}/{}".format(_path, a_mb, y, m, d)) + # ignore completely, no wasting space on nextcloud + continue + ymdir = os.path.join(PHOTOS_DIR, y, m) mkdir(ymdir) debug("{} -> {}".format(a, ymdir)) - move(a, ymdir) + move(_path, ymdir) + except ValueError: + mkdir(UNSORTED_DIR) + print("{} -> {}".format(a, UNSORTED_DIR)) + move(os.path.join(args.src, a), UNSORTED_DIR) except KeyError: # Could not parse date mkdir(UNSORTED_DIR) print("{} -> {}".format(a, UNSORTED_DIR)) - move(a, UNSORTED_DIR) + move(os.path.join(args.src, a), UNSORTED_DIR) except IsADirectoryError: # leave directories, happens if you didn't give the # correct full path - print("Skipping directory: {}".format(a)) + print("Skipping directory: {}".format(os.path.join(args.src, a))) continue