Step 3 - The Code

You can either use the code attached right below or see the script and copy it into your editor of choice
python
import requests import random import tkinter as tk from tkinter import messagebox, ttk import webbrowser from PIL import Image, ImageTk import io import threading import os import json # Directory for cached posters POSTER_CACHE_DIR = 'poster_cache' os.makedirs(POSTER_CACHE_DIR, exist_ok=True) # File to store API credentials API_CREDENTIALS_FILE = 'api_credentials.json' # Function to load API credentials def load_api_credentials(): if os.path.exists(API_CREDENTIALS_FILE): with open(API_CREDENTIALS_FILE, 'r') as f: return json.load(f) return {} # Function to save API credentials def save_api_credentials(credentials): with open(API_CREDENTIALS_FILE, 'w') as f: json.dump(credentials, f) # Function to ask for API credentials def ask_for_api_credentials(): credentials = {} def on_submit(): credentials['TRAKT_CLIENT_ID'] = trakt_client_id_entry.get() credentials['TRAKT_ACCESS_TOKEN'] = trakt_access_token_entry.get() credentials['TMDB_API_KEY'] = tmdb_api_key_entry.get() save_api_credentials(credentials) credentials_window.destroy() credentials_window = tk.Toplevel(root) credentials_window.title("Enter API Credentials") tk.Label(credentials_window, text="Trakt Client ID:").pack() trakt_client_id_entry = tk.Entry(credentials_window) trakt_client_id_entry.pack() tk.Label(credentials_window, text="Trakt Access Token:").pack() trakt_access_token_entry = tk.Entry(credentials_window) trakt_access_token_entry.pack() tk.Label(credentials_window, text="TMDB API Key:").pack() tmdb_api_key_entry = tk.Entry(credentials_window) tmdb_api_key_entry.pack() submit_button = tk.Button(credentials_window, text="Submit", command=on_submit) submit_button.pack(pady=10) credentials_window.transient(root) credentials_window.grab_set() root.wait_window(credentials_window) return credentials # Create GUI root = tk.Tk() root.title("Trakt Watchlist") root.geometry("1438x875") # Set the initial size of the window root.resizable(True, True) # Load or ask for API credentials credentials = load_api_credentials() if not credentials: credentials = ask_for_api_credentials() # Trakt and TMDB API credentials TRAKT_CLIENT_ID = credentials['TRAKT_CLIENT_ID'] TRAKT_ACCESS_TOKEN = credentials['TRAKT_ACCESS_TOKEN'] TMDB_API_KEY = credentials['TMDB_API_KEY'] selected_label = None # Track the currently selected label genres_dict = {} # Store genres for each movie posters_dict = {} # Store posters for each movie languages_dict = {} # Store languages for each movie runtimes_dict = {} # Store runtimes for each movie ratings_dict = {} # Store ratings for each movie current_index = 0 # Define current_index # Variable definitions for filters decade_var = tk.StringVar(value="All") genre_var = tk.StringVar(value="All") language_var = tk.StringVar(value="All") runtime_var = tk.StringVar(value="All") letter_var = tk.StringVar(value="All") sort_order_var = tk.StringVar(value="By Date Added to My List (Newest to Oldest)") search_var = tk.StringVar(value="") # Function to get watchlist from Trakt def get_watchlist(): url = "https://api.trakt.tv/users/me/watchlist/movies" headers = { "Content-Type": "application/json", "trakt-api-version": "2", "trakt-api-key": TRAKT_CLIENT_ID, "Authorization": f"Bearer {TRAKT_ACCESS_TOKEN}" } response = requests.get(url, headers=headers) if response.status_code == 403: messagebox.showerror("Error", "Access forbidden: Check your API credentials.") return [] response.raise_for_status() return response.json() # Function to get movie details from TMDB def get_tmdb_movie_details(tmdb_id): url = f"https://api.themoviedb.org/3/movie/{tmdb_id}" params = { "api_key": TMDB_API_KEY, "append_to_response": "credits" } response = requests.get(url, params=params) response.raise_for_status() return response.json() # Function to get movie poster from TMDB def get_movie_poster(tmdb_id): poster_path = os.path.join(POSTER_CACHE_DIR, f'{tmdb_id}.jpg') if os.path.exists(poster_path): return poster_path url = f"https://api.themoviedb.org/3/movie/{tmdb_id}/images" params = { "api_key": TMDB_API_KEY } response = requests.get(url, params=params) response.raise_for_status() images = response.json().get("posters", []) if images: poster_url = f"https://image.tmdb.org/t/p/w200{images[0]['file_path']}" poster_image = requests.get(poster_url).content with open(poster_path, 'wb') as f: f.write(poster_image) return poster_path return None # Function to get movie trailer from TMDB def get_movie_trailer(tmdb_id): url = f"https://api.themoviedb.org/3/movie/{tmdb_id}/videos" params = { "api_key": TMDB_API_KEY } response = requests.get(url, params=params) response.raise_for_status() videos = response.json().get("results", []) for video in videos: if video["type"] == "Trailer" and video["site"] == "YouTube": return f"https://www.youtube.com/watch?v={video['key']}" return None # Function to display movie details def show_movie_details(title, year, slug, tmdb_id, detail_frame): for widget in detail_frame.winfo_children(): widget.destroy() movie_details = get_tmdb_movie_details(tmdb_id) # Movie title and year label_title = tk.Label(detail_frame, text=f"{title} ({year})", font=("Helvetica", 16, "bold")) label_title.pack(pady=10) # Poster poster_url = get_movie_poster(tmdb_id) if poster_url: poster_image = Image.open(poster_url) poster_image = poster_image.resize((100, 150), Image.LANCZOS) poster_photo = ImageTk.PhotoImage(poster_image) poster_label = tk.Label(detail_frame, image=poster_photo) poster_label.image = poster_photo poster_label.pack(pady=10) else: poster_label = tk.Label(detail_frame, text="Poster not available") poster_label.pack(pady=10) # Director directors = [person['name'] for person in movie_details.get('credits', {}).get('crew', []) if person['job'] == 'Director'] label_director = tk.Label(detail_frame, text=f"Director: {', '.join(directors) if directors else 'Unknown'}") label_director.pack(pady=5) # Cast cast = [person['name'] for person in movie_details.get('credits', {}).get('cast', [])[:5]] label_cast = tk.Label(detail_frame, text=f"Cast: {', '.join(cast) if cast else 'Unknown'}") label_cast.pack(pady=5) # Language language = languages_dict.get(tmdb_id, "Unknown") label_language = tk.Label(detail_frame, text=f"Language: {language}") label_language.pack(pady=5) # Runtime runtime = runtimes_dict.get(tmdb_id, "Unknown") label_runtime = tk.Label(detail_frame, text=f"Runtime: {runtime} minutes") label_runtime.pack(pady=5) # Genre genres = genres_dict.get(tmdb_id, []) label_genre = tk.Label(detail_frame, text=f"Genre: {', '.join(genres).title() if genres else 'Unknown'}") label_genre.pack(pady=5) # Rating rating = movie_details.get("vote_average", "N/A") if isinstance(rating, (float, int)): rating = f"{rating:.1f}" label_rating = tk.Label(detail_frame, text=f"Rating: {rating}") label_rating.pack(pady=5) # Synopsis synopsis = movie_details.get("overview", "Synopsis not available.") label_synopsis = tk.Label(detail_frame, text=synopsis, wraplength=400, justify="left") label_synopsis.pack(pady=5) # Buttons frame buttons_frame = tk.Frame(detail_frame) buttons_frame.pack(pady=10) # Trakt URL button trakt_url = f"https://trakt.tv/movies/{slug}" open_link_button = tk.Button(buttons_frame, text="View on Trakt", command=lambda: webbrowser.open(trakt_url)) open_link_button.pack(side=tk.LEFT, padx=10) # Trailer button trailer_url = get_movie_trailer(tmdb_id) if trailer_url: trailer_button = tk.Button(buttons_frame, text="Watch Trailer", command=lambda: webbrowser.open(trailer_url)) trailer_button.pack(side=tk.LEFT, padx=10) else: trailer_button = tk.Button(buttons_frame, text="No Trailer Available", state=tk.DISABLED) trailer_button.pack(side=tk.LEFT, padx=10) # Function to filter movies based on selected decade, genre, language, runtime, starting letter, and search query def filter_movies(): selected_decade = decade_var.get() selected_genre = genre_var.get().lower() selected_language = language_var.get().lower() selected_runtime = runtime_var.get() selected_letter = letter_var.get().lower() sort_order = sort_order_var.get() search_query = search_var.get().lower() filtered_movies = [] for movie in movies: movie_title = movie['movie']['title'] movie_year = movie['movie']['year'] tmdb_id = movie['movie']['ids'].get('tmdb') movie_genres = genres_dict.get(tmdb_id, []) movie_language = languages_dict.get(tmdb_id, 'unknown').lower() movie_runtime = runtimes_dict.get(tmdb_id, 0) movie_rating = ratings_dict.get(tmdb_id, 0) if selected_decade != "All": start_year = int(selected_decade) if not (start_year <= movie_year < start_year + 10): continue if selected_genre != "all": if not any(selected_genre in genre for genre in movie_genres): continue if selected_language != "all": if selected_language != movie_language: continue if selected_runtime != "All": if selected_runtime == "Less than 60 minutes" and movie_runtime >= 60: continue elif selected_runtime == "60 to 90 minutes" and not (60 <= movie_runtime <= 90): continue elif selected_runtime == "90 to 120 minutes" and not (90 <= movie_runtime <= 120): continue elif selected_runtime == "More than 120 minutes" and movie_runtime <= 120: continue if selected_letter != "all": if not movie_title.lower().startswith(selected_letter): continue if search_query and search_query not in movie_title.lower(): continue filtered_movies.append(movie) # Sorting the filtered movies based on the selected sort order if sort_order == "Alphabetically (A-Z)": filtered_movies.sort(key=lambda x: x['movie']['title']) elif sort_order == "Alphabetically (Z-A)": filtered_movies.sort(key=lambda x: x['movie']['title'], reverse=True) elif sort_order == "By Date Released (Newest to Oldest)": filtered_movies.sort(key=lambda x: x['movie']['year'], reverse=True) elif sort_order == "By Date Released (Oldest to Newest)": filtered_movies.sort(key=lambda x: x['movie']['year']) elif sort_order == "By Date Added to My List (Newest to Oldest)": filtered_movies.sort(key=lambda x: x['listed_at'], reverse=True) elif sort_order == "By Date Added to My List (Oldest to Newest)": filtered_movies.sort(key=lambda x: x['listed_at']) elif sort_order == "Ranking (Highest to Lowest)": filtered_movies.sort(key=lambda x: ratings_dict.get(x['movie']['ids']['tmdb'], 0), reverse=True) elif sort_order == "Ranking (Lowest to Highest)": filtered_movies.sort(key=lambda x: ratings_dict.get(x['movie']['ids']['tmdb'], 0)) return filtered_movies # Function to pick a random movie from the watchlist def pick_random_movie(): filtered_movies = filter_movies() if filtered_movies: random_movie = random.choice(filtered_movies) movie_title = random_movie['movie']['title'] movie_year = random_movie['movie']['year'] movie_slug = random_movie['movie']['ids']['slug'] tmdb_id = random_movie['movie']['ids'].get('tmdb') show_movie_details(movie_title, movie_year, movie_slug, tmdb_id, detail_frame) set_open_link_button(f"https://trakt.tv/movies/{movie_slug}") else: messagebox.showwarning("Warning", "No movies match the selected filters.") # Function to update displayed movies def update_displayed_movies(*args): global current_index current_index = 0 # Reset to the first page whenever filters are changed display_movies() def display_movies(): for widget in watchlist_frame.winfo_children(): widget.destroy() start_index = current_index * 5 end_index = start_index + 5 filtered_movies = filter_movies() for movie in filtered_movies[start_index:end_index]: movie_title = movie['movie']['title'] movie_year = movie['movie']['year'] movie_slug = movie['movie']['ids'].get('slug') tmdb_id = movie['movie']['ids'].get('tmdb') movie_url = f"https://trakt.tv/movies/{movie_slug}" frame = tk.Frame(watchlist_frame) frame.pack(fill=tk.X, pady=5) # Poster poster_url = posters_dict.get(tmdb_id) if poster_url: poster_image = Image.open(poster_url) poster_image = poster_image.resize((50, 75), Image.LANCZOS) poster_photo = ImageTk.PhotoImage(poster_image) poster_label = tk.Label(frame, image=poster_photo) poster_label.image = poster_photo poster_label.pack(side=tk.TOP) else: poster_label = tk.Label(frame, text="No Poster", width=10) poster_label.pack() label = tk.Label(frame, text=f"{movie_title} ({movie_year})", fg="blue", cursor="hand2", bg=root.cget("bg")) label.bind("<Button-1>", lambda e, url=movie_url, lbl=label: toggle_selection(lbl, url)) label.bind("<Double-Button-1>", lambda e, title=movie_title, year=movie_year, slug=movie_slug, tmdb_id=tmdb_id: show_movie_details(title, year, slug, tmdb_id, detail_frame)) label.pack() update_page_navigation(len(filtered_movies)) # Function to toggle selection of a movie label def toggle_selection(label, url): global selected_label if selected_label is label: # Unselect the label label.config(bg=root.cget("bg")) selected_label = None open_link_button.config(state=tk.DISABLED) else: # Unselect the previous label if selected_label: selected_label.config(bg=root.cget("bg")) # Select the new label label.config(bg="yellow") selected_label = label set_open_link_button(url) # Function to show next set of movies def show_next(): global current_index, selected_label selected_label = None open_link_button.config(state=tk.DISABLED) filtered_movies = filter_movies() if (current_index + 1) * 5 < len(filtered_movies): current_index += 1 display_movies() # Function to show previous set of movies def show_previous(): global current_index, selected_label selected_label = None open_link_button.config(state=tk.DISABLED) if current_index > 0: current_index -= 1 display_movies() # Function to show the first set of movies def show_first(): global current_index, selected_label selected_label = None open_link_button.config(state=tk.DISABLED) current_index = 0 display_movies() # Function to show the last set of movies def show_last(): global current_index, selected_label selected_label = None open_link_button.config(state=tk.DISABLED) filtered_movies = filter_movies() total_pages = (len(filtered_movies) + 4) // 5 current_index = total_pages - 1 display_movies() # Function to set the link for the open link button def set_open_link_button(url): open_link_button.config(command=lambda: webbrowser.open(url)) open_link_button.pack(pady=10) open_link_button.config(state=tk.NORMAL) # Function to reset filters def reset_filters(): decade_var.set("All") genre_var.set("All") language_var.set("All") runtime_var.set("All") letter_var.set("All") sort_order_var.set("By Date Added to My List (Newest to Oldest)") search_var.set("") update_displayed_movies() # Function to confirm reset of API credentials def confirm_reset_api_credentials(): confirm_window = tk.Toplevel(root) confirm_window.title("Confirm Reset") tk.Label(confirm_window, text="This will reset all API keys. Click Yes to proceed or No to cancel.").pack(pady=10) def reset_keys(): confirm_window.destroy() if os.path.exists(API_CREDENTIALS_FILE): os.remove(API_CREDENTIALS_FILE) credentials = ask_for_api_credentials() global TRAKT_CLIENT_ID, TRAKT_ACCESS_TOKEN, TMDB_API_KEY TRAKT_CLIENT_ID = credentials['TRAKT_CLIENT_ID'] TRAKT_ACCESS_TOKEN = credentials['TRAKT_ACCESS_TOKEN'] TMDB_API_KEY = credentials['TMDB_API_KEY'] update_displayed_movies() def cancel(): confirm_window.destroy() button_frame = tk.Frame(confirm_window) button_frame.pack(pady=10) yes_button = tk.Button(button_frame, text="Yes", command=reset_keys) yes_button.pack(side=tk.LEFT, padx=5) no_button = tk.Button(button_frame, text="No", command=cancel) no_button.pack(side=tk.LEFT, padx=5) # Function to update the page navigation display def update_page_navigation(total_movies): for widget in page_nav_frame.winfo_children(): widget.destroy() total_pages = (total_movies + 4) // 5 if total_pages > 1: first_btn = tk.Button(page_nav_frame, text="<<", command=show_first) first_btn.pack(side=tk.LEFT, padx=2) if current_index > 0: prev_btn = tk.Button(page_nav_frame, text="<", command=show_previous) prev_btn.pack(side=tk.LEFT, padx=2) page_label = tk.Label(page_nav_frame, text=f"Page {current_index + 1} of {total_pages}") page_label.pack(side=tk.LEFT, padx=2) if (current_index + 1) < total_pages: next_btn = tk.Button(page_nav_frame, text=">", command=show_next) next_btn.pack(side=tk.LEFT, padx=2) last_btn = tk.Button(page_nav_frame, text=">>", command=show_last) last_btn.pack(side=tk.LEFT, padx=2) # Function to preload cached posters def preload_cached_posters(): for movie in movies: tmdb_id = movie['movie']['ids'].get('tmdb') if tmdb_id: poster_path = os.path.join(POSTER_CACHE_DIR, f'{tmdb_id}.jpg') if os.path.exists(poster_path): posters_dict[tmdb_id] = poster_path # Function to fetch genres, posters, languages, runtimes, and ratings in the background def fetch_additional_movie_data(): for movie in movies: tmdb_id = movie['movie']['ids'].get('tmdb') if tmdb_id: movie_details = get_tmdb_movie_details(tmdb_id) genres_dict[tmdb_id] = [genre['name'].lower() for genre in movie_details.get('genres', [])] poster_url = get_movie_poster(tmdb_id) if poster_url: posters_dict[tmdb_id] = poster_url languages_dict[tmdb_id] = movie_details.get('original_language', 'unknown').lower() runtimes_dict[tmdb_id] = movie_details.get('runtime', 0) ratings_dict[tmdb_id] = movie_details.get('vote_average', 0) loading_label.config(text=f"Loading {movie['movie']['title']}...") display_movies() loading_label.pack_forget() # Fetch watchlist movies = get_watchlist() # Preload cached posters preload_cached_posters() # Create a frame for movie list and detail main_frame = tk.Frame(root) main_frame.pack(fill=tk.BOTH, expand=True) # Create watchlist frame watchlist_frame = tk.Frame(main_frame) watchlist_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10) # Create a frame for movie details detail_frame = tk.Frame(main_frame) detail_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10) # Add a divider line between movie list and details divider = tk.Frame(main_frame, width=2, bg="black") divider.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) # Page navigation frame page_nav_frame = tk.Frame(root) page_nav_frame.pack(pady=10) # Display initial set of movies display_movies() # Add filters for decade, genre, language, runtime, letter filter_frame = tk.Frame(root) filter_frame.pack(pady=10, fill=tk.X) # Decade filter decade_frame = tk.Frame(filter_frame) decade_frame.pack(side=tk.LEFT, padx=10) tk.Label(decade_frame, text="Decade:").pack(side=tk.LEFT) decades = ["All"] + [str(year) for year in range(1900, 2021, 10)] decade_menu = ttk.Combobox(decade_frame, textvariable=decade_var, values=decades) decade_menu.pack(side=tk.LEFT) decade_menu.bind("<<ComboboxSelected>>", update_displayed_movies) # Genre filter genre_frame = tk.Frame(filter_frame) genre_frame.pack(side=tk.LEFT, padx=10) tk.Label(genre_frame, text="Genre:").pack(side=tk.LEFT) genres = ["All", "Action", "Adventure", "Animation", "Comedy", "Crime", "Documentary", "Drama", "Family", "Fantasy", "History", "Horror", "Music", "Mystery", "Romance", "Science Fiction", "TV Movie", "Thriller", "War", "Western"] genre_menu = ttk.Combobox(genre_frame, textvariable=genre_var, values=genres) genre_menu.pack(side=tk.LEFT) genre_menu.bind("<<ComboboxSelected>>", update_displayed_movies) # Language filter language_frame = tk.Frame(filter_frame) language_frame.pack(side=tk.LEFT, padx=10) tk.Label(language_frame, text="Language:").pack(side=tk.LEFT) languages = ["All", "en", "fr", "es", "de", "it", "ja", "ko", "zh"] language_menu = ttk.Combobox(language_frame, textvariable=language_var, values=languages) language_menu.pack(side=tk.LEFT) language_menu.bind("<<ComboboxSelected>>", update_displayed_movies) # Runtime filter runtime_frame = tk.Frame(filter_frame) runtime_frame.pack(side=tk.LEFT, padx=10) tk.Label(runtime_frame, text="Runtime:").pack(side=tk.LEFT) runtimes = ["All", "Less than 60 minutes", "60 to 90 minutes", "90 to 120 minutes", "More than 120 minutes"] runtime_menu = ttk.Combobox(runtime_frame, textvariable=runtime_var, values=runtimes) runtime_menu.pack(side=tk.LEFT) runtime_menu.bind("<<ComboboxSelected>>", update_displayed_movies) # Letter filter letter_frame = tk.Frame(filter_frame) letter_frame.pack(side=tk.LEFT, padx=10) tk.Label(letter_frame, text="Letter:").pack(side=tk.LEFT) letters = ["All"] + [chr(i) for i in range(65, 91)] letter_menu = ttk.Combobox(letter_frame, textvariable=letter_var, values=letters) letter_menu.pack(side=tk.LEFT) letter_menu.bind("<<ComboboxSelected>>", update_displayed_movies) # Search box search_label = tk.Label(root, text="Search:") search_label.place(relx=1.0, x=-10, y=10, anchor='ne') search_entry = ttk.Entry(root, textvariable=search_var) search_entry.place(relx=1.0, x=-10, y=30, anchor='ne') search_var.trace_add('write', update_displayed_movies) # Sort order filter sort_order_frame = tk.Frame(root) sort_order_frame.pack(pady=10) tk.Label(sort_order_frame, text="Sort Order:").pack(side=tk.LEFT) sort_orders = ["Alphabetically (A-Z)", "Alphabetically (Z-A)", "By Date Released (Newest to Oldest)", "By Date Released (Oldest to Newest)", "By Date Added to My List (Newest to Oldest)", "By Date Added to My List (Oldest to Newest)", "Ranking (Highest to Lowest)", "Ranking (Lowest to Highest)"] sort_order_menu = ttk.Combobox(sort_order_frame, textvariable=sort_order_var, values=sort_orders, width=40) sort_order_menu.pack(side=tk.LEFT) sort_order_menu.bind("<<ComboboxSelected>>", update_displayed_movies) # Add button to pick random movie, open link, reset filters, and reset API credentials button_frame = tk.Frame(root) button_frame.pack(pady=10) random_button = tk.Button(button_frame, text="Pick a Random Movie", command=pick_random_movie) random_button.pack(side=tk.LEFT, padx=5) open_link_button = tk.Button(button_frame, text="Open Movie Link", state=tk.DISABLED) open_link_button.pack(side=tk.LEFT, padx=5) reset_button = tk.Button(button_frame, text="Reset Filters", command=reset_filters) reset_button.pack(side=tk.LEFT, padx=5) reset_api_button = tk.Button(button_frame, text="Reset API Keys", command=confirm_reset_api_credentials) reset_api_button.pack(side=tk.LEFT, padx=5) # Loading indicator loading_label = tk.Label(root, text="Loading...") loading_label.pack(pady=20) # Start background thread to fetch additional movie data thread = threading.Thread(target=fetch_additional_movie_data) thread.start() # Run the GUI loop root.mainloop()
Share