Skip to content

Instantly share code, notes, and snippets.

@egeste
Last active August 6, 2024 05:06
Show Gist options
  • Save egeste/a021640a968eb69f02fda3519b83a6aa to your computer and use it in GitHub Desktop.
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
#!/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