diff --git a/README.md b/README.md index add114d9..f42a45cc 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The system supports live, recent, and upcoming game information for multiple spo - NCAA Football - NCAA Men's Basketball - NCAA Men's Baseball -- Soccer +- Soccer (Premier League, La Liga, Bundesliga, Serie A, Ligue 1, Liga Portugal, Champions League, Europa League, MLS) - (Note, some of these sports seasons were not active during development and might need fine tuning when games are active) diff --git a/assets/sports/all_team_abbreviations.txt b/assets/sports/all_team_abbreviations.txt index 4397af35..e2ae5c27 100644 --- a/assets/sports/all_team_abbreviations.txt +++ b/assets/sports/all_team_abbreviations.txt @@ -755,6 +755,20 @@ MLB Conferences/Divisions OAK => Oakland Athletics SEA => Seattle Mariners TEX => Texas Rangers + +Soccer Leagues: + LEAGUE_SLUGS = { + "eng.1": "Premier League", + "esp.1": "La Liga", + "ger.1": "Bundesliga", + "ita.1": "Serie A", + "fra.1": "Ligue 1", + "uefa.champions": "Champions League", + "uefa.europa": "Europa League", + "usa.1": "MLS", + "por.1": "Liga Portugal", # Add this line + } + Soccer - Premier League (England) ARS => Arsenal AVL => Aston Villa @@ -886,6 +900,24 @@ Soccer - Champions League VFB => VfB Stuttgart VIL => Villarreal +Soccer - Liga Portugal (Portugal) + ARO => Arouca + BEN => SL Benfica + BRA => SC Braga + CHA => Chaves + EST => Estoril Praia + FAM => Famalicão + GIL => Gil Vicente + MOR => Moreirense + POR => FC Porto + PTM => Portimonense + RIO => Rio Ave + SR => Sporting CP + VGU => Vitória de Guimarães + VSC => Vitória de Setúbal + + + Soccer - Other Teams austin => Austin FC cf_montral => CF Montréal diff --git a/assets/sports/soccer_logos/BEN.png b/assets/sports/soccer_logos/BEN.png new file mode 100644 index 00000000..f725c1ce Binary files /dev/null and b/assets/sports/soccer_logos/BEN.png differ diff --git a/assets/sports/soccer_logos/SCP.png b/assets/sports/soccer_logos/SCP.png new file mode 100644 index 00000000..bfbb3bce Binary files /dev/null and b/assets/sports/soccer_logos/SCP.png differ diff --git a/src/logo_downloader.py b/src/logo_downloader.py index 702048d4..b7dd4548 100644 --- a/src/logo_downloader.py +++ b/src/logo_downloader.py @@ -31,7 +31,17 @@ class LogoDownloader: 'ncaa_fb_all': 'https://site.api.espn.com/apis/site/v2/sports/football/college-football/teams', # Includes FCS 'fcs': 'https://site.api.espn.com/apis/site/v2/sports/football/college-football/teams', # FCS teams from same endpoint 'ncaam_basketball': 'https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/teams', - 'ncaa_baseball': 'https://site.api.espn.com/apis/site/v2/sports/baseball/college-baseball/teams' + 'ncaa_baseball': 'https://site.api.espn.com/apis/site/v2/sports/baseball/college-baseball/teams', + # Soccer leagues + 'soccer_eng.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/eng.1/teams', + 'soccer_esp.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/esp.1/teams', + 'soccer_ger.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/ger.1/teams', + 'soccer_ita.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/ita.1/teams', + 'soccer_fra.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/fra.1/teams', + 'soccer_por.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/por.1/teams', + 'soccer_uefa.champions': 'https://site.api.espn.com/apis/site/v2/sports/soccer/uefa.champions/teams', + 'soccer_uefa.europa': 'https://site.api.espn.com/apis/site/v2/sports/soccer/uefa.europa/teams', + 'soccer_usa.1': 'https://site.api.espn.com/apis/site/v2/sports/soccer/usa.1/teams' } # Directory mappings for different leagues @@ -44,7 +54,17 @@ class LogoDownloader: 'ncaa_fb_all': 'assets/sports/ncaa_fbs_logos', # FCS teams go in same directory 'fcs': 'assets/sports/ncaa_fbs_logos', # FCS teams go in same directory 'ncaam_basketball': 'assets/sports/ncaa_fbs_logos', - 'ncaa_baseball': 'assets/sports/ncaa_fbs_logos' + 'ncaa_baseball': 'assets/sports/ncaa_fbs_logos', + # Soccer leagues - all use the same soccer_logos directory + 'soccer_eng.1': 'assets/sports/soccer_logos', + 'soccer_esp.1': 'assets/sports/soccer_logos', + 'soccer_ger.1': 'assets/sports/soccer_logos', + 'soccer_ita.1': 'assets/sports/soccer_logos', + 'soccer_fra.1': 'assets/sports/soccer_logos', + 'soccer_por.1': 'assets/sports/soccer_logos', + 'soccer_uefa.champions': 'assets/sports/soccer_logos', + 'soccer_uefa.europa': 'assets/sports/soccer_logos', + 'soccer_usa.1': 'assets/sports/soccer_logos' } def __init__(self, request_timeout: int = 30, retry_attempts: int = 3): @@ -605,6 +625,20 @@ class LogoDownloader: return converted_count, failed_count +# Helper function to map soccer league codes to logo downloader format +def get_soccer_league_key(league_code: str) -> str: + """ + Map soccer league codes to logo downloader format. + + Args: + league_code: Soccer league code (e.g., 'eng.1', 'por.1') + + Returns: + Logo downloader league key (e.g., 'soccer_eng.1', 'soccer_por.1') + """ + return f"soccer_{league_code}" + + # Convenience function for easy integration def download_missing_logo(team_abbreviation: str, league: str, team_name: str = None, create_placeholder: bool = True) -> bool: """ diff --git a/src/soccer_managers.py b/src/soccer_managers.py index b39921dd..8ecbeed5 100644 --- a/src/soccer_managers.py +++ b/src/soccer_managers.py @@ -12,6 +12,7 @@ from src.display_manager import DisplayManager from src.cache_manager import CacheManager from src.config_manager import ConfigManager from src.odds_manager import OddsManager +from src.logo_downloader import download_missing_logo, get_soccer_league_key import pytz # Import the API counter function from web interface @@ -32,6 +33,7 @@ LEAGUE_SLUGS = { "ger.1": "Bundesliga", "ita.1": "Serie A", "fra.1": "Ligue 1", + "por.1": "Liga Portugal", "uefa.champions": "Champions League", "uefa.europa": "Europa League", "usa.1": "MLS", @@ -408,42 +410,61 @@ class BaseSoccerManager: try: if not os.path.exists(logo_path) and not (cache_logo_path and os.path.exists(cache_logo_path)): - self.logger.info(f"Creating placeholder logo for {team_abbrev}") - # Try to create placeholder in cache directory instead of assets directory - cache_logo_path = None - try: - # Use cache directory for placeholder logos - if hasattr(self.cache_manager, 'cache_dir') and self.cache_manager.cache_dir: - cache_logo_dir = os.path.join(self.cache_manager.cache_dir, 'placeholder_logos') - os.makedirs(cache_logo_dir, exist_ok=True) - cache_logo_path = os.path.join(cache_logo_dir, f"{team_abbrev}.png") + self.logger.info(f"Logo not found for {team_abbrev} at {logo_path}. Attempting to download from ESPN.") + + # Try to download the logo from ESPN API for each configured league + download_success = False + for league_code in self.target_leagues_config: + if league_code in LEAGUE_SLUGS: + soccer_league_key = get_soccer_league_key(league_code) + self.logger.debug(f"Attempting to download {team_abbrev} logo from {league_code} ({soccer_league_key})") - # Create placeholder logo + success = download_missing_logo(team_abbrev, soccer_league_key, team_abbrev) + if success: + self.logger.info(f"Successfully downloaded logo for {team_abbrev} from {league_code}") + download_success = True + break + else: + self.logger.debug(f"Failed to download {team_abbrev} logo from {league_code}") + + if not download_success: + self.logger.warning(f"Failed to download logo for {team_abbrev} from any configured league. Creating placeholder.") + # Try to create placeholder in cache directory instead of assets directory + cache_logo_path = None + try: + # Use cache directory for placeholder logos + if hasattr(self.cache_manager, 'cache_dir') and self.cache_manager.cache_dir: + cache_logo_dir = os.path.join(self.cache_manager.cache_dir, 'placeholder_logos') + os.makedirs(cache_logo_dir, exist_ok=True) + cache_logo_path = os.path.join(cache_logo_dir, f"{team_abbrev}.png") + + # Create placeholder logo + logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255)) + draw = ImageDraw.Draw(logo) + # Optionally add text to placeholder + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + font_4x6 = os.path.abspath(os.path.join(script_dir, "../assets/fonts/4x6-font.ttf")) + placeholder_font = ImageFont.truetype(font_4x6, 12) + text_width = draw.textlength(team_abbrev, font=placeholder_font) + text_x = (36 - text_width) // 2 + text_y = 10 + draw.text((text_x, text_y), team_abbrev, fill=(0,0,0,255), font=placeholder_font) + except IOError: + pass # Font not found, skip text + logo.save(cache_logo_path) + self.logger.info(f"Created placeholder logo in cache at {cache_logo_path}") + # Update logo_path to use cache version + logo_path = cache_logo_path + else: + # No cache directory available, just use in-memory placeholder + raise PermissionError("No writable cache directory available") + except (PermissionError, OSError) as pe: + self.logger.debug(f"Could not create placeholder logo file for {team_abbrev}: {pe}") + # Return a simple in-memory placeholder instead logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255)) - draw = ImageDraw.Draw(logo) - # Optionally add text to placeholder - try: - font_4x6 = os.path.abspath(os.path.join(script_dir, "../assets/fonts/4x6-font.ttf")) - placeholder_font = ImageFont.truetype(font_4x6, 12) - text_width = draw.textlength(team_abbrev, font=placeholder_font) - text_x = (36 - text_width) // 2 - text_y = 10 - draw.text((text_x, text_y), team_abbrev, fill=(0,0,0,255), font=placeholder_font) - except IOError: - pass # Font not found, skip text - logo.save(cache_logo_path) - self.logger.info(f"Created placeholder logo in cache at {cache_logo_path}") - # Update logo_path to use cache version - logo_path = cache_logo_path - else: - # No cache directory available, just use in-memory placeholder - raise PermissionError("No writable cache directory available") - except (PermissionError, OSError) as pe: - self.logger.debug(f"Could not create placeholder logo file for {team_abbrev}: {pe}") - # Return a simple in-memory placeholder instead - logo = Image.new('RGBA', (36, 36), (random.randint(50, 200), random.randint(50, 200), random.randint(50, 200), 255)) - self._logo_cache[team_abbrev] = logo - return logo + self._logo_cache[team_abbrev] = logo + return logo # Try to load logo from original path or cache directory logo_to_load = None