mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-12 05:42:59 +00:00
display improvements for the scrolling
This commit is contained in:
@@ -427,14 +427,9 @@ class BaseMLBManager:
|
|||||||
home_abbr = home_team['team']['abbreviation']
|
home_abbr = home_team['team']['abbreviation']
|
||||||
away_abbr = away_team['team']['abbreviation']
|
away_abbr = away_team['team']['abbreviation']
|
||||||
|
|
||||||
# Get team records
|
# Get team records - Corrected path
|
||||||
home_record = "0-0"
|
home_record = home_team.get('records', [{}])[0].get('summary', '') if home_team.get('records') else ''
|
||||||
if 'record' in home_team and len(home_team['record']) > 0:
|
away_record = away_team.get('records', [{}])[0].get('summary', '') if away_team.get('records') else ''
|
||||||
home_record = home_team['record'][0].get('summary', '0-0')
|
|
||||||
|
|
||||||
away_record = "0-0"
|
|
||||||
if 'record' in away_team and len(away_team['record']) > 0:
|
|
||||||
away_record = away_team['record'][0].get('summary', '0-0')
|
|
||||||
|
|
||||||
# Check if this is a favorite team game
|
# Check if this is a favorite team game
|
||||||
is_favorite_game = (home_abbr in self.favorite_teams or away_abbr in self.favorite_teams)
|
is_favorite_game = (home_abbr in self.favorite_teams or away_abbr in self.favorite_teams)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class OddsTickerManager:
|
|||||||
self.last_scroll_time = 0
|
self.last_scroll_time = 0
|
||||||
self.games_data = []
|
self.games_data = []
|
||||||
self.current_game_index = 0
|
self.current_game_index = 0
|
||||||
self.current_image = None
|
self.ticker_image = None # This will hold the single, wide image
|
||||||
self.last_display_time = 0
|
self.last_display_time = 0
|
||||||
|
|
||||||
# Font setup
|
# Font setup
|
||||||
@@ -356,118 +356,140 @@ class OddsTickerManager:
|
|||||||
"""Create a display image for a game in the new format."""
|
"""Create a display image for a game in the new format."""
|
||||||
width = self.display_manager.matrix.width
|
width = self.display_manager.matrix.width
|
||||||
height = self.display_manager.matrix.height
|
height = self.display_manager.matrix.height
|
||||||
image = Image.new('RGB', (width * 2, height), color=(0, 0, 0)) # Wider image for scrolling
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
|
|
||||||
# Load logos
|
# Create a wider image for scrolling. The width will be determined by the content.
|
||||||
home_logo = self._get_team_logo(game['home_team'], game['logo_dir'])
|
# Let's start with a placeholder and calculate the actual width.
|
||||||
away_logo = self._get_team_logo(game['away_team'], game['logo_dir'])
|
|
||||||
|
|
||||||
|
# --- Pre-calculate widths ---
|
||||||
|
logo_size = 24 # Assuming square logos
|
||||||
|
logo_padding = 5
|
||||||
|
vs_padding = 8
|
||||||
|
section_padding = 12
|
||||||
|
|
||||||
# Fonts
|
# Fonts
|
||||||
team_font = self.fonts['medium']
|
team_font = self.fonts['medium']
|
||||||
record_font = self.fonts['small']
|
|
||||||
odds_font = self.fonts['small']
|
odds_font = self.fonts['small']
|
||||||
|
vs_font = self.fonts['medium']
|
||||||
# Team names and records
|
|
||||||
away_team_text = f"{game['away_team']} ({game['away_record']})"
|
|
||||||
home_team_text = f"{game['home_team']} ({game['home_record']})"
|
|
||||||
|
|
||||||
# Calculate positions
|
|
||||||
logo_size = 24
|
|
||||||
x_pos = 10
|
|
||||||
|
|
||||||
# Away team logo and info
|
|
||||||
if away_logo:
|
|
||||||
away_logo = away_logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
|
|
||||||
image.paste(away_logo, (x_pos, (height - logo_size) // 2), away_logo)
|
|
||||||
x_pos += logo_size + 5
|
|
||||||
|
|
||||||
draw.text((x_pos, 5), away_team_text, font=team_font, fill=(255, 255, 255))
|
|
||||||
|
|
||||||
# Home team logo and info
|
|
||||||
x_pos += int(draw.textlength(away_team_text, font=team_font)) + 10
|
|
||||||
if home_logo:
|
|
||||||
home_logo = home_logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
|
|
||||||
image.paste(home_logo, (x_pos, (height - logo_size) // 2), home_logo)
|
|
||||||
x_pos += logo_size + 5
|
|
||||||
|
|
||||||
draw.text((x_pos, 18), home_team_text, font=team_font, fill=(255, 255, 255))
|
|
||||||
|
|
||||||
# Odds
|
|
||||||
odds = game.get('odds', {})
|
|
||||||
if odds:
|
|
||||||
home_team_odds = odds.get('home_team_odds', {})
|
|
||||||
away_team_odds = odds.get('away_team_odds', {})
|
|
||||||
home_spread = home_team_odds.get('spread_odds')
|
|
||||||
over_under = odds.get('over_under')
|
|
||||||
|
|
||||||
# Determine favorite
|
|
||||||
home_favored = home_spread is not None and home_spread < 0
|
|
||||||
|
|
||||||
# Draw odds
|
|
||||||
x_pos += int(draw.textlength(home_team_text, font=team_font)) + 10
|
|
||||||
if home_favored:
|
|
||||||
draw.text((x_pos, 18), f"{home_spread}", font=odds_font, fill=(0, 255, 0))
|
|
||||||
if over_under:
|
|
||||||
draw.text((x_pos, 5), f"O/U {over_under}", font=odds_font, fill=(255, 255, 0))
|
|
||||||
else:
|
|
||||||
away_spread = away_team_odds.get('spread_odds')
|
|
||||||
if away_spread is not None:
|
|
||||||
draw.text((x_pos, 5), f"{away_spread}", font=odds_font, fill=(0, 255, 0))
|
|
||||||
if over_under:
|
|
||||||
draw.text((x_pos, 18), f"O/U {over_under}", font=odds_font, fill=(255, 255, 0))
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
def _create_ticker_image(self, game: Dict[str, Any]) -> Image.Image:
|
# Get team logos
|
||||||
"""Create a scrolling ticker image for a game."""
|
|
||||||
width = self.display_manager.matrix.width
|
|
||||||
height = self.display_manager.matrix.height
|
|
||||||
|
|
||||||
logger.debug(f"Creating ticker image for {width}x{height} display")
|
|
||||||
|
|
||||||
# Create a wider image for scrolling
|
|
||||||
scroll_width = width * 3 # 3x width for smooth scrolling
|
|
||||||
image = Image.new('RGB', (scroll_width, height), color=(0, 0, 0))
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
|
|
||||||
# Format the odds text
|
|
||||||
odds_text = self._format_odds_text(game)
|
|
||||||
logger.debug(f"Formatted odds text: '{odds_text}'")
|
|
||||||
|
|
||||||
# Load team logos
|
|
||||||
home_logo = self._get_team_logo(game['home_team'], game['logo_dir'])
|
home_logo = self._get_team_logo(game['home_team'], game['logo_dir'])
|
||||||
away_logo = self._get_team_logo(game['away_team'], game['logo_dir'])
|
away_logo = self._get_team_logo(game['away_team'], game['logo_dir'])
|
||||||
|
|
||||||
# Calculate text position (start off-screen to the right)
|
|
||||||
text_width = draw.textlength(odds_text, font=self.fonts['medium'])
|
|
||||||
text_x = scroll_width - text_width - 10 # Start off-screen right
|
|
||||||
text_y = (height - self.fonts['medium'].size) // 2
|
|
||||||
|
|
||||||
logger.debug(f"Drawing text at position ({text_x}, {text_y})")
|
|
||||||
|
|
||||||
# Draw the text
|
|
||||||
self._draw_text_with_outline(draw, odds_text, (text_x, text_y), self.fonts['medium'])
|
|
||||||
|
|
||||||
# Add team logos if available
|
|
||||||
logo_size = 16
|
|
||||||
logo_y = (height - logo_size) // 2
|
|
||||||
|
|
||||||
if away_logo:
|
|
||||||
away_logo.thumbnail((logo_size, logo_size), Image.Resampling.LANCZOS)
|
|
||||||
away_x = int(text_x - logo_size - 5)
|
|
||||||
image.paste(away_logo, (away_x, logo_y), away_logo)
|
|
||||||
logger.debug(f"Added away team logo at ({away_x}, {logo_y})")
|
|
||||||
|
|
||||||
if home_logo:
|
if home_logo:
|
||||||
home_logo.thumbnail((logo_size, logo_size), Image.Resampling.LANCZOS)
|
home_logo = home_logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
|
||||||
home_x = int(text_x + text_width + 5)
|
if away_logo:
|
||||||
image.paste(home_logo, (home_x, logo_y), home_logo)
|
away_logo = away_logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
|
||||||
logger.debug(f"Added home team logo at ({home_x}, {logo_y})")
|
|
||||||
|
# Create a temporary draw object to measure text
|
||||||
|
temp_draw = ImageDraw.Draw(Image.new('RGB', (1, 1)))
|
||||||
|
|
||||||
|
# "vs." text
|
||||||
|
vs_text = "vs."
|
||||||
|
vs_width = temp_draw.textlength(vs_text, font=vs_font)
|
||||||
|
|
||||||
|
# Team and record text
|
||||||
|
away_team_text = f"{game.get('away_team', 'N/A')} ({game.get('away_record', '')})"
|
||||||
|
home_team_text = f"{game.get('home_team', 'N/A')} ({game.get('home_record', '')})"
|
||||||
|
away_team_width = temp_draw.textlength(away_team_text, font=team_font)
|
||||||
|
home_team_width = temp_draw.textlength(home_team_text, font=team_font)
|
||||||
|
team_info_width = max(away_team_width, home_team_width)
|
||||||
|
|
||||||
|
# Odds text
|
||||||
|
odds = game.get('odds', {})
|
||||||
|
home_team_odds = odds.get('home_team_odds', {})
|
||||||
|
away_team_odds = odds.get('away_team_odds', {})
|
||||||
|
home_spread = home_team_odds.get('spread_odds')
|
||||||
|
away_spread = away_team_odds.get('spread_odds')
|
||||||
|
over_under = odds.get('over_under')
|
||||||
|
|
||||||
logger.debug(f"Created ticker image with size {image.size}")
|
home_favored = home_spread is not None and home_spread < 0
|
||||||
|
away_favored = away_spread is not None and away_spread < 0
|
||||||
|
|
||||||
|
away_odds_text = ""
|
||||||
|
home_odds_text = ""
|
||||||
|
|
||||||
|
if home_favored:
|
||||||
|
home_odds_text = f"{home_spread}"
|
||||||
|
if over_under:
|
||||||
|
away_odds_text = f"O/U {over_under}"
|
||||||
|
elif away_favored:
|
||||||
|
away_odds_text = f"{away_spread}"
|
||||||
|
if over_under:
|
||||||
|
home_odds_text = f"O/U {over_under}"
|
||||||
|
elif over_under: # No clear favorite, put O/U on home line
|
||||||
|
home_odds_text = f"O/U {over_under}"
|
||||||
|
|
||||||
|
away_odds_width = temp_draw.textlength(away_odds_text, font=odds_font)
|
||||||
|
home_odds_width = temp_draw.textlength(home_odds_text, font=odds_font)
|
||||||
|
odds_width = max(away_odds_width, home_odds_width)
|
||||||
|
|
||||||
|
# --- Calculate total width ---
|
||||||
|
total_width = (logo_size + logo_padding + vs_width + vs_padding + logo_size + section_padding +
|
||||||
|
team_info_width + section_padding + odds_width + section_padding)
|
||||||
|
|
||||||
|
# --- Create final image ---
|
||||||
|
image = Image.new('RGB', (int(total_width), height), color=(0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(image)
|
||||||
|
|
||||||
|
# --- Draw elements ---
|
||||||
|
current_x = logo_padding
|
||||||
|
|
||||||
|
# Away Logo
|
||||||
|
if away_logo:
|
||||||
|
y_pos = (height - logo_size) // 2
|
||||||
|
image.paste(away_logo, (int(current_x), y_pos), away_logo if away_logo.mode == 'RGBA' else None)
|
||||||
|
current_x += logo_size + vs_padding
|
||||||
|
|
||||||
|
# "vs."
|
||||||
|
y_pos = (height - vs_font.size) // 2 if hasattr(vs_font, 'size') else (height - 8) // 2 # Added fallback for default font
|
||||||
|
draw.text((current_x, y_pos), vs_text, font=vs_font, fill=(255, 255, 255))
|
||||||
|
current_x += vs_width + vs_padding
|
||||||
|
|
||||||
|
# Home Logo
|
||||||
|
if home_logo:
|
||||||
|
y_pos = (height - logo_size) // 2
|
||||||
|
image.paste(home_logo, (int(current_x), y_pos), home_logo if home_logo.mode == 'RGBA' else None)
|
||||||
|
current_x += logo_size + section_padding
|
||||||
|
|
||||||
|
# Team Info (stacked)
|
||||||
|
team_font_height = team_font.size if hasattr(team_font, 'size') else 8
|
||||||
|
away_y = 2
|
||||||
|
home_y = height - team_font_height - 2
|
||||||
|
draw.text((current_x, away_y), away_team_text, font=team_font, fill=(255, 255, 255))
|
||||||
|
draw.text((current_x, home_y), home_team_text, font=team_font, fill=(255, 255, 255))
|
||||||
|
current_x += team_info_width + section_padding
|
||||||
|
|
||||||
|
# Odds (stacked)
|
||||||
|
odds_font_height = odds_font.size if hasattr(odds_font, 'size') else 6
|
||||||
|
odds_y_away = 2
|
||||||
|
odds_y_home = height - odds_font_height - 2
|
||||||
|
draw.text((current_x, odds_y_away), away_odds_text, font=odds_font, fill=(255, 255, 0)) # Yellow for odds
|
||||||
|
draw.text((current_x, odds_y_home), home_odds_text, font=odds_font, fill=(0, 255, 0)) # Green for favorite
|
||||||
|
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
def _create_ticker_image(self):
|
||||||
|
"""Create a single wide image containing all game tickers."""
|
||||||
|
if not self.games_data:
|
||||||
|
self.ticker_image = None
|
||||||
|
return
|
||||||
|
|
||||||
|
game_images = [self._create_game_display(game) for game in self.games_data]
|
||||||
|
if not game_images:
|
||||||
|
self.ticker_image = None
|
||||||
|
return
|
||||||
|
|
||||||
|
gap_width = self.display_manager.matrix.width // 2 # Gap between games
|
||||||
|
total_width = sum(img.width for img in game_images) + gap_width * (len(game_images))
|
||||||
|
height = self.display_manager.matrix.height
|
||||||
|
|
||||||
|
self.ticker_image = Image.new('RGB', (total_width, height), color=(0, 0, 0))
|
||||||
|
|
||||||
|
current_x = 0
|
||||||
|
for img in game_images:
|
||||||
|
self.ticker_image.paste(img, (current_x, 0))
|
||||||
|
current_x += img.width + gap_width
|
||||||
|
|
||||||
def _draw_text_with_outline(self, draw: ImageDraw.Draw, text: str, position: tuple, font: ImageFont.FreeTypeFont,
|
def _draw_text_with_outline(self, draw: ImageDraw.Draw, text: str, position: tuple, font: ImageFont.FreeTypeFont,
|
||||||
fill: tuple = (255, 255, 255), outline_color: tuple = (0, 0, 0)) -> None:
|
fill: tuple = (255, 255, 255), outline_color: tuple = (0, 0, 0)) -> None:
|
||||||
"""Draw text with a black outline for better readability."""
|
"""Draw text with a black outline for better readability."""
|
||||||
@@ -496,8 +518,9 @@ class OddsTickerManager:
|
|||||||
|
|
||||||
self.games_data = self._fetch_upcoming_games()
|
self.games_data = self._fetch_upcoming_games()
|
||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
self.current_position = 0
|
self.scroll_position = 0
|
||||||
self.current_game_index = 0
|
self.current_game_index = 0
|
||||||
|
self._create_ticker_image() # Create the composite image
|
||||||
|
|
||||||
if self.games_data:
|
if self.games_data:
|
||||||
logger.info(f"Updated odds ticker with {len(self.games_data)} games")
|
logger.info(f"Updated odds ticker with {len(self.games_data)} games")
|
||||||
@@ -528,24 +551,17 @@ class OddsTickerManager:
|
|||||||
self._display_fallback_message()
|
self._display_fallback_message()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.ticker_image is None:
|
||||||
|
logger.warning("Ticker image is not available. Attempting to create it.")
|
||||||
|
self._create_ticker_image()
|
||||||
|
if self.ticker_image is None:
|
||||||
|
logger.error("Failed to create ticker image.")
|
||||||
|
self._display_fallback_message()
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
# Check if it's time to switch games
|
|
||||||
if current_time - self.last_display_time >= self.display_duration:
|
|
||||||
self.current_game_index = (self.current_game_index + 1) % len(self.games_data)
|
|
||||||
self.scroll_position = 0
|
|
||||||
self.last_display_time = current_time
|
|
||||||
self.current_image = None # Force recreate image
|
|
||||||
|
|
||||||
# Get current game
|
|
||||||
current_game = self.games_data[self.current_game_index]
|
|
||||||
logger.debug(f"Displaying game: {current_game['away_team']} @ {current_game['home_team']}")
|
|
||||||
|
|
||||||
# Create ticker image if needed
|
|
||||||
if self.current_image is None:
|
|
||||||
self.current_image = self._create_game_display(current_game)
|
|
||||||
|
|
||||||
# Scroll the image
|
# Scroll the image
|
||||||
if current_time - self.last_scroll_time >= self.scroll_delay:
|
if current_time - self.last_scroll_time >= self.scroll_delay:
|
||||||
self.scroll_position += self.scroll_speed
|
self.scroll_position += self.scroll_speed
|
||||||
@@ -555,22 +571,21 @@ class OddsTickerManager:
|
|||||||
width = self.display_manager.matrix.width
|
width = self.display_manager.matrix.width
|
||||||
height = self.display_manager.matrix.height
|
height = self.display_manager.matrix.height
|
||||||
|
|
||||||
# Reset position when we've scrolled past the end
|
# Reset position when we've scrolled past the end for a continuous loop
|
||||||
if self.scroll_position >= self.current_image.width:
|
if self.scroll_position >= self.ticker_image.width:
|
||||||
self.scroll_position = 0
|
self.scroll_position = 0
|
||||||
|
|
||||||
# Crop the scrolling region
|
# Create the visible part of the image by pasting from the ticker_image
|
||||||
crop_x = self.scroll_position
|
|
||||||
|
|
||||||
# Create the visible part of the image
|
|
||||||
visible_image = Image.new('RGB', (width, height))
|
visible_image = Image.new('RGB', (width, height))
|
||||||
visible_image.paste(self.current_image, (-crop_x, 0))
|
|
||||||
|
|
||||||
|
# Main part
|
||||||
|
visible_image.paste(self.ticker_image, (-self.scroll_position, 0))
|
||||||
|
|
||||||
# Handle wrap-around for continuous scroll
|
# Handle wrap-around for continuous scroll
|
||||||
if crop_x + width > self.current_image.width:
|
if self.scroll_position + width > self.ticker_image.width:
|
||||||
wrap_around_width = (crop_x + width) - self.current_image.width
|
wrap_around_width = (self.scroll_position + width) - self.ticker_image.width
|
||||||
wrap_around_image = self.current_image.crop((0, 0, wrap_around_width, height))
|
wrap_around_image = self.ticker_image.crop((0, 0, wrap_around_width, height))
|
||||||
visible_image.paste(wrap_around_image, (self.current_image.width - crop_x, 0))
|
visible_image.paste(wrap_around_image, (self.ticker_image.width - self.scroll_position, 0))
|
||||||
|
|
||||||
# Display the cropped image
|
# Display the cropped image
|
||||||
self.display_manager.image = visible_image
|
self.display_manager.image = visible_image
|
||||||
|
|||||||
Reference in New Issue
Block a user