605 lines · 20.6 KB
Raw Download
1
import sys
2
import subprocess
3
import argparse
4
5
def install_package(package):
6
	subprocess.check_call([sys.executable, "-m", "pip", "install", package])
7
8
# Check and install required packages
9
required_packages = ['plexapi', 'requests', 'fuzzywuzzy', 'colorama', 'python-Levenshtein']
10
for package in required_packages:
11
	try:
12
		__import__(package.replace('-', '_'))  # Replace hyphen with underscore for import
13
	except ImportError:
14
		print(f"Installing {package}...")
15
		install_package(package)
16
17
import requests
18
from plexapi.server import PlexServer
19
import re
20
import csv
21
import time
22
import os
23
import configparser
24
from datetime import datetime
25
from fuzzywuzzy import fuzz
26
from fuzzywuzzy import process
27
from colorama import init, Fore, Style
28
29
CONFIG_FILE = 'plex_collection_config.ini'
30
31
# Initialize colorama
32
init()
33
34
### Plex server details - will be set via args or user input ###
35
PLEX_URL = ''
36
PLEX_TOKEN = ''
37
MOVIE_LIBRARY_NAME = ''
38
CSV_FILE_PATH = ''
39
IMDB_COLLECTION_NAME = ''
40
LOG_FILE_PATH = ''
41
REPORT_FILE_PATH = ''
42
43
# Fuzzy matching thresholds
44
TITLE_YEAR_MATCH_RATIO = 85  # Minimum match ratio for title + year
45
TITLE_ONLY_MATCH_RATIO = 90  # Minimum match ratio for title only
46
47
def load_config():
48
	"""Load saved configuration"""
49
	config = configparser.ConfigParser()
50
	if os.path.exists(CONFIG_FILE):
51
		config.read(CONFIG_FILE)
52
	return config
53
54
def save_config(url, token, library):
55
	"""Save configuration to file"""
56
	config = configparser.ConfigParser()
57
	config['DEFAULT'] = {
58
		'PLEX_URL': url,
59
		'PLEX_TOKEN': token,
60
		'MOVIE_LIBRARY_NAME': library
61
	}
62
	with open(CONFIG_FILE, 'w') as configfile:
63
		config.write(configfile)
64
65
def display_instructions():
66
	"""Display instructions for users who need guidance"""
67
	print(f"\n{Fore.CYAN}=== Instructions ==={Style.RESET_ALL}")
68
	print(f"{Fore.WHITE}1. Go to an IMDB list and export the list as a .CSV (like https://www.imdb.com/list/ls569744078/)")
69
	print(f"2. Rename that .CSV file from the IMDB list to something more memorable and friendly")
70
	print(f"3. Get your Plex token via these instructions: https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/{Style.RESET_ALL}\n")
71
72
def get_existing_collections():
73
	"""Get list of existing collections in the movie library"""
74
	try:
75
		plex = PlexServer(PLEX_URL, PLEX_TOKEN)
76
		movie_library = plex.library.section(MOVIE_LIBRARY_NAME)
77
		
78
		# Get all collections from the movie library
79
		collections = movie_library.collections()
80
		return [collection.title for collection in collections]
81
	except Exception as e:
82
		log_message(f"Error retrieving existing collections: {str(e)}", message_type="ERROR", print_to_console=True)
83
		return []
84
85
def parse_arguments():
86
	"""Parse command line arguments"""
87
	parser = argparse.ArgumentParser(description="Plex Collection Creator")
88
	parser.add_argument('-u', '--url', help='Plex server URL (default: http://127.0.0.1:32400)')
89
	parser.add_argument('-t', '--token', help='Plex authentication token')
90
	parser.add_argument('-c', '--csv', help='Path to the CSV file containing the movie list')
91
	parser.add_argument('-n', '--name', help='Name for the collection to be created or updated')
92
	parser.add_argument('-l', '--library', help='Plex movie library name (default: Movies)')
93
	parser.add_argument('-i', '--instructions', action='store_true', help='Display instructions and exit')
94
	return parser.parse_args()
95
96
def initialize_logging():
97
	"""Create log file with timestamp"""
98
	global LOG_FILE_PATH, REPORT_FILE_PATH
99
	timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
100
	LOG_FILE_PATH = f'plex_collection_log_{IMDB_COLLECTION_NAME}_{timestamp}.txt'
101
	REPORT_FILE_PATH = f'plex_collection_report_{IMDB_COLLECTION_NAME}_{timestamp}.csv'
102
	
103
	with open(LOG_FILE_PATH, 'w', encoding='utf-8') as log_file:
104
		log_file.write(f"Plex Collection Creation Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
105
		log_file.write(f"Collection Name: {IMDB_COLLECTION_NAME}\n")
106
		log_file.write(f"CSV File: {CSV_FILE_PATH}\n")
107
		log_file.write(f"Plex URL: {PLEX_URL}\n")
108
		log_file.write(f"Plex Library: {MOVIE_LIBRARY_NAME}\n")
109
		log_file.write("="*50 + "\n\n")
110
111
def log_message(message, print_to_console=True, message_type="INFO"):
112
	"""Log messages to file and optionally print to console with colors"""
113
	timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
114
	log_entry = f"[{timestamp}] {message}\n"
115
	
116
	with open(LOG_FILE_PATH, 'a', encoding='utf-8') as log_file:
117
		log_file.write(log_entry)
118
	
119
	if print_to_console:
120
		if message_type == "SUCCESS":
121
			print(f"{Fore.GREEN}{message}{Style.RESET_ALL}")
122
		elif message_type == "ERROR":
123
			print(f"{Fore.RED}{message}{Style.RESET_ALL}")
124
		elif message_type == "WARNING":
125
			print(f"{Fore.YELLOW}{message}{Style.RESET_ALL}")
126
		else:
127
			print(message)
128
129
def add_collection(library_key, rating_key, movie_title):
130
	"""Add movie to collection and return success status"""
131
	headers = {"X-Plex-Token": PLEX_TOKEN}
132
	params = {
133
		"type": 1,
134
		"id": rating_key,
135
		"collection[0].tag.tag": IMDB_COLLECTION_NAME,
136
		"collection.locked": 1
137
	}
138
139
	url = f"{PLEX_URL}/library/sections/{library_key}/all"
140
	try:
141
		response = requests.put(url, headers=headers, params=params)
142
		return response.status_code == 200
143
	except Exception as e:
144
		log_message(f"Error adding collection for {movie_title}: {str(e)}", message_type="ERROR")
145
		return False
146
147
def find_imdb_id(imdb_url):
148
	"""Extract id from imdb_url"""
149
	match = re.search(r'(tt\d+)', imdb_url)
150
	return match.group(0) if match else None
151
152
def clean_title(title):
153
	"""Clean title for better matching"""
154
	if not title:
155
		return ""
156
	return title.lower().replace("the ", "").strip()
157
158
def find_movie_by_title_year(movies, title, year, original_title=None):
159
	"""Find movie by title and year with fuzzy matching"""
160
	# Try exact match first
161
	for movie in movies:
162
		if (movie.title.lower() == title.lower() or 
163
			(original_title and movie.title.lower() == original_title.lower())):
164
			if str(movie.year) == str(year):
165
				return movie
166
	
167
	# Try fuzzy matching
168
	best_match = None
169
	highest_score = 0
170
	
171
	for movie in movies:
172
		# Compare with main title
173
		title_score = fuzz.ratio(clean_title(movie.title), clean_title(title))
174
		
175
		# Compare with original title if available
176
		original_score = 0
177
		if original_title:
178
			original_score = fuzz.ratio(clean_title(movie.title), clean_title(original_title))
179
		
180
		current_score = max(title_score, original_score)
181
		
182
		# Only consider if year matches
183
		if str(movie.year) == str(year) and current_score > highest_score:
184
			highest_score = current_score
185
			best_match = movie
186
	
187
	if best_match and highest_score >= TITLE_YEAR_MATCH_RATIO:
188
		log_message(f"Fuzzy matched '{title}' ({year}) to '{best_match.title}' ({best_match.year}) with score {highest_score}", message_type="WARNING")
189
		return best_match
190
	
191
	return None
192
193
def find_movie_by_title(movies, title, original_title=None):
194
	"""Find movie by title only with fuzzy matching"""
195
	best_match = None
196
	highest_score = 0
197
	
198
	for movie in movies:
199
		title_score = fuzz.ratio(clean_title(movie.title), clean_title(title))
200
		
201
		original_score = 0
202
		if original_title:
203
			original_score = fuzz.ratio(clean_title(movie.title), clean_title(original_title))
204
		
205
		current_score = max(title_score, original_score)
206
		
207
		if current_score > highest_score:
208
			highest_score = current_score
209
			best_match = movie
210
	
211
	if best_match and highest_score >= TITLE_ONLY_MATCH_RATIO:
212
		log_message(f"Title-only matched '{title}' to '{best_match.title}' with score {highest_score}", message_type="WARNING")
213
		return best_match
214
	
215
	return None
216
217
def find_existing_collection_items(plex, collection_name):
218
	"""Find all movies that are already in the specified collection"""
219
	existing_items = {}
220
	
221
	# Search all movie sections
222
	try:
223
		movie_library = plex.library.section(MOVIE_LIBRARY_NAME)
224
		collection_items = movie_library.search(collection=collection_name)
225
		
226
		# Create a map of both IMDB IDs and rating keys
227
		for item in collection_items:
228
			# Add by rating key
229
			existing_items[item.ratingKey] = item
230
			
231
			# Add by IMDB ID if available
232
			if 'imdb://' in item.guid:
233
				imdb_id = item.guid.split('imdb://')[1].split('?')[0]
234
				existing_items[imdb_id] = item
235
				
236
		log_message(f"Found {len(collection_items)} movies already in collection '{collection_name}'")
237
		return existing_items
238
	except Exception as e:
239
		log_message(f"Error finding existing collection items: {str(e)}", message_type="ERROR")
240
		return {}
241
242
def generate_report(report_data):
243
	"""Generate CSV report of processed movies"""
244
	with open(REPORT_FILE_PATH, 'w', newline='', encoding='utf-8') as csvfile:
245
		fieldnames = ['Title', 'Year', 'Original Title', 'IMDB_ID', 'Status', 'Match_Method', 'Plex_Title', 'Plex_Year', 'Notes']
246
		writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
247
		writer.writeheader()
248
		
249
		for item in report_data:
250
			writer.writerow({
251
				'Title': item['title'],
252
				'Year': item['year'],
253
				'Original Title': item.get('original_title', ''),
254
				'IMDB_ID': item['imdb_id'],
255
				'Status': item['status'],
256
				'Match_Method': item.get('match_method', ''),
257
				'Plex_Title': item.get('plex_title', ''),
258
				'Plex_Year': item.get('plex_year', ''),
259
				'Notes': item.get('notes', '')
260
			})
261
262
def verify_plex_connection():
263
	"""Verify connection to Plex server"""
264
	try:
265
		plex = PlexServer(PLEX_URL, PLEX_TOKEN)
266
		log_message(f"Connected to Plex server at {PLEX_URL}")
267
		return plex
268
	except Exception as e:
269
		log_message(f"Failed to connect to Plex server: {str(e)}", message_type="ERROR")
270
		print(f"\n{Fore.RED}Unable to connect to Plex server. Please verify your URL and token.{Style.RESET_ALL}")
271
		display_instructions()
272
		return None
273
274
def run_imdb_list():
275
	initialize_logging()
276
	report_data = []
277
	
278
	# Verify Plex connection
279
	plex = verify_plex_connection()
280
	if not plex:
281
		return []
282
283
	# Read local CSV file
284
	log_message(f"Reading IMDB list from: {CSV_FILE_PATH}")
285
	try:
286
		with open(CSV_FILE_PATH, mode='r', encoding='utf-8') as csv_file:
287
			reader = csv.DictReader(csv_file)
288
			rows = list(reader)
289
			url_column = 'URL' if 'URL' in reader.fieldnames else 'url'
290
			
291
			log_message(f"Found {len(rows)} movies in the CSV file")
292
	except Exception as e:
293
		log_message(f"Error reading CSV file: {str(e)}", message_type="ERROR")
294
		return []
295
296
	# Get all movies from Plex library
297
	log_message(f"Retrieving movies from Plex library: {MOVIE_LIBRARY_NAME}")
298
	try:
299
		movie_library = plex.library.section(MOVIE_LIBRARY_NAME)
300
		all_movies = movie_library.all()
301
		log_message(f"Found {len(all_movies)} movies in Plex library")
302
	except Exception as e:
303
		log_message(f"Error accessing Plex library: {str(e)}", message_type="ERROR")
304
		print(f"\n{Fore.RED}Error: Library '{MOVIE_LIBRARY_NAME}' not found. Please check your library name.{Style.RESET_ALL}")
305
		return []
306
307
	# Find existing collection items
308
	existing_collection_items = find_existing_collection_items(plex, IMDB_COLLECTION_NAME)
309
310
	# Create IMDB ID to movie mapping
311
	imdb_map = {}
312
	for movie in all_movies:
313
		if 'imdb://' in movie.guid:
314
			imdb_id = movie.guid.split('imdb://')[1].split('?')[0]
315
			imdb_map[imdb_id] = movie
316
317
	# Process each movie from CSV in order
318
	log_message(f"\nStarting collection creation/update for {IMDB_COLLECTION_NAME}")
319
	added_count = 0
320
	skipped_count = 0
321
	missing_count = 0
322
	
323
	# First pass - find all matches and create ordered list
324
	ordered_movies = []
325
	
326
	for row in rows:
327
		imdb_url = row.get(url_column, '')
328
		imdb_id = find_imdb_id(imdb_url)
329
		title = row.get('Title', 'Unknown')
330
		original_title = row.get('Original Title', '')
331
		year = row.get('Year', '')
332
		
333
		report_item = {
334
			'title': title,
335
			'year': year,
336
			'original_title': original_title,
337
			'imdb_id': imdb_id or '',
338
			'status': '',
339
			'plex_title': '',
340
			'plex_year': '',
341
			'notes': ''
342
		}
343
344
		if not imdb_id:
345
			status = "ERROR: No IMDB ID found"
346
			report_item.update({
347
				'status': status,
348
				'notes': f'Invalid URL: {imdb_url}'
349
			})
350
			log_message(f"{status} - {title} ({year})", message_type="ERROR")
351
			missing_count += 1
352
			report_data.append(report_item)
353
			continue
354
355
		# Try to find movie by IMDB ID first
356
		movie = imdb_map.get(imdb_id)
357
		match_method = "IMDB ID" if movie else ""
358
		
359
		# If not found by IMDB ID, try title + year
360
		if not movie:
361
			movie = find_movie_by_title_year(all_movies, title, year, original_title)
362
			if movie:
363
				match_method = "Title + Year Fuzzy Match"
364
		
365
		# If still not found, try title only
366
		if not movie:
367
			movie = find_movie_by_title(all_movies, title, original_title)
368
			if movie:
369
				match_method = "Title Only Fuzzy Match"
370
				# Verify year isn't too different
371
				if year and abs(int(movie.year) - int(year)) > 2:
372
					log_message(f"Rejected title-only match for {title} due to year mismatch (Plex: {movie.year}, CSV: {year})", message_type="WARNING")
373
					movie = None
374
		
375
		if not movie:
376
			status = "MISSING: Not found in Plex library"
377
			report_item.update({
378
				'status': status,
379
				'notes': 'Could not match by IMDB ID, title+year, or title only'
380
			})
381
			log_message(f"{status} - {title} ({year})", message_type="ERROR")
382
			missing_count += 1
383
			report_data.append(report_item)
384
			continue
385
386
		# Check if the movie is already in the collection
387
		already_in_collection = False
388
		if movie.ratingKey in existing_collection_items:
389
			already_in_collection = True
390
		elif imdb_id in existing_collection_items:
391
			already_in_collection = True
392
393
		if already_in_collection:
394
			status = "SKIPPED:"
395
			report_item.update({
396
				'status': status,
397
				'match_method': match_method,
398
				'plex_title': movie.title,
399
				'plex_year': movie.year
400
			})
401
			log_message(f"{status} {movie.title} ({movie.year})", message_type="INFO")
402
			skipped_count += 1
403
			report_data.append(report_item)
404
			continue
405
406
		# Add to our ordered list for movies not in collection
407
		ordered_movies.append({
408
			'movie': movie,
409
			'report_item': report_item,
410
			'match_method': match_method,
411
			'original_position': len(ordered_movies)  # Maintain original order
412
		})
413
414
	# Second pass - add to collection in order
415
	for item in ordered_movies:
416
		movie = item['movie']
417
		report_item = item['report_item']
418
		match_method = item['match_method']
419
		
420
		# Try to add to collection
421
		success = add_collection(movie.librarySectionID, movie.ratingKey, movie.title)
422
		if success:
423
			status = "ADDED:"
424
			report_item.update({
425
				'status': status,
426
				'match_method': match_method,
427
				'plex_title': movie.title,
428
				'plex_year': movie.year
429
			})
430
			message_type = "SUCCESS" if match_method == "IMDB ID" else "WARNING"
431
			log_message(f"{status} {movie.title} ({movie.year}) - (matched by {match_method})", message_type=message_type)
432
			added_count += 1
433
		else:
434
			status = "ERROR:"
435
			report_item.update({
436
				'status': status,
437
				'match_method': match_method,
438
				'plex_title': movie.title,
439
				'plex_year': movie.year,
440
				'notes': 'Plex API error'
441
			})
442
			log_message(f"{status} {movie.title} ({movie.year})", message_type="ERROR")
443
			missing_count += 1
444
		
445
		report_data.append(report_item)
446
447
	# Generate reports
448
	log_message(f"\nProcessing complete: {Fore.GREEN}{added_count} added{Style.RESET_ALL}, {Fore.YELLOW}{skipped_count} already in collection{Style.RESET_ALL}, {Fore.RED}{missing_count} not found/failed{Style.RESET_ALL}.")
449
	generate_report(report_data)
450
	log_message(f"Detailed report saved to: {REPORT_FILE_PATH}")
451
	log_message(f"Full log saved to: {LOG_FILE_PATH}")
452
453
	return report_data
454
455
def test_plex_connection():
456
	"""Test connection to Plex server and validate library name"""
457
	try:
458
		plex = PlexServer(PLEX_URL, PLEX_TOKEN)
459
		print(f"{Fore.GREEN}✓ Successfully connected to Plex server{Style.RESET_ALL}")
460
		
461
		# Validate library name
462
		try:
463
			library = plex.library.section(MOVIE_LIBRARY_NAME)
464
			print(f"{Fore.GREEN}✓ Successfully found library: {MOVIE_LIBRARY_NAME} (contains {len(library.all())} items){Style.RESET_ALL}")
465
			return True
466
		except Exception as e:
467
			print(f"{Fore.RED}✗ Library '{MOVIE_LIBRARY_NAME}' not found{Style.RESET_ALL}")
468
			print(f"{Fore.CYAN}Available libraries:{Style.RESET_ALL}")
469
			for section in plex.library.sections():
470
				print(f"  - {section.title}")
471
			return False
472
			
473
	except Exception as e:
474
		print(f"{Fore.RED}✗ Failed to connect to Plex server: {str(e)}{Style.RESET_ALL}")
475
		return False
476
477
def get_user_input():
478
	"""Get all required parameters from user if not provided via args"""
479
	global CSV_FILE_PATH, IMDB_COLLECTION_NAME, PLEX_URL, PLEX_TOKEN, MOVIE_LIBRARY_NAME
480
	
481
	# Parse command line arguments
482
	args = parse_arguments()
483
	
484
	# Check if the user just wants instructions
485
	if args.instructions:
486
		display_instructions()
487
		sys.exit(0)
488
	
489
	# Load saved config
490
	config = load_config()
491
	
492
	# Get Plex URL
493
	if args.url:
494
		PLEX_URL = args.url
495
	else:
496
		default_url = config.get('DEFAULT', 'PLEX_URL', fallback='http://127.0.0.1:32400')
497
		PLEX_URL = input(f"Enter your Plex server URL [Press Enter for {default_url}]: ").strip()
498
		if not PLEX_URL:
499
			PLEX_URL = default_url
500
	
501
	# Make sure URL has proper format
502
	if not PLEX_URL.startswith("http"):
503
		PLEX_URL = "http://" + PLEX_URL
504
	
505
	# Get Plex token
506
	if args.token:
507
		PLEX_TOKEN = args.token
508
	else:
509
		default_token = config.get('DEFAULT', 'PLEX_TOKEN', fallback='')
510
		PLEX_TOKEN = input(f"Enter your Plex authentication token [Press Enter to use saved token]: ").strip()
511
		if not PLEX_TOKEN:
512
			PLEX_TOKEN = default_token
513
		if not PLEX_TOKEN:
514
			print(f"{Fore.RED}Plex token is required{Style.RESET_ALL}")
515
			display_instructions()
516
			sys.exit(1)
517
	
518
	# Get Plex library name
519
	if args.library:
520
		MOVIE_LIBRARY_NAME = args.library
521
	else:
522
		default_library = config.get('DEFAULT', 'MOVIE_LIBRARY_NAME', fallback='Movies')
523
		MOVIE_LIBRARY_NAME = input(f"Enter your Plex movie library name [Press Enter for {default_library}]: ").strip()
524
		if not MOVIE_LIBRARY_NAME:
525
			MOVIE_LIBRARY_NAME = default_library
526
	
527
	# Save the current configuration (only if not using command line args)
528
	if not (args.url or args.token or args.library):
529
		save_config(PLEX_URL, PLEX_TOKEN, MOVIE_LIBRARY_NAME)
530
	
531
	# Test Plex connection
532
	print(f"\n{Fore.CYAN}Testing Plex connection...{Style.RESET_ALL}")
533
	if not test_plex_connection():
534
		retry = input(f"\n{Fore.YELLOW}Would you like to retry with different Plex settings? (y/n): {Style.RESET_ALL}").lower()
535
		if retry == 'y':
536
			PLEX_URL = ""
537
			PLEX_TOKEN = ""
538
			MOVIE_LIBRARY_NAME = ""
539
			get_user_input()
540
			return
541
		else:
542
			print(f"{Fore.RED}Exiting due to Plex connection issues.{Style.RESET_ALL}")
543
			sys.exit(1)
544
	
545
	# Get CSV file path
546
	if args.csv:
547
		CSV_FILE_PATH = args.csv
548
	else:
549
		while not (CSV_FILE_PATH and os.path.isfile(CSV_FILE_PATH)):
550
			path = input("\nEnter the path to your IMDB CSV file: ")
551
			if os.path.isfile(path):
552
				CSV_FILE_PATH = path
553
			else:
554
				print(f"{Fore.RED}File not found: {path}{Style.RESET_ALL}")
555
				print(f"{Fore.YELLOW}Remember to export a list from IMDB as a CSV file first.{Style.RESET_ALL}")
556
	
557
	# Prompt for new or existing collection
558
	if args.name:
559
		IMDB_COLLECTION_NAME = args.name
560
	else:
561
		# Get existing collections to offer as options
562
		existing_collections = get_existing_collections()
563
		
564
		if existing_collections:
565
			update_existing = input(f"\n{Fore.CYAN}Would you like to update an existing collection? (y/n): {Style.RESET_ALL}").lower() == 'y'
566
			
567
			if update_existing:
568
				print(f"\n{Fore.CYAN}Available collections:{Style.RESET_ALL}")
569
				for i, collection in enumerate(existing_collections, 1):
570
					print(f"{i}. {collection}")
571
				
572
				while True:
573
					choice = input(f"\nEnter the number of the collection to update (or 'n' for new collection): ")
574
					if choice.lower() == 'n':
575
						break
576
					try:
577
						index = int(choice) - 1
578
						if 0 <= index < len(existing_collections):
579
							IMDB_COLLECTION_NAME = existing_collections[index]
580
							print(f"{Fore.GREEN}Selected collection: {IMDB_COLLECTION_NAME}{Style.RESET_ALL}")
581
							return
582
						else:
583
							print(f"{Fore.RED}Invalid selection. Please try again.{Style.RESET_ALL}")
584
					except ValueError:
585
						print(f"{Fore.RED}Please enter a valid number or 'n'.{Style.RESET_ALL}")
586
		
587
		# Ask for new collection name
588
		while not IMDB_COLLECTION_NAME:
589
			name = input("\nEnter the name for your new collection: ")
590
			if name.strip():
591
				IMDB_COLLECTION_NAME = name.strip()
592
			else:
593
				print(f"{Fore.RED}Collection name cannot be empty{Style.RESET_ALL}")
594
595
if __name__ == "__main__":
596
	print(f"{Fore.CYAN}=== Plex Collection Creator ==={Style.RESET_ALL}")
597
	
598
	# Get user input or command line arguments
599
	get_user_input()
600
	
601
	# Run the script
602
	run_imdb_list()
603
	
604
	print(f"\n{Fore.CYAN}Processing complete. Check the log and report files for details.{Style.RESET_ALL}")
605