Created
November 4, 2023 03:58
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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