Compare commits

..

10 Commits

+147 -36
View File
@@ -1,62 +1,173 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
import shutil
def scan_photos(root_dir):
def get_exif_date(file_path):
"""
Recursively scans the given directory and lists all specified photo file types.
Uses exiftool to extract the File Creation Date/Time (CreateDate).
Returns the formatted date string or an error message if extraction fails.
"""
# Define accepted extensions (in lowercase)
accepted_extensions = (
'.jpg', '.jpeg', '.raf', '.dng', '.raw', '.cr2', '.cr3'
)
print(f"--- Starting scan in: {root_dir} ---")
try:
result = subprocess.run(
['exiftool', '-d', "%Y-%m-%d", '-CreateDate', file_path],
capture_output=True,
text=True,
check=True
)
output = result.stdout.strip()
if ':' in output:
date_string = output.split(':', 1)[1].strip()
else:
date_string = output
if date_string:
return date_string.replace('-', '\\')
return "Date not found in metadata."
except subprocess.CalledProcessError as e:
return f"ERROR: exiftool failed. Check file integrity or permissions. (Details: {e.stderr.strip()})"
except FileNotFoundError:
return "CRITICAL ERROR: exiftool command not found. Please ensure exiftool is installed and in your system PATH."
def find_existing_file(base_path, filename):
"""
Check if a file already exists at base_path or any of its subdirectories.
Returns the full path to the existing file, or None if not found.
"""
# First check the base_path directory itself
candidate = os.path.join(base_path, filename)
if os.path.exists(candidate):
return candidate
# Then search all subdirectories recursively
for foldername, subfolders, files in os.walk(base_path):
for f in files:
if f == filename:
return os.path.join(foldername, f)
return None
def scan_photos(root_dir, base_target_path):
"""
Recursively scans the given directory (root_dir), extracts EXIF date,
prints file details with proposed destination paths, then asks for confirmation
before copying files. Before copying, it checks if the file already exists
in the target date folder or any of its subdirectories.
"""
accepted_extensions = ('.jpg', '.jpeg', '.dng', '.raw', '.cr2', '.cr3', '.raf')
print(f"--- Starting photo scan in: {root_dir} ---")
total_files_checked = 0
found_files = []
try:
for foldername, subfolders, filenames in os.walk(root_dir):
# Count files we are about to check in this folder
num_in_folder = len(filenames)
if num_in_folder > 0 or subfolders:
rel_folder = os.path.relpath(foldername, root_dir)
print(f" Scanning: {rel_folder} ({num_in_folder} files to check)...")
for filename in filenames:
# Get the full path
total_files_checked += 1
full_path = os.path.join(foldername, filename)
# Check the file extension
# os.path.splitext splits filename into (root, extension)
_, ext = os.path.splitext(filename)
# Convert extension to lowercase for case-insensitive matching
if ext.lower() in accepted_extensions:
found_files.append(full_path)
print(f" [{len(found_files)}] {filename}")
# Progress checkpoint every 50 files checked
if total_files_checked % 50 == 0:
print(f" ...checked {total_files_checked} files, found {len(found_files)} photos so far.")
except FileNotFoundError:
print(f"Error: The path '{root_dir}' was not found. Please check the drive letter.")
print(f"\nFATAL ERROR: The path '{root_dir}' was not found. Please check the drive letter.")
return
except Exception as e:
print(f"An unexpected error occurred: {e}")
print(f"\nAN UNEXPECTED ERROR OCCURRED DURING DIRECTORY TRAVERSAL: {e}")
return
if found_files:
print("\n--- Files Found ---")
for file_path in found_files:
print(file_path)
print(f"\n--- Scan complete. Found {len(found_files)} files. ---")
else:
print(f" ...scan finished: checked {total_files_checked} total files.")
if not found_files:
print("\n--- Scan complete. No specified photo files found in this directory or its subdirectories. ---")
return
# Collect file info first (file_path, date_str, dest_dir)
file_info_list = []
for file_path in found_files:
date_time_str = get_exif_date(file_path)
if not base_target_path.endswith('\\'):
destination_dir = base_target_path + '\\' + date_time_str
else:
destination_dir = base_target_path + date_time_str
file_info_list.append((file_path, date_time_str, destination_dir))
# Print all items in compact format
print()
for file_path, date_time_str, dest_dir in file_info_list:
filename = os.path.basename(file_path)
print(f"{filename} -> {dest_dir}")
print(f"\nFound {len(file_info_list)} files.")
# Ask for confirmation
response = input("\nDo you want to copy these items? (y/yes): ").strip().lower()
if response not in ('y', 'yes'):
print("Aborted. No files were copied.")
return
# Copy files
copied_count = 0
skipped_exists_count = 0
created_dirs_count = 0
for file_path, date_time_str, dest_dir in file_info_list:
filename = os.path.basename(file_path)
# Create destination directory if it doesn't exist
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
created_dirs_count += 1
# Check if the file already exists in dest_dir or any of its subdirectories
existing = find_existing_file(dest_dir, filename)
if existing:
print(f"File {filename} already exists at: {existing} (skipped)")
skipped_exists_count += 1
else:
dest_file = os.path.join(dest_dir, filename)
print("\n--- Starting copy of: " + filename + " ---")
print("Source: " + file_path)
print(f"Destination: {dest_file}")
shutil.copy2(file_path, dest_file)
print(f"Successfully copied: {filename}")
copied_count += 1
# Summary
print("\n========================================================")
print("--- Copy Operation Complete ---")
print("========================================================")
print(f"Files copied: {copied_count}")
if skipped_exists_count > 0:
print(f"Files skipped: {skipped_exists_count} (already exists)")
if created_dirs_count > 0:
print(f"Directories created: {created_dirs_count}")
if __name__ == "__main__":
# Check if a directory path argument is provided
if len(sys.argv) != 2:
print("Usage: python photoimport.py <drive_path>")
print("Example (for Windows): python photoimport.py Q:\\")
if len(sys.argv) != 3:
print("Usage: python photoimport.py <drive_path> <target_display_path>")
print("Example (for Windows): python photoimport.py Q:\\ I:\\Photos")
sys.exit(1)
# The first argument (index 1) is the path
target_path = sys.argv[1]
# Ensure the path ends with a directory separator if it's not empty
# This helps when the user types "Q:" instead of "Q:\"
if not target_path.endswith(os.sep):
target_path = target_path + os.sep
scan_photos(target_path)
root_scan_dir = sys.argv[1]
base_target_path = sys.argv[2]
print(f"Using base target path for grouping: {base_target_path}")
if not root_scan_dir.endswith(os.sep):
root_scan_dir = root_scan_dir + os.sep
scan_photos(root_scan_dir, base_target_path)