# Rename and move images to a destination directory based on a text file

As a followup to this question, I removed the Repository class and do the copy in the Folder class.

Suppose I have the following image directories under the Pictures directory of a Windows user profile:

C:\Users\Kiska\Pictures\Computers
- comp-amd-21343.jpg
- 12345.jpg
C:\Users\Kiska\Pictures\Cars
- car-random.jpg
- 54321.jpg


In each of those directories, they're are two images each. Notice that the names aren't reflective of what the images depicts.

My script will take a source directory, rename the images based on the folder name and the order in which it was renamed, and move it to a destination directory.

So the above images would be renamed:

computers_1.jpg
computers_2.jpg

cars_1.jpg
cars_2.jpg


Source.txt:

Computers
Cars


Folder.py:

import os
import shutil

class Folder:

def __init__(self, directory: str):
self._check_if_str_parameters_are_empty(parameter=directory, error_message="Directory cannot be empty")
self._directory = directory.strip()

def _check_if_str_parameters_are_empty(self, parameter, error_message):
if not parameter:
raise ValueError(error_message)

@property
def name(self) -> str:
return self._directory

def get_lst_of_files(self) -> list:
return os.listdir(self._directory)

def copy_files_with(self, extension: str, to_location: "Folder"):
if self == to_location:
raise ValueError("Source and Destination cannot be the same")
if "." in extension:
raise ValueError("Extension is incorrect type")

lst_of_images_in_source = os.listdir(self._directory)
number_of_images_in_source = len(lst_of_images_in_source)

print(f"Number of images: {number_of_images_in_source}")

if number_of_images_in_source:
number_of_images_in_destination = len(to_location.get_lst_of_files()) + 1

for number, image in enumerate(lst_of_images_in_source, start=number_of_images_in_destination):
source_image = os.path.join(self._directory, image)
destination_image = os.path.join(to_location.name,
self._construct_destination_string(self._get_base_name(str_to_split=self._directory),
number, extension))
print(f"{source_image} will be renamed to {destination_image}")
shutil.move(source_image,  destination_image)
else:
print("No images to rename")

def _get_base_name(self, str_to_split: str) -> str:
return str_to_split.split("\\")[-1]

def _construct_destination_string(self, image_name: str, current_number: str, extension: str) -> str:
return "{0}_{1}.{2}".format(image_name.lower().replace(" ", "_"), current_number, extension)

def __eq__(self, other):
if isinstance(other, Folder):
return (self._directory) == (other._directory)
return NotImplemented

def __hash__(self):
return hash(self._directory)


Main.py:

import os
from Folder import Folder

def main():

source_txt = "source.txt"
source_pictures = "{0}\\{1}".format(os.getenv("USERPROFILE"), "Pictures")
destination_pictures = None
extension = "jpg"

try:

if not destination_pictures:
raise ValueError("Please provide a valid destination path.")

if os.path.getsize(source_txt):
if os.path.exists(source_txt) and os.path.exists(destination_pictures):
source_folder = os.path.join(source_pictures, folder)
if os.path.exists(source_folder):
source = Folder(directory=source_folder)
destination_folder = os.path.join(destination_pictures, folder)
os.makedirs(destination_folder, exist_ok=True)
destination = Folder(directory=destination_folder)
source.copy_files_with(extension=extension, to_location=destination)
else:
print(f"{folder} doesn't exist")
else:
print("Source file or Destination drive is missing")
else:
print("Source file is empty")

except(ValueError, OSError) as error:
print(error)
finally:
pass

if __name__ == '__main__':
main()


I removed all the custom validation because as pointed out in my previous question, it was unnecessary.

Areas of concern:

• The finally block of the try statement has the pass under it. I handle the exceptions, and use the with statement to read the file, so I don't really know what should go there.

• I use type hinting, but I should also use docstrings and comments, but I didn't add them to save space.

• Am I understanding your code correctly? Are you basically copying all files from src/{name}/* to dst/{name}_{i}? – Peilonrayz Jul 14 at 19:03
• @Peilonrayz - Not all the files in Pictures, just the ones specified in the text file, it gets renamed and placed in the equivalent destination folder. – киска Jul 14 at 20:53

I'm in a rush so this is half baked :(

• It's best if you provide everything, currently your code isn't PEP 8 compliant due to you removing the docstrings. Some people may moan at you for this.
• You should prefer guard statements over if else statements when possible. You can easily change if os.path.getsize(source_txt) to use a not which would reduce the amount of indentation and reduce the complexity to read.
• You seem to have half baked error handling and I wouldn't be surprised if you're duplicating errors you can get from open and pathlib.
• I don't think you need a Folder class. If you use pathlib then everything you need should be fairly simple. I'm probably missing a couple of features but I think what you want is as simple as the below code.
from pathlib import Path

def main(source, src, dst, extensions):
src = Path(src)
dst = Path(dst)
with open(source) as folders:
for folder_name in folders:
for i, file in enumerate(
file
for file in (src / folder_name).iterdir()
if file.suffix in extensions
):
shutil.move(file,  dst / f'{folder_name}_{i}{file.suffix}')

if __name__ == '__main__':
try:
main(
"source.txt",
"{0}\\{1}".format(os.getenv("USERPROFILE"), "Pictures"),
None,
(".jpg",),
)
except Exception as e:
print(e)
raise SystemExit(1) from None