Skip to content

Instantly share code, notes, and snippets.

@huyaoyu
Created November 4, 2023 03:58
Show Gist options
  • Save huyaoyu/8fa17b0f412898a6a0c508e2728e242d to your computer and use it in GitHub Desktop.
Save huyaoyu/8fa17b0f412898a6a0c508e2728e242d to your computer and use it in GitHub Desktop.
Rename the media files copied from an iPhone on Windows. Use time string to compose a filename.
import argparse
import datetime
import glob
import os
import pathlib
import PIL.Image
import PIL.ExifTags
import pytz
import re
from win32com.propsys import propsys, pscon
def handle_args():
parser = argparse.ArgumentParser(description='Rename image filenames from iphone. ')
parser.add_argument('--in_dir', type=str, required=True,
help='The input directory. ')
parser.add_argument('--exe', action='store_true', default=False,
help='Set this flag to perform the renaming. ')
return parser.parse_args()
def find_files(d):
files_jpg = sorted(glob.glob( os.path.join(d, '*.jpg')))
files_png = sorted(glob.glob( os.path.join(d, '*.png')))
files_mp4 = sorted(glob.glob( os.path.join(d, '*.mp4')))
files_mov = sorted(glob.glob( os.path.join(d, '*.mov')))
files = sorted( [ *files_jpg, *files_png, *files_mp4, *files_mov ] )
assert len(files) > 0, f'No files find from {d}. '
return files
def is_image(fn):
fn = fn.lower()
if fn.endswith('.jpg') or fn.endswith('.png'):
return True
else:
return False
def is_video(fn):
fn = fn.lower()
if fn.endswith('.mp4') or fn.endswith('.mov'):
return True
else:
return False
def parse_ori_filename(fn):
'''Assume that the image filename is in the form of
IMG_1234.xxx
Then this filename is devided into "IMG", "1234", and ".xxx". And they are
returned.
If the filename does not following the assumption. Then it will be parsed as
"stem" and ".jpg. And the return values are "IMG", "stem", and ".xxx"
Here stem is os.path.stem().
'''
s = os.path.splitext(os.path.basename(fn))
res = ['IMG']
m = re.search(r'^IMG_(\d+)', s[0])
if m:
# This is pattern IMG_1234
res.append(m[1])
res.append(s[1])
else:
# Not pattern IMG_1234
res.append(s[0])
res.append(s[1])
return res
def parse_time_string(s):
'''Naive time string parsing.
The time string is in the form of
YYYY:MM:DD HH:MM:SS
'''
ss = s.split(' ')
ymd = ss[0].split(':')
hms = ss[1].split(':')
return ymd, hms
def get_time_string_image(fn):
'''https://stackoverflow.com/questions/4764932/in-python-how-do-i-read-the-exif-data-for-an-image
'''
img = PIL.Image.open(fn)
exif_data = img._getexif()
exif = {PIL.ExifTags.TAGS[k]: v for k, v in exif_data.items() if k in PIL.ExifTags.TAGS}
ts = exif['DateTimeOriginal']
ymd, hms = parse_time_string(ts)
new_ts = f'{ymd[0]}{ymd[1]}{ymd[2]}_{hms[0]}{hms[1]}{hms[2]}'
return new_ts
def get_time_string_video(fn):
'''https://stackoverflow.com/questions/31507038/python-how-to-read-windows-media-created-date-not-file-creation-date
'''
# https://github.com/mhammond/pywin32/issues/1360
# https://docs.python.org/3/library/pathlib.html#operators
p = pathlib.Path(fn)
properties = propsys.SHGetPropertyStoreFromParsingName( str( p.resolve() ) )
dt = properties.GetValue( pscon.PKEY_Media_DateEncoded ).GetValue()
if dt is None:
raise Exception(f'>>> Video {fn} does not have pscon.PKEY_Media_DateEncoded. ')
# https://stackoverflow.com/questions/5946499/how-to-get-the-common-name-for-a-pytz-timezone-eg-est-edt-for-america-new-york
dt = dt.astimezone(pytz.timezone('US/Eastern'))
return dt.strftime('%Y%m%d_%H%M%S')
def get_time_string_file(fn):
p = pathlib.Path(fn)
ts = p.stat().st_mtime # Modification time.
dt = datetime.datetime.fromtimestamp(ts)
return dt.strftime('%Y%m%d_%H%M%S')
if __name__ == '__main__':
args = handle_args()
files = find_files(args.in_dir)
for f in files:
try:
if is_image(f):
new_ts = get_time_string_image(f)
elif is_video(f):
new_ts = get_time_string_video(f)
else:
new_ts = get_time_string_file(f)
except Exception as exp:
print(exp)
print('Fall back to file modification time. ')
new_ts = get_time_string_file(f)
# Get the components of the filename.
comp = parse_ori_filename(f)
new_fn = os.path.join(
os.path.dirname(f),
f'{comp[0]}_{new_ts}_{comp[1]}{comp[2]}' )
print(
f'{f} with time string {new_ts} -> '
f'{new_fn}')
if not args.exe:
continue
# Rename.
os.rename( f, new_fn )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment