I have a script that looks in two directories movies and series and I’ve my external hard connected and mounted on my system. However when I run the script it doesn’t seem to scan the watched directories and in turn make the wanted symlinks. Has anyone had this issue before and if so how was it fixed?
Here is a link to the script
https://pastebin.com/8ts5dFr8
import os
import sys
import json
import subprocess
import argparse
from time import sleep
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from rich.console import Console
from rich.logging import RichHandler
import logging
from guessit import guessit
from imdb import IMDb
# Initialize Rich logging
console = Console()
logger = logging.getLogger(__name__)
logger.addHandler(RichHandler(console=console, markup=True))
logger.setLevel(logging.DEBUG) # Set to DEBUG level for more detailed logs
# Path to the directory containing this script
script_directory = os.path.dirname(os.path.abspath(__file__))
# Path to Cinesync.py (adjust as needed)
script_path = os.path.join(script_directory, "Cinesync.py")
# Determine the paths based on the operating system
if os.name == 'posix': # Linux
MOVIES_WATCH_DIRECTORY = os.getenv('MOVIES_WATCH_DIRECTORY', "/mnt/remote/realdebrid/movies")
MOVIES_TARGET_DIRECTORY = os.getenv('MOVIES_TARGET_DIRECTORY', "/media-files")
SERIES_WATCH_DIRECTORY = os.getenv('SERIES_WATCH_DIRECTORY', "/mnt/remote/realdebrid/shows")
SERIES_TARGET_DIRECTORY = os.getenv('SERIES_TARGET_DIRECTORY', "/media-files")
WORKING_DIRECTORY = os.getenv('WORKING_DIRECTORY', "/path/to")
elif os.name == 'nt': # Windows
MOVIES_WATCH_DIRECTORY = os.getenv('MOVIES_WATCH_DIRECTORY', r"E:movies")
MOVIES_TARGET_DIRECTORY = os.getenv('MOVIES_TARGET_DIRECTORY', r"C:test")
SERIES_WATCH_DIRECTORY = os.getenv('SERIES_WATCH_DIRECTORY', r"E:shows")
SERIES_TARGET_DIRECTORY = os.getenv('SERIES_TARGET_DIRECTORY', r"C:test")
WORKING_DIRECTORY = os.getenv('WORKING_DIRECTORY', r"C:pathto")
else:
raise NotImplementedError("Unsupported operating system")
# Normalize paths and convert to absolute paths
MOVIES_WATCH_DIRECTORY = os.path.abspath(os.path.normpath(MOVIES_WATCH_DIRECTORY))
MOVIES_TARGET_DIRECTORY = os.path.abspath(os.path.normpath(MOVIES_TARGET_DIRECTORY))
SERIES_WATCH_DIRECTORY = os.path.abspath(os.path.normpath(SERIES_WATCH_DIRECTORY))
SERIES_TARGET_DIRECTORY = os.path.abspath(os.path.normpath(SERIES_TARGET_DIRECTORY))
WORKING_DIRECTORY = os.path.abspath(os.path.normpath(WORKING_DIRECTORY))
logger.info(f"Movies Watch Directory: {MOVIES_WATCH_DIRECTORY}")
logger.info(f"Movies Target Directory: {MOVIES_TARGET_DIRECTORY}")
logger.info(f"Series Watch Directory: {SERIES_WATCH_DIRECTORY}")
logger.info(f"Series Target Directory: {SERIES_TARGET_DIRECTORY}")
logger.info(f"Working Directory: {WORKING_DIRECTORY}")
class Handler(FileSystemEventHandler):
def __init__(self, movies_watch_directory, movies_target_directory, series_watch_directory, series_target_directory):
self.movies_watch_directory = movies_watch_directory
self.movies_target_directory = movies_target_directory
self.series_watch_directory = series_watch_directory
self.series_target_directory = series_target_directory
self.imdb = IMDb()
self.symlink_map_file = os.path.join(movies_target_directory, 'symlink_map.json')
self.load_symlink_map()
def load_symlink_map(self):
if os.path.exists(self.symlink_map_file):
with open(self.symlink_map_file, 'r') as f:
self.symlink_map = json.load(f)
else:
self.symlink_map = {}
def save_symlink_map(self):
with open(self.symlink_map_file, 'w') as f:
json.dump(self.symlink_map, f, indent=4)
def on_created(self, event):
logger.debug(f"Event created: {event.src_path}")
if not event.is_directory:
self.process(event.src_path)
def process(self, file_path):
if file_path.endswith(('.mp4', '.mkv')):
try:
file_info = guessit(file_path)
title = file_info.get('title')
file_type = file_info.get('type')
imdb_id = self.get_imdb_id(title)
if file_path.startswith(self.movies_watch_directory):
if file_type == 'movie':
self.process_movie(file_path, title, imdb_id)
elif file_path.startswith(self.series_watch_directory):
if file_type == 'episode':
self.process_series(file_path, file_info, imdb_id)
else:
logger.warning(f"Unknown file type for {file_path}")
except Exception as e:
logger.error(f"Error processing file: {e}")
def process_movie(self, file_path, title, imdb_id):
movie_dir = os.path.join(self.movies_target_directory, 'Movies')
logger.debug(f"Movie directory: {movie_dir}")
if not os.path.exists(movie_dir):
try:
os.makedirs(movie_dir)
logger.debug(f"Created movie directory: {movie_dir}")
except Exception as e:
logger.error(f"Failed to create movie directory: {e}")
return
movie_file_name = f"{title}.mp4"
symlink_path = os.path.join(movie_dir, movie_file_name)
logger.debug(f"Symlink path: {symlink_path}")
try:
if file_path in self.symlink_map:
logger.info(f"File {file_path} already has a symlink: {self.symlink_map[file_path]}")
else:
if not os.path.exists(symlink_path):
logger.debug(f"Creating symlink from {file_path} to {symlink_path}")
os.symlink(file_path, symlink_path)
if os.path.islink(symlink_path):
self.symlink_map[file_path] = symlink_path
self.save_symlink_map()
logger.info(f"Symlink created: {symlink_path}")
else:
logger.error(f"Failed to create symlink for {file_path}")
else:
logger.warning(f"Symlink already exists: {symlink_path}")
except OSError as e:
logger.error(f"Error creating symlink for {file_path}: {e}")
movies_folder_imdb_id = f"Movies - {imdb_id}"
movies_folder_path = os.path.join(self.movies_target_directory, movies_folder_imdb_id)
logger.debug(f"Movies folder IMDb ID path: {movies_folder_path}")
try:
if not os.path.exists(movies_folder_path):
os.rename(movie_dir, movies_folder_path)
logger.info(f"Renamed movies folder: {movie_dir} to {movies_folder_path}")
else:
logger.warning(f"Movies folder with IMDb ID already exists: {movies_folder_path}")
except Exception as e:
logger.error(f"Error renaming movies folder: {e}")
def process_series(self, file_path, file_info, imdb_id):
series_title = file_info.get('series')
season_number = file_info.get('season')
episode_number = file_info.get('episode')
logger.debug(f"Series title: {series_title}, Season: {season_number}, Episode: {episode_number}")
if series_title and season_number is not None and episode_number is not None:
series_dir = os.path.join(self.series_target_directory, 'Series', series_title)
if not os.path.exists(series_dir):
try:
os.makedirs(series_dir)
logger.debug(f"Created series directory: {series_dir}")
except Exception as e:
logger.error(f"Failed to create series directory: {e}")
return
season_dir = os.path.join(series_dir, f"Season {season_number}")
if not os.path.exists(season_dir):
try:
os.makedirs(season_dir)
logger.debug(f"Created season directory: {season_dir}")
except Exception as e:
logger.error(f"Failed to create season directory: {e}")
return
episode_file_name = f"{series_title} - S{season_number:02d}E{episode_number:02d}.mp4"
symlink_path = os.path.join(season_dir, episode_file_name)
logger.debug(f"Symlink path: {symlink_path}")
try:
if file_path in self.symlink_map:
logger.info(f"File {file_path} already has a symlink: {self.symlink_map[file_path]}")
else:
if not os.path.exists(symlink_path):
logger.debug(f"Creating symlink from {file_path} to {symlink_path}")
os.symlink(file_path, symlink_path)
if os.path.islink(symlink_path):
self.symlink_map[file_path] = symlink_path
self.save_symlink_map()
logger.info(f"Symlink created: {symlink_path}")
else:
logger.error(f"Failed to create symlink for {file_path}")
else:
logger.warning(f"Symlink already exists: {symlink_path}")
except OSError as e:
logger.error(f"Error creating symlink for {file_path}: {e}")
series_folder_imdb_id = f"{series_title} - {imdb_id}"
series_folder_path = os.path.join(self.series_target_directory, 'Series', series_folder_imdb_id)
logger.debug(f"Series folder IMDb ID path: {series_folder_path}")
try:
if not os.path.exists(series_folder_path):
os.rename(series_dir, series_folder_path)
logger.info(f"Renamed series folder: {series_dir} to {series_folder_path}")
else:
logger.warning(f"Series folder with IMDb ID already exists: {series_folder_path}")
except Exception as e:
logger.error(f"Error renaming series folder: {e}")
def get_imdb_id(self, title):
try:
results = self.imdb.search_movie(title)
if results:
return results[0].movieID
else:
logger.warning(f"No IMDb ID found for title: {title}")
return "N/A"
except Exception as e:
logger.error(f"Error fetching IMDb ID: {e}")
return "N/A"
def run_watcher(movies_watch_directory, movies_target_directory, series_watch_directory, series_target_directory):
event_handler = Handler(movies_watch_directory, movies_target_directory, series_watch_directory, series_target_directory)
observer = Observer()
observer.schedule(event_handler, movies_watch_directory, recursive=True)
observer.schedule(event_handler, series_watch_directory, recursive=True)
observer.start()
try:
logger.info("Watching for new files...")
while True:
sleep(1) # Add a sleep to prevent high CPU usage
except KeyboardInterrupt:
observer.stop()
observer.join()
def run_first_time_setup(movies_watch_directory, movies_target_directory, series_watch_directory, series_target_directory):
logger.info("Running first-time setup...")
if not os.path.exists(movies_watch_directory):
logger.error(f"Movies watch directory does not exist: {movies_watch_directory}")
return
if not os.path.exists(series_watch_directory):
logger.error(f"Series watch directory does not exist: {series_watch_directory}")
return
handler = Handler(movies_watch_directory, movies_target_directory, series_watch_directory, series_target_directory)
logger.info(f"Processing files in movies watch directory: {movies_watch_directory}")
for filename in os.listdir(movies_watch_directory):
file_path = os.path.join(movies_watch_directory, filename)
if os.path.isfile(file_path):
logger.info(f"Processing file: {file_path}")
handler.process(file_path)
logger.info(f"Processing files in series watch directory: {series_watch_directory}")
for filename in os.listdir(series_watch_directory):
file_path = os.path.join(series_watch_directory, filename)
if os.path.isfile(file_path):
logger.info(f"Processing file: {file_path}")
handler.process(file_path)
logger.info("First-time setup completed.")
def setup_service():
print("Choose the service setup:")
print("1. Systemd (Linux)")
print("2. Windows")
choice = input("Enter your choice: ")
if choice == "1":
# Setup systemd service
setup_systemd_service()
elif choice == "2":
# Setup Windows service
setup_windows_service()
else:
print("Invalid choice. Please enter '1' or '2'.")
def setup_systemd_service():
# Write systemd service file
service_content = f"""
[Unit]
Description=File Watcher Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 {script_path} --watch
WorkingDirectory={WORKING_DIRECTORY}
StandardOutput=syslog
StandardError=syslog
Restart=always
[Install]
WantedBy=multi-user.target
"""
with open('/etc/systemd/system/file_watcher.service', 'w') as f:
f.write(service_content)
# Enable and start the service
subprocess.run(['sudo', 'systemctl', 'daemon-reload'])
subprocess.run(['sudo', 'systemctl', 'enable', 'file_watcher.service'])
subprocess.run(['sudo', 'systemctl', 'start', 'file_watcher.service'])
print("Systemd service setup complete.")
def setup_windows_service():
# Path to the batch script that runs the Python script
batch_script_path = os.path.abspath("CineSync.bat")
# Create the batch script content
batch_script_content = f"""
@echo off
cd /d %~dp0
python {script_path} --watch
"""
# Write the batch script to a file
with open(batch_script_path, "w") as batch_file:
batch_file.write(batch_script_content)
# Install the service using SC command
service_name = "FileWatcherService"
service_exe_path = os.path.abspath("CineSync.bat")
sc_create_command = f'sc create {service_name} binPath= "{service_exe_path}" start= auto'
# Start the service
sc_start_command = f'sc start {service_name}'
# Execute SC commands
subprocess.run(sc_create_command, shell=True)
subprocess.run(sc_start_command, shell=True)
print("Windows service setup complete.")
def main():
parser = argparse.ArgumentParser(description="File Watcher and Organizer")
parser.add_argument('--watch', action='store_true', help='Run watcher mode')
parser.add_argument('--setup', action='store_true', help='Run first-time setup')
parser.add_argument('--service', action='store_true', help='Setup service')
args = parser.parse_args()
if args.watch:
run_watcher(MOVIES_WATCH_DIRECTORY, MOVIES_TARGET_DIRECTORY, SERIES_WATCH_DIRECTORY, SERIES_TARGET_DIRECTORY)
elif args.setup:
run_first_time_setup(MOVIES_WATCH_DIRECTORY, MOVIES_TARGET_DIRECTORY, SERIES_WATCH_DIRECTORY, SERIES_TARGET_DIRECTORY)
elif args.service:
setup_service()
else:
print("Welcome to the script main menu:")
print("1. Perform first-time setup")
print("2. Run watcher")
print("3. Setup service")
print("4. Exit")
choice = input("Enter your choice: ")
if choice == "1":
run_first_time_setup(MOVIES_WATCH_DIRECTORY, MOVIES_TARGET_DIRECTORY, SERIES_WATCH_DIRECTORY, SERIES_TARGET_DIRECTORY)
elif choice == "2":
run_watcher(MOVIES_WATCH_DIRECTORY, MOVIES_TARGET_DIRECTORY, SERIES_WATCH_DIRECTORY, SERIES_TARGET_DIRECTORY)
elif choice == "3":
setup_service()
elif choice == "4":
print("Quitting Script")
sys.exit(1)
else:
print("Invalid choice. Please enter '1', '2', or '3'.")
sys.exit(1)
if __name__ == '__main__':
main()
I have tried everything my novice brain can think of.