]> code.delx.au - monosys/blobdiff - rename-by-date
smart-stats: smartctl returns non-zero if there are errors in the smart log
[monosys] / rename-by-date
index 0dde55f8fb34d305c0af0a642a3ef21eb65741c1..5424aa8623e00ec3124636b6e9a1c0f0fd8b58bc 100755 (executable)
@@ -1,8 +1,139 @@
-#!/bin/bash
+#!/usr/bin/python3
 
-num=1
-ls -tr | while read f; do
-       mv "$f" "$num $f"
-       let "num=num+1"
-done
+import argparse
+import datetime
+import os
+import subprocess
+import sys
+
+
+def read_directories(src_directories):
+    result = []
+    for src_directory in src_directories:
+        for root, dirnames, filenames in os.walk(src_directory):
+            for filename in filenames:
+                filename = os.path.join(root, filename)
+                result.append(filename)
+    result.sort()
+    return result
+
+def get_timestamp(filename):
+    ext = os.path.splitext(filename.lower())[1]
+
+    if ext == ".jpg":
+        return get_jpg_timestamp(filename)
+
+    if ext == ".mp4":
+        return get_mp4_timestamp(filename)
+
+    raise NotImplementedError("Unsupported extension: " + ext)
+
+def get_mp4_timestamp(filename):
+    output = subprocess.check_output([
+        "ffprobe", filename, "-show_format", "-v", "quiet",
+    ]).decode("utf-8")
+    line = [line for line in output.split("\n") if line.startswith("TAG:creation_time=")][0]
+    value = line.split("=")[1].split(".")[0]
+    return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S")
+
+def get_jpg_timestamp(filename):
+    output = subprocess.check_output([
+        "exiv2", "-pt", "-g", "Exif.Photo.DateTimeOriginal", filename
+    ]).decode("utf-8")
+    first_line = output.split("\n")[0]
+    timestamp = " ".join(first_line.split()[-2:])
+    return datetime.datetime.strptime(timestamp, "%Y:%m:%d %H:%M:%S")
+
+class FilesByDate(object):
+    def __init__(self, dest, src_directories):
+        self.dest = dest
+        self.src_directories = src_directories
+
+    def plan(self):
+        sorted_filenames = self.get_sorted_filenames()
+
+        for i, filename in enumerate(sorted_filenames, 1):
+            yield filename, self.get_new_filename(filename, i)
+
+    def get_sorted_filenames(self):
+        src_filenames = read_directories(self.src_directories)
+
+        ts_filenames = []
+        for filename in src_filenames:
+            timestamp = get_timestamp(filename)
+            ts_filenames.append((timestamp, filename))
+        ts_filenames.sort()
+
+        return [filename for _, filename in sorted(ts_filenames)]
+
+    def get_new_filename(self, orig_filename, i):
+        prefix = "S" + str(i).zfill(3) + "_"
+        orig_filename = os.path.basename(orig_filename)
+        return os.path.join(self.dest, prefix + orig_filename)
+
+class DirectoryFanout(object):
+    def __init__(self, dest, src_directories):
+        self.dest = dest
+        self.src_directories = src_directories
+
+    def plan(self):
+        src_filenames = read_directories(self.src_directories)
+
+        for filename in src_filenames:
+            timestamp = get_timestamp(filename)
+            yield filename, self.get_new_filename(filename, timestamp)
+
+    def get_new_filename(self, orig_filename, timestamp):
+        prefix = timestamp.strftime("%Y-%m-%d") + "/"
+        orig_filename = os.path.basename(orig_filename)
+        return os.path.join(self.dest, prefix + orig_filename)
+
+def print_plan(plan):
+    for orig_filename, new_filename in plan:
+        print("    ", orig_filename, "->", new_filename)
+
+def execute_plan(plan):
+    for orig_filename, new_filename in plan:
+        os.makedirs(os.path.dirname(new_filename), exist_ok=True)
+        os.link(orig_filename, new_filename)
+
+def parse_args():
+    parser = argparse.ArgumentParser(description="Relink photos based on EXIF dates")
+
+    parser.add_argument("--dry-run", action="store_true")
+
+    parser.add_argument("dest", nargs=1)
+    parser.add_argument("src", nargs="+")
+
+    action_group = parser.add_mutually_exclusive_group(required=True)
+
+    action_group.add_argument("--directory-fanout", action="store_true",
+        help="Create directories with names like 2015-01-01, place files into them")
+
+    action_group.add_argument("--rename-files", action="store_true",
+        help="Rename files from different cameras based on timestamps")
+
+    args = parser.parse_args()
+    args.dest = args.dest[0]
+    return args
+
+def main():
+    args = parse_args()
+
+    def planner():
+        raise NotImplementedError()
+
+    if args.directory_fanout:
+        planner = DirectoryFanout(args.dest, args.src).plan
+    elif args.rename_files:
+        planner = FilesByDate(args.dest, args.src).plan
+
+    plan = planner()
+    if args.dry_run:
+        print_plan(plan)
+    else:
+        execute_plan(plan)
+
+if __name__ == "__main__":
+    main()