1010from io import BytesIO
1111import vdf
1212import sys
13- import concurrent .futures
13+ from concurrent .futures import ThreadPoolExecutor
1414import time
1515import winreg
1616import threading
@@ -53,14 +53,16 @@ def find_steam_path_fallback():
5353 return None
5454
5555def resource_path (relative_path ):
56- """ Get the absolute path to the resource, works for both development and PyInstaller. """
56+ """Get the absolute path to the resource, works for both development and PyInstaller."""
5757 try :
58- # PyInstaller creates a temp folder and stores the path to the bundled app
59- base_path = sys ._MEIPASS
58+ base_path = sys ._MEIPASS # PyInstaller temp directory
6059 except Exception :
61- base_path = os .path .abspath ("." ) # Use the current directory in development
60+ base_path = os .path .dirname (os .path .abspath (__file__ )) # Development mode
61+
62+ resolved_path = os .path .join (base_path , relative_path )
6263
63- return os .path .join (base_path , relative_path )
64+ print (f"Resolved path for { relative_path } : { resolved_path } " ) # Debugging output
65+ return resolved_path
6466
6567# Constants
6668STEAM_PATH = get_steam_install_path () or find_steam_path_fallback ()
@@ -100,24 +102,21 @@ def fetch_header_image(app_id, cache_dir, timeout=10):
100102 """Fetch game header image from Steam or return a placeholder."""
101103 cache_file_path = os .path .join (cache_dir , f"{ app_id } .jpg" )
102104
103- # If the image is already cached, return the cached image
104105 if os .path .exists (cache_file_path ):
105106 print (f"Using cached image for app_id { app_id } " )
106- return Image .open (cache_file_path )
107+ return Image .open (cache_file_path ) # Open the cached image
107108
108- # If not cached, fetch the image from Steam and save it to the cache
109+ # If not cached, fetch from Steam
109110 urls = [
110111 f"https://cdn.cloudflare.steamstatic.com/steam/apps/{ app_id } /header.jpg" ,
111112 f"https://cdn.cloudflare.steamstatic.com/steam/apps/{ app_id } /page_bg.jpg" ,
112113 ]
113114 for url in urls :
114115 try :
115- print (f"Attempting to fetch image for app_id { app_id } from URL: { url } " )
116116 response = requests .get (url , timeout = timeout )
117117 if response .status_code == 200 :
118118 img = Image .open (BytesIO (response .content ))
119- # Save the image to the cache
120- img .save (cache_file_path , "JPEG" )
119+ img .save (cache_file_path , "JPEG" ) # Save it to the cache
121120 return img
122121 except Exception as e :
123122 print (f"Error fetching image for app_id { app_id } : { e } " )
@@ -329,7 +328,7 @@ def __init__(self, root, installed_games, drives):
329328 self .frame_delay = 16 # Controls the speed of the animation (time between frames)
330329
331330 self .root .title ("Steam Roulette" )
332- self .root .geometry ("600x700 " )
331+ self .root .geometry ("600x750 " )
333332 self .root .resizable (False , False )
334333
335334 # Define the relative path to the 'header_images' folder
@@ -347,14 +346,25 @@ def __init__(self, root, installed_games, drives):
347346 # Display a random header image on startup
348347 self .display_random_header_image ()
349348
350- # Label displaying "Games Found on Drives" (bottom right corner)
349+ # Label displaying "Games Found on Drives"
351350 self .label_game_count = tk .Label (root , text = self .generate_games_found_text (), font = ("Arial" , 10 ))
352- self .label_game_count .place (relx = 0.0 , rely = 0.0 , anchor = 'nw' , x = 5 , y = 60 )
351+
352+ # Get the width of the window and place the label in the top-right corner
353+ window_width = root .winfo_width () # Get current width of the window
354+ self .label_game_count .place (x = window_width - 5 , y = 5 , anchor = 'ne' ) # 10px from the right and 10px from the top
353355
354356 # Label displaying copyright notice in the top-left corner
355357 self .copyright_notice = tk .Label (root , text = "© Streetbackguy 2024" , font = ("Arial" , 8 ))
356358 self .copyright_notice .place (relx = 0.0 , rely = 0.0 , anchor = 'nw' , x = 5 , y = 5 )
357359
360+ # Welcome label
361+ self .label_welcome = tk .Label (frame , text = "Welcome to Steam Roulette!" , font = ("Arial" , 16 ), bg = self .light_mode_bg )
362+ self .label_welcome .grid (row = 1 , pady = 5 )
363+
364+ # Game name label
365+ self .label_game_name = tk .Label (frame , text = "" , wraplength = 600 , font = ("Arial" , 20 ), bg = self .light_mode_bg )
366+ self .label_game_name .grid (row = 2 , pady = 5 )
367+
358368 # Initial theme mode (light mode by default)
359369 self .is_dark_mode = False
360370
@@ -420,8 +430,8 @@ def __init__(self, root, installed_games, drives):
420430 self .yes_no_frame .place (relx = 1.0 , rely = 1.0 , anchor = 'se' , x = - 2 , y = - 2 ) # Padding for the frame
421431
422432 # Label for Selecting whether to include uninstalled games or not
423- self .label_number_of_games = tk .Label (self .yes_no_frame , text = "Include Uninstalled Games?" , font = ("Arial" , 8 ))
424- self .label_number_of_games .grid (row = 0 , column = 0 , columnspan = 2 , pady = 2 )
433+ self .label_include_uninstalled_games = tk .Label (self .yes_no_frame , text = "Include Uninstalled Games?" , font = ("Arial" , 8 ))
434+ self .label_include_uninstalled_games .grid (row = 0 , column = 0 , columnspan = 2 , pady = 2 )
425435
426436 # Yes button
427437 self .button_yes = tk .Button (self .yes_no_frame , text = "Yes" , command = self .on_yes_click )
@@ -436,29 +446,49 @@ def __init__(self, root, installed_games, drives):
436446 self .selected_game_item = None
437447 self .animation_id = None
438448
439- # Use resource_path to get the correct logo path
440- logo_path = resource_path ("SteamRouletteLogo.png" )
441449 try :
450+ # Use resource_path to get the correct logo path
451+ logo_path = resource_path ("SteamRouletteLogo.png" )
452+ print (f"Trying to load logo from: { logo_path } " )
453+
454+ if not os .path .exists (logo_path ):
455+ raise FileNotFoundError (f"Logo file not found at: { logo_path } " )
456+
457+ # Load the logo image
442458 logo_image = Image .open (logo_path )
443- logo_image = ImageTk .PhotoImage (logo_image )
459+ logo_image_tk = ImageTk .PhotoImage (logo_image )
444460
445- self .label_logoimage = tk .Label (frame , image = logo_image , bg = self .light_mode_bg )
446- self .label_logoimage .image = logo_image # Keep a reference to the image
461+ # Display logo
462+ self .label_logoimage = tk .Label (frame , image = logo_image_tk , bg = self .light_mode_bg )
463+ self .label_logoimage .image = logo_image_tk # Keep reference to avoid garbage collection
447464 self .label_logoimage .grid (row = 0 , pady = 5 )
448465
449- # Welcome label
450- self .label_welcome = tk .Label (frame , text = "Welcome to Steam Roulette!" , font = ("Arial" , 16 ), bg = self .light_mode_bg )
451- self .label_welcome .grid (row = 1 , pady = 5 )
466+ except FileNotFoundError as fnfe :
467+ print (fnfe )
468+ # In case logo or icon is missing, display a fallback message
469+ self .label_logoimage = tk .Label (frame , text = "Logo Missing" , font = ("Arial" , 16 ), bg = self .light_mode_bg )
470+ self .label_logoimage .grid (row = 0 , pady = 5 )
452471
453- # Remove text from label_game_name after logo is displayed
454- self .label_game_name = tk .Label (frame , text = "" , wraplength = 600 , font = ("Arial" , 20 ), bg = self .light_mode_bg )
455- self .label_game_name .grid (row = 2 , pady = 5 )
472+ except Exception as e :
473+ print (f"Error: { e } " )
474+
475+ try :
476+ # Get the path for the .ico file
477+ icon_path = resource_path ("SteamRouletteIcon.ico" )
478+ print (f"Trying to load icon from: { icon_path } " )
456479
457- # Ensure the window updates after packing elements
458- self .root .update_idletasks ()
480+ # Ensure the icon file exists
481+ if not os .path .exists (icon_path ):
482+ raise FileNotFoundError (f"Icon file not found at: { icon_path } " )
459483
484+ # Set the window icon
485+ self .root .iconbitmap (icon_path )
486+ print ("Window icon successfully set." )
487+
488+ except FileNotFoundError as fnfe :
489+ print (fnfe )
460490 except Exception as e :
461- print (f"Error loading logo image : { e } " )
491+ print (f"Error setting window icon : { e } " )
462492
463493 # Set Light Mode
464494 self .set_light_mode ()
@@ -467,32 +497,18 @@ def __init__(self, root, installed_games, drives):
467497 self .display_random_header_image ()
468498
469499 def preload_images (self ):
470- """Preload images for both installed and uninstalled games."""
471- # Get the directory of the executable or script
472- base_dir = os .path .dirname (sys .executable if getattr (sys , 'frozen' , False ) else __file__ )
473- cache_dir = os .path .join (base_dir , "image_cache" )
474- os .makedirs (cache_dir , exist_ok = True )
475-
476- for game in self .installed_games + getattr (self , 'uninstalled_games' , []):
500+ """Preload images for both installed and uninstalled games using threading."""
501+ def preload_image (game ):
477502 app_id = game .get ("app_id" )
478- if not app_id :
479- continue
480-
481- # Check if image already exists in the cache
482- cache_file_path = os .path .join (cache_dir , f"{ app_id } .jpg" )
483- if os .path .exists (cache_file_path ):
484- print (f"Using cached image for app_id { app_id } " )
485- continue # Skip if the image is already cached
486-
487- print (f"Fetching image for app_id { app_id } ..." )
488- img = fetch_header_image (app_id , self .cache_dir ) # Replace with actual image fetching logic
489-
503+ if not app_id or app_id in self .preloaded_images :
504+ return
505+ img = fetch_header_image (app_id , self .cache_dir )
490506 if img :
491- # Save the image to the cache
492- img . save ( cache_file_path , "JPEG" )
493- print ( f"Image cached for app_id { app_id } " )
494- else :
495- print ( f"Failed to fetch image for app_id { app_id } " )
507+ self . preloaded_images [ app_id ] = img
508+
509+ # Preload images in parallel
510+ with ThreadPoolExecutor ( max_workers = 10 ) as executor :
511+ executor . map ( preload_image , self . installed_games + getattr ( self , 'uninstalled_games' , []) )
496512
497513 def generate_games_found_text (self ):
498514 """Generate a summary of games found on each drive."""
@@ -645,16 +661,13 @@ def on_no_click(self):
645661 del self .uninstalled_games # Clear the uninstalled games list
646662 messagebox .showinfo ("Uninstalled Games Removed" , "Uninstalled games have been removed from the cycle." )
647663
648- def load_images_in_parallel (self ):
649- """Preload images for uninstalled games in the background."""
650- self .is_images_preloaded = False # Set flag to indicate images are not preloaded yet
651-
652- # Preload images for all uninstalled games
653- for game in self .uninstalled_games :
654- app_id = game ["app_id" ]
655- fetch_header_image (app_id , self .cache_dir ) # Replace with your actual image loading function
656-
657- # After all images are preloaded, re-enable the button on the main thread
664+ def load_images_in_parallel (self , batch_size = 10 ):
665+ """Preload images for uninstalled games in batches."""
666+ games_to_load = self .uninstalled_games
667+ for i in range (0 , len (games_to_load ), batch_size ):
668+ batch = games_to_load [i :i + batch_size ]
669+ with ThreadPoolExecutor (max_workers = 5 ) as executor :
670+ executor .map (lambda game : fetch_header_image (game ["app_id" ], self .cache_dir ), batch )
658671 self .root .after (0 , self .on_images_preloaded )
659672
660673 def on_images_preloaded (self ):
@@ -698,18 +711,35 @@ def start_animation(self):
698711
699712 def display_random_header_image (self ):
700713 """Display a random header image on the canvas initially."""
701- random_app_id = random .choice (self .installed_games )["app_id" ] # Choose a random installed game
702- random_image = fetch_header_image (random_app_id , self .cache_dir )
714+ random_game = random .choice (self .installed_games )
715+ random_app_id = random_game ["app_id" ] # Get the app_id of the selected game
716+ print (f"Fetching image for app_id { random_app_id } " )
703717
718+ random_image = fetch_header_image (random_app_id , self .cache_dir )
719+
720+ if random_image :
721+ print (f"Image fetched successfully for app_id { random_app_id } " )
722+
704723 # Resize the image to match the canvas size
705724 canvas_width = self .canvas .winfo_width ()
706725 canvas_height = self .canvas .winfo_height ()
726+ print (f"Canvas width: { canvas_width } , Canvas height: { canvas_height } " )
727+
707728 random_image_resized = random_image .resize ((canvas_width , canvas_height ), Image .Resampling .LANCZOS )
708729
709730 random_image_tk = ImageTk .PhotoImage (random_image_resized )
731+
732+ # Make sure to keep a reference to the image
710733 self .canvas .create_image (canvas_width // 2 , canvas_height // 2 , image = random_image_tk , anchor = tk .CENTER )
711- self .active_images = [(random_image_tk , random_image )] # Store for later reference
712- print ("Random header image displayed." )
734+
735+ # Store the image reference to prevent it from being garbage collected
736+ self .active_images = [(random_image_tk , random_image )]
737+
738+ print ("Random header image displayed on canvas." )
739+
740+ # Force Tkinter to process the events and refresh the canvas
741+ self .canvas .update_idletasks ()
742+ self .root .update ()
713743
714744 def display_image_from_url (self , image_url ):
715745 """Download and display the image from a URL on the canvas."""
@@ -810,7 +840,7 @@ def submit_number_of_games(self):
810840 self .selected_num_games = num_games_to_spin # Store the selected number of games
811841
812842 # Update the label to show the selected number of games
813- self .label_number_of_games .config (text = f"Number of games selected: { num_games_to_spin } " )
843+ self .label_number_of_games .config (text = f"Number selected:\n { num_games_to_spin } " )
814844
815845 # Close the popup window after submitting
816846 self .popup .destroy ()
@@ -941,33 +971,14 @@ def prepare_images(self, games):
941971 def load_image (self , app_id ):
942972 """Load and resize an image, checking the cache if not in preloaded_images."""
943973 img = self .preloaded_images .get (app_id )
944- if img is None :
945- # Fallback to load from the cache if not found in preloaded_images
946- cache_dir = os .path .join (os .path .dirname (sys .executable ), "image_cache" )
947-
948- # Check if the directory exists, create it if it doesn't
949- if not os .path .exists (cache_dir ):
950- os .makedirs (cache_dir )
951- print (f"Cache directory created at: { cache_dir } " )
952- else :
953- print (f"Cache directory already exists at: { cache_dir } " )
954-
955- cache_file_path = os .path .join (cache_dir , f"{ app_id } .jpg" )
956- if os .path .exists (cache_file_path ):
957- print (f"Loading image from cache for app_id: { app_id } " )
958- try :
959- img = Image .open (cache_file_path )
960- img = img .resize ((600 , 300 ), Image .Resampling .LANCZOS ) # Resize for consistency
961- self .preloaded_images [app_id ] = img
962- except Exception as e :
963- print (f"Error loading image for app_id { app_id } : { e } " )
964- return None # Return None if there was an error loading the image
965- else :
966- print (f"Warning: No image found for app_id { app_id } " )
967- return None # Return None if image isn't found in the cache
968-
969- return img
970-
974+ if not img :
975+ img = fetch_header_image (app_id , self .cache_dir )
976+ if img :
977+ self .preloaded_images [app_id ] = img
978+ if img :
979+ return img .resize ((600 , 300 ), Image .Resampling .LANCZOS )
980+ return create_placeholder_image ("Image Unavailable" )
981+
971982 def cycle_images (self , selected_games ):
972983 """Cycle through the images as part of the spinning effect, with no padding and images resized to fit the canvas."""
973984 self .active_images = []
@@ -1037,7 +1048,7 @@ def animate_images(self):
10371048 total_distance = len (self .active_images ) * canvas_width
10381049
10391050 # Set the desired duration (in milliseconds)
1040- desired_duration = 6800 if include_uninstalled_games else 7400 # 7.4 seconds for animation
1051+ desired_duration = 7600 # 8 seconds for animation
10411052 self .frame_delay = 16 # Default frame delay in ms (60 FPS)
10421053 frames = desired_duration // self .frame_delay # Total number of frames
10431054 self .animation_speed = max (20 , total_distance // frames ) # Pixels per frame
@@ -1048,7 +1059,7 @@ def slide():
10481059 self .canvas .move (image_item , - self .animation_speed , 0 ) # Move images to the left
10491060
10501061 # Start slowing down earlier if uninstalled games are included
1051- slowdown_start_index = - 12 if include_uninstalled_games else - 4 # Start earlier if uninstalled games are included
1062+ slowdown_start_index = - 3
10521063
10531064 # Check if the current image is in the last 10 images (based on whether uninstalled games are included)
10541065 for i , (image_item , img_tk ) in enumerate (self .active_images [slowdown_start_index :]):
0 commit comments