Merge development branch with recovered PRs and latest improvements

- All missing PRs (#15, #17, #35, #36) are now in both main and development
- Latest development improvements merged to main
- Resolved conflicts by keeping development branch merged content
This commit is contained in:
Chuck
2025-09-17 10:01:05 -04:00
7 changed files with 93 additions and 86 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 651 B

View File

@@ -32,7 +32,7 @@ class LogoDownloader:
'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',
"ncaam_hockey": "https://site.api.espn.com/apis/site/v2/sports/hockey/mens-college-hockey/teams",
'ncaam_hockey': 'https://site.api.espn.com/apis/site/v2/sports/hockey/mens-college-hockey/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',

View File

@@ -769,22 +769,13 @@ class MusicManager:
ARTIST_Y_PERCENT = 0.34 # 34% from top
ALBUM_Y_PERCENT = 0.60 # 60% from top
# Get font height for artist/album text
try:
artist_album_font_height = self.display_manager.get_font_height(font_artist_album)
except:
artist_album_font_height = LINE_HEIGHT_BDF # Fallback to BDF height
# Use fixed positioning to ensure consistency across all songs
# Add a consistent font baseline shift for BDF fonts (not dynamic)
FIXED_BDF_BASELINE_SHIFT = 6 # Fixed shift for proper BDF font positioning
# Ensure we have a reasonable shift (minimum 6 pixels)
font_shift = max(artist_album_font_height, 6)
# Calculate actual pixel positions, shifted down by font height
y_pos_title_top = 1
y_pos_artist_top = int(matrix_height * ARTIST_Y_PERCENT) + font_shift
# For album, use a smaller shift to ensure it fits above progress bar
album_shift = min(font_shift, 5) # Cap album shift at 5 pixels to preserve space
y_pos_album_top = int(matrix_height * ALBUM_Y_PERCENT) + album_shift
y_pos_artist_top = int(matrix_height * ARTIST_Y_PERCENT) + FIXED_BDF_BASELINE_SHIFT
y_pos_album_top = int(matrix_height * ALBUM_Y_PERCENT) + FIXED_BDF_BASELINE_SHIFT
TEXT_SCROLL_DIVISOR = 5

View File

@@ -36,7 +36,7 @@ class BaseNCAABaseballManager:
self.logger.setLevel(logging.DEBUG) # Set logger level to DEBUG
# Logo handling
self.logo_dir = self.ncaa_baseball_config.get('logo_dir', os.path.join('assets', 'sports', 'ncaa_fbs_logos'))
self.logo_dir = self.ncaa_baseball_config.get('logo_dir', os.path.join('assets', 'sports', 'ncaa_logos'))
if not os.path.exists(self.logo_dir):
self.logger.warning(f"NCAA Baseball logos directory not found: {self.logo_dir}")
try:

View File

@@ -51,7 +51,7 @@ class BaseNCAAFBManager: # Renamed class
self.is_enabled = self.ncaa_fb_config.get("enabled", False)
self.show_odds = self.ncaa_fb_config.get("show_odds", False)
self.test_mode = self.ncaa_fb_config.get("test_mode", False)
self.logo_dir = self.ncaa_fb_config.get("logo_dir", "assets/sports/ncaa_fbs_logos") # Changed logo dir
self.logo_dir = self.ncaa_fb_config.get("logo_dir", "assets/sports/ncaa_logos") # Changed logo dir
self.update_interval = self.ncaa_fb_config.get("update_interval_seconds", 60)
self.show_records = self.ncaa_fb_config.get('show_records', False)
self.show_ranking = self.ncaa_fb_config.get('show_ranking', False)
@@ -246,21 +246,6 @@ class BaseNCAAFBManager: # Renamed class
# For immediate response, fetch current/recent games only
year_events = self._fetch_immediate_games(year)
all_events.extend(year_events)
# Also try to fetch full season data immediately as fallback
try:
url = f"https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard"
response = self.session.get(url, params={"dates": year,"seasontype":2,"limit":1000},headers=self.headers, timeout=15)
response.raise_for_status()
data = response.json()
events = data.get('events', [])
if use_cache:
self.cache_manager.set(cache_key, events)
self.logger.info(f"[NCAAFB] Successfully fetched and cached {len(events)} events for {year} season.")
all_events.extend(events)
except requests.exceptions.RequestException as e:
self.logger.error(f"[NCAAFB] API error fetching full schedule for {year}: {e}")
continue
if not all_events:
self.logger.warning("[NCAAFB] No events found in schedule data.")
@@ -538,20 +523,25 @@ class BaseNCAAFBManager: # Renamed class
if not os.path.exists(logo_path):
self.logger.info(f"Logo not found for {team_abbrev} at {logo_path}. Attempting to download.")
# Try to download the logo from ESPN API
# Try to download the logo from ESPN API (this will create placeholder if download fails)
success = download_missing_logo(team_abbrev, 'ncaa_fb', team_name)
if not success:
# Create placeholder if download fails
self.logger.warning(f"Failed to download logo for {team_abbrev}. Creating placeholder.")
# If still no logo exists after download attempt, create a fallback placeholder
if not success and not os.path.exists(logo_path):
self.logger.warning(f"Failed to download logo for {team_abbrev}. Creating fallback placeholder.")
os.makedirs(os.path.dirname(logo_path), exist_ok=True)
logo = Image.new('RGBA', (32, 32), (200, 200, 200, 255)) # Gray placeholder
draw = ImageDraw.Draw(logo)
draw.text((2, 10), team_abbrev, fill=(0, 0, 0, 255))
logo.save(logo_path)
self.logger.info(f"Created placeholder logo at {logo_path}")
self.logger.info(f"Created fallback placeholder logo at {logo_path}")
logo = Image.open(logo_path)
# Only try to open the logo if the file exists
if os.path.exists(logo_path):
logo = Image.open(logo_path)
else:
self.logger.error(f"Logo file still doesn't exist at {logo_path} after download attempt")
return None
if logo.mode != 'RGBA':
logo = logo.convert('RGBA')
@@ -1349,8 +1339,10 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
if self.show_records or self.show_ranking:
try:
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
self.logger.debug(f"Loaded 6px record font successfully")
except IOError:
record_font = ImageFont.load_default()
self.logger.warning(f"Failed to load 6px font, using default font (size: {record_font.size})")
# Get team abbreviations
away_abbr = game.get('away_abbr', '')
@@ -1359,57 +1351,68 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
record_bbox = draw_overlay.textbbox((0,0), "0-0", font=record_font)
record_height = record_bbox[3] - record_bbox[1]
record_y = self.display_height - record_height
self.logger.debug(f"Record positioning: height={record_height}, record_y={record_y}, display_height={self.display_height}")
# Display away team info
if away_abbr:
if self.show_ranking:
# Show ranking if available
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
rankings = self._fetch_team_rankings()
away_rank = rankings.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
elif self.show_records:
# Only show record if show_records is enabled
away_text = game.get('away_record', '')
else:
# Show nothing if show_records is false and team is unranked
# Show nothing for unranked teams when rankings are prioritized
away_text = ''
elif self.show_ranking:
# Show ranking only if available
rankings = self._fetch_team_rankings()
away_rank = rankings.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
away_text = ''
elif self.show_records:
# Show record only when rankings are disabled
away_text = game.get('away_record', '')
else:
# Show record only if show_records is enabled
if self.show_records:
away_text = game.get('away_record', '')
else:
away_text = ''
away_text = ''
if away_text:
away_record_x = 0
self.logger.debug(f"Drawing away ranking '{away_text}' at ({away_record_x}, {record_y}) with font size {record_font.size if hasattr(record_font, 'size') else 'unknown'}")
self._draw_text_with_outline(draw_overlay, away_text, (away_record_x, record_y), record_font)
# Display home team info
if home_abbr:
if self.show_ranking:
# Show ranking if available
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
rankings = self._fetch_team_rankings()
home_rank = rankings.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
elif self.show_records:
# Only show record if show_records is enabled
home_text = game.get('home_record', '')
else:
# Show nothing if show_records is false and team is unranked
# Show nothing for unranked teams when rankings are prioritized
home_text = ''
elif self.show_ranking:
# Show ranking only if available
rankings = self._fetch_team_rankings()
home_rank = rankings.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else:
home_text = ''
elif self.show_records:
# Show record only when rankings are disabled
home_text = game.get('home_record', '')
else:
# Show record only if show_records is enabled
if self.show_records:
home_text = game.get('home_record', '')
else:
home_text = ''
home_text = ''
if home_text:
home_record_bbox = draw_overlay.textbbox((0,0), home_text, font=record_font)
home_record_width = home_record_bbox[2] - home_record_bbox[0]
home_record_x = self.display_width - home_record_width
self.logger.debug(f"Drawing home ranking '{home_text}' at ({home_record_x}, {record_y}) with font size {record_font.size if hasattr(record_font, 'size') else 'unknown'}")
self._draw_text_with_outline(draw_overlay, home_text, (home_record_x, record_y), record_font)
# Composite and display
@@ -1713,8 +1716,10 @@ class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class
if self.show_records or self.show_ranking:
try:
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
self.logger.debug(f"Loaded 6px record font successfully")
except IOError:
record_font = ImageFont.load_default()
self.logger.warning(f"Failed to load 6px font, using default font (size: {record_font.size})")
# Get team abbreviations
away_abbr = game.get('away_abbr', '')
@@ -1723,57 +1728,68 @@ class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class
record_bbox = draw_overlay.textbbox((0,0), "0-0", font=record_font)
record_height = record_bbox[3] - record_bbox[1]
record_y = self.display_height - record_height
self.logger.debug(f"Record positioning: height={record_height}, record_y={record_y}, display_height={self.display_height}")
# Display away team info
if away_abbr:
if self.show_ranking:
# Show ranking if available
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
rankings = self._fetch_team_rankings()
away_rank = rankings.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
elif self.show_records:
# Only show record if show_records is enabled
away_text = game.get('away_record', '')
else:
# Show nothing if show_records is false and team is unranked
# Show nothing for unranked teams when rankings are prioritized
away_text = ''
elif self.show_ranking:
# Show ranking only if available
rankings = self._fetch_team_rankings()
away_rank = rankings.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
away_text = ''
elif self.show_records:
# Show record only when rankings are disabled
away_text = game.get('away_record', '')
else:
# Show record only if show_records is enabled
if self.show_records:
away_text = game.get('away_record', '')
else:
away_text = ''
away_text = ''
if away_text:
away_record_x = 0
self.logger.debug(f"Drawing away ranking '{away_text}' at ({away_record_x}, {record_y}) with font size {record_font.size if hasattr(record_font, 'size') else 'unknown'}")
self._draw_text_with_outline(draw_overlay, away_text, (away_record_x, record_y), record_font)
# Display home team info
if home_abbr:
if self.show_ranking:
# Show ranking if available
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
rankings = self._fetch_team_rankings()
home_rank = rankings.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
elif self.show_records:
# Only show record if show_records is enabled
home_text = game.get('home_record', '')
else:
# Show nothing if show_records is false and team is unranked
# Show nothing for unranked teams when rankings are prioritized
home_text = ''
elif self.show_ranking:
# Show ranking only if available
rankings = self._fetch_team_rankings()
home_rank = rankings.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else:
home_text = ''
elif self.show_records:
# Show record only when rankings are disabled
home_text = game.get('home_record', '')
else:
# Show record only if show_records is enabled
if self.show_records:
home_text = game.get('home_record', '')
else:
home_text = ''
home_text = ''
if home_text:
home_record_bbox = draw_overlay.textbbox((0,0), home_text, font=record_font)
home_record_width = home_record_bbox[2] - home_record_bbox[0]
home_record_x = self.display_width - home_record_width
self.logger.debug(f"Drawing home ranking '{home_text}' at ({home_record_x}, {record_y}) with font size {record_font.size if hasattr(record_font, 'size') else 'unknown'}")
self._draw_text_with_outline(draw_overlay, home_text, (home_record_x, record_y), record_font)
# Composite and display

View File

@@ -53,7 +53,7 @@ class BaseNCAAMBasketballManager:
self.is_enabled = self.ncaam_basketball_config.get("enabled", False)
self.show_odds = self.ncaam_basketball_config.get("show_odds", False)
self.test_mode = self.ncaam_basketball_config.get("test_mode", False)
self.logo_dir = self.ncaam_basketball_config.get("logo_dir", "assets/sports/ncaa_fbs_logos")
self.logo_dir = self.ncaam_basketball_config.get("logo_dir", "assets/sports/ncaa_logos")
self.update_interval = self.ncaam_basketball_config.get("update_interval_seconds", 60)
self.show_records = self.ncaam_basketball_config.get('show_records', False)
self.last_update = 0

View File

@@ -154,7 +154,7 @@ class OddsTickerManager:
'sport': 'football',
'league': 'college-football',
'logo_league': 'ncaa_fb', # ESPN API league identifier for logo downloading
'logo_dir': 'assets/sports/ncaa_fbs_logos',
'logo_dir': 'assets/sports/ncaa_logos',
'favorite_teams': config.get('ncaa_fb_scoreboard', {}).get('favorite_teams', []),
'enabled': config.get('ncaa_fb_scoreboard', {}).get('enabled', False)
},
@@ -178,7 +178,7 @@ class OddsTickerManager:
'sport': 'basketball',
'league': 'mens-college-basketball',
'logo_league': 'ncaam_basketball', # ESPN API league identifier for logo downloading
'logo_dir': 'assets/sports/ncaa_fbs_logos',
'logo_dir': 'assets/sports/ncaa_logos',
'favorite_teams': config.get('ncaam_basketball_scoreboard', {}).get('favorite_teams', []),
'enabled': config.get('ncaam_basketball_scoreboard', {}).get('enabled', False)
},
@@ -186,7 +186,7 @@ class OddsTickerManager:
'sport': 'baseball',
'league': 'college-baseball',
'logo_league': 'ncaa_baseball', # ESPN API league identifier for logo downloading
'logo_dir': 'assets/sports/ncaa_fbs_logos',
'logo_dir': 'assets/sports/ncaa_logos',
'favorite_teams': config.get('ncaa_baseball_scoreboard', {}).get('favorite_teams', []),
'enabled': config.get('ncaa_baseball_scoreboard', {}).get('enabled', False)
},