Last active
August 6, 2024 05:06
-
-
Save egeste/a021640a968eb69f02fda3519b83a6aa to your computer and use it in GitHub Desktop.
Rename movie files on your plex server based on matched data from the plex API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import re | |
import shutil | |
import argparse | |
from unidecode import unidecode | |
from plexapi.server import PlexServer | |
def sanitize_filename(name): | |
name = unidecode(name) | |
name = re.sub(" None$", "", name) | |
name = re.sub(r"[^a-zA-Z0-9&_\-\s]", "", name) | |
name = re.sub(r"\s+", " ", name) | |
name = name.strip() | |
return name | |
def rename_files(files): | |
if len(files) > 0: | |
print("-" * 80) | |
print("The following files can be renamed:") | |
for old_file, new_file in files: | |
print(f"{old_file} -> {new_file}") | |
rename_files = input("Would you like to rename these files? (y/n): ") | |
if rename_files.lower() == "y": | |
for old_file, new_file in files: | |
try: | |
print(f"Renaming {old_file} -> {new_file}") | |
os.makedirs(os.path.dirname(new_file), exist_ok=True) | |
os.rename(old_file, new_file) | |
except Exception as e: | |
print(f"Failed to rename {old_file} -> {new_file}: {e}") | |
def find_deletable_dir(file_path, base_directory): | |
deletable_basedir = os.path.dirname(file_path) | |
while deletable_basedir.startswith(base_directory) and deletable_basedir != base_directory: | |
new_deletable_basedir = os.path.dirname(deletable_basedir) | |
if (new_deletable_basedir == base_directory): | |
break | |
deletable_basedir = new_deletable_basedir | |
return deletable_basedir | |
def delete_directories(directories): | |
if len(directories) > 0: | |
print("-" * 80) | |
print("The following directories can be deleted:") | |
for delete_dir in directories: | |
print(f"{delete_dir}/") | |
delete_dirs = input("Would you like to delete these directories? (y/n): ") | |
if delete_dirs.lower() == "y": | |
for delete_dir in directories: | |
try: | |
print(f"Deleting {delete_dir}") | |
shutil.rmtree(delete_dir) | |
except Exception as e: | |
print(f"Failed to delete {delete_dir}: {e}") | |
def organize_movies(plex, base_directory): | |
movies = plex.library.section("Movies") | |
dirs_to_delete = [] | |
files_to_rename = [] | |
for movie in movies.all(): | |
file_path = movie.media[0].parts[0].file | |
old_filename = os.path.basename(file_path) | |
extension = os.path.splitext(old_filename)[1] | |
sanitized_title = sanitize_filename(movie.title) | |
new_filename = f"{sanitized_title} ({movie.year}){extension}" | |
new_file_path = os.path.join(base_directory, new_filename) | |
if file_path != new_file_path: | |
if (file_path, new_file_path) not in files_to_rename: | |
files_to_rename.append((file_path, new_file_path)) | |
delete_dir = find_deletable_dir(file_path, base_directory) | |
if delete_dir != base_directory: | |
if not any(new_file_path.startswith(delete_dir) for _, new_file_path in files_to_rename): | |
if delete_dir not in dirs_to_delete: | |
dirs_to_delete.append(delete_dir) | |
rename_files(files_to_rename) | |
delete_directories(dirs_to_delete) | |
def organize_tv_shows(plex, base_directory): | |
tv_shows = plex.library.section("TV Shows") | |
dirs_to_delete = [] | |
files_to_rename = [] | |
for show in tv_shows.all(): | |
for season in show.seasons(): | |
for episode in season.episodes(): | |
file_path = episode.media[0].parts[0].file | |
old_filename = os.path.basename(file_path) | |
sanitized_show_title = sanitize_filename(show.title) | |
season_folder = f"Season {season.index:02d}" | |
sanitized_episode_title = sanitize_filename(episode.title) | |
extension = os.path.splitext(old_filename)[1] | |
new_filename = f"{sanitized_show_title} - s{season.index:02d}e{episode.index:02d} - {sanitized_episode_title}{extension}" | |
new_file_path = os.path.join(base_directory, sanitized_show_title, season_folder, new_filename) | |
if file_path != new_file_path: | |
if (file_path, new_file_path) not in files_to_rename: | |
files_to_rename.append((file_path, new_file_path)) | |
delete_dir = find_deletable_dir(file_path, base_directory) | |
delete_new_dir = find_deletable_dir(new_file_path, base_directory) | |
if (delete_dir != delete_new_dir): | |
if delete_dir not in dirs_to_delete: | |
dirs_to_delete.append(delete_dir) | |
rename_files(files_to_rename) | |
delete_directories(dirs_to_delete) | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="Organize files in a Plex library") | |
parser.add_argument( | |
"-c", | |
"--collection", | |
help="The name of the collection to organize", | |
choices=["Movies", "Television"], | |
required=True, | |
) | |
parser.add_argument("-d", "--directory", help="The base directory to organize files in", required=True) | |
parser.add_argument("--baseurl", help="The base URL of the Plex server", default="http://localhost:32400") | |
parser.add_argument("--token", help="The Plex API token", default="YOUR_TOKEN_HERE") | |
args = parser.parse_args() | |
plex = PlexServer(args.baseurl, args.token) | |
if args.collection == "Movies": | |
organize_movies(plex, args.directory) | |
elif args.collection == "Television": | |
organize_tv_shows(plex, args.directory) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment