feat(rename_camera_folder): restructuring, add path to subprocess
This commit is contained in:
1 parent
2af4724093
commit
25432d1650
1 file changed
+79
-79
@@ -1,37 +1,45 @@
|
||||
#!/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 serverlibrary import safe_rename
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import ExifTags, Image
|
||||
|
||||
from serverlibrary import safe_rename
|
||||
|
||||
# Folder to scan
|
||||
folder_path = '.'
|
||||
folder_path = "."
|
||||
# Supported image and video extensions
|
||||
|
||||
|
||||
|
||||
video_extensions = {
|
||||
'.mp4', '.mov', '.avi', '.mkv', '.hevc', '.mov',
|
||||
".mp4",
|
||||
".mov",
|
||||
".avi",
|
||||
".mkv",
|
||||
".hevc",
|
||||
}
|
||||
img_extensions = {
|
||||
'.jpg', '.jpeg', '.png', '.heic', '.webp',
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".heic",
|
||||
".webp",
|
||||
}
|
||||
|
||||
extensions = img_extensions | video_extensions
|
||||
prefixes = { 'IMG', 'VID'}
|
||||
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)'
|
||||
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
|
||||
rf"^{prefix_group}_(\d{{8}}_\d{{6}}).*\.{ext_group}$", re.IGNORECASE
|
||||
)
|
||||
|
||||
# Alternate timestamp formats and their patterns
|
||||
@@ -39,51 +47,40 @@ 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
|
||||
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'
|
||||
"%Y_%m_%d_%H_%M_%S",
|
||||
),
|
||||
|
||||
# PREFIX_YYYYMMDD_HHMMSS.ext
|
||||
(
|
||||
re.compile(
|
||||
rf'^(?:{prefix_group}_)?(\d{{8}})_(\d{{6}}).*\.{ext_group}$',
|
||||
re.IGNORECASE
|
||||
rf"^(?:{prefix_group}_)?(\d{{8}})_(\d{{6}}).*\.{ext_group}$", re.IGNORECASE
|
||||
),
|
||||
'%Y%m%d_%H%M%S'
|
||||
"%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
|
||||
rf"^(?:{prefix_group}_)?(\d{{4}})\.(\d{{2}})\.(\d{{2}})-(\d{{6}}).*\.{ext_group}$",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
'%Y.%m.%d-%H%M%S'
|
||||
"%Y.%m.%d-%H%M%S",
|
||||
),
|
||||
|
||||
# PREFIX_YYYYMMDD.ext
|
||||
(
|
||||
re.compile(
|
||||
rf'^(?:{prefix_group}_)?(\d{{8}})\.{ext_group}$',
|
||||
re.IGNORECASE
|
||||
),
|
||||
'%Y%m%d'
|
||||
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'
|
||||
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']
|
||||
EXIF_DATE_TAGS = ["DateTimeOriginal", "DateTimeDigitized", "DateTime"]
|
||||
|
||||
|
||||
def local_safe_rename(src_path, new_filename):
|
||||
@@ -100,21 +97,20 @@ def local_safe_rename(src_path, new_filename):
|
||||
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)
|
||||
)
|
||||
|
||||
return datetime.datetime.fromtimestamp(os.path.getmtime(filepath))
|
||||
|
||||
tags = ["CreateDate", "MediaCreateDate", "TrackCreateDate"]
|
||||
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"exiftool",
|
||||
"/usr/bin/exiftool",
|
||||
"-j",
|
||||
"-CreateDate",
|
||||
"-MediaCreateDate",
|
||||
@@ -125,56 +121,52 @@ def get_exif_creation_datetime(filepath):
|
||||
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"
|
||||
metadata[tag], "%Y:%m:%d %H:%M:%S"
|
||||
)
|
||||
|
||||
|
||||
# Fallback: use filesystem modification time
|
||||
return datetime.datetime.fromtimestamp(
|
||||
os.path.getmtime(filepath)
|
||||
)
|
||||
|
||||
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'
|
||||
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
|
||||
["/usr/bin/exiftool", "-j", file_path], # -j outputs JSON
|
||||
capture_output=True,
|
||||
text=True
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
# Parse JSON output
|
||||
@@ -192,6 +184,7 @@ def get_manufacturer(file_path):
|
||||
author_no_spaces = author.replace(" ", "")
|
||||
return author_no_spaces
|
||||
|
||||
|
||||
def process_images(folder):
|
||||
folder = Path(folder)
|
||||
for fname in os.listdir(folder):
|
||||
@@ -207,7 +200,7 @@ def process_images(folder):
|
||||
continue
|
||||
|
||||
# Step 1: Normalize filename by replacing '-' with '_'
|
||||
normalized_name = fname.replace('-', '_')
|
||||
normalized_name = fname.replace("-", "_")
|
||||
|
||||
if normalized_name != fname:
|
||||
# Rename the file to the normalized name before continuing
|
||||
@@ -233,12 +226,15 @@ def process_images(folder):
|
||||
|
||||
# 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
|
||||
if (
|
||||
videoManufacturer != ""
|
||||
and file_prefix == "VID"
|
||||
and 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
|
||||
@@ -249,13 +245,14 @@ def process_images(folder):
|
||||
if m:
|
||||
try:
|
||||
parts = m.groups()
|
||||
timestamp_str = '_'.join(parts[:2]) if len(parts) >= 2 else parts[0]
|
||||
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)
|
||||
safe_rename(Path(fpath), folder / new_fname)
|
||||
matched = True
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Exception {e}")
|
||||
continue
|
||||
|
||||
if matched:
|
||||
@@ -264,23 +261,26 @@ def process_images(folder):
|
||||
# 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)
|
||||
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:
|
||||
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)
|
||||
safe_rename(Path(fpath), folder / new_fname)
|
||||
|
||||
|
||||
# ---- Main script entry ----
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
folder_path = "/data/nextcloud/syncuser/pictures/Camera"
|
||||
else:
|
||||
@@ -289,7 +289,7 @@ if __name__ == '__main__':
|
||||
if not os.path.isdir(folder_path):
|
||||
print(f"Error: '{folder_path}' is not a valid directory.")
|
||||
sys.exit(1)
|
||||
subprocess.run(["syncNC", "-c"])
|
||||
subprocess.run(["/usr/bin/syncNC", "-c"], check=True)
|
||||
print(f"Processing images in: {os.path.abspath(folder_path)}\n")
|
||||
process_images(folder_path)
|
||||
subprocess.run(["syncNC", "-c"])
|
||||
subprocess.run(["/usr/bin/syncNC", "-c"], check=True)
|
||||
Reference in new issue
Block a user