Compare commits
10 Commits
0124f0f7a8
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ee6a29dd1 | |||
| 2443f4b954 | |||
| 4f3a72b81e | |||
| 091cfe5562 | |||
| da13400e3a | |||
| 13a1a3ae99 | |||
| 5f2f4e8e0c | |||
| 2cc0ffc9b5 | |||
| a5638e653a | |||
| ba7a549f6f |
+147
-36
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user