Skip to content

Instantly share code, notes, and snippets.

@Lightfire228
Last active May 7, 2024 17:29
Show Gist options
  • Save Lightfire228/52671b171dc7fbe154c98c615ac338a8 to your computer and use it in GitHub Desktop.
Save Lightfire228/52671b171dc7fbe154c98c615ac338a8 to your computer and use it in GitHub Desktop.
Immich update exif timestamps from file names

This is a script to fix a bunch of timestamps that got mangled. It uses the timestamps from the filenames to update the exif info

The host is Immich, who's API documentation can be found here

The code is a little sloppy, but should work as a reference if someone else needs to do batch update their images

This does not account for DST, because I couldn't be bothered to

import datetime
import requests
import re
KEY = ''
DOMAIN = ''
TIMEZONE = '-05:00'
URL = f'https://{DOMAIN}/api'
def main():
data = get_all_assets()
for f in get_all_filenames(data):
d = extract_time(f)
set_date(f, d)
def get_all_assets() -> list[dict]:
# probably don't need the take=5000 param
# I was having issues getting all 3k images in one request, but i think it was something else
r = requests.get(f'{URL}/asset', params={'take': '5000'}, headers={
'x-api-key': KEY,
'Accept': 'application/json',
})
return r.json()
def get_all_filenames(data) -> list[dict]:
"""
get all assets with timestamp looking file names
"""
def name(f):
return f['originalFileName']
# filter out screenshots because i don't care about them
filenames = [x for x in data if not name(x).startswith('Screenshot')]
# white list filenames by pattern
timestamps = []
timestamps.extend([f for f in filenames if re.match(r'^((IMG|VID|PANO|JPEG)_)?(\d{8})_', name(f))])
timestamps.extend([f for f in filenames if re.match(r'^(\d{4}-\d{2}-\d{2})', name(f))])
timestamps.extend([f for f in filenames if re.match(r'^IMG_\d{4}_(\d{8})_', name(f))])
timestamps.extend([f for f in filenames if re.match(r'^16\d{11}', name(f))])
return timestamps
def extract_time(f) -> datetime.datetime:
"""
convert the filename into a python datetime
"""
name = f['originalFileName']
#
# IMG_20240101_131110
# VID_20240101_131110
# 20240101_131110
#
if m := re.match(r'^(?:(?:IMG|VID|PANO|JPEG)_)?(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})', name):
return _parse_date(m)
# 2024-01-01-13-11-10
elif m:= re.match(r'^(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})', name):
return _parse_date(m)
# IMG_1234_20240101_131110
elif m := re.match(r'^IMG_\d{4}_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})', name):
return _parse_date(m)
# unix timestamp (to the milliseconds, for some reason)
# 17041326700000
elif m := re.match(r'^(\d{10})', name):
return _parse_unix(m)
else:
raise Exception('lol wut')
def _parse_date(m) -> datetime.datetime:
year = int(m.group(1))
month = int(m.group(2))
day = int(m.group(3))
hour = int(m.group(4))
minute = int(m.group(5))
second = int(m.group(6))
return datetime.datetime(
year = year,
month = month,
day = day,
hour = hour,
minute = minute,
second = second,
)
def _parse_unix(m) -> datetime.datetime:
timestamp = int(m.group(1))
return datetime.datetime.fromtimestamp(timestamp)
def _format_date(d: datetime.datetime) -> str:
return d.strftime(f'%Y-%m-%dT%H:%M:%S.000{TIMEZONE}')
def set_date(f, d):
"""
send the request to update the exif info
"""
id = f['id']
date = _format_date(d)
# print(date)
r = requests.put(
f'{URL}/asset/{id}',
headers={
'x-api-key': KEY,
'Accept': 'application/json',
},
data={
'dateTimeOriginal': date
}
)
r.raise_for_status()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment