shutil.move only coping files but not moving (or deleting the original). Permission error

Question:

I’ve made some code in python that should move photos from one folder into another and sorts them by date taken. However the script only seems to be copying the files (or at least copying without deleting the original).

It’s coming up with a permission error however I have tried everything i can think of – run VSC as administrator, changed the permissions of the folders to ensure they are ‘full control’; ensured there were no ‘read only’ bits. I’ve changed the photos and changed the folders but it’s the same thing.

I’m quite new to python so it’s possible it’s the script but can anyone help please?

import os
import shutil
import exifread
import datetime
import sys
import time
import datetime
 
# Set the source directory path
source_path = os.path.join('D:', 'Test new')
dest_path = os.path.join('F:', 'Test new')
photo_extensions = ['.jpg', 'bmp', '.png', '.raw', '.jpeg','.heic','.gif']
 
def read_source_directory_recursive(directory_path, num_calls=0, max_recursive_calls=1000):
    # If the function has been called more than the maximum number of times, return early
    if num_calls > max_recursive_calls:
        return
    # Read the directory and get a list of files
    files = os.listdir(directory_path)
    # Initialize the variable that keeps track of the number of photos processed
    num_photos_processed = 0
    # Loop through the files
    for dir_name in files:
        file_name = dir_name
        # If the file is a photo based on the extension
        if any(file_name.lower().endswith(ext) for ext in photo_extensions):
            # Get the absolute path of the photo
            photo_path = os.path.join(directory_path, file_name)
 
            # Open image file for reading (binary mode)
            with open(photo_path, 'rb') as f:
                tags = exifread.process_file(f)
                # Get the date the photo was taken
                try:
                    date_taken = tags["EXIF DateTimeOriginal"]
                    date_taken = datetime.datetime.strptime(str(date_taken), '%Y:%m:%d %H:%M:%S')
                except KeyError:
                    date_taken = datetime.datetime.fromtimestamp(os.path.getmtime(photo_path))
 
                year = date_taken.year
                month = date_taken.month
                # Create the destination folder
                dest_folder = os.path.join(dest_path, f"{year}-{month:02d}")
                # Create the destination folder if it does not already exist
                if not os.path.exists(dest_folder):
                    os.mkdir(dest_folder)
                # Create the new file name
                new_file_name = os.path.basename(photo_path)
                if os.path.exists(os.path.join(dest_folder, os.path.basename(photo_path))):
                                        new_file_name = f"{os.path.basename(photo_path).split('.')[0]}_{int(time.time())}.{os.path.basename(photo_path).split('.')[-1]}"
                # Move the photo to the destination folder
                try:
                    shutil.move(photo_path, os.path.join(dest_folder, new_file_name))
                except:
                    # Handle any errors that occur while moving the photo
                    print(f'An error occurred: {sys.exc_info()[0]}')
                # Log a message to indicate that the photo has been moved
                print(f"{photo_path} has been moved to {os.path.join(dest_folder, new_file_name)}")
                num_photos_processed += 1
        # If the file is a directory
        if os.path.isdir(os.path.join(directory_path, dir_name)):
            # Get the absolute path of the directory
            current_directory_path = os.path.join(directory_path, dir_name)
            # Read the directory
            if current_directory_path == source_path:
                return
            try:
                num_photos_processed += read_source_directory_recursive(current_directory_path, num_calls + 1, max_recursive_calls)
            except:
                # Handle any errors that occur while reading the directory recursively
                print(f'An error occurred: {sys.exc_info()[0]}')
    print(f"{num_photos_processed} photos were processed.")
    return num_photos_processed
 
# Call the read_source_directory_recursive function to process the files in the source directory
# Set the maximum number of recursive calls to 2000
read_source_directory_recursive(source_path, 0, 2000)

This is the full traceback:

Traceback (most recent call last):
  File "C:Python311Libshutil.py", line 825, in move
    os.rename(src, real_dst)
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'D:Test\(1).JPG' -> 'D:Test 2\2015-05\(1).JPG'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:UsersEdDocumentsCodeorganise_photos.py", line 54, in read_source_directory_recursive
    shutil.move(photo_path, os.path.join(dest_folder, new_file_name))
  File "C:Python311Libshutil.py", line 846, in move
    os.unlink(src)
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'D:Test\(1).JPG'
Traceback (most recent call last):
  File "C:UsersEdDocumentsCodeorganise_photos.py", line 77, in <module>
    read_source_directory_recursive(source_path, 0, 1)
  File "C:UsersEdDocumentsCodeorganise_photos.py", line 59, in read_source_directory_recursive
    print(f'An error occurred: {e}')
                                ^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
Asked By: Boswell

||

Answers:

Credit goes to @qouify who suggested this in the comment above however, the file was still being ‘read’ and so could not be deleted at the same time. To counter this, I had to close the with statement that was reading the file. The completed and working code is shown below:

Edit: I’ve also taken out HEIC files as they will not work with this code.
Final edit: changes to the code based on the @LhasaDad’s response – thank you!

import os
import shutil
import exifread
import datetime
import sys
import time

# Set the source directory path
source_path = os.path.join('D:', 'Test 2')
dest_path = os.path.join('D:', 'Test')
photo_extensions = ['.jpg', 'bmp', '.png', '.raw', '.jpeg']
 
def read_source_directory_recursive(directory_path, num_calls=0, max_recursive_calls=1000):
    # If the function has been called more than the maximum number of times, return early
    if num_calls > max_recursive_calls:
        return
    # Read the directory and get a list of files
    files = os.listdir(directory_path)
    # Initialize the variable that keeps track of the number of photos processed
    num_photos_processed = 0
    # Loop through the files
    for dir_name in files:
        file_name = dir_name
        # If the file is a photo based on the extension
        if any(file_name.lower().endswith(ext) for ext in photo_extensions):
            # Get the absolute path of the photo
            photo_path = os.path.join(directory_path, file_name)
 
            # Open image file for reading (binary mode)
            with open(photo_path, 'rb') as f:
                tags = exifread.process_file(f)
                #f.close()
            # Get the date the photo was taken
            try:
                date_taken = tags["EXIF DateTimeOriginal"]
                date_taken = datetime.datetime.strptime(str(date_taken), '%Y:%m:%d %H:%M:%S')
            except KeyError:
                    date_taken = datetime.datetime.fromtimestamp(os.path.getmtime(photo_path))
            year = date_taken.year
            month = date_taken.month
            # Create the destination folder
            dest_folder = os.path.join(dest_path, f"{year}-{month:02d}")
                # Create the destination folder if it does not already exist
            if not os.path.exists(dest_folder):
                os.mkdir(dest_folder)
            # Create the new file name
            new_file_name = os.path.basename(photo_path)
            if os.path.exists(os.path.join(dest_folder, os.path.basename(photo_path))):
                                    new_file_name = f"{os.path.basename(photo_path).split('.')[0]}_{int(time.time())}.{os.path.basename(photo_path).split('.')[-1]}"
            # Move the photo to the destination folder
            try:
                shutil.move(photo_path, os.path.join(dest_folder, new_file_name))
            except:
                    # Handle any errors that occur while moving the photo
                print(f'An error occurred: {sys.exc_info()[0]}')
            # Log a message to indicate that the photo has been moved
            print(f"{photo_path} has been moved to {os.path.join(dest_folder, new_file_name)}")
            num_photos_processed += 1
        # If the file is a directory
        if os.path.isdir(os.path.join(directory_path, dir_name)):
            # Get the absolute path of the directory
            current_directory_path = os.path.join(directory_path, dir_name)
            # Recursively call the function to process the contents of the directory
            read_source_directory_recursive(current_directory_path, num_calls + 1)
    return num_photos_processed
 
# Call the function to process the contents of the source directory
num_photos_processed = read_source_directory_recursive(source_path)
# Print the number of photos processed
print(f"{num_photos_processed} photos have been processed.")
Answered By: Boswell

As noted above the issue is the closing of the file. a clean way to handle this would be to just get the tags with the with statement and have the rest of the code as a peer indent level to the with statement:

   # Open image file for reading (binary mode)
    with open(photo_path, 'rb') as f:
        tags = exifread.process_file(f)
    # Get the date the photo was taken
    try:
        date_taken = tags["EXIF DateTimeOriginal"]
        date_taken = datetime.datetime.strptime(str(date_taken), '%Y:%m:%d %H:%M:%S')
     except KeyError:
        date_taken = datetime.datetime.fromtimestamp(os.path.getmtime(photo_path))
     ...
Answered By: LhasaDad
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.