DEV Community

loading...

Need To Rename Files in Bulk? Here's a Python CLI Called file-renamer

Isa Levine
Isa (ee-suh). She/her pronouns. Full stack developer working with Java, React, Jenkins, and Kubernetes. Drinks too much bubbly water.
・5 min read

Github repo: https://github.com/isalevine/file-renamer

One of my hobbies is making electronic music, and thanks to resources like samplesfrommars.com, I have tons and tons of audio files to manage. One drawback of the my main drum machine is that it limits readable filenames--anything after a certain number of characters is cut off, and any name-collisions will point to the first file with that name.

A lot of my music sample library has folders full of long, repetitive filenames, like this:

screenshot of folder showing long, repetitive audio file names

I got really tired of renaming them one-by-one, so I wrote my first Python script, file-renamer, as a CLI to do mass-renaming!

file-renamer Overview

Basically, I wanted a way to change or remove specific strings from multiple filenames at once. As a CLI, file-renamer also provides a way to navigate a filesystem to modify files in different folders quickly and easily from the command line.

gif of file-renamer in use on the command line

Setup

After cloning the repo to your computer, you can run file-renamer with $ python3 script.py in the repo's directory.

The only configuration you will need to do is setting a default path. See script.py line 37 (as of this writing) for the code to change:

# script.py

path = os.path.realpath('./drum-samples') # set default path here

Use

You can navigate your filesystem using the standard Unix commands pwd, ls, and cd. Renaming is done inside a particular folder with the rn or rn all commands. To quit, enter q or quit.

Change Directory:

cd

List Files:

ls

Print Directory:

pwd

Rename (single file, change entire filename):

rn <current_filename.ext> <new_filename.ext>

Ex.

$ Enter command: rn Airhorn.wav AIRHOOOORN.wav

Rename All (multiple files, replace LAST string match found):

rn all <string_input> <string_output>

Ex.

$ Enter command: rn all Hihat HH

Note: Currently, rn all will replace the LAST matching instance of <string_input> encountered in the filename (i.e. first match encountered when reading right-to-left). See str.rpartition() in the Python docs for more information.

Exit Program:

q / quit

Python's pathlib, os, and os.path modules

The functionality of this script is powered by Python's pathlib, os, and os.path modules. Here's how they're used:

Navigating filesystem:

All of these methods return strings which can be passed to pathlib.Path(str) to create a Path object, which is similar to a string but has different functionality:

In the file-renamer code, here's how they are implemented:

# script.py
import pathlib
import os

path = os.path.realpath('./drum-samples')   # set default path here
current_directory = pathlib.Path(path)

...

# inside main loop, after getting user_input
        if user_input[0:2] == "cd":
            temp_directory = current_directory

            if user_input[3:5] == "..":
                new_path = current_directory.parent
                current_directory = pathlib.Path(new_path)
            else:
                new_path = os.path.join(current_directory, user_input[3:])
                current_directory = pathlib.Path(new_path)
...

        if not os.path.isdir(current_directory):
            print("Not a valid directory!")
            current_directory = temp_directory

Here, when user_input is cd .. , a new_path string is created from current_directory.parent, and is turned back into a Path object with current_directory = pathlib.Path(new_path).

Otherwise, when user input is cd followed by something else, new_path is a string created by using os.path.join to add the contents of user_input onto the end of current_directory.

Validate file and directory names:

In the snippet above, we also see that os.path.isdir() is used to throw an error if no directory is found.

The os.path module provides a number of useful validation methods, including two which are used in this script:

In addition to the above snippet, we see os.path.isfile() in action here:

# script.py

# Here, array comes from using .split() on user_input
    if array[1] and array[2] and os.path.isfile(array[1]):
        rename_whole_filename(array[1], array[2])

    else:
        print("Rename aborted! (Probably because the original filename wasn't found.)")

After validating that the user_input variable, now split into array, contains an existing filename at array[1] and a new filename at array[2], os.path.isfile(array[1]) allows us to confirm that the existing filename actually exists. Otherwise, it will return false, which we handle with an else: error message.

Same thing happens with os.path.isdir(current_directory) above--it confirms that current_directory is an existing directory.

Rename files:

Hilariously, the core functionality of this whole CLI boils down to one simple os method:

Yep, that's it. Supply a string for src and dst, and os.rename() will take care of changing the filename.

Here's how it's used in the code:

# script.py
def rename_partial_filename(filename, input, output):
    src = str(filename)
    head, sep, tail = src.rpartition(input)
    dst = head + output + tail
    os.rename(src, dst)
    return dst

Here, src.rpartition(input) allows us to search the filename-string src from right-to-left, searching for a match for the string input. If found, that one instance is replaced with the given string output, and reconcatenated as the string dst. Calling os.rename(src, dst) will change the filename in your system.

Note: This method currently encounters errors when a file does NOT contain the given input! Instead, it will continue searching with .rpartition() until it finds a match in its parent directories, potentially causing directory errors! You've been warned...

That's it! Now you're ready to mass-rename your files! :)

Interested in contributing?

Once again, here's the link to the GitHub repo: https://github.com/isalevine/file-renamer

Please feel free to fork/clone/hack apart as you wish! If you do, I'd love to hear how you're using the script, whether it's for music sample libraries or something else entirely.

If you'd like to tackle some specific issues with the current code, here are a few on my list:

  1. In rn and rn all, build in handling for spaces--currently, there is no working way to remove a substring without adding any characters!
  2. In rn all, build in an option to specify how many substring matches are replaced. (Originally, all matches were replaced--hence the switch to using .rpartition()...)
  3. Add output logs for filename changes--currently, text is output to the console, but could easily be captured in a log file.

Thanks for reading! Please feel free to share any tips or advice for working with Python's pathlib, os, and os.path modules below!

Discussion (0)