Files
Server-scripts/file processing/rename_camera_folder.py
T

294 lines
8.8 KiB
Python
Executable File

#!/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_<random> (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 <image_folder_path>")
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)