diff --git a/plugins.json b/plugins.json index 759485c..3045da2 100644 --- a/plugins.json +++ b/plugins.json @@ -299,7 +299,7 @@ "last_updated": "2026-03-29", "verified": true, "screenshot": "", - "latest_version": "1.5.5" + "latest_version": "1.5.6" }, { "id": "soccer-scoreboard", @@ -352,10 +352,10 @@ "plugin_path": "plugins/odds-ticker", "stars": 0, "downloads": 0, - "last_updated": "2026-02-23", + "last_updated": "2026-03-29", "verified": true, "screenshot": "", - "latest_version": "1.1.1" + "latest_version": "1.1.3" }, { "id": "leaderboard", diff --git a/plugins/baseball-scoreboard/baseball.py b/plugins/baseball-scoreboard/baseball.py index 054c8a2..c0e7832 100644 --- a/plugins/baseball-scoreboard/baseball.py +++ b/plugins/baseball-scoreboard/baseball.py @@ -136,9 +136,9 @@ def _extract_game_details(self, game_event: Dict) -> Optional[Dict]: try: game_status = status["type"]["name"].lower() status_state = status["type"]["state"].lower() - # Get team abbreviations - home_abbr = home_team["team"]["abbreviation"] - away_abbr = away_team["team"]["abbreviation"] + # Get team abbreviations (some NCAA teams lack 'abbreviation') + home_abbr = home_team["team"].get("abbreviation") or (home_team["team"].get("name") or "?")[:3] + away_abbr = away_team["team"].get("abbreviation") or (away_team["team"].get("name") or "?")[:3] # Check if this is a favorite team game is_favorite_game = ( diff --git a/plugins/baseball-scoreboard/game_renderer.py b/plugins/baseball-scoreboard/game_renderer.py index 6a6441d..8f17615 100644 --- a/plugins/baseball-scoreboard/game_renderer.py +++ b/plugins/baseball-scoreboard/game_renderer.py @@ -8,7 +8,8 @@ import logging from datetime import datetime from pathlib import Path -from typing import Dict, Optional +import os +from typing import Any, Dict, Optional import pytz from PIL import Image, ImageDraw, ImageFont @@ -56,27 +57,86 @@ def __init__( # Load fonts self.fonts = self._load_fonts() - def _load_fonts(self): - """Load fonts used by the renderer.""" + def _load_fonts(self) -> Dict[str, ImageFont.FreeTypeFont]: + """Load fonts used by the scoreboard from config or use defaults.""" fonts = {} + + # Get customization config + customization = self.config.get('customization', {}) + + # Load fonts from config with defaults for backward compatibility + score_config = customization.get('score_text', {}) + period_config = customization.get('period_text', {}) + team_config = customization.get('team_name', {}) + status_config = customization.get('status_text', {}) + detail_config = customization.get('detail_text', {}) + rank_config = customization.get('rank_text', {}) + try: - fonts['score'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 10) - fonts['time'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) - fonts['team'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) - fonts['status'] = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6) - fonts['detail'] = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6) - fonts['rank'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 10) - self.logger.debug("Successfully loaded fonts") - except IOError: - self.logger.warning("Fonts not found, using default PIL font.") - fonts['score'] = ImageFont.load_default() - fonts['time'] = ImageFont.load_default() - fonts['team'] = ImageFont.load_default() - fonts['status'] = ImageFont.load_default() - fonts['detail'] = ImageFont.load_default() - fonts['rank'] = ImageFont.load_default() + fonts["score"] = self._load_custom_font(score_config, default_size=10) + fonts["time"] = self._load_custom_font(period_config, default_size=8) + fonts["team"] = self._load_custom_font(team_config, default_size=8) + fonts["status"] = self._load_custom_font(status_config, default_size=6) + fonts["detail"] = self._load_custom_font(detail_config, default_size=6, default_font='4x6-font.ttf') + fonts["rank"] = self._load_custom_font(rank_config, default_size=10) + self.logger.debug("Successfully loaded fonts from config") + except Exception as e: + self.logger.exception("Error loading fonts, using defaults") + # Fallback to hardcoded defaults + try: + fonts["score"] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 10) + fonts["time"] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) + fonts["team"] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) + fonts["status"] = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6) + fonts["detail"] = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6) + fonts["rank"] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 10) + except IOError: + self.logger.warning("Fonts not found, using default PIL font.") + default_font = ImageFont.load_default() + fonts = {k: default_font for k in ["score", "time", "team", "status", "detail", "rank"]} + return fonts + def _load_custom_font(self, element_config: Dict[str, Any], default_size: int = 8, default_font: str = 'PressStart2P-Regular.ttf') -> ImageFont.FreeTypeFont: + """Load a custom font from an element configuration dictionary.""" + font_name = element_config.get('font', default_font) + font_size = int(element_config.get('font_size', default_size)) + font_path = os.path.join('assets', 'fonts', font_name) + + try: + if os.path.exists(font_path): + if font_path.lower().endswith('.ttf') or font_path.lower().endswith('.otf'): + return ImageFont.truetype(font_path, font_size) + elif font_path.lower().endswith('.bdf'): + # BDF fonts require pre-conversion: pilfont.py font.bdf -> font.pil + font.pbm + pil_font_path = font_path.rsplit('.', 1)[0] + '.pil' + if os.path.exists(pil_font_path): + try: + return ImageFont.load(pil_font_path) + except Exception as e: + self.logger.warning(f"Failed to load pre-converted BDF font {pil_font_path}: {e}") + else: + self.logger.warning( + f"BDF font {font_name} requires conversion. " + f"Run: pilfont.py {font_path}" + ) + else: + self.logger.warning(f"Unknown font file type: {font_name}") + else: + self.logger.warning(f"Font file not found: {font_path}") + except Exception as e: + self.logger.warning(f"Error loading font {font_name}: {e}") + + # Fallback to default font + default_font_path = os.path.join('assets', 'fonts', 'PressStart2P-Regular.ttf') + try: + if os.path.exists(default_font_path): + return ImageFont.truetype(default_font_path, font_size) + except Exception as e: + self.logger.warning(f"Error loading default font {default_font_path}: {e}") + + return ImageFont.load_default() + def _get_logo_path(self, league: str, team_abbrev: str) -> Path: """Get the logo path for a team based on league.""" if league == 'mlb': @@ -501,7 +561,7 @@ def _draw_dynamic_odds(self, draw, odds: Dict) -> None: favored_side = 'away' # Odds row below the status/inning text row - status_bbox = draw.textbbox((0, 0), "A", font=self.fonts['time']) + status_bbox = draw.textbbox((0, 0), "A", font=self.fonts['detail']) odds_y = status_bbox[3] + 2 # just below the status row # Show the negative spread on the appropriate side diff --git a/plugins/baseball-scoreboard/manifest.json b/plugins/baseball-scoreboard/manifest.json index 43fab42..69fc693 100644 --- a/plugins/baseball-scoreboard/manifest.json +++ b/plugins/baseball-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "baseball-scoreboard", "name": "Baseball Scoreboard", - "version": "1.5.5", + "version": "1.5.6", "author": "ChuckBuilds", "description": "Live, recent, and upcoming baseball games across MLB, MiLB, and NCAA Baseball with real-time scores and schedules", "category": "sports", @@ -30,6 +30,11 @@ "branch": "main", "plugin_path": "plugins/baseball-scoreboard", "versions": [ + { + "released": "2026-03-29", + "version": "1.5.6", + "ledmatrix_min": "2.0.0" + }, { "released": "2026-03-29", "version": "1.5.5", @@ -106,7 +111,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-02-24", + "last_updated": "2026-03-29", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/odds-ticker/manager.py b/plugins/odds-ticker/manager.py index fa19a3e..e2d6797 100644 --- a/plugins/odds-ticker/manager.py +++ b/plugins/odds-ticker/manager.py @@ -1148,8 +1148,8 @@ def _fetch_league_games(self, league_config: Dict[str, Any], now: datetime, cano away_team = next(c for c in competitors if c['homeAway'] == 'away') home_id = home_team['team']['id'] away_id = away_team['team']['id'] - home_abbr = home_team['team']['abbreviation'] - away_abbr = away_team['team']['abbreviation'] + home_abbr = home_team['team'].get('abbreviation') or (home_team['team'].get('name') or '?')[:3] + away_abbr = away_team['team'].get('abbreviation') or (away_team['team'].get('name') or '?')[:3] home_name = home_team['team'].get('name', home_abbr) away_name = away_team['team'].get('name', away_abbr) diff --git a/plugins/odds-ticker/manifest.json b/plugins/odds-ticker/manifest.json index 6918baf..2657d8a 100644 --- a/plugins/odds-ticker/manifest.json +++ b/plugins/odds-ticker/manifest.json @@ -1,7 +1,7 @@ { "id": "odds-ticker", "name": "Odds Ticker", - "version": "1.1.1", + "version": "1.1.3", "description": "Displays scrolling odds and betting lines for upcoming games across multiple sports leagues including NFL, NBA, MLB, NCAA Football, and more", "author": "ChuckBuilds", "category": "sports", @@ -10,6 +10,16 @@ "branch": "main", "plugin_path": "plugins/odds-ticker", "versions": [ + { + "version": "1.1.3", + "ledmatrix_min": "2.0.0", + "released": "2026-03-29" + }, + { + "version": "1.1.2", + "ledmatrix_min": "2.0.0", + "released": "2026-03-29" + }, { "version": "1.1.1", "ledmatrix_min": "2.0.0", @@ -33,7 +43,7 @@ ], "stars": 0, "downloads": 0, - "last_updated": "2026-02-23", + "last_updated": "2026-03-29", "verified": true, "screenshot": "", "display_modes": [