#!/usr/bin/env python3 import os import sys import subprocess import shutil def get_exif_date(file_path): """ Uses exiftool to extract the File Creation Date/Time (CreateDate). Returns the formatted date string or an error message if extraction fails. """ 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: total_files_checked += 1 full_path = os.path.join(foldername, filename) _, ext = os.path.splitext(filename) 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"\nFATAL ERROR: The path '{root_dir}' was not found. Please check the drive letter.") return except Exception as e: print(f"\nAN UNEXPECTED ERROR OCCURRED DURING DIRECTORY TRAVERSAL: {e}") return 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__": if len(sys.argv) != 3: print("Usage: python photoimport.py ") print("Example (for Windows): python photoimport.py Q:\\ I:\\Photos") sys.exit(1) 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)