mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
improve odds ticker dynamic duration
This commit is contained in:
@@ -6,7 +6,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import requests
|
import requests
|
||||||
from typing import Union
|
from typing import Union, Dict, Any, Optional
|
||||||
from PIL import Image, ImageEnhance
|
from PIL import Image, ImageEnhance
|
||||||
import queue # Added import
|
import queue # Added import
|
||||||
|
|
||||||
@@ -15,6 +15,14 @@ from .spotify_client import SpotifyClient
|
|||||||
from .ytm_client import YTMClient
|
from .ytm_client import YTMClient
|
||||||
# Removed: import config
|
# Removed: import config
|
||||||
|
|
||||||
|
# Import the API counter function from web interface
|
||||||
|
try:
|
||||||
|
from web_interface_v2 import increment_api_counter
|
||||||
|
except ImportError:
|
||||||
|
# Fallback if web interface is not available
|
||||||
|
def increment_api_counter(kind: str, count: int = 1):
|
||||||
|
pass
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -313,6 +321,10 @@ class MusicManager:
|
|||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=5) # 5-second timeout for image download
|
response = requests.get(url, timeout=5) # 5-second timeout for image download
|
||||||
response.raise_for_status() # Raise an exception for bad status codes
|
response.raise_for_status() # Raise an exception for bad status codes
|
||||||
|
|
||||||
|
# Increment API counter for music data
|
||||||
|
increment_api_counter('music', 1)
|
||||||
|
|
||||||
img_data = BytesIO(response.content)
|
img_data = BytesIO(response.content)
|
||||||
img = Image.open(img_data)
|
img = Image.open(img_data)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ from src.config_manager import ConfigManager
|
|||||||
from src.odds_manager import OddsManager
|
from src.odds_manager import OddsManager
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
# Import the API counter function from web interface
|
||||||
|
try:
|
||||||
|
from web_interface_v2 import increment_api_counter
|
||||||
|
except ImportError:
|
||||||
|
# Fallback if web interface is not available
|
||||||
|
def increment_api_counter(kind: str, count: int = 1):
|
||||||
|
pass
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
ESPN_NCAAMB_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard"
|
ESPN_NCAAMB_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard"
|
||||||
|
|
||||||
@@ -328,6 +336,9 @@ class BaseNCAAMBasketballManager:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
|
# Increment API counter for sports data
|
||||||
|
increment_api_counter('sports', 1)
|
||||||
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
self.cache_manager.set(cache_key, data)
|
self.cache_manager.set(cache_key, data)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
import requests
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import requests
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from src.cache_manager import CacheManager
|
from src.cache_manager import CacheManager
|
||||||
from src.config_manager import ConfigManager
|
import pytz
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Dict, Any, Optional, List
|
||||||
|
|
||||||
|
# Import the API counter function from web interface
|
||||||
|
try:
|
||||||
|
from web_interface_v2 import increment_api_counter
|
||||||
|
except ImportError:
|
||||||
|
# Fallback if web interface is not available
|
||||||
|
def increment_api_counter(kind: str, count: int = 1):
|
||||||
|
pass
|
||||||
|
|
||||||
class OddsManager:
|
class OddsManager:
|
||||||
def __init__(self, cache_manager: CacheManager, config_manager: ConfigManager):
|
def __init__(self, cache_manager: CacheManager, config_manager=None):
|
||||||
self.cache_manager = cache_manager
|
self.cache_manager = cache_manager
|
||||||
self.config_manager = config_manager
|
self.config_manager = config_manager
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
@@ -31,6 +40,9 @@ class OddsManager:
|
|||||||
response = requests.get(url, timeout=10)
|
response = requests.get(url, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
raw_data = response.json()
|
raw_data = response.json()
|
||||||
|
|
||||||
|
# Increment API counter for odds data
|
||||||
|
increment_api_counter('odds', 1)
|
||||||
self.logger.debug(f"Received raw odds data from ESPN: {json.dumps(raw_data, indent=2)}")
|
self.logger.debug(f"Received raw odds data from ESPN: {json.dumps(raw_data, indent=2)}")
|
||||||
|
|
||||||
odds_data = self._extract_espn_data(raw_data)
|
odds_data = self._extract_espn_data(raw_data)
|
||||||
|
|||||||
@@ -1194,6 +1194,8 @@ class OddsTickerManager:
|
|||||||
|
|
||||||
def calculate_dynamic_duration(self):
|
def calculate_dynamic_duration(self):
|
||||||
"""Calculate the exact time needed to display all odds ticker content"""
|
"""Calculate the exact time needed to display all odds ticker content"""
|
||||||
|
logger.debug(f"calculate_dynamic_duration called - dynamic_duration_enabled: {self.dynamic_duration_enabled}, total_scroll_width: {self.total_scroll_width}")
|
||||||
|
|
||||||
# If dynamic duration is disabled, use fixed duration from config
|
# If dynamic duration is disabled, use fixed duration from config
|
||||||
if not self.dynamic_duration_enabled:
|
if not self.dynamic_duration_enabled:
|
||||||
self.dynamic_duration = self.odds_ticker_config.get('display_duration', 60)
|
self.dynamic_duration = self.odds_ticker_config.get('display_duration', 60)
|
||||||
@@ -1202,6 +1204,7 @@ class OddsTickerManager:
|
|||||||
|
|
||||||
if not self.total_scroll_width:
|
if not self.total_scroll_width:
|
||||||
self.dynamic_duration = self.min_duration # Use configured minimum
|
self.dynamic_duration = self.min_duration # Use configured minimum
|
||||||
|
logger.debug(f"total_scroll_width is 0, using minimum duration: {self.min_duration}s")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1257,7 +1260,12 @@ class OddsTickerManager:
|
|||||||
logger.debug("get_dynamic_duration called but total_scroll_width is 0, attempting update...")
|
logger.debug("get_dynamic_duration called but total_scroll_width is 0, attempting update...")
|
||||||
try:
|
try:
|
||||||
# Force an update to get the data and calculate proper duration
|
# Force an update to get the data and calculate proper duration
|
||||||
self._perform_update()
|
# Bypass the update interval check for duration calculation
|
||||||
|
self.games_data = self._fetch_upcoming_games()
|
||||||
|
self.scroll_position = 0
|
||||||
|
self.current_game_index = 0
|
||||||
|
self._create_ticker_image() # Create the composite image
|
||||||
|
logger.debug(f"Force update completed, total_scroll_width: {self.total_scroll_width}px")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error updating odds ticker for dynamic duration: {e}")
|
logger.error(f"Error updating odds ticker for dynamic duration: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ from src.config_manager import ConfigManager
|
|||||||
from src.odds_manager import OddsManager
|
from src.odds_manager import OddsManager
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
# Import the API counter function from web interface
|
||||||
|
try:
|
||||||
|
from web_interface_v2 import increment_api_counter
|
||||||
|
except ImportError:
|
||||||
|
# Fallback if web interface is not available
|
||||||
|
def increment_api_counter(kind: str, count: int = 1):
|
||||||
|
pass
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
# ESPN_SOCCER_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/soccer/scoreboards" # Old URL
|
# ESPN_SOCCER_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/soccer/scoreboards" # Old URL
|
||||||
ESPN_SOCCER_LEAGUE_SCOREBOARD_URL_FORMAT = "http://site.api.espn.com/apis/site/v2/sports/soccer/{}/scoreboard" # New format string
|
ESPN_SOCCER_LEAGUE_SCOREBOARD_URL_FORMAT = "http://site.api.espn.com/apis/site/v2/sports/soccer/{}/scoreboard" # New format string
|
||||||
@@ -192,6 +200,9 @@ class BaseSoccerManager:
|
|||||||
response = requests.get(url, params=params, timeout=10) # Add timeout
|
response = requests.get(url, params=params, timeout=10) # Add timeout
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
|
# Increment API counter for sports data
|
||||||
|
increment_api_counter('sports', 1)
|
||||||
cls.logger.debug(f"[Soccer Map Build] Fetched data for {league_slug}")
|
cls.logger.debug(f"[Soccer Map Build] Fetched data for {league_slug}")
|
||||||
|
|
||||||
for event in data.get("events", []):
|
for event in data.get("events", []):
|
||||||
@@ -264,6 +275,10 @@ class BaseSoccerManager:
|
|||||||
response = requests.get(url, params=params)
|
response = requests.get(url, params=params)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
|
# Increment API counter for sports data
|
||||||
|
increment_api_counter('sports', 1)
|
||||||
|
|
||||||
self.logger.info(f"[Soccer] Fetched data from ESPN API for {league_slug} on {fetch_date}")
|
self.logger.info(f"[Soccer] Fetched data from ESPN API for {league_slug} on {fetch_date}")
|
||||||
|
|
||||||
if use_cache:
|
if use_cache:
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ from rgbmatrix import RGBMatrix, RGBMatrixOptions
|
|||||||
import os
|
import os
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
# Import the API counter function from web interface
|
||||||
|
try:
|
||||||
|
from web_interface_v2 import increment_api_counter
|
||||||
|
except ImportError:
|
||||||
|
# Fallback if web interface is not available
|
||||||
|
def increment_api_counter(kind: str, count: int = 1):
|
||||||
|
pass
|
||||||
|
|
||||||
# Get logger without configuring
|
# Get logger without configuring
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -57,6 +65,10 @@ class YouTubeDisplay:
|
|||||||
try:
|
try:
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
|
# Increment API counter for YouTube data
|
||||||
|
increment_api_counter('youtube', 1)
|
||||||
|
|
||||||
if data['items']:
|
if data['items']:
|
||||||
channel = data['items'][0]
|
channel = data['items'][0]
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1996,6 +1996,9 @@
|
|||||||
<div><strong>Stocks:</strong> ${u.stocks || 0} used / ${f.stocks || 0} forecast</div>
|
<div><strong>Stocks:</strong> ${u.stocks || 0} used / ${f.stocks || 0} forecast</div>
|
||||||
<div><strong>Sports:</strong> ${u.sports || 0} used / ${f.sports || 0} forecast</div>
|
<div><strong>Sports:</strong> ${u.sports || 0} used / ${f.sports || 0} forecast</div>
|
||||||
<div><strong>News:</strong> ${u.news || 0} used / ${f.news || 0} forecast</div>
|
<div><strong>News:</strong> ${u.news || 0} used / ${f.news || 0} forecast</div>
|
||||||
|
<div><strong>Odds:</strong> ${u.odds || 0} used / ${f.odds || 0} forecast</div>
|
||||||
|
<div><strong>Music:</strong> ${u.music || 0} used / ${f.music || 0} forecast</div>
|
||||||
|
<div><strong>YouTube:</strong> ${u.youtube || 0} used / ${f.youtube || 0} forecast</div>
|
||||||
`;
|
`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
|||||||
@@ -815,6 +815,9 @@ api_counters = {
|
|||||||
'stocks': {'used': 0},
|
'stocks': {'used': 0},
|
||||||
'sports': {'used': 0},
|
'sports': {'used': 0},
|
||||||
'news': {'used': 0},
|
'news': {'used': 0},
|
||||||
|
'odds': {'used': 0},
|
||||||
|
'music': {'used': 0},
|
||||||
|
'youtube': {'used': 0},
|
||||||
}
|
}
|
||||||
api_window_start = time.time()
|
api_window_start = time.time()
|
||||||
api_window_seconds = 24 * 3600
|
api_window_seconds = 24 * 3600
|
||||||
@@ -875,6 +878,27 @@ def get_metrics():
|
|||||||
except Exception:
|
except Exception:
|
||||||
forecast['news'] = 0
|
forecast['news'] = 0
|
||||||
|
|
||||||
|
# Odds ticker
|
||||||
|
try:
|
||||||
|
o_int = int(config.get('odds_ticker', {}).get('update_interval', 3600))
|
||||||
|
forecast['odds'] = max(1, int(api_window_seconds / max(1, o_int)))
|
||||||
|
except Exception:
|
||||||
|
forecast['odds'] = 0
|
||||||
|
|
||||||
|
# Music manager (image downloads)
|
||||||
|
try:
|
||||||
|
m_int = int(config.get('music', {}).get('POLLING_INTERVAL_SECONDS', 5))
|
||||||
|
forecast['music'] = max(1, int(api_window_seconds / max(1, m_int)))
|
||||||
|
except Exception:
|
||||||
|
forecast['music'] = 0
|
||||||
|
|
||||||
|
# YouTube display
|
||||||
|
try:
|
||||||
|
y_int = int(config.get('youtube', {}).get('update_interval', 300))
|
||||||
|
forecast['youtube'] = max(1, int(api_window_seconds / max(1, y_int)))
|
||||||
|
except Exception:
|
||||||
|
forecast['youtube'] = 0
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'window_seconds': api_window_seconds,
|
'window_seconds': api_window_seconds,
|
||||||
|
|||||||
Reference in New Issue
Block a user