#!/home/lucas/scripts/.venv/bin/python3 import os import re import random import datetime import sys from PIL import Image, ExifTags from pillow_heif import open_heif import subprocess import json from serverlibary import safe_rename from pathlib import Path # Folder to scan folder_path = '.' # Supported image and video extensions video_extensions = { '.mp4', '.mov', '.avi', '.mkv', '.hevc', '.mov', } img_extensions = { '.jpg', '.jpeg', '.png', '.heic', '.webp', } extensions = img_extensions | video_extensions prefixes = { 'IMG', 'VID'} # Build reusable groups for regex ext_group = r'(jpg|jpeg|png|heic|webp|mp4|mov|avi|mkv|hevc)' prefix_group = r'(?:IMG|VID)' # Correct format to keep correct_format_re = re.compile( rf'^{prefix_group}_(\d{{8}}_\d{{6}}).*\.{ext_group}$', re.IGNORECASE ) # Alternate timestamp formats and their patterns alt_patterns = [ # PREFIX_YYYY_MM_DD_HH_MM_SS.ext ( re.compile( rf'^(?:{prefix_group}_)?(\d{{4}})_(\d{{2}})_(\d{{2}})_(\d{{2}})_(\d{{2}})_(\d{{2}}).*\.{ext_group}$', re.IGNORECASE ), '%Y_%m_%d_%H_%M_%S' ), # PREFIX_YYYYMMDD_HHMMSS.ext ( re.compile( rf'^(?:{prefix_group}_)?(\d{{8}})_(\d{{6}}).*\.{ext_group}$', re.IGNORECASE ), '%Y%m%d_%H%M%S' ), # PREFIX_YYYY.MM.DD-HHMMSS.ext ( re.compile( rf'^(?:{prefix_group}_)?(\d{{4}})\.(\d{{2}})\.(\d{{2}})-(\d{{6}}).*\.{ext_group}$', re.IGNORECASE ), '%Y.%m.%d-%H%M%S' ), # PREFIX_YYYYMMDD.ext ( re.compile( rf'^(?:{prefix_group}_)?(\d{{8}})\.{ext_group}$', re.IGNORECASE ), '%Y%m%d' ), # PREFIX_HHMMSS.ext ( re.compile( rf'^(?:{prefix_group}_)?(\d{{6}})\.{ext_group}$', re.IGNORECASE ), '%H%M%S' ), ] # EXIF tag name for datetime EXIF_DATE_TAGS = ['DateTimeOriginal', 'DateTimeDigitized', 'DateTime'] def local_safe_rename(src_path, new_filename): """Rename file safely, avoiding overwrites""" directory = os.path.dirname(src_path) base, ext = os.path.splitext(new_filename) counter = 1 final_name = new_filename while os.path.exists(os.path.join(directory, final_name)): final_name = f"{base}_{counter}{ext}" counter += 1 os.rename(src_path, os.path.join(directory, final_name)) print(f"Renamed: {os.path.basename(src_path)} ➜ {final_name}") def get_exif_creation_datetime(filepath): """Try to extract creation datetime from image EXIF metadata""" try: if filepath.lower().endswith((".mp4", ".mov", ".m4v")): # WhatsApp videos: use filesystem modification time if "WA" in os.path.basename(filepath): return datetime.datetime.fromtimestamp( os.path.getmtime(filepath) ) tags = ["CreateDate", "MediaCreateDate", "TrackCreateDate"] result = subprocess.run( [ "exiftool", "-j", "-CreateDate", "-MediaCreateDate", "-TrackCreateDate", filepath, ], capture_output=True, text=True, check=True, ) metadata = json.loads(result.stdout)[0] for tag in tags: if metadata.get(tag): return datetime.datetime.strptime( metadata[tag], "%Y:%m:%d %H:%M:%S" ) # Fallback: use filesystem modification time return datetime.datetime.fromtimestamp( os.path.getmtime(filepath) ) else: with Image.open(filepath) as img: exif = img._getexif() if not exif: print("Exif failed") return None tag_label = { ExifTags.TAGS.get(k): v for k, v in exif.items() if k in ExifTags.TAGS } for tag in EXIF_DATE_TAGS: print(tag) if tag in tag_label: print(tag) return datetime.datetime.strptime( tag_label[tag], '%Y:%m:%d %H:%M:%S' ) except Exception as e: print(f"Exif Error {e}") pass return None def get_manufacturer(file_path): # Call exiftool and get JSON output result = subprocess.run( ["exiftool", "-j", file_path], # -j outputs JSON capture_output=True, text=True ) # Parse JSON output metadata = json.loads(result.stdout)[0] # Extract Author tag and remove spaces author = metadata.get("Author", "") if author == "": author = metadata.get("AndroidMake", "") if author == "": author = metadata.get("AndroidManufacturer", "") if author == "": author = metadata.get("Model", "") if author == "": author = "NoAuthor" author_no_spaces = author.replace(" ", "") return author_no_spaces def process_images(folder): folder = Path(folder) for fname in os.listdir(folder): fpath = os.path.join(folder, fname) if not os.path.isfile(fpath): continue _, ext = os.path.splitext(fname) ext = ext.lower() if ext not in extensions: continue # Step 1: Normalize filename by replacing '-' with '_' normalized_name = fname.replace('-', '_') if normalized_name != fname: # Rename the file to the normalized name before continuing new_path = os.path.join(folder, normalized_name) safe_rename(fpath, new_path) print(f"Normalized: {fname} ➜ {os.path.basename(new_path)}") # Update fname and fpath to the new normalized filename fname = os.path.basename(new_path) fpath = new_path videoManufacturer = "" file_prefix = "IMG" for extention in video_extensions: if extention in ext: file_prefix = "VID" videoManufacturer = "_" + get_manufacturer(fpath) for extention in img_extensions: if extention in ext: file_prefix = "IMG" # Now continue with your existing logic on normalized fname if correct_format_re.match(fname): if videoManufacturer != "" and file_prefix == "VID": if videoManufacturer not in fname: basename, _ = os.path.splitext(fname) new_fname = f"{basename}{videoManufacturer}{ext}" safe_rename(Path(fpath),folder / new_fname) continue print(f"[OK] {fname} already correctly named.") continue matched = False for pattern, fmt in alt_patterns: m = pattern.match(normalized_name) if m: try: parts = m.groups() timestamp_str = '_'.join(parts[:2]) if len(parts) >= 2 else parts[0] dt = datetime.datetime.strptime(timestamp_str, fmt) new_fname = f"{file_prefix}_{dt.strftime('%Y%m%d_%H%M%S')}{videoManufacturer}{ext}" safe_rename(Path(fpath),folder / new_fname) matched = True break except Exception as e: continue if matched: continue # 3. Try reading creation date from metadata dt = get_exif_creation_datetime(fpath) if dt: new_fname = f"{file_prefix}_{dt.strftime('%Y%m%d_%H%M%S')}{videoManufacturer}{ext}" safe_rename(Path(fpath),folder / new_fname) continue else: print(f"[Fail] Creation Date for {fpath} not found") # 4. Fallback: keep original name, add _RX_ (only if not already added) if '_RX_' in fname: print(f"[SKIP] {fname} already has fallback name.") continue basename, _ = os.path.splitext(fname) rand_num = random.randint(100000, 999999) new_fname = f"{basename}_RX_{rand_num}{videoManufacturer}{ext}" safe_rename(Path(fpath),folder / new_fname) # ---- Main script entry ---- if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: python script.py ") sys.exit(1) folder_path = sys.argv[1] if not os.path.isdir(folder_path): print(f"Error: '{folder_path}' is not a valid directory.") sys.exit(1) print(f"Processing images in: {os.path.abspath(folder_path)}\n") process_images(folder_path)