2025-09-23 13:59:06 +01:00
#!/usr/bin/env python3
"""
Standalone FM NewGens - GUI Interface
A user - friendly graphical interface for generating Football Manager faces
"""
import tkinter as tk
from tkinter import ttk , filedialog , messagebox , scrolledtext
import os
import sys
import json
import configparser
import threading
import queue
import time
from pathlib import Path
from PIL import Image , ImageTk
import logging
# Import the main generation logic
2025-09-23 15:33:01 +01:00
from comfy_fm_newgen import generate_image , get_country_name , generate_prompts_for_players , get_prompt_for_image
2025-09-23 13:59:06 +01:00
try :
from lib . rtf_parser import RTF_Parser
except ImportError :
# Fallback for when striprtf is not available
class RTF_Parser :
def parse_rtf ( self , file_path ) :
print ( f " Warning: RTF parsing not available. Please install striprtf: pip install striprtf==0.0.28 " )
return [ ]
2025-09-23 14:10:16 +01:00
# Check if background removal is available
try :
from lib . remove_bg import remove_bg_from_file_list , REMBG_AVAILABLE
BG_REMOVAL_AVAILABLE = REMBG_AVAILABLE
except ImportError :
BG_REMOVAL_AVAILABLE = False
print ( " Warning: Background removal not available " )
2025-09-23 13:59:06 +01:00
from lib . generate_xml import create_config_xml , append_to_config_xml
from lib . xml_reader import extract_from_values
2025-09-23 14:30:05 +01:00
from lib . general import list_profiles
2025-09-23 13:59:06 +01:00
class FMFaceGeneratorGUI :
def __init__ ( self , root ) :
self . root = root
self . root . title ( " FM Face Generator - Standalone Version " )
self . root . geometry ( " 1000x700 " )
self . root . minsize ( 800 , 600 )
2025-09-23 14:25:59 +01:00
# Set environment variable to indicate GUI mode
os . environ [ ' FM_NEWGEN_GUI_MODE ' ] = ' true '
2025-09-23 13:59:06 +01:00
# Initialize variables
self . config = None
self . app_config = None
self . rtf = RTF_Parser ( )
self . generation_thread = None
self . stop_generation = False
self . image_queue = queue . Queue ( )
2025-09-23 14:25:59 +01:00
self . current_profile = None
2025-09-23 13:59:06 +01:00
2025-09-23 14:52:52 +01:00
# Preview tracking variables
self . generated_images = [ ] # List of generated image UIDs
self . current_image_index = - 1 # Current image being displayed
2025-09-23 13:59:06 +01:00
# Set up the GUI
self . setup_gui ( )
2025-09-23 14:25:59 +01:00
# Show profile selection dialog first
self . show_profile_selection ( )
2025-09-23 13:59:06 +01:00
2025-09-23 14:28:56 +01:00
# Refresh system information
self . refresh_system_info ( )
2025-09-23 13:59:06 +01:00
# Start image preview update loop
self . update_image_preview ( )
2025-09-23 14:25:59 +01:00
def show_profile_selection ( self ) :
""" Show profile selection dialog at startup """
# Load existing profiles
profiles = self . list_profiles ( )
if not profiles :
# No profiles exist, create default one
self . create_default_profile ( )
self . current_profile = " NewGens "
self . load_config ( )
return
# Create selection dialog
dialog = tk . Toplevel ( self . root )
dialog . title ( " Select Profile " )
dialog . geometry ( " 400x300 " )
dialog . transient ( self . root )
dialog . grab_set ( )
# Center the dialog
dialog . update_idletasks ( )
x = ( dialog . winfo_screenwidth ( ) - dialog . winfo_width ( ) ) / / 2
y = ( dialog . winfo_screenheight ( ) - dialog . winfo_height ( ) ) / / 2
dialog . geometry ( f " + { x } + { y } " )
# Profile selection
ttk . Label ( dialog , text = " Select a profile to use: " ) . pack ( pady = 10 )
profile_var = tk . StringVar ( )
profile_combo = ttk . Combobox ( dialog , textvariable = profile_var , width = 30 )
profile_combo [ ' values ' ] = profiles
profile_combo . pack ( pady = 10 )
profile_combo . set ( profiles [ 0 ] ) # Select first profile by default
def on_select ( ) :
selected = profile_var . get ( )
if selected :
self . current_profile = selected
dialog . destroy ( )
self . load_config ( )
self . log_message ( f " Selected profile: { selected } " )
def on_create_new ( ) :
dialog . destroy ( )
self . show_create_profile_dialog ( )
def on_manage ( ) :
dialog . destroy ( )
self . notebook . select ( 1 ) # Switch to profiles tab
# Buttons
btn_frame = ttk . Frame ( dialog )
btn_frame . pack ( pady = 20 )
ttk . Button ( btn_frame , text = " Select " , command = on_select ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Create New " , command = on_create_new ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Manage Profiles " , command = on_manage ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Cancel " , command = self . root . quit ) . pack ( side = tk . LEFT , padx = 5 )
# Wait for dialog to close
self . root . wait_window ( dialog )
def show_create_profile_dialog ( self ) :
""" Show dialog to create a new profile """
dialog = tk . Toplevel ( self . root )
dialog . title ( " Create New Profile " )
dialog . geometry ( " 400x250 " )
dialog . transient ( self . root )
dialog . grab_set ( )
# Center the dialog
dialog . update_idletasks ( )
x = ( dialog . winfo_screenwidth ( ) - dialog . winfo_width ( ) ) / / 2
y = ( dialog . winfo_screenheight ( ) - dialog . winfo_height ( ) ) / / 2
dialog . geometry ( f " + { x } + { y } " )
# Profile name
ttk . Label ( dialog , text = " Profile Name: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 10 , padx = 10 )
profile_name_var = tk . StringVar ( )
profile_name_entry = ttk . Entry ( dialog , textvariable = profile_name_var , width = 30 )
profile_name_entry . grid ( row = 0 , column = 1 , sticky = tk . W , padx = 10 , pady = 10 )
# FM Version
ttk . Label ( dialog , text = " FM Version: " ) . grid ( row = 1 , column = 0 , sticky = tk . W , pady = 5 , padx = 10 )
fm_version_var = tk . StringVar ( value = " 2024 " )
fm_version_entry = ttk . Entry ( dialog , textvariable = fm_version_var , width = 30 )
fm_version_entry . grid ( row = 1 , column = 1 , sticky = tk . W , padx = 10 , pady = 5 )
# RTF File
ttk . Label ( dialog , text = " RTF File: " ) . grid ( row = 2 , column = 0 , sticky = tk . W , pady = 5 , padx = 10 )
rtf_file_var = tk . StringVar ( )
rtf_entry = ttk . Entry ( dialog , textvariable = rtf_file_var , width = 30 )
rtf_entry . grid ( row = 2 , column = 1 , sticky = tk . W , padx = 10 , pady = 5 )
ttk . Button ( dialog , text = " Browse... " , command = lambda : self . browse_file ( rtf_file_var , " RTF files " , " *.rtf " ) ) . grid ( row = 2 , column = 2 , padx = 10 , pady = 5 )
# Output Directory
ttk . Label ( dialog , text = " Output Directory: " ) . grid ( row = 3 , column = 0 , sticky = tk . W , pady = 5 , padx = 10 )
output_dir_var = tk . StringVar ( )
output_entry = ttk . Entry ( dialog , textvariable = output_dir_var , width = 30 )
output_entry . grid ( row = 3 , column = 1 , sticky = tk . W , padx = 10 , pady = 5 )
ttk . Button ( dialog , text = " Browse... " , command = lambda : self . browse_directory ( output_dir_var ) ) . grid ( row = 3 , column = 2 , padx = 10 , pady = 5 )
def on_create ( ) :
name = profile_name_var . get ( ) . strip ( )
if not name :
messagebox . showerror ( " Error " , " Profile name cannot be empty " )
return
if self . profile_exists ( name ) :
messagebox . showerror ( " Error " , f " Profile ' { name } ' already exists " )
return
# Create the profile
self . create_profile ( name , fm_version_var . get ( ) , rtf_file_var . get ( ) , output_dir_var . get ( ) )
self . current_profile = name
dialog . destroy ( )
self . load_config ( )
self . log_message ( f " Created and selected profile: { name } " )
def on_cancel ( ) :
dialog . destroy ( )
self . show_profile_selection ( ) # Go back to profile selection
# Buttons
btn_frame = ttk . Frame ( dialog )
btn_frame . grid ( row = 4 , column = 0 , columnspan = 3 , pady = 20 )
ttk . Button ( btn_frame , text = " Create " , command = on_create ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Cancel " , command = on_cancel ) . pack ( side = tk . LEFT , padx = 5 )
def list_profiles ( self ) :
""" List all available profiles """
if not os . path . exists ( ' user_config.cfg ' ) :
return [ ]
config = configparser . ConfigParser ( )
config . read ( ' user_config.cfg ' )
profiles = [ section . split ( ' : ' , 1 ) [ 1 ] for section in config . sections ( ) if section . startswith ( ' profile: ' ) ]
return profiles
def profile_exists ( self , profile_name ) :
""" Check if a profile exists """
profiles = self . list_profiles ( )
return profile_name in profiles
def create_profile ( self , name , fm_version , rtf_file , output_dir ) :
""" Create a new profile """
if not self . config :
self . config = configparser . ConfigParser ( )
self . config . add_section ( ' models ' )
section_name = f ' profile: { name } '
self . config . add_section ( section_name )
self . config . set ( section_name , ' football_manager_version ' , fm_version )
self . config . set ( section_name , ' rtf_file ' , rtf_file )
self . config . set ( section_name , ' output_dir ' , output_dir )
# Save to file
with open ( ' user_config.cfg ' , ' w ' ) as configfile :
self . config . write ( configfile )
def create_default_profile ( self ) :
""" Create a default profile if none exists """
self . config = configparser . ConfigParser ( )
self . config . add_section ( ' models ' )
self . config . add_section ( ' profile:NewGens ' )
self . config . set ( ' models ' , ' model_name ' , ' SG161222/Realistic_Vision_V6.0_B1 ' )
self . config . set ( ' profile:NewGens ' , ' football_manager_version ' , ' 2024 ' )
self . config . set ( ' profile:NewGens ' , ' rtf_file ' , ' ' )
self . config . set ( ' profile:NewGens ' , ' output_dir ' , ' ./NewGens/ ' )
with open ( ' user_config.cfg ' , ' w ' ) as configfile :
self . config . write ( configfile )
def browse_file ( self , var , title , filetypes ) :
""" Browse for a file """
file_path = filedialog . askopenfilename ( title = title , filetypes = [ ( title , filetypes ) , ( " All files " , " *.* " ) ] )
if file_path :
var . set ( file_path )
def browse_directory ( self , var ) :
""" Browse for a directory """
directory = filedialog . askdirectory ( title = " Select Directory " )
if directory :
var . set ( directory )
def setup_profiles_tab ( self ) :
""" Set up the profiles management tab """
# Profile list
list_frame = ttk . LabelFrame ( self . profiles_frame , text = " Available Profiles " , padding = 10 )
list_frame . pack ( fill = tk . BOTH , expand = True , padx = 10 , pady = 5 )
# Profile listbox
self . profiles_listbox = tk . Listbox ( list_frame , height = 10 )
self . profiles_listbox . pack ( fill = tk . BOTH , expand = True , pady = 5 )
# Buttons
btn_frame = ttk . Frame ( list_frame )
btn_frame . pack ( fill = tk . X , pady = 5 )
ttk . Button ( btn_frame , text = " Refresh List " , command = self . refresh_profiles_list ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Select Profile " , command = self . select_profile_from_list ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Create New " , command = self . show_create_profile_dialog ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( btn_frame , text = " Delete " , command = self . delete_profile ) . pack ( side = tk . RIGHT , padx = 5 )
# Profile details
details_frame = ttk . LabelFrame ( self . profiles_frame , text = " Profile Details " , padding = 10 )
details_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
# Current profile display
self . current_profile_var = tk . StringVar ( )
ttk . Label ( details_frame , text = " Current Profile: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 5 )
ttk . Label ( details_frame , textvariable = self . current_profile_var ) . grid ( row = 0 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
# Refresh the profiles list
self . refresh_profiles_list ( )
def refresh_profiles_list ( self ) :
""" Refresh the profiles listbox """
self . profiles_listbox . delete ( 0 , tk . END )
profiles = self . list_profiles ( )
for profile in profiles :
self . profiles_listbox . insert ( tk . END , profile )
if self . current_profile :
self . current_profile_var . set ( self . current_profile )
2025-09-23 14:28:56 +01:00
def refresh_system_info ( self ) :
""" Refresh and display system information """
try :
import torch
# PyTorch version
self . pytorch_version_var . set ( torch . __version__ )
# CUDA availability
cuda_available = torch . cuda . is_available ( )
self . cuda_available_var . set ( " Yes " if cuda_available else " No " )
# Check CUDA availability
if cuda_available :
gpu_count = torch . cuda . device_count ( )
current_device = torch . cuda . current_device ( )
gpu_name = torch . cuda . get_device_name ( current_device )
gpu_memory = torch . cuda . get_device_properties ( current_device ) . total_memory / 1024 * * 3 # GB
self . gpu_status_var . set ( f " Available ( { gpu_name } ) " )
self . gpu_memory_var . set ( f " { gpu_memory : .1f } GB " )
# Check if forcing CPU
if self . force_cpu_var . get ( ) == " true " :
self . gpu_status_var . set ( f " Available but disabled (CPU forced) " )
self . log_message ( f " GPU available: { gpu_name } ( { gpu_memory : .1f } GB) - CPU mode forced " )
else :
self . log_message ( f " GPU available: { gpu_name } ( { gpu_memory : .1f } GB) " )
else :
self . gpu_status_var . set ( " Not available (using CPU) " )
self . gpu_memory_var . set ( " N/A " )
self . log_message ( " GPU not available - using CPU. Install CUDA for GPU support. " )
except ImportError :
self . gpu_status_var . set ( " PyTorch not available " )
self . gpu_memory_var . set ( " N/A " )
self . pytorch_version_var . set ( " Not installed " )
self . cuda_available_var . set ( " N/A " )
self . log_message ( " PyTorch not available - cannot detect GPU status " )
except Exception as e :
self . gpu_status_var . set ( " Error detecting GPU " )
self . gpu_memory_var . set ( " N/A " )
self . pytorch_version_var . set ( " Error " )
self . cuda_available_var . set ( " Error " )
self . log_message ( f " Error detecting GPU: { str ( e ) } " )
def update_gpu_settings ( self ) :
""" Update GPU settings when force CPU option changes """
force_cpu = self . force_cpu_var . get ( ) == " true "
os . environ [ ' FM_NEWGEN_FORCE_CPU ' ] = ' true ' if force_cpu else ' false '
if force_cpu :
self . log_message ( " GPU usage disabled - will use CPU " )
else :
self . log_message ( " GPU usage enabled (if available) " )
self . refresh_system_info ( ) # Refresh to show actual status
def update_gpu_memory_usage ( self ) :
""" Update GPU memory usage display during generation """
try :
import torch
if torch . cuda . is_available ( ) and not self . force_cpu_var . get ( ) == " true " :
# Get current GPU memory usage
if torch . cuda . is_available ( ) :
allocated = torch . cuda . memory_allocated ( 0 ) / 1024 * * 3 # GB
reserved = torch . cuda . memory_reserved ( 0 ) / 1024 * * 3 # GB
total = torch . cuda . get_device_properties ( 0 ) . total_memory / 1024 * * 3 # GB
self . gpu_usage_var . set ( f " GPU Memory: { allocated : .1f } GB / { total : .1f } GB (Reserved: { reserved : .1f } GB) " )
else :
self . gpu_usage_var . set ( " GPU Memory: N/A " )
else :
self . gpu_usage_var . set ( " GPU Memory: CPU Mode " )
except :
self . gpu_usage_var . set ( " GPU Memory: N/A " )
# Schedule next update if generation is running
if hasattr ( self , ' generation_thread ' ) and self . generation_thread and self . generation_thread . is_alive ( ) :
self . root . after ( 2000 , self . update_gpu_memory_usage ) # Update every 2 seconds during generation
2025-09-23 14:25:59 +01:00
def select_profile_from_list ( self ) :
""" Select a profile from the list """
selection = self . profiles_listbox . curselection ( )
if selection :
profile_name = self . profiles_listbox . get ( selection [ 0 ] )
self . current_profile = profile_name
self . load_config ( )
self . log_message ( f " Selected profile: { profile_name } " )
self . refresh_profiles_list ( )
def delete_profile ( self ) :
""" Delete the selected profile """
selection = self . profiles_listbox . curselection ( )
if not selection :
messagebox . showerror ( " Error " , " Please select a profile to delete " )
return
profile_name = self . profiles_listbox . get ( selection [ 0 ] )
if profile_name == self . current_profile :
messagebox . showerror ( " Error " , " Cannot delete the currently active profile " )
return
if messagebox . askyesno ( " Confirm Delete " , f " Are you sure you want to delete profile ' { profile_name } ' ? " ) :
# Remove from config
if self . config :
self . config . remove_section ( f ' profile: { profile_name } ' )
with open ( ' user_config.cfg ' , ' w ' ) as configfile :
self . config . write ( configfile )
self . log_message ( f " Deleted profile: { profile_name } " )
self . refresh_profiles_list ( )
2025-09-23 13:59:06 +01:00
def setup_gui ( self ) :
""" Set up the main GUI components """
# Create main notebook (tabs)
self . notebook = ttk . Notebook ( self . root )
self . notebook . pack ( fill = tk . BOTH , expand = True , padx = 10 , pady = 10 )
# Configuration tab
self . config_frame = ttk . Frame ( self . notebook )
self . notebook . add ( self . config_frame , text = " Configuration " )
2025-09-23 14:25:59 +01:00
# Profiles tab
self . profiles_frame = ttk . Frame ( self . notebook )
self . notebook . add ( self . profiles_frame , text = " Profiles " )
2025-09-23 13:59:06 +01:00
# Generation tab
self . generation_frame = ttk . Frame ( self . notebook )
self . notebook . add ( self . generation_frame , text = " Generation " )
# Preview tab
self . preview_frame = ttk . Frame ( self . notebook )
self . notebook . add ( self . preview_frame , text = " Preview " )
# Setup each tab
self . setup_config_tab ( )
2025-09-23 14:25:59 +01:00
self . setup_profiles_tab ( )
2025-09-23 13:59:06 +01:00
self . setup_generation_tab ( )
self . setup_preview_tab ( )
# Status bar
self . status_var = tk . StringVar ( )
self . status_var . set ( " Ready " )
self . status_bar = ttk . Label ( self . root , textvariable = self . status_var , relief = tk . SUNKEN )
self . status_bar . pack ( side = tk . BOTTOM , fill = tk . X )
# Progress bar
self . progress_var = tk . DoubleVar ( )
self . progress_bar = ttk . Progressbar ( self . root , variable = self . progress_var , maximum = 100 )
self . progress_bar . pack ( side = tk . BOTTOM , fill = tk . X )
def setup_config_tab ( self ) :
""" Set up the configuration tab """
# Model selection
model_frame = ttk . LabelFrame ( self . config_frame , text = " AI Model " , padding = 10 )
model_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
ttk . Label ( model_frame , text = " Model: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 5 )
self . model_var = tk . StringVar ( )
model_combo = ttk . Combobox ( model_frame , textvariable = self . model_var , width = 50 )
model_combo [ ' values ' ] = [
' SG161222/Realistic_Vision_V6.0_B1 ' ,
' digiplay/AbsoluteReality ' ,
' stabilityai/stable-diffusion-2-1 ' ,
' runwayml/stable-diffusion-v1-5 '
]
model_combo . grid ( row = 0 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
model_combo . set ( ' SG161222/Realistic_Vision_V6.0_B1 ' )
# Model directory
ttk . Label ( model_frame , text = " Model Directory: " ) . grid ( row = 1 , column = 0 , sticky = tk . W , pady = 5 )
self . model_dir_var = tk . StringVar ( )
model_dir_entry = ttk . Entry ( model_frame , textvariable = self . model_dir_var , width = 50 )
model_dir_entry . grid ( row = 1 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
ttk . Button ( model_frame , text = " Browse... " , command = self . browse_model_dir ) . grid ( row = 1 , column = 2 , padx = 5 , pady = 5 )
# FM Configuration
fm_frame = ttk . LabelFrame ( self . config_frame , text = " Football Manager " , padding = 10 )
fm_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
ttk . Label ( fm_frame , text = " FM Version: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 5 )
self . fm_version_var = tk . StringVar ( value = " 2024 " )
fm_version_entry = ttk . Entry ( fm_frame , textvariable = self . fm_version_var , width = 20 )
fm_version_entry . grid ( row = 0 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
ttk . Label ( fm_frame , text = " RTF File: " ) . grid ( row = 1 , column = 0 , sticky = tk . W , pady = 5 )
self . rtf_file_var = tk . StringVar ( )
rtf_entry = ttk . Entry ( fm_frame , textvariable = self . rtf_file_var , width = 50 )
rtf_entry . grid ( row = 1 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
ttk . Button ( fm_frame , text = " Browse... " , command = self . browse_rtf_file ) . grid ( row = 1 , column = 2 , padx = 5 , pady = 5 )
ttk . Label ( fm_frame , text = " Output Directory: " ) . grid ( row = 2 , column = 0 , sticky = tk . W , pady = 5 )
self . output_dir_var = tk . StringVar ( )
output_entry = ttk . Entry ( fm_frame , textvariable = self . output_dir_var , width = 50 )
output_entry . grid ( row = 2 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
ttk . Button ( fm_frame , text = " Browse... " , command = self . browse_output_dir ) . grid ( row = 2 , column = 2 , padx = 5 , pady = 5 )
2025-09-23 14:28:56 +01:00
# System Information
system_frame = ttk . LabelFrame ( self . config_frame , text = " System Information " , padding = 10 )
system_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
# GPU Status
self . gpu_status_var = tk . StringVar ( )
self . gpu_status_var . set ( " Checking GPU status... " )
ttk . Label ( system_frame , text = " GPU Status: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 2 )
ttk . Label ( system_frame , textvariable = self . gpu_status_var ) . grid ( row = 0 , column = 1 , sticky = tk . W , padx = 5 , pady = 2 )
# GPU Memory
self . gpu_memory_var = tk . StringVar ( )
self . gpu_memory_var . set ( " N/A " )
ttk . Label ( system_frame , text = " GPU Memory: " ) . grid ( row = 1 , column = 0 , sticky = tk . W , pady = 2 )
ttk . Label ( system_frame , textvariable = self . gpu_memory_var ) . grid ( row = 1 , column = 1 , sticky = tk . W , padx = 5 , pady = 2 )
# Force CPU option
self . force_cpu_var = tk . StringVar ( value = " false " )
ttk . Checkbutton ( system_frame , text = " Force CPU usage (disable GPU) " , variable = self . force_cpu_var , onvalue = " true " , offvalue = " false " , command = self . update_gpu_settings ) . grid ( row = 2 , column = 0 , columnspan = 2 , sticky = tk . W , pady = 5 )
# System details
ttk . Label ( system_frame , text = " PyTorch Version: " ) . grid ( row = 4 , column = 0 , sticky = tk . W , pady = 2 )
self . pytorch_version_var = tk . StringVar ( )
self . pytorch_version_var . set ( " Checking... " )
ttk . Label ( system_frame , textvariable = self . pytorch_version_var ) . grid ( row = 4 , column = 1 , sticky = tk . W , padx = 5 , pady = 2 )
ttk . Label ( system_frame , text = " CUDA Available: " ) . grid ( row = 5 , column = 0 , sticky = tk . W , pady = 2 )
self . cuda_available_var = tk . StringVar ( )
self . cuda_available_var . set ( " Checking... " )
ttk . Label ( system_frame , textvariable = self . cuda_available_var ) . grid ( row = 5 , column = 1 , sticky = tk . W , padx = 5 , pady = 2 )
# Refresh system info button
ttk . Button ( system_frame , text = " Refresh System Info " , command = self . refresh_system_info ) . grid ( row = 6 , column = 0 , columnspan = 2 , pady = 5 )
2025-09-23 13:59:06 +01:00
# Control buttons
button_frame = ttk . Frame ( self . config_frame )
button_frame . pack ( fill = tk . X , padx = 10 , pady = 10 )
ttk . Button ( button_frame , text = " Save Configuration " , command = self . save_config ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( button_frame , text = " Load Configuration " , command = self . load_config ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( button_frame , text = " Reset to Defaults " , command = self . reset_config ) . pack ( side = tk . LEFT , padx = 5 )
def setup_generation_tab ( self ) :
""" Set up the generation tab """
# Player selection
player_frame = ttk . LabelFrame ( self . generation_frame , text = " Player Selection " , padding = 10 )
player_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
self . player_mode_var = tk . StringVar ( value = " all " )
ttk . Radiobutton ( player_frame , text = " Process all players " , variable = self . player_mode_var , value = " all " , command = self . update_player_controls ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 5 )
ttk . Radiobutton ( player_frame , text = " Process specific player " , variable = self . player_mode_var , value = " specific " , command = self . update_player_controls ) . grid ( row = 0 , column = 1 , sticky = tk . W , pady = 5 )
ttk . Label ( player_frame , text = " Player UID: " ) . grid ( row = 1 , column = 0 , sticky = tk . W , pady = 5 )
self . player_uid_var = tk . StringVar ( )
self . player_uid_entry = ttk . Entry ( player_frame , textvariable = self . player_uid_var , width = 20 , state = tk . DISABLED )
self . player_uid_entry . grid ( row = 1 , column = 1 , sticky = tk . W , padx = 5 , pady = 5 )
# Generation options
gen_frame = ttk . LabelFrame ( self . generation_frame , text = " Generation Options " , padding = 10 )
gen_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
self . update_mode_var = tk . StringVar ( value = " false " )
ttk . Checkbutton ( gen_frame , text = " Update existing (skip already processed) " , variable = self . update_mode_var , onvalue = " true " , offvalue = " false " ) . grid ( row = 0 , column = 0 , sticky = tk . W , pady = 5 )
2025-09-23 14:28:56 +01:00
# GPU Memory usage during generation
self . gpu_usage_var = tk . StringVar ( )
self . gpu_usage_var . set ( " GPU Memory: N/A " )
ttk . Label ( gen_frame , textvariable = self . gpu_usage_var ) . grid ( row = 1 , column = 0 , sticky = tk . W , pady = 5 )
2025-09-23 13:59:06 +01:00
# Progress display
progress_frame = ttk . LabelFrame ( self . generation_frame , text = " Progress " , padding = 10 )
progress_frame . pack ( fill = tk . BOTH , expand = True , padx = 10 , pady = 5 )
# Status text
self . status_text = scrolledtext . ScrolledText ( progress_frame , height = 10 , wrap = tk . WORD )
self . status_text . pack ( fill = tk . BOTH , expand = True , pady = 5 )
# Control buttons
control_frame = ttk . Frame ( self . generation_frame )
control_frame . pack ( fill = tk . X , padx = 10 , pady = 10 )
self . start_button = ttk . Button ( control_frame , text = " Start Generation " , command = self . start_generation )
self . start_button . pack ( side = tk . LEFT , padx = 5 )
self . stop_button = ttk . Button ( control_frame , text = " Stop Generation " , command = self . stop_generation_thread , state = tk . DISABLED )
self . stop_button . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( control_frame , text = " Clear Log " , command = self . clear_log ) . pack ( side = tk . RIGHT , padx = 5 )
def setup_preview_tab ( self ) :
""" Set up the preview tab """
# Image preview
preview_frame = ttk . LabelFrame ( self . preview_frame , text = " Generated Images " , padding = 10 )
preview_frame . pack ( fill = tk . BOTH , expand = True , padx = 10 , pady = 5 )
# Image display
self . image_label = ttk . Label ( preview_frame , text = " No images generated yet " )
self . image_label . pack ( pady = 20 )
# Image info
info_frame = ttk . Frame ( preview_frame )
info_frame . pack ( fill = tk . X , pady = 10 )
ttk . Label ( info_frame , text = " Current Image: " ) . grid ( row = 0 , column = 0 , sticky = tk . W )
self . current_image_var = tk . StringVar ( )
ttk . Label ( info_frame , textvariable = self . current_image_var ) . grid ( row = 0 , column = 1 , sticky = tk . W , padx = 5 )
ttk . Label ( info_frame , text = " Total Generated: " ) . grid ( row = 1 , column = 0 , sticky = tk . W )
self . total_generated_var = tk . StringVar ( value = " 0 " )
ttk . Label ( info_frame , textvariable = self . total_generated_var ) . grid ( row = 1 , column = 1 , sticky = tk . W , padx = 5 )
2025-09-23 15:33:01 +01:00
# Prompt display
prompt_frame = ttk . LabelFrame ( preview_frame , text = " Prompt Used " , padding = 10 )
prompt_frame . pack ( fill = tk . X , padx = 10 , pady = 5 )
self . prompt_text = scrolledtext . ScrolledText ( prompt_frame , height = 4 , wrap = tk . WORD )
self . prompt_text . pack ( fill = tk . X , pady = 5 )
self . prompt_text . config ( state = tk . DISABLED ) # Make it read-only
2025-09-23 13:59:06 +01:00
# Navigation buttons
nav_frame = ttk . Frame ( preview_frame )
nav_frame . pack ( fill = tk . X , pady = 10 )
ttk . Button ( nav_frame , text = " Previous " , command = self . show_previous_image ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( nav_frame , text = " Next " , command = self . show_next_image ) . pack ( side = tk . LEFT , padx = 5 )
ttk . Button ( nav_frame , text = " Refresh " , command = self . refresh_preview ) . pack ( side = tk . RIGHT , padx = 5 )
2025-09-23 14:52:52 +01:00
# Scan for existing images on startup
self . root . after ( 1000 , self . scan_existing_images ) # Delay to allow config to load
2025-09-23 13:59:06 +01:00
def browse_model_dir ( self ) :
""" Browse for model directory """
directory = filedialog . askdirectory ( title = " Select Model Directory " )
if directory :
self . model_dir_var . set ( directory )
def browse_rtf_file ( self ) :
""" Browse for RTF file """
file_path = filedialog . askopenfilename (
title = " Select RTF File " ,
filetypes = [ ( " RTF files " , " *.rtf " ) , ( " All files " , " *.* " ) ]
)
if file_path :
self . rtf_file_var . set ( file_path )
def browse_output_dir ( self ) :
""" Browse for output directory """
directory = filedialog . askdirectory ( title = " Select Output Directory " )
if directory :
self . output_dir_var . set ( directory )
def update_player_controls ( self ) :
""" Update player selection controls based on mode """
if self . player_mode_var . get ( ) == " specific " :
self . player_uid_entry . config ( state = tk . NORMAL )
else :
self . player_uid_entry . config ( state = tk . DISABLED )
self . player_uid_var . set ( " " )
def save_config ( self ) :
""" Save configuration to file """
try :
# Update config object
if not self . config :
self . config = configparser . ConfigParser ( )
self . config . add_section ( ' models ' )
2025-09-23 14:25:59 +01:00
# Use current profile or create NewGens if none selected
profile_name = self . current_profile or " NewGens "
profile_section = f ' profile: { profile_name } '
if not self . config . has_section ( profile_section ) :
self . config . add_section ( profile_section )
2025-09-23 13:59:06 +01:00
self . config . set ( ' models ' , ' model_name ' , self . model_var . get ( ) )
if self . model_dir_var . get ( ) :
self . config . set ( ' models ' , ' model_dir ' , self . model_dir_var . get ( ) )
2025-09-23 14:25:59 +01:00
self . config . set ( profile_section , ' football_manager_version ' , self . fm_version_var . get ( ) )
self . config . set ( profile_section , ' rtf_file ' , self . rtf_file_var . get ( ) )
self . config . set ( profile_section , ' output_dir ' , self . output_dir_var . get ( ) )
2025-09-23 13:59:06 +01:00
# Save to file
with open ( ' user_config.cfg ' , ' w ' ) as configfile :
self . config . write ( configfile )
2025-09-23 14:25:59 +01:00
messagebox . showinfo ( " Success " , f " Configuration saved to profile ' { profile_name } ' ! " )
self . log_message ( f " Configuration saved to profile ' { profile_name } ' " )
2025-09-23 13:59:06 +01:00
except Exception as e :
messagebox . showerror ( " Error " , f " Failed to save configuration: { str ( e ) } " )
self . log_message ( f " Error saving configuration: { str ( e ) } " )
def load_config ( self ) :
""" Load configuration from file """
try :
if os . path . exists ( ' user_config.cfg ' ) :
self . config = configparser . ConfigParser ( )
self . config . read ( ' user_config.cfg ' )
# Load model settings
if ' models ' in self . config :
self . model_var . set ( self . config . get ( ' models ' , ' model_name ' , fallback = ' SG161222/Realistic_Vision_V6.0_B1 ' ) )
self . model_dir_var . set ( self . config . get ( ' models ' , ' model_dir ' , fallback = ' ' ) )
2025-09-23 14:25:59 +01:00
# Load current profile settings
if self . current_profile :
profile_section = f ' profile: { self . current_profile } '
if profile_section in self . config :
self . fm_version_var . set ( self . config . get ( profile_section , ' football_manager_version ' , fallback = ' 2024 ' ) )
self . rtf_file_var . set ( self . config . get ( profile_section , ' rtf_file ' , fallback = ' ' ) )
self . output_dir_var . set ( self . config . get ( profile_section , ' output_dir ' , fallback = ' ' ) )
self . log_message ( f " Configuration loaded for profile ' { self . current_profile } ' " )
else :
# Profile doesn't exist, create it with defaults
self . create_profile ( self . current_profile , ' 2024 ' , ' ' , ' ./NewGens/ ' )
self . fm_version_var . set ( ' 2024 ' )
self . rtf_file_var . set ( ' ' )
self . output_dir_var . set ( ' ./NewGens/ ' )
self . log_message ( f " Created new profile ' { self . current_profile } ' with defaults " )
else :
# No profile selected, use defaults
self . fm_version_var . set ( ' 2024 ' )
self . rtf_file_var . set ( ' ' )
self . output_dir_var . set ( ' ./NewGens/ ' )
self . log_message ( " No profile selected, using defaults " )
2025-09-23 14:52:52 +01:00
# Scan for existing images after loading config
self . root . after ( 500 , self . scan_existing_images )
2025-09-23 13:59:06 +01:00
else :
self . log_message ( " No configuration file found, using defaults " )
except Exception as e :
messagebox . showerror ( " Error " , f " Failed to load configuration: { str ( e ) } " )
self . log_message ( f " Error loading configuration: { str ( e ) } " )
def reset_config ( self ) :
""" Reset configuration to defaults """
self . model_var . set ( ' SG161222/Realistic_Vision_V6.0_B1 ' )
self . model_dir_var . set ( ' ' )
self . fm_version_var . set ( ' 2024 ' )
self . rtf_file_var . set ( ' ' )
2025-09-23 14:25:59 +01:00
self . output_dir_var . set ( ' ./NewGens/ ' )
# Update current profile with defaults
if self . current_profile :
profile_section = f ' profile: { self . current_profile } '
if self . config and self . config . has_section ( profile_section ) :
self . config . set ( profile_section , ' football_manager_version ' , ' 2024 ' )
self . config . set ( profile_section , ' rtf_file ' , ' ' )
self . config . set ( profile_section , ' output_dir ' , ' ./NewGens/ ' )
with open ( ' user_config.cfg ' , ' w ' ) as configfile :
self . config . write ( configfile )
2025-09-23 13:59:06 +01:00
self . log_message ( " Configuration reset to defaults " )
def log_message ( self , message ) :
""" Add message to log """
self . status_text . insert ( tk . END , f " [ { time . strftime ( ' % H: % M: % S ' ) } ] { message } \n " )
self . status_text . see ( tk . END )
def clear_log ( self ) :
""" Clear the log """
self . status_text . delete ( 1.0 , tk . END )
def start_generation ( self ) :
""" Start the generation process """
if not self . validate_config ( ) :
return
2025-09-23 14:25:59 +01:00
# Set environment variables for the main script
os . environ [ ' FM_NEWGEN_UPDATE_MODE ' ] = ' true ' if self . update_mode_var . get ( ) == " true " else ' false '
os . environ [ ' FM_NEWGEN_PROCESS_PLAYER ' ] = ' true ' if self . player_mode_var . get ( ) == " specific " else ' false '
if self . player_mode_var . get ( ) == " specific " :
os . environ [ ' FM_NEWGEN_PLAYER_UID ' ] = self . player_uid_var . get ( )
2025-09-23 13:59:06 +01:00
# Disable start button, enable stop button
self . start_button . config ( state = tk . DISABLED )
self . stop_button . config ( state = tk . NORMAL )
# Reset progress
self . progress_var . set ( 0 )
self . stop_generation = False
# Start generation thread
self . generation_thread = threading . Thread ( target = self . generation_worker )
self . generation_thread . daemon = True
self . generation_thread . start ( )
2025-09-23 14:28:56 +01:00
# Start GPU memory monitoring
self . update_gpu_memory_usage ( )
2025-09-23 13:59:06 +01:00
def stop_generation_thread ( self ) :
""" Stop the generation process """
self . stop_generation = True
self . stop_button . config ( state = tk . DISABLED )
2025-09-23 14:28:56 +01:00
self . gpu_usage_var . set ( " GPU Memory: N/A " ) # Reset GPU usage display
2025-09-23 13:59:06 +01:00
self . log_message ( " Stopping generation... " )
def generation_worker ( self ) :
""" Worker thread for generation process """
try :
self . log_message ( " Starting generation process... " )
# Load configurations
with open ( " app_config.json " , " r " ) as f :
app_config = json . load ( f )
# Parse RTF file
rtf_file = self . rtf . parse_rtf ( self . rtf_file_var . get ( ) )
self . log_message ( f " Parsed RTF file successfully. Found { len ( rtf_file ) } players. " )
# Determine players to process
update = self . update_mode_var . get ( ) == " true "
process_player = self . player_mode_var . get ( ) == " specific "
if process_player :
player_uuid = int ( self . player_uid_var . get ( ) )
players_to_process = [ p for p in rtf_file if int ( p [ 0 ] ) == player_uuid ]
elif update :
# Filter out already processed players
try :
2025-09-23 14:25:59 +01:00
output_dir = self . output_dir_var . get ( ) if self . output_dir_var . get ( ) else " ./NewGens/ "
values_from_config = extract_from_values ( f " { output_dir } /config.xml " )
2025-09-23 13:59:06 +01:00
ids_in_b = [ item for item in values_from_config ]
players_to_process = [ item for item in rtf_file if item [ 0 ] not in ids_in_b ]
except FileNotFoundError :
players_to_process = rtf_file
else :
players_to_process = rtf_file
if len ( players_to_process ) == 0 :
self . log_message ( " No players to process " )
return
self . log_message ( f " Processing { len ( players_to_process ) } players " )
# Generate prompts
prompts = generate_prompts_for_players ( players_to_process , app_config )
# Generate images
total_players = len ( prompts )
for i , prompt in enumerate ( prompts ) :
if self . stop_generation :
break
uid = prompt . split ( " : " ) [ 0 ]
comfy_prompt = prompt . split ( " : " ) [ 1 ]
self . log_message ( f " Generating image for player { uid } ( { i + 1 } / { total_players } ) " )
generate_image ( uid , comfy_prompt )
# Update progress
progress = ( i + 1 ) / total_players * 100
self . progress_var . set ( progress )
# Queue image for preview
self . image_queue . put ( uid )
# Post-processing
if not self . stop_generation :
try :
2025-09-23 14:10:16 +01:00
# Remove background from images if available
if BG_REMOVAL_AVAILABLE :
try :
2025-09-23 15:15:29 +01:00
output_dir = self . output_dir_var . get ( ) if self . output_dir_var . get ( ) else " ./NewGens/ "
player_ids = [ item [ 0 ] for item in players_to_process ]
self . log_message ( f " Starting background removal for { len ( player_ids ) } images... " )
# Check if images exist before processing
missing_images = [ ]
for uid in player_ids :
image_path = os . path . join ( output_dir , f " { uid } .png " )
if not os . path . exists ( image_path ) :
missing_images . append ( uid )
if missing_images :
self . log_message ( f " Warning: { len ( missing_images ) } images not found: { missing_images [ : 5 ] } ... " )
# Only process existing images
existing_ids = [ uid for uid in player_ids if os . path . exists ( os . path . join ( output_dir , f " { uid } .png " ) ) ]
if existing_ids :
self . log_message ( f " Processing { len ( existing_ids ) } images for background removal... " )
processed_count = remove_bg_from_file_list ( output_dir , existing_ids , use_gpu = False )
self . log_message ( f " Background removal completed: { processed_count } / { len ( existing_ids ) } images processed successfully. " )
else :
self . log_message ( " No images found for background removal. " )
2025-09-23 14:10:16 +01:00
except Exception as e :
2025-09-23 15:15:29 +01:00
self . log_message ( f " Background removal failed: { str ( e ) } " )
import traceback
self . log_message ( f " Error details: { traceback . format_exc ( ) } " )
2025-09-23 14:10:16 +01:00
else :
self . log_message ( " Background removal not available (rembg not installed). Images will have original backgrounds. " )
# Update or create configuration XML
2025-09-23 13:59:06 +01:00
if update :
append_to_config_xml (
self . output_dir_var . get ( ) ,
[ item [ 0 ] for item in players_to_process ] ,
self . fm_version_var . get ( )
)
else :
create_config_xml (
self . output_dir_var . get ( ) ,
[ item [ 0 ] for item in players_to_process ] ,
self . fm_version_var . get ( )
)
self . log_message ( " Configuration XML updated " )
except Exception as e :
self . log_message ( f " Post-processing failed: { e } " )
self . log_message ( " Generation complete! " )
except Exception as e :
self . log_message ( f " Generation failed: { str ( e ) } " )
import traceback
self . log_message ( traceback . format_exc ( ) )
finally :
# Re-enable start button
self . start_button . config ( state = tk . NORMAL )
self . stop_button . config ( state = tk . DISABLED )
def validate_config ( self ) :
""" Validate configuration before starting """
if not self . rtf_file_var . get ( ) :
messagebox . showerror ( " Error " , " Please select an RTF file " )
return False
if not self . output_dir_var . get ( ) :
messagebox . showerror ( " Error " , " Please select an output directory " )
return False
if not os . path . exists ( self . rtf_file_var . get ( ) ) :
messagebox . showerror ( " Error " , " RTF file does not exist " )
return False
return True
def update_image_preview ( self ) :
""" Update image preview periodically """
try :
while not self . image_queue . empty ( ) :
uid = self . image_queue . get ( )
self . show_image ( uid )
except :
pass
# Schedule next update
self . root . after ( 1000 , self . update_image_preview )
def show_image ( self , uid ) :
""" Show an image in the preview """
try :
image_path = f " { self . output_dir_var . get ( ) } / { uid } .png "
if os . path . exists ( image_path ) :
2025-09-23 14:52:52 +01:00
# Add to generated images list if not already present
if uid not in self . generated_images :
self . generated_images . append ( uid )
self . update_total_count ( )
# Find the index of current image
if uid in self . generated_images :
self . current_image_index = self . generated_images . index ( uid )
2025-09-23 13:59:06 +01:00
# Load and resize image
image = Image . open ( image_path )
image . thumbnail ( ( 300 , 300 ) , Image . Resampling . LANCZOS )
# Convert to PhotoImage
photo = ImageTk . PhotoImage ( image )
# Update display
self . image_label . config ( image = photo , text = " " )
self . image_label . image = photo # Keep reference
self . current_image_var . set ( f " Player { uid } " )
2025-09-23 15:33:01 +01:00
# Load and display the prompt for this image
try :
prompt = get_prompt_for_image ( uid )
self . prompt_text . config ( state = tk . NORMAL )
self . prompt_text . delete ( 1.0 , tk . END )
self . prompt_text . insert ( tk . END , prompt )
self . prompt_text . config ( state = tk . DISABLED )
except Exception as e :
self . prompt_text . config ( state = tk . NORMAL )
self . prompt_text . delete ( 1.0 , tk . END )
self . prompt_text . insert ( tk . END , f " Error loading prompt: { str ( e ) } " )
self . prompt_text . config ( state = tk . DISABLED )
2025-09-23 13:59:06 +01:00
self . log_message ( f " Preview updated: { uid } .png " )
except Exception as e :
self . log_message ( f " Failed to load image { uid } : { str ( e ) } " )
def show_previous_image ( self ) :
""" Show previous image """
2025-09-23 14:52:52 +01:00
if not self . generated_images :
self . log_message ( " No images available to navigate " )
return
if self . current_image_index < = 0 :
self . log_message ( " Already at first image " )
return
self . current_image_index - = 1
uid = self . generated_images [ self . current_image_index ]
self . show_image ( uid )
2025-09-23 13:59:06 +01:00
def show_next_image ( self ) :
""" Show next image """
2025-09-23 14:52:52 +01:00
if not self . generated_images :
self . log_message ( " No images available to navigate " )
return
if self . current_image_index > = len ( self . generated_images ) - 1 :
self . log_message ( " Already at last image " )
return
self . current_image_index + = 1
uid = self . generated_images [ self . current_image_index ]
self . show_image ( uid )
def update_total_count ( self ) :
""" Update the total generated count display """
count = len ( self . generated_images )
self . total_generated_var . set ( str ( count ) )
self . log_message ( f " Total generated images: { count } " )
def scan_existing_images ( self ) :
""" Scan the output directory for existing images """
try :
output_dir = self . output_dir_var . get ( )
if not output_dir or not os . path . exists ( output_dir ) :
return
# Find all PNG files in the output directory
existing_images = [ ]
for file in os . listdir ( output_dir ) :
if file . endswith ( ' .png ' ) and file [ : - 4 ] . isdigit ( ) :
uid = file [ : - 4 ] # Remove .png extension
existing_images . append ( uid )
# Sort by UID (numerically)
existing_images . sort ( key = int )
# Update our tracking list
self . generated_images = existing_images
self . update_total_count ( )
if existing_images :
self . log_message ( f " Found { len ( existing_images ) } existing images " )
2025-09-23 15:33:01 +01:00
# Show the first image if available
self . show_image ( existing_images [ 0 ] )
2025-09-23 14:52:52 +01:00
else :
self . log_message ( " No existing images found " )
except Exception as e :
self . log_message ( f " Error scanning existing images: { str ( e ) } " )
2025-09-23 13:59:06 +01:00
def refresh_preview ( self ) :
""" Refresh the preview """
self . log_message ( " Refreshing preview... " )
2025-09-23 14:52:52 +01:00
self . scan_existing_images ( )
if self . generated_images :
self . show_image ( self . generated_images [ 0 ] ) # Show first image
2025-09-23 13:59:06 +01:00
def main ( ) :
""" Main function """
root = tk . Tk ( )
app = FMFaceGeneratorGUI ( root )
root . mainloop ( )
if __name__ == " __main__ " :
main ( )