188 lines
6.7 KiB
Python
188 lines
6.7 KiB
Python
import subprocess
|
|
import json
|
|
import os
|
|
import datetime
|
|
import glob
|
|
from PIL import Image
|
|
import shutil
|
|
import re
|
|
|
|
def get_time(fn):
|
|
try:
|
|
output = subprocess.check_output([
|
|
"ffprobe", "-v", "quiet", fn, "-print_format", "json",
|
|
"-show_entries",
|
|
"stream=index,codec_type:stream_tags=creation_time:format_tags=creation_time"
|
|
])
|
|
except subprocess.CalledProcessError as e:
|
|
print("FFPROBE failed for ", fn)
|
|
return ValueError()
|
|
fmt = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
data = json.loads(output)
|
|
if "streams" in data:
|
|
for d in data["streams"]:
|
|
if "tags" in d and "creation_time" in d["tags"]:
|
|
return datetime.datetime.strptime(d["tags"]["creation_time"], fmt)
|
|
if "format" in data and "tags" in data["format"] and "creation_time" in data["format"]["tags"]:
|
|
return datetime.datetime.strptime(data["format"]["tags"]["creation_time"], fmt)
|
|
# import sys
|
|
# import zoneinfo
|
|
# dt = datetime.datetime.fromtimestamp(
|
|
# os.stat(fn).st_ctime, zoneinfo.ZoneInfo("UTC"))
|
|
# dt = dt.replace(tzinfo=None)
|
|
# return dt
|
|
return ValueError()
|
|
def get_time_image(fn):
|
|
fmt = "%Y:%m:%d %H:%M:%S"
|
|
return datetime.datetime.strptime(Image.open(fn)._getexif()[36867], fmt)
|
|
|
|
def get_new_fn(fn):
|
|
base, ext = os.path.splitext(fn)
|
|
directory, base_fn = os.path.split(base)
|
|
ext = ext.lower()
|
|
|
|
image_files = list(
|
|
filter(lambda fn: os.path.splitext(fn)[1].lower() in [".jpeg", ".jpg", ".png"],
|
|
glob.iglob(glob.escape(base) + "*")))
|
|
assert len(image_files) < 2
|
|
fmt_folder = "%Y%m%d"
|
|
fmt_base = "%Y%m%d_%H%M"
|
|
if image_files:
|
|
f = image_files[0]
|
|
image_ext = os.path.splitext(f)[-1]
|
|
image_dt = get_time_image(f)
|
|
image_size = os.path.getsize(f)
|
|
new_base = os.path.join(os.path.expanduser("~/Pictures/Darktable"), image_dt.strftime(fmt_folder), image_dt.strftime(fmt_base))
|
|
|
|
g = glob.escape(new_base) + "_*" + image_ext
|
|
matches = [g for g in glob.glob(g) if image_size == os.path.getsize(g)]
|
|
if len(matches) != 1:
|
|
print()
|
|
print("NO MATCH")
|
|
print(image_files)
|
|
print(matches)
|
|
print(g)
|
|
print(image_dt)
|
|
|
|
return None
|
|
assert len(matches) == 1, fn
|
|
nfn = os.path.splitext(matches[0])[0] + ext
|
|
nfn = list(os.path.split(nfn))
|
|
nfn[-1] = "." + nfn[-1]
|
|
return os.path.join(*nfn)
|
|
|
|
time = get_time(fn)
|
|
if isinstance(time, ValueError):
|
|
print("NO TIME")
|
|
return None
|
|
new_base = os.path.join(os.path.expanduser("~/Pictures/Darktable"), time.strftime(fmt_folder), time.strftime(fmt_base))
|
|
g = glob.glob(glob.escape(new_base) + "_*" + ext)
|
|
if len(g) == 0:
|
|
return new_base + "_0000" + ext
|
|
n = 0
|
|
s = os.path.getsize(fn)
|
|
for f in g:
|
|
if os.path.getsize(f) == s:
|
|
return f
|
|
p = r".*_(\d+)" + ext
|
|
nn = re.match(p, f)
|
|
n = max(n, int(nn.group(1)))
|
|
n = new_base + "_%04d" % (n + 1) + ext
|
|
return n
|
|
|
|
def main(dir, dry):
|
|
from datetime import timedelta, datetime
|
|
print(dir)
|
|
offsets = {}
|
|
for date in sorted(os.listdir(dir)):
|
|
path = os.path.join(dir, date)
|
|
if not os.path.isdir(path):
|
|
continue
|
|
print(date)
|
|
date = datetime.strptime(date, '%Y%m%d')
|
|
|
|
offset = None
|
|
for fn in sorted(os.listdir(path)):
|
|
|
|
fn = os.path.join(path, fn)
|
|
_, ext = os.path.splitext(fn)
|
|
if ext.lower() not in ['.jpeg', '.jpg']:
|
|
continue
|
|
|
|
import pprint
|
|
from PIL import ExifTags
|
|
import exifread
|
|
import re
|
|
|
|
with open(fn, 'rb') as f:
|
|
|
|
tags = exifread.process_file(f)
|
|
|
|
try:
|
|
model = str(tags['Image Model'])
|
|
except KeyError:
|
|
continue
|
|
|
|
if str(model) != 'Pixel 6' or str(model).startswith("iPhone"):
|
|
continue
|
|
|
|
offset_tags = ['EXIF OffsetTime', 'EXIF OffsetTimeOriginal', 'EXIF OffsetTimeDigitized', 'EXIF TimeZoneOffset']
|
|
|
|
for tag in offset_tags:
|
|
try:
|
|
offset_str = str(tags[tag])
|
|
except KeyError:
|
|
continue
|
|
assert re.match(r"(\+|-)\d\d:\d\d", offset_str)
|
|
offset = timedelta(hours=int(offset_str[1:3]), minutes=int(offset_str[4:6]))
|
|
if offset_str[0] == '-':
|
|
offset = -offset
|
|
|
|
break
|
|
|
|
break
|
|
import pprint
|
|
|
|
if offset:
|
|
offsets[date] = offset
|
|
|
|
for p, folders, files in os.walk(os.path.expanduser(dir)):
|
|
for f in sorted(files):
|
|
fn = os.path.join(p, f)
|
|
|
|
base, ext = os.path.splitext(f)
|
|
if ext.lower() not in [".mp4", ".mov", ".mts"]:
|
|
continue
|
|
|
|
time = get_time(fn)
|
|
if isinstance(time, ValueError):
|
|
continue
|
|
|
|
diff = sorted(offsets.keys(), key=lambda d: abs((d - time).total_seconds()))
|
|
|
|
# print(diff)
|
|
# print(diff[0])
|
|
# print(offsets[diff[0]])
|
|
|
|
print(time)
|
|
print(time + offsets[diff[0]])
|
|
print()
|
|
continue
|
|
nfn = get_new_fn(fn)
|
|
if not nfn:
|
|
print("SKIP: ", fn)
|
|
continue
|
|
assert nfn
|
|
|
|
print(os.path.basename(fn))
|
|
print(os.path.basename(nfn))
|
|
print()
|
|
|
|
|
|
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--dry", action="store_true")
|
|
options = parser.parse_args()
|
|
main("/home/oke/Pictures/DarktableLocal", options.dry)
|