Merge development branch into main - resolved conflict in ncaa_fb_managers.py

This commit is contained in:
Chuck
2025-09-12 19:21:28 -04:00
174 changed files with 8824 additions and 673 deletions

Submodule LEDMatrix.wiki updated: a01c72e156...8d2c143954

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1,404 +1,53 @@
NCAAF
AAMU => Alabama A&M Bulldogs
ACU => Abilene Christian Wildcats
ADA => Adams State Grizzlies
ADR => Adrian Bulldogs
AFA => Air Force Falcons
AIC => American International Yellow Jackets
AKR => Akron Zips
ALA => Alabama Crimson Tide
ALB => Albright Lions
ALBS => Albany State (GA) Golden Rams
ALCN => Alcorn State Braves
ALD => Alderson Broaddus Battlers
ALF => Alfred Saxons
ALL => Allegheny Gators
ALST => Alabama State Hornets
AMH => Amherst College Mammoths
AND => Anderson (IN) Ravens
ANG => Angelo State Rams
AMH => Amherst Mammoths
ANN => Anna Maria College Amcats
APP => Appalachian State Mountaineers
APSU => Austin Peay Governors
ARIZ => Arizona Wildcats
ARK => Arkansas-Monticello Boll Weevils
ARMY => Army Black Knights
ARST => Arkansas State Red Wolves
ASH => Ashland Eagles
ASP => Assumption Greyhounds
ARK => Arkansas Razorbacks
ASU => Arizona State Sun Devils
AUB => Auburn Tigers
AUG => St. Augustine's Falcons
AUR => Aurora Spartans
AUS => Austin College 'Roos
AVE => Averett Cougars
AVI => Avila College Eagles
AZU => Azusa Pacific Cougars
BAK => Baker University Wildcats
BAL => Baldwin Wallace Yellow Jackets
BALL => Ball State Cardinals
BAT => Bates College Bobcats
BAY => Baylor Bears
BC => Boston College Eagles
BEC => Becker College Hawks
BEL => Beloit College Buccaneers
BEN => Benedictine University (IL) Eagles
BENT => Bentley Falcons
BET => Bethel (TN) Wildcats
BGSU => Bowling Green Falcons
BHS => Black Hills State Yellow Jackets
BIR => Birmingham-Southern Panthers
BKN => Bacone College Warriors
BLA => Blackburn Beavers
BLOM => Bloomsburg Huskies
BLU => Bluffton Beavers
BOW => Bowdoin Polar Bears
BRI => British Columbia Thunderbirds
BRWN => Brown Bears
BST => Bemidji State Beavers
BSU => Bowie State Bulldogs
BUCK => Bucknell Bison
BUE => Buena Vista Beavers
BUF => Buffalo State Bengals
BUFF => Buffalo Bulls
BUT => Butler Bulldogs
BYU => BYU Cougars
CAL => California Lutheran Kingsmen
CAM => Campbell Fighting Camels
CAP => Capital University Crusaders
CAR => Carthage College Red Men
CARK => Central Arkansas Bears
CAS => Castleton Spartans
CAT => Catholic University Cardinals
CCSU => Central Connecticut Blue Devils
CCU => Coastal Carolina Chanticleers
CEN => Centre College Colonels
CHA => Chapman University Panthers
CHI => Chicago Maroons
CHSO => Charleston Southern Buccaneers
CIN => Cincinnati Bearcats
CLA => Clarion Golden Eagles
CLEM => Clemson Tigers
CLMB => Columbia Lions
CLT => Charlotte 49ers
CMU => Central Michigan Chippewas
COE => Coe College Kohawks
COL => Colorado School of Mines Orediggers
COLC => Colorado College Tigers
COLG => Colgate Raiders
BOIS => Boise State Broncos
BRST => Bridgewater State Bears
BUENA => Buena Vista Beavers
CAL => California Golden Bears
CAR => Carroll University (WI) Pioneers
CLA => Claremont-Mudd-Scripps College Stags
COLBY => Colby College White Mules
COLO => Colorado Buffaloes
CON => Concordia-Minnesota Cobbers
COR => Cornell College (IA) Rams
CONN => UConn Huskies
CP => Cal Poly Mustangs
CRO => Crown Storm
CSU => Colorado State Rams
CUL => Culver-Stockton Wildcats
CUM => Cumberland College Indians
CUR => Curry College Colonels
DAK => Dakota Wesleyan Tigers
DART => Dartmouth Big Green
DAV => Davidson Wildcats
DAY => Dayton Flyers
DEF => Defiance Yellow Jackets
DEL => Delta State Statesmen
DEN => Denison Big Red
DEP => DePauw Tigers
DIC => Dickinson State Blue Hawks
DRKE => Drake Bulldogs
DSU => Delaware State Hornets
DEL => Delaware Blue Hens
DUB => Dubuque Spartans
DUKE => Duke Blue Devils
DUQ => Duquesne Dukes
EAS => Eastern New Mexico Greyhounds
ECU => East Carolina Pirates
EDI => Edinboro Fighting Scots
EIU => Eastern Illinois Panthers
EKU => Eastern Kentucky Colonels
ELI => Elizabeth City State Vikings
ELM => Elmhurst Blue Jays
ELON => Elon Phoenix
EMO => Emory & Henry Wasps
EMP => Emporia State Hornets
EMU => Eastern Michigan Eagles
END => Endicott College Gulls
EOR => Eastern Oregon Mountaineers
ETSU => East Tennessee State Buccaneers
EUR => Eureka College Red Devils
EWU => Eastern Washington Eagles
FAU => Florida Atlantic Owls
FAY => Fayetteville State Broncos
FDU => FDU-Florham Devils
FER => Ferrum Panthers
FIN => Findlay Oilers
FIT => Fitchburg State Falcons
FIU => Florida International Panthers
ELM => Elmhurst Bluejays
FAMU => Florida A&M Rattlers
FLA => Florida Gators
FOR => Fort Valley State Wildcats
FRA => Franklin Grizzlies
FRES => Fresno State Bulldogs
FRO => Frostburg State Bobcats
FRST => Ferris State Bulldogs
FSU => Florida State Seminoles
FTLW => Fort Lewis Skyhawks
FUR => Furman Paladins
GAL => Gallaudet Bison
GAN => Gannon Golden Knights
GASO => Georgia Southern Eagles
GAST => Georgia State Panthers
GEN => Geneva College Golden Tornadoes
GEO => George Fox University Bruins
GET => Gettysburg Bullets
GLE => Glenville State Pioneers
GMU => George Mason Patriots
GRA => Grand Valley State Lakers
GRE => Greenville Panthers
GRI => Grinnell Pioneers
GRO => Grove City College Wolverines
GT => Georgia Tech Yellow Jackets
GUI => Guilford Quakers
GWEB => Gardner-Webb Bulldogs
HAM => Hampden-Sydney Tigers
HAMP => Hampton Pirates
HAN => Hanover Panthers
HAR => Hartwick Hawks
HARV => Harvard Crimson
HAS => Haskell Indian Nations Jayhawks
GTWN => Georgetown Hoyas
HAW => Hawai'i Rainbow Warriors
HBU => Houston Baptist Huskies
HC => Holy Cross Crusaders
HEI => Heidelberg Student Princes
HEN => Hendrix College Warriors
HIL => Hillsdale Chargers
HIR => Hiram College Terriers
HOB => Hobart Statesmen
HOU => Houston Cougars
HOW => Howard Bison
HUS => Husson Eagles
IDHO => Idaho Vandals
IDST => Idaho State Bengals
ILL => Illinois Fighting Illini
ILST => Illinois State Redbirds
ILW => Illinois Wesleyan Titans
IND => Indianapolis
INST => Indiana State Sycamores
IOW => Iowa Wesleyan Tigers
IOWA => Iowa Hawkeyes
ISU => Iowa State Cyclones
ITH => Ithaca Bombers
IU => Indiana Hoosiers
JKST => Jackson State Tigers
JMU => James Madison Dukes
JOH => Johnson C Smith Golden Bulls
JUN => Juniata Eagles
JVST => Jacksonville State Gamecocks
KAL => Kalamazoo Hornets
KAN => Kansas Wesleyan University Coyotes
KEN => Kenyon Lords
KENN => Kennesaw State Owls
KENT => Kent State Golden Flashes
KIN => King's College (PA) Monarchs
KNO => Knox College Prairie Fire
KSU => Kansas State Wildcats
KU => Kansas Jayhawks
KUT => Kutztown Golden Bears
KYST => Kentucky State Thorobreds
KYW => Kentucky Wesleyan Panthers
LA => La Verne Leopards
LAC => Lane Dragons
LAF => Lafayette Leopards
LAG => LaGrange College Panthers
LAK => Lake Forest Foresters
LAM => Lambuth Eagles
LAN => Langston Lions
LAW => Lawrence Vikings
LEB => Lebanon Valley Flying Dutchmen
LEH => Lehigh Mountain Hawks
LEN => Lenoir-Rhyne Bears
LEW => Lewis & Clark Pioneers
LIB => Liberty Flames
LIM => Limestone Saints
LIN => Linfield Wildcats
LOC => Lock Haven Bald Eagles
LOR => Loras College Duhawks
LOU => Louisville Cardinals
LSU => LSU Tigers
LT => Louisiana Tech Bulldogs
JXST => Jacksonville State Gamecocks
LUT => Luther Norse
LYC => Lycoming Warriors
M-OH => Miami (OH) RedHawks
MAC => Macalester Scots
MAI => Maine Maritime Mariners
MAN => Mansfield Mountaineers
MAR => Maryville College Fighting Scots
MAS => Mass Maritime Buccaneers
MASS => UMass Minutemen
MAY => Mayville State Comets
MCM => McMurry War Hawks
MCN => McNeese Cowboys
MD => Maryland Terrapins
MEM => Memphis Tigers
MEN => Menlo College Oaks
MER => Merchant Marine Mariners
MERC => Mercyhurst Lakers
MES => Colorado Mesa Mavericks
MET => Methodist Monarchs
MH => Mars Hill Mountain Lions
MIAMI => Miami Hurricanes
MICH => Michigan Wolverines
MID => Midwestern State Mustangs
MIL => Millsaps Majors
MIN => Minot State Beavers
MINN => Minnesota Golden Gophers
MIS => Missouri Western Griffons
MISS => Ole Miss Rebels
MIZ => Missouri Tigers
MNST => Minnesota State Mavericks
MONM => Monmouth Hawks
MONT => Montana Grizzlies
MOR => Morningside Chiefs
MORE => Morehead State Eagles
MORG => Morgan State Bears
MOU => Mount Union Raiders
MRSH => Marshall Thundering Herd
MRST => Marist Red Foxes
MSST => Mississippi State Bulldogs
MSU => Michigan State Spartans
MTST => Montana State Bobcats
MTSU => Middle Tennessee Blue Raiders
MTU => Michigan Tech Huskies
MUH => Muhlenberg Mules
MUR => Murray State Racers
MUS => Muskingum Fighting Muskies
MVSU => Mississippi Valley State Delta Devils
NAU => Northern Arizona Lumberjacks
NAVY => Navy Midshipmen
NBY => Newberry Wolves
NCAT => North Carolina A&T Aggies
NCCU => North Carolina Central Eagles
NCST => NC State Wolfpack
ND => Notre Dame Fighting Irish
NDOH => Notre Dame College Falcons
NDSU => North Dakota State Bison
NEB => Nebraska-Kearney Lopers
NEV => Nevada Wolf Pack
NH => New Haven Chargers
NICH => Nicholls Colonels
NIU => Northern Illinois Huskies
NMH => New Mexico Highlands Cowboys
NMI => Northern Michigan Wildcats
NMSU => New Mexico State Aggies
NOR => Univ. of Northwestern-St. Paul Eagles
NORF => Norfolk State Spartans
NW => Northwestern Wildcats
OBE => Oberlin Yeomen
ODU => Old Dominion Monarchs
OHI => Ohio Northern Polar Bears
OHIO => Ohio Bobcats
OKL => Oklahoma Baptist Bison
OKST => Oklahoma State Cowboys
OLI => Olivet College Comets
OMA => Omaha Mavericks
ORST => Oregon State Beavers
OSU => Ohio State Buckeyes
OTT => Otterbein Cardinals
OU => Oklahoma Sooners
PAC => Pacific (OR) Boxers
PENN => Pennsylvania Quakers
PIKE => Pikeville Bears
PITT => Pittsburgh Panthers
PRE => Presentation College Saints
PRI => Principia College Panthers
PRIN => Princeton Tigers
PST => Pittsburg State Gorillas
PSU => Penn State Nittany Lions
MESA => Colorado Mesa Mavericks
MIL => Millikin Big Blue
MOR => Morehouse College Maroon Tigers
NOR => North Park Vikings
RED => Redlands Bulldogs
RICE => Rice Owls
RICH => Richmond Spiders
RIT => Rochester Yellow Jackets
ROB => Robert Morris (IL) Eagles
ROS => Rose-Hulman Engineers
RUTG => Rutgers Scarlet Knights
SAC => Sacramento State Hornets
SAG => Saginaw Valley Cardinals
SDAK => South Dakota Coyotes
SDSU => San Diego State Aztecs
SET => Seton Hill Griffins
SIU => Southern Illinois Salukis
SJSU => San José State Spartans
SLI => Slippery Rock The Rock
SOU => Southwestern College Moundbuilders
SPR => Springfield College Pride
ST => St. Scholastica Saints
STAN => Stanford Cardinal
STE => Stevenson University Mustangs
STET => Stetson Hatters
STO => Stonehill College Skyhawks
SUS => Susquehanna University River Hawks
SUU => Southern Utah Thunderbirds
SYR => Syracuse Orange
TA&M => Texas A&M Aggies
TAY => Taylor Trojans
TEM => Temple Owls
TEX => Texas Longhorns
TIF => Tiffin University Dragons
TLSA => Tulsa Golden Hurricane
TRI => Trinity University (TX) Tigers
TUF => Tufts University Jumbos
TXST => Texas State Bobcats
UAB => UAB Blazers
UAPB => Arkansas-Pine Bluff Golden Lions
UCD => UC Davis Aggies
UCF => UCF Knights
UCLA => UCLA Bruins
UCONN => UConn Huskies
UGA => Georgia Bulldogs
UK => Kentucky Wildcats
UL => Louisiana Ragin' Cajuns
ULM => UL Monroe Warhawks
UMD => Minnesota-Duluth Bulldogs
UMDA => UMASS Dartmouth Corsairs
UML => UMass Lowell River Hawks
UNA => North Alabama Lions
UNC => North Carolina Tar Heels
UNCO => Northern Colorado Bears
UND => North Dakota Fighting Hawks
UNH => New Hampshire Wildcats
UNI => University of Mary Marauders
UNLV => UNLV Rebels
UNM => New Mexico Lobos
UNNY => Union Dutchmen
UNT => North Texas Mean Green
UPP => Upper Iowa Peacocks
URI => Rhode Island Rams
USA => South Alabama Jaguars
USC => USC Trojans
USD => San Diego Toreros
USF => South Florida Bulls
USU => Utah State Aggies
UTAH => Utah Utes
UTC => Chattanooga Mocs
UTI => Utica College Pioneers
UVA => Virginia Cavaliers
VAL => Valley City State Vikings
VAN => Vanderbilt Commodores
VILL => Villanova Wildcats
VIR => Virginia State Trojans
VT => Virginia Tech Hokies
WAB => Wabash College Little Giants
WAKE => Wake Forest Demon Deacons
WAS => Washington-Missouri Bears
WASH => Washington Huskies
WAY => Wayne State (MI) Warriors
WES => Westminster College (MO) Blue Jays
WHE => Wheaton College Illinois Thunder
WIL => Wilkes University Colonels
WIN => Wingate Bulldogs
WIS => Wisconsin-Platteville Pioneers
WISC => Wisconsin Badgers
WKU => Western Kentucky Hilltoppers
WOR => Worcester State College Lancers
WSU => Washington State Cougars
WVU => West Virginia Mountaineers
YALE => Yale Bulldogs
NBA
@@ -1106,6 +755,149 @@ MLB Conferences/Divisions
OAK => Oakland Athletics
SEA => Seattle Mariners
TEX => Texas Rangers
Soccer - Premier League (England)
ARS => Arsenal
AVL => Aston Villa
BHA => Brighton & Hove Albion
BOU => AFC Bournemouth
BRE => Brentford
BUR => Burnley
CHE => Chelsea
CRY => Crystal Palace
EVE => Everton
FUL => Fulham
LIV => Liverpool
LUT => Luton Town
MCI => Manchester City
MUN => Manchester United
NEW => Newcastle United
NFO => Nottingham Forest
SHU => Sheffield United
TOT => Tottenham Hotspur
WHU => West Ham United
WOL => Wolverhampton Wanderers
Soccer - La Liga (Spain)
ALA => Alavés
ATH => Athletic Bilbao
ATM => Atlético Madrid
BAR => Barcelona
BET => Real Betis
CAG => Cagliari
CEL => Celta Vigo
ESP => Espanyol
GET => Getafe
GIR => Girona
LAZ => Lazio
LEG => Leganés
RAY => Rayo Vallecano
RMA => Real Madrid
SEV => Sevilla
VAL => Valencia
VLD => Valladolid
Soccer - Bundesliga (Germany)
BOC => VfL Bochum
BOL => VfL Bochum
DOR => Borussia Dortmund
FCA => FC Augsburg
FCB => Bayern Munich
FCU => FC Union Berlin
HAC => Hannover 96
HDH => Hertha BSC
KOL => 1. FC Köln
LEV => Bayer Leverkusen
M05 => Mainz 05
RBL => RB Leipzig
SCF => SC Freiburg
SGE => Eintracht Frankfurt
STU => VfB Stuttgart
SVW => Werder Bremen
TSG => TSG Hoffenheim
WOB => VfL Wolfsburg
Soccer - Serie A (Italy)
ATA => Atalanta
CAG => Cagliari
EMP => Empoli
FIO => Fiorentina
INT => Inter Milan
JUV => Juventus
LAZ => Lazio
MIL => AC Milan
MON => Monza
NAP => Napoli
ROM => Roma
TOR => Torino
UDI => Udinese
VER => Hellas Verona
Soccer - Ligue 1 (France)
LIL => Lille
LPM => Lille
LYON => Lyon
MAR => Marseille
MON => Monaco
NAN => Nantes
NICE => Nice
OL => Olympique Lyonnais
OM => Olympique de Marseille
PAR => Paris Saint-Germain
PSG => Paris Saint-Germain
REN => Rennes
STR => Strasbourg
Soccer - Champions League
AJA => Ajax
ASM => AS Monaco
ASS => AS Saint-Étienne
BOC => VfL Bochum
CEL => Celtic
COM => Club Brugge
FCA => FC Augsburg
FCB => Bayern Munich
FCU => FC Union Berlin
FIO => Fiorentina
GEN => Genoa
HAC => Hannover 96
IPS => Ipswich Town
KSV => Kaiserslautern
LEC => Lecce
LIL => Lille
LIV => Liverpool
M05 => Mainz 05
MCI => Manchester City
MUN => Manchester United
NAN => Nantes
OSA => Osasuna
RBL => RB Leipzig
RCL => RC Lens
RMA => Real Madrid
SCF => SC Freiburg
SGE => Eintracht Frankfurt
SR => Sporting CP
STP => St. Pauli
SVW => Werder Bremen
TFC => Toulouse FC
TOT => Tottenham Hotspur
TSG => TSG Hoffenheim
UDI => Udinese
VEN => Venezia
VFB => VfB Stuttgart
VIL => Villarreal
Soccer - Other Teams
austin => Austin FC
cf_montral => CF Montréal
charlotte => Charlotte FC
dortmund => Borussia Dortmund
gladbach => Borussia Mönchengladbach
lafc => Los Angeles FC
leverkusen => Bayer Leverkusen
nycfc => New York City FC
paris_sg => Paris Saint-Germain
st_louis => St. Louis City SC
MLS Conferences/Divisions
Conferences currently unsupported

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

1
check_team_images.py Normal file
View File

@@ -0,0 +1 @@

View File

@@ -5,10 +5,10 @@
"start_time": "07:00",
"end_time": "23:00"
},
"timezone": "America/Chicago",
"timezone": "America/New_York",
"location": {
"city": "Dallas",
"state": "Texas",
"city": "Tampa",
"state": "Florida",
"country": "US"
},
"display": {
@@ -39,6 +39,7 @@
"daily_forecast": 30,
"stock_news": 20,
"odds_ticker": 60,
"leaderboard": 60,
"nhl_live": 30,
"nhl_recent": 30,
"nhl_upcoming": 30,
@@ -81,7 +82,7 @@
"update_interval": 1
},
"weather": {
"enabled": false,
"enabled": true,
"update_interval": 1800,
"units": "imperial",
"display_format": "{temp}\u00b0F\n{condition}"
@@ -129,11 +130,12 @@
"duration_buffer": 0.1
},
"odds_ticker": {
"enabled": true,
"enabled": false,
"show_favorite_teams_only": true,
"games_per_favorite_team": 1,
"max_games_per_league": 5,
"show_odds_only": false,
"fetch_odds": true,
"sort_order": "soonest",
"enabled_leagues": [
"nfl",
@@ -150,6 +152,46 @@
"dynamic_duration": true,
"min_duration": 30,
"max_duration": 300,
"duration_buffer": 0.05
},
"leaderboard": {
"enabled": false,
"enabled_sports": {
"nfl": {
"enabled": true,
"top_teams": 10
},
"nba": {
"enabled": false,
"top_teams": 10
},
"mlb": {
"enabled": false,
"top_teams": 10
},
"ncaa_fb": {
"enabled": true,
"top_teams": 25,
"show_ranking": true
},
"nhl": {
"enabled": false,
"top_teams": 10
},
"ncaam_basketball": {
"enabled": false,
"top_teams": 25
}
},
"update_interval": 3600,
"scroll_speed": 1,
"scroll_delay": 0.01,
"display_duration": 60,
"loop": false,
"request_timeout": 30,
"dynamic_duration": true,
"min_duration": 45,
"max_duration": 600,
"duration_buffer": 0.1
},
"calendar": {
@@ -257,6 +299,7 @@
],
"logo_dir": "assets/sports/ncaa_fbs_logos",
"show_records": true,
"show_ranking": true,
"display_modes": {
"ncaa_fb_live": true,
"ncaa_fb_recent": true ,
@@ -366,7 +409,7 @@
}
},
"text_display": {
"enabled": false,
"enabled": true,
"text": "Subscribe to ChuckBuilds",
"font_path": "assets/fonts/press-start-2p.ttf",
"font_size": 8,

1
create_league_logos.py Normal file
View File

@@ -0,0 +1 @@

1
create_ncaa_logos.py Normal file
View File

@@ -0,0 +1 @@

107
debug_of_the_day.py Normal file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Debug script for OfTheDayManager issues
Run this on the Raspberry Pi to diagnose the problem
Usage:
1. Copy this file to your Raspberry Pi
2. Run: python3 debug_of_the_day.py
3. Check the output for any errors or issues
This script will help identify why the OfTheDayManager is not loading data files.
"""
import json
import os
import sys
from datetime import date
def debug_of_the_day():
print("=== OfTheDayManager Debug Script ===")
print(f"Current working directory: {os.getcwd()}")
print(f"Python path: {sys.path}")
# Check if we're in the right directory
if not os.path.exists('config/config.json'):
print("ERROR: config/config.json not found. Make sure you're running from the LEDMatrix root directory.")
return
# Load the actual config
try:
with open('config/config.json', 'r') as f:
config = json.load(f)
print("✓ Successfully loaded config.json")
except Exception as e:
print(f"ERROR loading config.json: {e}")
return
# Check of_the_day configuration
of_the_day_config = config.get('of_the_day', {})
print(f"OfTheDay enabled: {of_the_day_config.get('enabled', False)}")
if not of_the_day_config.get('enabled', False):
print("OfTheDay is disabled in config!")
return
categories = of_the_day_config.get('categories', {})
print(f"Categories configured: {list(categories.keys())}")
# Test each category
today = date.today()
day_of_year = today.timetuple().tm_yday
print(f"Today is day {day_of_year} of the year")
for category_name, category_config in categories.items():
print(f"\n--- Testing category: {category_name} ---")
print(f"Category enabled: {category_config.get('enabled', True)}")
if not category_config.get('enabled', True):
print("Category is disabled, skipping...")
continue
data_file = category_config.get('data_file')
print(f"Data file: {data_file}")
# Test path resolution
if not os.path.isabs(data_file):
if data_file.startswith('of_the_day/'):
file_path = os.path.join(os.getcwd(), data_file)
else:
file_path = os.path.join(os.getcwd(), 'of_the_day', data_file)
else:
file_path = data_file
file_path = os.path.abspath(file_path)
print(f"Resolved path: {file_path}")
print(f"File exists: {os.path.exists(file_path)}")
if not os.path.exists(file_path):
print(f"ERROR: Data file not found at {file_path}")
continue
# Test JSON loading
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"✓ Successfully loaded JSON with {len(data)} items")
# Check for today's entry
day_key = str(day_of_year)
if day_key in data:
item = data[day_key]
print(f"✓ Found entry for day {day_of_year}: {item.get('title', 'No title')}")
else:
print(f"✗ No entry found for day {day_of_year}")
# Show some nearby entries
nearby_days = [k for k in data.keys() if k.isdigit() and abs(int(k) - day_of_year) <= 5]
print(f"Nearby days with entries: {sorted(nearby_days)}")
except Exception as e:
print(f"ERROR loading JSON: {e}")
import traceback
traceback.print_exc()
print("\n=== Debug complete ===")
if __name__ == "__main__":
debug_of_the_day()

1
list_missing_teams.py Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

657
missing_team_logos.txt Normal file
View File

@@ -0,0 +1,657 @@
================================================================================
MISSING TEAM LOGOS - COMPLETE LIST
================================================================================
Total missing teams: 309
MLB:
---
OAK => Oakland Athletics
NCAAF:
-----
AAMU => Alabama A&M Bulldogs
ACU => Abilene Christian Wildcats
ADA => Adams State Grizzlies
ADR => Adrian Bulldogs
AIC => American International Yellow Jackets
ALB => Albright Lions
ALBS => Albany State (GA) Golden Rams
ALCN => Alcorn State Braves
ALD => Alderson Broaddus Battlers
ALF => Alfred Saxons
ALL => Allegheny Gators
ALST => Alabama State Hornets
AMH => Amherst College Mammoths
AND => Anderson (IN) Ravens
ANG => Angelo State Rams
ANN => Anna Maria College Amcats
APSU => Austin Peay Governors
ASH => Ashland Eagles
ASP => Assumption Greyhounds
ASU => Arizona State Sun Devils
AUG => St. Augustine's Falcons
AUR => Aurora Spartans
AUS => Austin College 'Roos
AVE => Averett Cougars
AVI => Avila College Eagles
AZU => Azusa Pacific Cougars
BAK => Baker University Wildcats
BAL => Baldwin Wallace Yellow Jackets
BAT => Bates College Bobcats
BEC => Becker College Hawks
BEL => Beloit College Buccaneers
BEN => Benedictine University (IL) Eagles
BENT => Bentley Falcons
BET => Bethel (TN) Wildcats
BHS => Black Hills State Yellow Jackets
BIR => Birmingham-Southern Panthers
BKN => Bacone College Warriors
BLA => Blackburn Beavers
BLOM => Bloomsburg Huskies
BLU => Bluffton Beavers
BOW => Bowdoin Polar Bears
BRI => British Columbia Thunderbirds
BRWN => Brown Bears
BST => Bemidji State Beavers
BUCK => Bucknell Bison
BUE => Buena Vista Beavers
BUF => Buffalo State Bengals
BUT => Butler Bulldogs
CAM => Campbell Fighting Camels
CAP => Capital University Crusaders
CAR => Carthage College Red Men
CARK => Central Arkansas Bears
CAS => Castleton Spartans
CAT => Catholic University Cardinals
CCSU => Central Connecticut Blue Devils
CEN => Centre College Colonels
CHA => Chapman University Panthers
CHI => Chicago Maroons
CHSO => Charleston Southern Buccaneers
CLA => Clarion Golden Eagles
CLMB => Columbia Lions
COE => Coe College Kohawks
COL => Colorado School of Mines Orediggers
COLC => Colorado College Tigers
COLG => Colgate Raiders
CON => Concordia-Minnesota Cobbers
COR => Cornell College (IA) Rams
CP => Cal Poly Mustangs
CRO => Crown Storm
CSU => Colorado State Rams
CUL => Culver-Stockton Wildcats
CUM => Cumberland College Indians
CUR => Curry College Colonels
DAK => Dakota Wesleyan Tigers
DART => Dartmouth Big Green
DAV => Davidson Wildcats
DAY => Dayton Flyers
DEF => Defiance Yellow Jackets
DEL => Delta State Statesmen
DEN => Denison Big Red
DEP => DePauw Tigers
DIC => Dickinson State Blue Hawks
DRKE => Drake Bulldogs
DSU => Delaware State Hornets
DUB => Dubuque Spartans
DUQ => Duquesne Dukes
EAS => Eastern New Mexico Greyhounds
EDI => Edinboro Fighting Scots
EIU => Eastern Illinois Panthers
EKU => Eastern Kentucky Colonels
ELI => Elizabeth City State Vikings
ELM => Elmhurst Blue Jays
ELON => Elon Phoenix
EMO => Emory & Henry Wasps
EMP => Emporia State Hornets
END => Endicott College Gulls
EOR => Eastern Oregon Mountaineers
ETSU => East Tennessee State Buccaneers
EUR => Eureka College Red Devils
EWU => Eastern Washington Eagles
FAY => Fayetteville State Broncos
FDU => FDU-Florham Devils
FER => Ferrum Panthers
FIN => Findlay Oilers
FIT => Fitchburg State Falcons
FLA => Florida Gators
FOR => Fort Valley State Wildcats
FRA => Franklin Grizzlies
FRO => Frostburg State Bobcats
FRST => Ferris State Bulldogs
FTLW => Fort Lewis Skyhawks
FUR => Furman Paladins
GAL => Gallaudet Bison
GAN => Gannon Golden Knights
GEN => Geneva College Golden Tornadoes
GEO => George Fox University Bruins
GET => Gettysburg Bullets
GLE => Glenville State Pioneers
GMU => George Mason Patriots
GRA => Grand Valley State Lakers
GRE => Greenville Panthers
GRI => Grinnell Pioneers
GRO => Grove City College Wolverines
GUI => Guilford Quakers
GWEB => Gardner-Webb Bulldogs
HAM => Hampden-Sydney Tigers
HAMP => Hampton Pirates
HAN => Hanover Panthers
HAR => Hartwick Hawks
HARV => Harvard Crimson
HAS => Haskell Indian Nations Jayhawks
HAW => Hawai'i Rainbow Warriors
HBU => Houston Baptist Huskies
HC => Holy Cross Crusaders
HEI => Heidelberg Student Princes
HEN => Hendrix College Warriors
HIL => Hillsdale Chargers
HIR => Hiram College Terriers
HOB => Hobart Statesmen
HOW => Howard Bison
HUS => Husson Eagles
IDHO => Idaho Vandals
IDST => Idaho State Bengals
ILST => Illinois State Redbirds
ILW => Illinois Wesleyan Titans
IND => Indianapolis
INST => Indiana State Sycamores
IOW => Iowa Wesleyan Tigers
ITH => Ithaca Bombers
JKST => Jackson State Tigers
JOH => Johnson C Smith Golden Bulls
JUN => Juniata Eagles
KAL => Kalamazoo Hornets
KAN => Kansas Wesleyan University Coyotes
KEN => Kenyon Lords
KIN => King's College (PA) Monarchs
KNO => Knox College Prairie Fire
KUT => Kutztown Golden Bears
KYST => Kentucky State Thorobreds
KYW => Kentucky Wesleyan Panthers
LA => La Verne Leopards
LAG => LaGrange College Panthers
LAK => Lake Forest Foresters
LAM => Lambuth Eagles
LAN => Langston Lions
LAW => Lawrence Vikings
LEB => Lebanon Valley Flying Dutchmen
LEH => Lehigh Mountain Hawks
LEN => Lenoir-Rhyne Bears
LEW => Lewis & Clark Pioneers
LIM => Limestone Saints
LIN => Linfield Wildcats
LOC => Lock Haven Bald Eagles
LOR => Loras College Duhawks
LUT => Luther Norse
LYC => Lycoming Warriors
M-OH => Miami (OH) RedHawks
MAC => Macalester Scots
MAI => Maine Maritime Mariners
MAN => Mansfield Mountaineers
MAR => Maryville College Fighting Scots
MAS => Mass Maritime Buccaneers
MAY => Mayville State Comets
MCM => McMurry War Hawks
MCN => McNeese Cowboys
MEN => Menlo College Oaks
MER => Merchant Marine Mariners
MERC => Mercyhurst Lakers
MES => Colorado Mesa Mavericks
MET => Methodist Monarchs
MH => Mars Hill Mountain Lions
MID => Midwestern State Mustangs
MIL => Millsaps Majors
MIN => Minot State Beavers
MIS => Missouri Western Griffons
MNST => Minnesota State Mavericks
MONM => Monmouth Hawks
MONT => Montana Grizzlies
MOR => Morningside Chiefs
MORE => Morehead State Eagles
MORG => Morgan State Bears
MOU => Mount Union Raiders
MRST => Marist Red Foxes
MSU => Michigan State Spartans
MTST => Montana State Bobcats
MTU => Michigan Tech Huskies
MUH => Muhlenberg Mules
MUR => Murray State Racers
MUS => Muskingum Fighting Muskies
MVSU => Mississippi Valley State Delta Devils
NAU => Northern Arizona Lumberjacks
NBY => Newberry Wolves
NCAT => North Carolina A&T Aggies
NCCU => North Carolina Central Eagles
NCST => NC State Wolfpack
NDOH => Notre Dame College Falcons
NDSU => North Dakota State Bison
NH => New Haven Chargers
NICH => Nicholls Colonels
NMH => New Mexico Highlands Cowboys
NMI => Northern Michigan Wildcats
NOR => Univ. of Northwestern-St. Paul Eagles
NORF => Norfolk State Spartans
OBE => Oberlin Yeomen
OHI => Ohio Northern Polar Bears
OKL => Oklahoma Baptist Bison
OLI => Olivet College Comets
OMA => Omaha Mavericks
OTT => Otterbein Cardinals
PAC => Pacific (OR) Boxers
PENN => Pennsylvania Quakers
PIKE => Pikeville Bears
PRE => Presentation College Saints
PRI => Principia College Panthers
PRIN => Princeton Tigers
PST => Pittsburg State Gorillas
RED => Redlands Bulldogs
RICH => Richmond Spiders
RIT => Rochester Yellow Jackets
ROB => Robert Morris (IL) Eagles
ROS => Rose-Hulman Engineers
SAC => Sacramento State Hornets
SAG => Saginaw Valley Cardinals
SDAK => South Dakota Coyotes
SET => Seton Hill Griffins
SIU => Southern Illinois Salukis
SLI => Slippery Rock The Rock
SOU => Southwestern College Moundbuilders
SPR => Springfield College Pride
ST => St. Scholastica Saints
STE => Stevenson University Mustangs
STET => Stetson Hatters
STO => Stonehill College Skyhawks
SUS => Susquehanna University River Hawks
SUU => Southern Utah Thunderbirds
TA&M => Texas A&M Aggies
TAY => Taylor Trojans
TIF => Tiffin University Dragons
TRI => Trinity University (TX) Tigers
TUF => Tufts University Jumbos
TXST => Texas State Bobcats
UAPB => Arkansas-Pine Bluff Golden Lions
UCD => UC Davis Aggies
UCONN => UConn Huskies
ULM => UL Monroe Warhawks
UMD => Minnesota-Duluth Bulldogs
UMDA => UMASS Dartmouth Corsairs
UML => UMass Lowell River Hawks
UNA => North Alabama Lions
UNCO => Northern Colorado Bears
UND => North Dakota Fighting Hawks
UNH => New Hampshire Wildcats
UNI => University of Mary Marauders
UNNY => Union Dutchmen
UNT => North Texas Mean Green
UPP => Upper Iowa Peacocks
URI => Rhode Island Rams
USA => South Alabama Jaguars
USD => San Diego Toreros
UTC => Chattanooga Mocs
UTI => Utica College Pioneers
VAL => Valley City State Vikings
VILL => Villanova Wildcats
VIR => Virginia State Trojans
VT => Virginia Tech Hokies
WAB => Wabash College Little Giants
WAS => Washington-Missouri Bears
WAY => Wayne State (MI) Warriors
WES => Westminster College (MO) Blue Jays
WHE => Wheaton College Illinois Thunder
WIL => Wilkes University Colonels
WIN => Wingate Bulldogs
WIS => Wisconsin-Platteville Pioneers
WOR => Worcester State College Lancers
YALE => Yale Bulldogs
NHL:
---
ARI => Arizona Coyotes
VGS => Vegas Golden Knights
SOCCER - BUNDESLIGA (GERMANY):
-----------------------------
DOR => Borussia Dortmund
KOL => 1. FC Köln
LEV => Bayer Leverkusen
STU => VfB Stuttgart
SOCCER - LIGUE 1 (FRANCE):
-------------------------
LYON => Lyon
MAR => Marseille
NICE => Nice
PSG => Paris Saint-Germain
SOCCER - PREMIER LEAGUE (ENGLAND):
---------------------------------
BUR => Burnley
LUT => Luton Town
SHU => Sheffield United
================================================================================
SUMMARY BY SPORT:
================================================================================
MLB: 1 missing
NCAAF: 295 missing
NHL: 2 missing
Soccer - Bundesliga (Germany): 4 missing
Soccer - Ligue 1 (France): 4 missing
Soccer - Premier League (England): 3 missing
================================================================================
FILENAMES NEEDED:
================================================================================
Add these PNG files to their respective directories:
assets/sports/mlb_logos/OAK.png
assets/sports/ncaa_fbs_logos/AAMU.png
assets/sports/ncaa_fbs_logos/ACU.png
assets/sports/ncaa_fbs_logos/ADA.png
assets/sports/ncaa_fbs_logos/ADR.png
assets/sports/ncaa_fbs_logos/AIC.png
assets/sports/ncaa_fbs_logos/ALB.png
assets/sports/ncaa_fbs_logos/ALBS.png
assets/sports/ncaa_fbs_logos/ALCN.png
assets/sports/ncaa_fbs_logos/ALD.png
assets/sports/ncaa_fbs_logos/ALF.png
assets/sports/ncaa_fbs_logos/ALL.png
assets/sports/ncaa_fbs_logos/ALST.png
assets/sports/ncaa_fbs_logos/AMH.png
assets/sports/ncaa_fbs_logos/AND.png
assets/sports/ncaa_fbs_logos/ANG.png
assets/sports/ncaa_fbs_logos/ANN.png
assets/sports/ncaa_fbs_logos/APSU.png
assets/sports/ncaa_fbs_logos/ASH.png
assets/sports/ncaa_fbs_logos/ASP.png
assets/sports/ncaa_fbs_logos/ASU.png
assets/sports/ncaa_fbs_logos/AUG.png
assets/sports/ncaa_fbs_logos/AUR.png
assets/sports/ncaa_fbs_logos/AUS.png
assets/sports/ncaa_fbs_logos/AVE.png
assets/sports/ncaa_fbs_logos/AVI.png
assets/sports/ncaa_fbs_logos/AZU.png
assets/sports/ncaa_fbs_logos/BAK.png
assets/sports/ncaa_fbs_logos/BAL.png
assets/sports/ncaa_fbs_logos/BAT.png
assets/sports/ncaa_fbs_logos/BEC.png
assets/sports/ncaa_fbs_logos/BEL.png
assets/sports/ncaa_fbs_logos/BEN.png
assets/sports/ncaa_fbs_logos/BENT.png
assets/sports/ncaa_fbs_logos/BET.png
assets/sports/ncaa_fbs_logos/BHS.png
assets/sports/ncaa_fbs_logos/BIR.png
assets/sports/ncaa_fbs_logos/BKN.png
assets/sports/ncaa_fbs_logos/BLA.png
assets/sports/ncaa_fbs_logos/BLOM.png
assets/sports/ncaa_fbs_logos/BLU.png
assets/sports/ncaa_fbs_logos/BOW.png
assets/sports/ncaa_fbs_logos/BRI.png
assets/sports/ncaa_fbs_logos/BRWN.png
assets/sports/ncaa_fbs_logos/BST.png
assets/sports/ncaa_fbs_logos/BUCK.png
assets/sports/ncaa_fbs_logos/BUE.png
assets/sports/ncaa_fbs_logos/BUF.png
assets/sports/ncaa_fbs_logos/BUT.png
assets/sports/ncaa_fbs_logos/CAM.png
assets/sports/ncaa_fbs_logos/CAP.png
assets/sports/ncaa_fbs_logos/CAR.png
assets/sports/ncaa_fbs_logos/CARK.png
assets/sports/ncaa_fbs_logos/CAS.png
assets/sports/ncaa_fbs_logos/CAT.png
assets/sports/ncaa_fbs_logos/CCSU.png
assets/sports/ncaa_fbs_logos/CEN.png
assets/sports/ncaa_fbs_logos/CHA.png
assets/sports/ncaa_fbs_logos/CHI.png
assets/sports/ncaa_fbs_logos/CHSO.png
assets/sports/ncaa_fbs_logos/CLA.png
assets/sports/ncaa_fbs_logos/CLMB.png
assets/sports/ncaa_fbs_logos/COE.png
assets/sports/ncaa_fbs_logos/COL.png
assets/sports/ncaa_fbs_logos/COLC.png
assets/sports/ncaa_fbs_logos/COLG.png
assets/sports/ncaa_fbs_logos/CON.png
assets/sports/ncaa_fbs_logos/COR.png
assets/sports/ncaa_fbs_logos/CP.png
assets/sports/ncaa_fbs_logos/CRO.png
assets/sports/ncaa_fbs_logos/CSU.png
assets/sports/ncaa_fbs_logos/CUL.png
assets/sports/ncaa_fbs_logos/CUM.png
assets/sports/ncaa_fbs_logos/CUR.png
assets/sports/ncaa_fbs_logos/DAK.png
assets/sports/ncaa_fbs_logos/DART.png
assets/sports/ncaa_fbs_logos/DAV.png
assets/sports/ncaa_fbs_logos/DAY.png
assets/sports/ncaa_fbs_logos/DEF.png
assets/sports/ncaa_fbs_logos/DEL.png
assets/sports/ncaa_fbs_logos/DEN.png
assets/sports/ncaa_fbs_logos/DEP.png
assets/sports/ncaa_fbs_logos/DIC.png
assets/sports/ncaa_fbs_logos/DRKE.png
assets/sports/ncaa_fbs_logos/DSU.png
assets/sports/ncaa_fbs_logos/DUB.png
assets/sports/ncaa_fbs_logos/DUQ.png
assets/sports/ncaa_fbs_logos/EAS.png
assets/sports/ncaa_fbs_logos/EDI.png
assets/sports/ncaa_fbs_logos/EIU.png
assets/sports/ncaa_fbs_logos/EKU.png
assets/sports/ncaa_fbs_logos/ELI.png
assets/sports/ncaa_fbs_logos/ELM.png
assets/sports/ncaa_fbs_logos/ELON.png
assets/sports/ncaa_fbs_logos/EMO.png
assets/sports/ncaa_fbs_logos/EMP.png
assets/sports/ncaa_fbs_logos/END.png
assets/sports/ncaa_fbs_logos/EOR.png
assets/sports/ncaa_fbs_logos/ETSU.png
assets/sports/ncaa_fbs_logos/EUR.png
assets/sports/ncaa_fbs_logos/EWU.png
assets/sports/ncaa_fbs_logos/FAY.png
assets/sports/ncaa_fbs_logos/FDU.png
assets/sports/ncaa_fbs_logos/FER.png
assets/sports/ncaa_fbs_logos/FIN.png
assets/sports/ncaa_fbs_logos/FIT.png
assets/sports/ncaa_fbs_logos/FLA.png
assets/sports/ncaa_fbs_logos/FOR.png
assets/sports/ncaa_fbs_logos/FRA.png
assets/sports/ncaa_fbs_logos/FRO.png
assets/sports/ncaa_fbs_logos/FRST.png
assets/sports/ncaa_fbs_logos/FTLW.png
assets/sports/ncaa_fbs_logos/FUR.png
assets/sports/ncaa_fbs_logos/GAL.png
assets/sports/ncaa_fbs_logos/GAN.png
assets/sports/ncaa_fbs_logos/GEN.png
assets/sports/ncaa_fbs_logos/GEO.png
assets/sports/ncaa_fbs_logos/GET.png
assets/sports/ncaa_fbs_logos/GLE.png
assets/sports/ncaa_fbs_logos/GMU.png
assets/sports/ncaa_fbs_logos/GRA.png
assets/sports/ncaa_fbs_logos/GRE.png
assets/sports/ncaa_fbs_logos/GRI.png
assets/sports/ncaa_fbs_logos/GRO.png
assets/sports/ncaa_fbs_logos/GUI.png
assets/sports/ncaa_fbs_logos/GWEB.png
assets/sports/ncaa_fbs_logos/HAM.png
assets/sports/ncaa_fbs_logos/HAMP.png
assets/sports/ncaa_fbs_logos/HAN.png
assets/sports/ncaa_fbs_logos/HAR.png
assets/sports/ncaa_fbs_logos/HARV.png
assets/sports/ncaa_fbs_logos/HAS.png
assets/sports/ncaa_fbs_logos/HAW.png
assets/sports/ncaa_fbs_logos/HBU.png
assets/sports/ncaa_fbs_logos/HC.png
assets/sports/ncaa_fbs_logos/HEI.png
assets/sports/ncaa_fbs_logos/HEN.png
assets/sports/ncaa_fbs_logos/HIL.png
assets/sports/ncaa_fbs_logos/HIR.png
assets/sports/ncaa_fbs_logos/HOB.png
assets/sports/ncaa_fbs_logos/HOW.png
assets/sports/ncaa_fbs_logos/HUS.png
assets/sports/ncaa_fbs_logos/IDHO.png
assets/sports/ncaa_fbs_logos/IDST.png
assets/sports/ncaa_fbs_logos/ILST.png
assets/sports/ncaa_fbs_logos/ILW.png
assets/sports/ncaa_fbs_logos/IND.png
assets/sports/ncaa_fbs_logos/INST.png
assets/sports/ncaa_fbs_logos/IOW.png
assets/sports/ncaa_fbs_logos/ITH.png
assets/sports/ncaa_fbs_logos/JKST.png
assets/sports/ncaa_fbs_logos/JOH.png
assets/sports/ncaa_fbs_logos/JUN.png
assets/sports/ncaa_fbs_logos/KAL.png
assets/sports/ncaa_fbs_logos/KAN.png
assets/sports/ncaa_fbs_logos/KEN.png
assets/sports/ncaa_fbs_logos/KIN.png
assets/sports/ncaa_fbs_logos/KNO.png
assets/sports/ncaa_fbs_logos/KUT.png
assets/sports/ncaa_fbs_logos/KYST.png
assets/sports/ncaa_fbs_logos/KYW.png
assets/sports/ncaa_fbs_logos/LA.png
assets/sports/ncaa_fbs_logos/LAG.png
assets/sports/ncaa_fbs_logos/LAK.png
assets/sports/ncaa_fbs_logos/LAM.png
assets/sports/ncaa_fbs_logos/LAN.png
assets/sports/ncaa_fbs_logos/LAW.png
assets/sports/ncaa_fbs_logos/LEB.png
assets/sports/ncaa_fbs_logos/LEH.png
assets/sports/ncaa_fbs_logos/LEN.png
assets/sports/ncaa_fbs_logos/LEW.png
assets/sports/ncaa_fbs_logos/LIM.png
assets/sports/ncaa_fbs_logos/LIN.png
assets/sports/ncaa_fbs_logos/LOC.png
assets/sports/ncaa_fbs_logos/LOR.png
assets/sports/ncaa_fbs_logos/LUT.png
assets/sports/ncaa_fbs_logos/LYC.png
assets/sports/ncaa_fbs_logos/M-OH.png
assets/sports/ncaa_fbs_logos/MAC.png
assets/sports/ncaa_fbs_logos/MAI.png
assets/sports/ncaa_fbs_logos/MAN.png
assets/sports/ncaa_fbs_logos/MAR.png
assets/sports/ncaa_fbs_logos/MAS.png
assets/sports/ncaa_fbs_logos/MAY.png
assets/sports/ncaa_fbs_logos/MCM.png
assets/sports/ncaa_fbs_logos/MCN.png
assets/sports/ncaa_fbs_logos/MEN.png
assets/sports/ncaa_fbs_logos/MER.png
assets/sports/ncaa_fbs_logos/MERC.png
assets/sports/ncaa_fbs_logos/MES.png
assets/sports/ncaa_fbs_logos/MET.png
assets/sports/ncaa_fbs_logos/MH.png
assets/sports/ncaa_fbs_logos/MID.png
assets/sports/ncaa_fbs_logos/MIL.png
assets/sports/ncaa_fbs_logos/MIN.png
assets/sports/ncaa_fbs_logos/MIS.png
assets/sports/ncaa_fbs_logos/MNST.png
assets/sports/ncaa_fbs_logos/MONM.png
assets/sports/ncaa_fbs_logos/MONT.png
assets/sports/ncaa_fbs_logos/MOR.png
assets/sports/ncaa_fbs_logos/MORE.png
assets/sports/ncaa_fbs_logos/MORG.png
assets/sports/ncaa_fbs_logos/MOU.png
assets/sports/ncaa_fbs_logos/MRST.png
assets/sports/ncaa_fbs_logos/MSU.png
assets/sports/ncaa_fbs_logos/MTST.png
assets/sports/ncaa_fbs_logos/MTU.png
assets/sports/ncaa_fbs_logos/MUH.png
assets/sports/ncaa_fbs_logos/MUR.png
assets/sports/ncaa_fbs_logos/MUS.png
assets/sports/ncaa_fbs_logos/MVSU.png
assets/sports/ncaa_fbs_logos/NAU.png
assets/sports/ncaa_fbs_logos/NBY.png
assets/sports/ncaa_fbs_logos/NCAT.png
assets/sports/ncaa_fbs_logos/NCCU.png
assets/sports/ncaa_fbs_logos/NCST.png
assets/sports/ncaa_fbs_logos/NDOH.png
assets/sports/ncaa_fbs_logos/NDSU.png
assets/sports/ncaa_fbs_logos/NH.png
assets/sports/ncaa_fbs_logos/NICH.png
assets/sports/ncaa_fbs_logos/NMH.png
assets/sports/ncaa_fbs_logos/NMI.png
assets/sports/ncaa_fbs_logos/NOR.png
assets/sports/ncaa_fbs_logos/NORF.png
assets/sports/ncaa_fbs_logos/OBE.png
assets/sports/ncaa_fbs_logos/OHI.png
assets/sports/ncaa_fbs_logos/OKL.png
assets/sports/ncaa_fbs_logos/OLI.png
assets/sports/ncaa_fbs_logos/OMA.png
assets/sports/ncaa_fbs_logos/OTT.png
assets/sports/ncaa_fbs_logos/PAC.png
assets/sports/ncaa_fbs_logos/PENN.png
assets/sports/ncaa_fbs_logos/PIKE.png
assets/sports/ncaa_fbs_logos/PRE.png
assets/sports/ncaa_fbs_logos/PRI.png
assets/sports/ncaa_fbs_logos/PRIN.png
assets/sports/ncaa_fbs_logos/PST.png
assets/sports/ncaa_fbs_logos/RED.png
assets/sports/ncaa_fbs_logos/RICH.png
assets/sports/ncaa_fbs_logos/RIT.png
assets/sports/ncaa_fbs_logos/ROB.png
assets/sports/ncaa_fbs_logos/ROS.png
assets/sports/ncaa_fbs_logos/SAC.png
assets/sports/ncaa_fbs_logos/SAG.png
assets/sports/ncaa_fbs_logos/SDAK.png
assets/sports/ncaa_fbs_logos/SET.png
assets/sports/ncaa_fbs_logos/SIU.png
assets/sports/ncaa_fbs_logos/SLI.png
assets/sports/ncaa_fbs_logos/SOU.png
assets/sports/ncaa_fbs_logos/SPR.png
assets/sports/ncaa_fbs_logos/ST.png
assets/sports/ncaa_fbs_logos/STE.png
assets/sports/ncaa_fbs_logos/STET.png
assets/sports/ncaa_fbs_logos/STO.png
assets/sports/ncaa_fbs_logos/SUS.png
assets/sports/ncaa_fbs_logos/SUU.png
assets/sports/ncaa_fbs_logos/TA&M.png
assets/sports/ncaa_fbs_logos/TAY.png
assets/sports/ncaa_fbs_logos/TIF.png
assets/sports/ncaa_fbs_logos/TRI.png
assets/sports/ncaa_fbs_logos/TUF.png
assets/sports/ncaa_fbs_logos/TXST.png
assets/sports/ncaa_fbs_logos/UAPB.png
assets/sports/ncaa_fbs_logos/UCD.png
assets/sports/ncaa_fbs_logos/UCONN.png
assets/sports/ncaa_fbs_logos/ULM.png
assets/sports/ncaa_fbs_logos/UMD.png
assets/sports/ncaa_fbs_logos/UMDA.png
assets/sports/ncaa_fbs_logos/UML.png
assets/sports/ncaa_fbs_logos/UNA.png
assets/sports/ncaa_fbs_logos/UNCO.png
assets/sports/ncaa_fbs_logos/UND.png
assets/sports/ncaa_fbs_logos/UNH.png
assets/sports/ncaa_fbs_logos/UNI.png
assets/sports/ncaa_fbs_logos/UNNY.png
assets/sports/ncaa_fbs_logos/UNT.png
assets/sports/ncaa_fbs_logos/UPP.png
assets/sports/ncaa_fbs_logos/URI.png
assets/sports/ncaa_fbs_logos/USA.png
assets/sports/ncaa_fbs_logos/USD.png
assets/sports/ncaa_fbs_logos/UTC.png
assets/sports/ncaa_fbs_logos/UTI.png
assets/sports/ncaa_fbs_logos/VAL.png
assets/sports/ncaa_fbs_logos/VILL.png
assets/sports/ncaa_fbs_logos/VIR.png
assets/sports/ncaa_fbs_logos/VT.png
assets/sports/ncaa_fbs_logos/WAB.png
assets/sports/ncaa_fbs_logos/WAS.png
assets/sports/ncaa_fbs_logos/WAY.png
assets/sports/ncaa_fbs_logos/WES.png
assets/sports/ncaa_fbs_logos/WHE.png
assets/sports/ncaa_fbs_logos/WIL.png
assets/sports/ncaa_fbs_logos/WIN.png
assets/sports/ncaa_fbs_logos/WIS.png
assets/sports/ncaa_fbs_logos/WOR.png
assets/sports/ncaa_fbs_logos/YALE.png
assets/sports/nhl_logos/ARI.png
assets/sports/nhl_logos/VGS.png
assets/sports/soccer_logos/DOR.png
assets/sports/soccer_logos/KOL.png
assets/sports/soccer_logos/LEV.png
assets/sports/soccer_logos/STU.png
assets/sports/soccer_logos/LYON.png
assets/sports/soccer_logos/MAR.png
assets/sports/soccer_logos/NICE.png
assets/sports/soccer_logos/PSG.png
assets/sports/soccer_logos/BUR.png
assets/sports/soccer_logos/LUT.png
assets/sports/soccer_logos/SHU.png

160
save_missing_teams.py Normal file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
Script to save the missing teams list to a file for future reference.
"""
import os
from pathlib import Path
def save_missing_teams():
"""Save the missing teams list to a file."""
# Define the sports directories and their corresponding sections in the abbreviations file
sports_dirs = {
'mlb_logos': 'MLB',
'nba_logos': 'NBA',
'nfl_logos': 'NFL',
'nhl_logos': 'NHL',
'ncaa_fbs_logos': ['NCAAF', 'NCAA Conferences/Divisions', 'NCAA_big10', 'NCAA_big12', 'NCAA_acc', 'NCAA_sec', 'NCAA_pac12', 'NCAA_american', 'NCAA_cusa', 'NCAA_mac', 'NCAA_mwc', 'NCAA_sunbelt', 'NCAA_ind', 'NCAA_ovc', 'NCAA_col', 'NCAA_usa', 'NCAA_bigw'],
'soccer_logos': ['Soccer - Premier League (England)', 'Soccer - La Liga (Spain)', 'Soccer - Bundesliga (Germany)', 'Soccer - Serie A (Italy)', 'Soccer - Ligue 1 (France)', 'Soccer - Champions League', 'Soccer - Other Teams'],
'milb_logos': 'MiLB'
}
# Read the abbreviations file
abbreviations_file = Path("assets/sports/all_team_abbreviations.txt")
if not abbreviations_file.exists():
print("Error: all_team_abbreviations.txt not found")
return
with open(abbreviations_file, 'r') as f:
content = f.read()
# Parse teams from the abbreviations file
teams_by_sport = {}
current_section = None
for line in content.split('\n'):
original_line = line
line = line.strip()
# Check if this is a section header (not indented and no arrow)
if line and not original_line.startswith(' ') and ' => ' not in line:
current_section = line
continue
# Check if this is a team entry (indented and has arrow)
if original_line.startswith(' ') and ' => ' in line:
parts = line.split(' => ')
if len(parts) == 2:
abbr = parts[0].strip()
team_name = parts[1].strip()
if current_section not in teams_by_sport:
teams_by_sport[current_section] = []
teams_by_sport[current_section].append((abbr, team_name))
# Collect all missing teams
all_missing_teams = []
for logo_dir, sections in sports_dirs.items():
logo_path = Path(f"assets/sports/{logo_dir}")
if not logo_path.exists():
print(f"⚠️ Logo directory not found: {logo_path}")
continue
# Get all PNG files in the directory
logo_files = [f.stem for f in logo_path.glob("*.png")]
# Check teams for this sport
if isinstance(sections, str):
sections = [sections]
for section in sections:
if section not in teams_by_sport:
continue
missing_teams = []
for abbr, team_name in teams_by_sport[section]:
# Check if logo exists (case-insensitive)
logo_found = False
for logo_file in logo_files:
if logo_file.lower() == abbr.lower():
logo_found = True
break
if not logo_found:
missing_teams.append((abbr, team_name))
if missing_teams:
all_missing_teams.extend([(section, abbr, team_name) for abbr, team_name in missing_teams])
# Sort by sport and then by team abbreviation
all_missing_teams.sort(key=lambda x: (x[0], x[1]))
# Save to file
output_file = "missing_team_logos.txt"
with open(output_file, 'w') as f:
f.write("=" * 80 + "\n")
f.write("MISSING TEAM LOGOS - COMPLETE LIST\n")
f.write("=" * 80 + "\n")
f.write(f"Total missing teams: {len(all_missing_teams)}\n")
f.write("\n")
current_sport = None
for section, abbr, team_name in all_missing_teams:
if section != current_sport:
current_sport = section
f.write(f"\n{section.upper()}:\n")
f.write("-" * len(section) + "\n")
f.write(f" {abbr:>8} => {team_name}\n")
f.write("\n" + "=" * 80 + "\n")
f.write("SUMMARY BY SPORT:\n")
f.write("=" * 80 + "\n")
# Count by sport
sport_counts = {}
for section, abbr, team_name in all_missing_teams:
if section not in sport_counts:
sport_counts[section] = 0
sport_counts[section] += 1
for sport, count in sorted(sport_counts.items()):
f.write(f"{sport:>30}: {count:>3} missing\n")
f.write("\n" + "=" * 80 + "\n")
f.write("FILENAMES NEEDED:\n")
f.write("=" * 80 + "\n")
f.write("Add these PNG files to their respective directories:\n")
f.write("\n")
for section, abbr, team_name in all_missing_teams:
# Determine the directory based on the section
if 'MLB' in section:
dir_name = 'mlb_logos'
elif 'NBA' in section:
dir_name = 'nba_logos'
elif 'NFL' in section:
dir_name = 'nfl_logos'
elif 'NHL' in section:
dir_name = 'nhl_logos'
elif 'NCAA' in section:
dir_name = 'ncaa_fbs_logos'
elif 'Soccer' in section:
dir_name = 'soccer_logos'
elif 'MiLB' in section:
dir_name = 'milb_logos'
else:
dir_name = 'unknown'
f.write(f"assets/sports/{dir_name}/{abbr}.png\n")
print(f"✅ Missing teams list saved to: {output_file}")
print(f"📊 Total missing teams: {len(all_missing_teams)}")
if __name__ == "__main__":
save_missing_teams()

View File

@@ -581,6 +581,11 @@ class CacheManager:
'memory_ttl': 172800,
'force_refresh': False
},
'leaderboard': {
'max_age': 604800, # 7 days (1 week) - football rankings updated weekly
'memory_ttl': 1209600, # 14 days in memory
'force_refresh': False
},
# News and odds
'news': {

View File

@@ -42,8 +42,7 @@ class CalendarManager:
logger.info(f"Calendar configuration: enabled={self.enabled}, update_interval={self.update_interval}, max_events={self.max_events}, calendars={self.calendars}")
# Get timezone from config
self.config_manager = ConfigManager()
timezone_str = self.config_manager.get_timezone()
timezone_str = self.config.get('timezone', 'UTC')
logger.info(f"Loading timezone from config: {timezone_str}")
try:
self.timezone = pytz.timezone(timezone_str)

View File

@@ -10,9 +10,15 @@ from src.display_manager import DisplayManager
logger = logging.getLogger(__name__)
class Clock:
def __init__(self, display_manager: DisplayManager = None):
self.config_manager = ConfigManager()
self.config = self.config_manager.load_config()
def __init__(self, display_manager: DisplayManager = None, config: Dict[str, Any] = None):
if config is not None:
# Use provided config
self.config = config
self.config_manager = None # Not needed when config is provided
else:
# Fallback: create ConfigManager and load config (for standalone usage)
self.config_manager = ConfigManager()
self.config = self.config_manager.load_config()
# Use the provided display_manager or create a new one if none provided
self.display_manager = display_manager or DisplayManager(self.config.get('display', {}))
logger.info("Clock initialized with display_manager: %s", id(self.display_manager))
@@ -31,7 +37,7 @@ class Clock:
def _get_timezone(self) -> pytz.timezone:
"""Get timezone from the config file."""
config_timezone = self.config_manager.get_timezone()
config_timezone = self.config.get('timezone', 'UTC')
try:
return pytz.timezone(config_timezone)
except pytz.exceptions.UnknownTimeZoneError:

View File

@@ -20,6 +20,7 @@ from src.cache_manager import CacheManager
from src.stock_manager import StockManager
from src.stock_news_manager import StockNewsManager
from src.odds_ticker_manager import OddsTickerManager
from src.leaderboard_manager import LeaderboardManager
from src.nhl_managers import NHLLiveManager, NHLRecentManager, NHLUpcomingManager
from src.nba_managers import NBALiveManager, NBARecentManager, NBAUpcomingManager
from src.mlb_manager import MLBLiveManager, MLBRecentManager, MLBUpcomingManager
@@ -55,16 +56,17 @@ class DisplayController:
# Initialize display modes
init_time = time.time()
self.clock = Clock(self.display_manager) if self.config.get('clock', {}).get('enabled', True) else None
self.clock = Clock(self.display_manager, self.config) if self.config.get('clock', {}).get('enabled', True) else None
self.weather = WeatherManager(self.config, self.display_manager) if self.config.get('weather', {}).get('enabled', False) else None
self.stocks = StockManager(self.config, self.display_manager) if self.config.get('stocks', {}).get('enabled', False) else None
self.news = StockNewsManager(self.config, self.display_manager) if self.config.get('stock_news', {}).get('enabled', False) else None
self.odds_ticker = OddsTickerManager(self.config, self.display_manager) if self.config.get('odds_ticker', {}).get('enabled', False) else None
self.leaderboard = LeaderboardManager(self.config, self.display_manager) if self.config.get('leaderboard', {}).get('enabled', False) else None
self.calendar = CalendarManager(self.display_manager, self.config) if self.config.get('calendar', {}).get('enabled', False) else None
self.youtube = YouTubeDisplay(self.display_manager, self.config) if self.config.get('youtube', {}).get('enabled', False) else None
self.text_display = TextDisplay(self.display_manager, self.config) if self.config.get('text_display', {}).get('enabled', False) else None
self.of_the_day = OfTheDayManager(self.display_manager, self.config) if self.config.get('of_the_day', {}).get('enabled', False) else None
self.news_manager = NewsManager(self.config, self.display_manager) if self.config.get('news_manager', {}).get('enabled', False) else None
self.news_manager = NewsManager(self.config, self.display_manager, self.config_manager) if self.config.get('news_manager', {}).get('enabled', False) else None
logger.info(f"Calendar Manager initialized: {'Object' if self.calendar else 'None'}")
logger.info(f"Text Display initialized: {'Object' if self.text_display else 'None'}")
logger.info(f"OfTheDay Manager initialized: {'Object' if self.of_the_day else 'None'}")
@@ -258,6 +260,7 @@ class DisplayController:
if self.stocks: self.available_modes.append('stocks')
if self.news: self.available_modes.append('stock_news')
if self.odds_ticker: self.available_modes.append('odds_ticker')
if self.leaderboard: self.available_modes.append('leaderboard')
if self.calendar: self.available_modes.append('calendar')
if self.youtube: self.available_modes.append('youtube')
if self.text_display: self.available_modes.append('text_display')
@@ -427,11 +430,9 @@ class DisplayController:
logger.info(f"Initial display mode: {self.current_display_mode}")
logger.info("DisplayController initialized with display_manager: %s", id(self.display_manager))
# --- SCHEDULING & CONFIG REFRESH ---
self.config_check_interval = 30
self.last_config_check = 0
# --- SCHEDULING ---
self.is_display_active = True
self._load_config() # Initial load of schedule
self._load_schedule_config() # Load schedule config once at startup
def _handle_music_update(self, track_info: Dict[str, Any], significant_change: bool = False):
"""Callback for when music track info changes."""
@@ -516,6 +517,20 @@ class DisplayController:
# Fall back to configured duration
return self.display_durations.get(mode_key, 60)
# Handle dynamic duration for leaderboard
if mode_key == 'leaderboard' and self.leaderboard:
try:
dynamic_duration = self.leaderboard.get_dynamic_duration()
# Only log if duration has changed or we haven't logged this duration yet
if not hasattr(self, '_last_logged_leaderboard_duration') or self._last_logged_leaderboard_duration != dynamic_duration:
logger.info(f"Using dynamic duration for leaderboard: {dynamic_duration} seconds")
self._last_logged_leaderboard_duration = dynamic_duration
return dynamic_duration
except Exception as e:
logger.error(f"Error getting dynamic duration for leaderboard: {e}")
# Fall back to configured duration
return self.display_durations.get(mode_key, 60)
# Simplify weather key handling
if mode_key.startswith('weather_'):
return self.display_durations.get(mode_key, 15)
@@ -530,14 +545,33 @@ class DisplayController:
def _update_modules(self):
"""Call update methods on active managers."""
if self.weather: self.weather.get_weather()
if self.stocks: self.stocks.update_stock_data()
if self.news: self.news.update_news_data()
if self.odds_ticker: self.odds_ticker.update()
if self.calendar: self.calendar.update(time.time())
if self.youtube: self.youtube.update()
if self.text_display: self.text_display.update()
if self.of_the_day: self.of_the_day.update(time.time())
# Check if we're currently scrolling and defer updates if so
if self.display_manager.is_currently_scrolling():
logger.debug("Display is currently scrolling, deferring module updates")
# Defer updates for modules that might cause lag during scrolling
if self.odds_ticker:
self.display_manager.defer_update(self.odds_ticker.update, priority=1)
if self.stocks:
self.display_manager.defer_update(self.stocks.update_stock_data, priority=2)
if self.news:
self.display_manager.defer_update(self.news.update_news_data, priority=2)
# Continue with non-scrolling-sensitive updates
if self.weather: self.weather.get_weather()
if self.calendar: self.calendar.update(time.time())
if self.youtube: self.youtube.update()
if self.text_display: self.text_display.update()
if self.of_the_day: self.of_the_day.update(time.time())
else:
# Not scrolling, perform all updates normally
if self.weather: self.weather.get_weather()
if self.stocks: self.stocks.update_stock_data()
if self.news: self.news.update_news_data()
if self.odds_ticker: self.odds_ticker.update()
if self.calendar: self.calendar.update(time.time())
if self.youtube: self.youtube.update()
if self.text_display: self.text_display.update()
if self.of_the_day: self.of_the_day.update(time.time())
# News manager fetches data when displayed, not during updates
# if self.news_manager: self.news_manager.fetch_news_data()
@@ -829,14 +863,14 @@ class DisplayController:
self.ncaa_fb_showing_recent = True # Reset to recent for the new team
# --- SCHEDULING METHODS ---
def _load_config(self):
"""Load configuration from the config manager and parse schedule settings."""
self.config = self.config_manager.load_config()
def _load_schedule_config(self):
"""Load schedule configuration once at startup."""
schedule_config = self.config.get('schedule', {})
self.schedule_enabled = schedule_config.get('enabled', False)
try:
self.start_time = datetime.strptime(schedule_config.get('start_time', '07:00'), '%H:%M').time()
self.end_time = datetime.strptime(schedule_config.get('end_time', '22:00'), '%H:%M').time()
logger.info(f"Schedule loaded: enabled={self.schedule_enabled}, start={self.start_time}, end={self.end_time}")
except (ValueError, TypeError):
logger.warning("Invalid time format in schedule config. Using defaults.")
self.start_time = time_obj(7, 0)
@@ -926,12 +960,7 @@ class DisplayController:
while True:
current_time = time.time()
# Periodically check for config changes
if current_time - self.last_config_check > self.config_check_interval:
self._load_config()
self.last_config_check = current_time
# Enforce the schedule
# Check the schedule (no config reload needed)
self._check_schedule()
if not self.is_display_active:
time.sleep(60)
@@ -940,6 +969,9 @@ class DisplayController:
# Update data for all modules first
self._update_modules()
# Process any deferred updates that may have accumulated
self.display_manager.process_deferred_updates()
# Update live modes in rotation if needed
self._update_live_modes_in_rotation()
@@ -1097,6 +1129,8 @@ class DisplayController:
manager_to_display = self.news
elif self.current_display_mode == 'odds_ticker' and self.odds_ticker:
manager_to_display = self.odds_ticker
elif self.current_display_mode == 'leaderboard' and self.leaderboard:
manager_to_display = self.leaderboard
elif self.current_display_mode == 'calendar' and self.calendar:
manager_to_display = self.calendar
elif self.current_display_mode == 'youtube' and self.youtube:

View File

@@ -30,6 +30,15 @@ class DisplayManager:
self._snapshot_path = "/tmp/led_matrix_preview.png"
self._snapshot_min_interval_sec = 0.2 # max ~5 fps
self._last_snapshot_ts = 0.0
# Scrolling state tracking for graceful updates
self._scrolling_state = {
'is_scrolling': False,
'last_scroll_activity': 0,
'scroll_inactivity_threshold': 2.0, # seconds of inactivity before considering "not scrolling"
'deferred_updates': []
}
self._setup_matrix()
logger.info("Matrix setup completed in %.3f seconds", time.time() - start_time)
@@ -634,6 +643,77 @@ class DisplayManager:
return dt.strftime(f"%b %-d{suffix}")
def set_scrolling_state(self, is_scrolling: bool):
"""Set the current scrolling state. Call this when a display starts/stops scrolling."""
current_time = time.time()
self._scrolling_state['is_scrolling'] = is_scrolling
if is_scrolling:
self._scrolling_state['last_scroll_activity'] = current_time
logger.debug(f"Scrolling state set to: {is_scrolling}")
def is_currently_scrolling(self) -> bool:
"""Check if the display is currently in a scrolling state."""
current_time = time.time()
# If explicitly not scrolling, return False
if not self._scrolling_state['is_scrolling']:
return False
# If we've been inactive for the threshold period, consider it not scrolling
if current_time - self._scrolling_state['last_scroll_activity'] > self._scrolling_state['scroll_inactivity_threshold']:
self._scrolling_state['is_scrolling'] = False
return False
return True
def defer_update(self, update_func, priority: int = 0):
"""Defer an update function to be called when not scrolling.
Args:
update_func: Function to call when not scrolling
priority: Priority level (lower numbers = higher priority)
"""
self._scrolling_state['deferred_updates'].append({
'func': update_func,
'priority': priority,
'timestamp': time.time()
})
# Sort by priority (lower numbers first)
self._scrolling_state['deferred_updates'].sort(key=lambda x: x['priority'])
logger.debug(f"Deferred update added. Total deferred: {len(self._scrolling_state['deferred_updates'])}")
def process_deferred_updates(self):
"""Process any deferred updates if not currently scrolling."""
if self.is_currently_scrolling():
return
if not self._scrolling_state['deferred_updates']:
return
# Process all deferred updates
updates_to_process = self._scrolling_state['deferred_updates'].copy()
self._scrolling_state['deferred_updates'].clear()
logger.debug(f"Processing {len(updates_to_process)} deferred updates")
for update_info in updates_to_process:
try:
update_info['func']()
logger.debug("Deferred update executed successfully")
except Exception as e:
logger.error(f"Error executing deferred update: {e}")
# Re-add failed updates for retry
self._scrolling_state['deferred_updates'].append(update_info)
def get_scrolling_stats(self) -> dict:
"""Get current scrolling statistics for debugging."""
return {
'is_scrolling': self._scrolling_state['is_scrolling'],
'last_activity': self._scrolling_state['last_scroll_activity'],
'deferred_count': len(self._scrolling_state['deferred_updates']),
'inactivity_threshold': self._scrolling_state['scroll_inactivity_threshold']
}
def _write_snapshot_if_due(self) -> None:
"""Write the current image to a PNG snapshot file at a limited frequency."""
try:

1279
src/leaderboard_manager.py Normal file

File diff suppressed because it is too large Load Diff

662
src/logo_downloader.py Normal file
View File

@@ -0,0 +1,662 @@
#!/usr/bin/env python3
"""
Centralized logo downloader utility for automatically fetching team logos from ESPN API.
This module provides functionality to download missing team logos for various sports leagues,
with special support for FCS teams and other NCAA divisions.
"""
import os
import time
import logging
import requests
import json
from typing import Dict, Any, List, Optional, Tuple
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
logger = logging.getLogger(__name__)
class LogoDownloader:
"""Centralized logo downloader for team logos from ESPN API."""
# ESPN API endpoints for different sports/leagues
API_ENDPOINTS = {
'nfl': 'https://site.api.espn.com/apis/site/v2/sports/football/nfl/teams',
'nba': 'https://site.api.espn.com/apis/site/v2/sports/basketball/nba/teams',
'mlb': 'https://site.api.espn.com/apis/site/v2/sports/baseball/mlb/teams',
'nhl': 'https://site.api.espn.com/apis/site/v2/sports/hockey/nhl/teams',
'ncaa_fb': 'https://site.api.espn.com/apis/site/v2/sports/football/college-football/teams',
'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'
}
# Directory mappings for different leagues
LOGO_DIRECTORIES = {
'nfl': 'assets/sports/nfl_logos',
'nba': 'assets/sports/nba_logos',
'mlb': 'assets/sports/mlb_logos',
'nhl': 'assets/sports/nhl_logos',
'ncaa_fb': 'assets/sports/ncaa_fbs_logos',
'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'
}
def __init__(self, request_timeout: int = 30, retry_attempts: int = 3):
"""Initialize the logo downloader with HTTP session and retry logic."""
self.request_timeout = request_timeout
self.retry_attempts = retry_attempts
# Set up session with retry logic
self.session = requests.Session()
retry_strategy = Retry(
total=retry_attempts,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "HEAD", "OPTIONS"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
# Set up headers
self.headers = {
'User-Agent': 'LEDMatrix/1.0 (https://github.com/yourusername/LEDMatrix; contact@example.com)',
'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
def normalize_abbreviation(self, abbreviation: str) -> str:
"""Normalize team abbreviation for consistent filename usage."""
# Handle special characters that can cause filesystem issues
normalized = abbreviation.upper()
# Replace problematic characters with safe alternatives
normalized = normalized.replace('&', 'AND')
normalized = normalized.replace('/', '_')
normalized = normalized.replace('\\', '_')
normalized = normalized.replace(':', '_')
normalized = normalized.replace('*', '_')
normalized = normalized.replace('?', '_')
normalized = normalized.replace('"', '_')
normalized = normalized.replace('<', '_')
normalized = normalized.replace('>', '_')
normalized = normalized.replace('|', '_')
return normalized
def get_logo_directory(self, league: str) -> str:
"""Get the logo directory for a given league."""
return self.LOGO_DIRECTORIES.get(league, f'assets/sports/{league}_logos')
def ensure_logo_directory(self, logo_dir: str) -> bool:
"""Ensure the logo directory exists, create if necessary."""
try:
os.makedirs(logo_dir, exist_ok=True)
return True
except Exception as e:
logger.error(f"Failed to create logo directory {logo_dir}: {e}")
return False
def download_logo(self, logo_url: str, filepath: Path, team_name: str) -> bool:
"""Download a single logo from URL and save to filepath."""
try:
response = self.session.get(logo_url, headers=self.headers, timeout=self.request_timeout)
response.raise_for_status()
# Verify it's actually an image
content_type = response.headers.get('content-type', '').lower()
if not any(img_type in content_type for img_type in ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']):
logger.warning(f"Downloaded content for {team_name} is not an image: {content_type}")
return False
with open(filepath, 'wb') as f:
f.write(response.content)
# Verify and convert the downloaded image to RGBA format
try:
with Image.open(filepath) as img:
# Convert to RGBA to avoid PIL warnings about palette images with transparency
if img.mode in ('P', 'LA', 'L'):
# Convert palette or grayscale images to RGBA
img = img.convert('RGBA')
elif img.mode == 'RGB':
# Convert RGB to RGBA (add alpha channel)
img = img.convert('RGBA')
elif img.mode != 'RGBA':
# For any other mode, convert to RGBA
img = img.convert('RGBA')
# Save the converted image
img.save(filepath, 'PNG')
logger.info(f"Successfully downloaded and converted logo for {team_name} -> {filepath.name}")
return True
except Exception as e:
logger.error(f"Downloaded file for {team_name} is not a valid image or conversion failed: {e}")
try:
os.remove(filepath) # Remove invalid file
except:
pass
return False
except requests.exceptions.RequestException as e:
logger.error(f"Failed to download logo for {team_name}: {e}")
return False
except Exception as e:
logger.error(f"Unexpected error downloading logo for {team_name}: {e}")
return False
def fetch_teams_data(self, league: str) -> Optional[Dict]:
"""Fetch team data from ESPN API for a specific league."""
api_url = self.API_ENDPOINTS.get(league)
if not api_url:
logger.error(f"No API endpoint configured for league: {league}")
return None
try:
logger.info(f"Fetching team data for {league} from ESPN API...")
response = self.session.get(api_url, headers=self.headers, timeout=self.request_timeout)
response.raise_for_status()
data = response.json()
logger.info(f"Successfully fetched team data for {league}")
return data
except requests.exceptions.RequestException as e:
logger.error(f"Error fetching team data for {league}: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"Error parsing JSON response for {league}: {e}")
return None
def extract_teams_from_data(self, data: Dict, league: str) -> List[Dict[str, str]]:
"""Extract team information from ESPN API response."""
teams = []
try:
sports = data.get('sports', [])
for sport in sports:
leagues_data = sport.get('leagues', [])
for league_data in leagues_data:
teams_data = league_data.get('teams', [])
for team_data in teams_data:
team_info = team_data.get('team', {})
abbreviation = team_info.get('abbreviation', '')
display_name = team_info.get('displayName', 'Unknown')
logos = team_info.get('logos', [])
if not abbreviation or not logos:
continue
# Get the default logo (first one is usually default)
logo_url = logos[0].get('href', '')
if not logo_url:
continue
# For NCAA football, try to determine if it's FCS or FBS
team_category = 'FBS' # Default
if league in ['ncaa_fb', 'ncaa_fb_all', 'fcs']:
# Check if this is an FCS team by looking at conference or other indicators
# ESPN API includes both FBS and FCS teams in the same endpoint
# We'll include all teams and let the user decide which ones to use
team_category = self._determine_ncaa_football_division(team_info, league_data)
teams.append({
'abbreviation': abbreviation,
'display_name': display_name,
'logo_url': logo_url,
'league': league,
'category': team_category,
'conference': league_data.get('name', 'Unknown')
})
logger.info(f"Extracted {len(teams)} teams for {league}")
return teams
except Exception as e:
logger.error(f"Error extracting teams for {league}: {e}")
return []
def _determine_ncaa_football_division(self, team_info: Dict, league_data: Dict) -> str:
"""Determine if an NCAA football team is FBS or FCS based on conference and other indicators."""
conference_name = league_data.get('name', '').lower()
# FBS Conferences (more comprehensive list)
fbs_conferences = {
'acc', 'american athletic', 'big 12', 'big ten', 'conference usa', 'c-usa',
'mid-american', 'mac', 'mountain west', 'pac-12', 'pac-10', 'sec',
'sun belt', 'independents', 'big east'
}
# FCS Conferences (more comprehensive list)
fcs_conferences = {
'big sky', 'big south', 'colonial athletic', 'caa', 'ivy league',
'meac', 'missouri valley', 'mvfc', 'northeast', 'nec',
'ohio valley', 'ovc', 'patriot league', 'pioneer football',
'southland', 'southern', 'southwestern athletic', 'swac',
'western athletic', 'wac', 'ncaa division i-aa'
}
# Also check for specific team indicators
team_abbreviation = team_info.get('abbreviation', '').upper()
# Known FBS teams that might be misclassified
known_fbs_teams = {
'ASU', 'ARIZ', 'ARK', 'AUB', 'BOIS', 'CSU', 'FLA', 'HAW', 'IDHO', 'USA'
}
# Check if it's a known FBS team first
if team_abbreviation in known_fbs_teams:
return 'FBS'
# Check conference names
if any(fbs_conf in conference_name for fbs_conf in fbs_conferences):
return 'FBS'
elif any(fcs_conf in conference_name for fcs_conf in fcs_conferences):
return 'FCS'
# If conference is just "NCAA - Football", we need to use other indicators
if conference_name == 'ncaa - football':
# Check team name for indicators of FCS (smaller schools, Division II/III)
team_name = team_info.get('displayName', '').lower()
fcs_indicators = ['college', 'university', 'state', 'tech', 'community']
# If it has typical FCS naming patterns and isn't a known FBS team
if any(indicator in team_name for indicator in fcs_indicators):
return 'FCS'
else:
return 'FBS'
# Default to FBS for unknown conferences
return 'FBS'
def _get_team_name_variations(self, abbreviation: str) -> List[str]:
"""Generate common variations of a team abbreviation for matching."""
variations = set()
abbr = abbreviation.upper()
variations.add(abbr)
# Add normalized version
variations.add(self.normalize_abbreviation(abbr))
# Common substitutions
substitutions = {
'&': ['AND', 'A'],
'A&M': ['TAMU', 'TA&M', 'TEXASAM'],
'STATE': ['ST', 'ST.'],
'UNIVERSITY': ['U', 'UNIV'],
'COLLEGE': ['C', 'COL'],
'TECHNICAL': ['TECH', 'T'],
'NORTHERN': ['NORTH', 'N'],
'SOUTHERN': ['SOUTH', 'S'],
'EASTERN': ['EAST', 'E'],
'WESTERN': ['WEST', 'W']
}
# Apply substitutions
for original, replacements in substitutions.items():
if original in abbr:
for replacement in replacements:
variations.add(abbr.replace(original, replacement))
variations.add(abbr.replace(original, '')) # Remove the word entirely
# Add common abbreviations for Texas A&M
if 'A&M' in abbr or 'TAMU' in abbr:
variations.update(['TAMU', 'TA&M', 'TEXASAM', 'TEXAS_A&M', 'TEXAS_AM'])
return list(variations)
def download_missing_logos_for_league(self, league: str, force_download: bool = False) -> Tuple[int, int]:
"""Download missing logos for a specific league."""
logger.info(f"Starting logo download for league: {league}")
# Get logo directory
logo_dir = self.get_logo_directory(league)
if not self.ensure_logo_directory(logo_dir):
logger.error(f"Failed to create logo directory for {league}")
return 0, 0
# Fetch team data
data = self.fetch_teams_data(league)
if not data:
logger.error(f"Failed to fetch team data for {league}")
return 0, 0
# Extract teams
teams = self.extract_teams_from_data(data, league)
if not teams:
logger.warning(f"No teams found for {league}")
return 0, 0
# Download missing logos
downloaded_count = 0
failed_count = 0
for team in teams:
abbreviation = team['abbreviation']
display_name = team['display_name']
logo_url = team['logo_url']
# Create filename
filename = f"{self.normalize_abbreviation(abbreviation)}.png"
filepath = Path(logo_dir) / filename
# Skip if already exists and not forcing download
if filepath.exists() and not force_download:
logger.debug(f"Skipping {display_name}: {filename} already exists")
continue
# Download logo
if self.download_logo(logo_url, filepath, display_name):
downloaded_count += 1
else:
failed_count += 1
# Small delay to be respectful to the API
time.sleep(0.1)
logger.info(f"Logo download complete for {league}: {downloaded_count} downloaded, {failed_count} failed")
return downloaded_count, failed_count
def download_all_ncaa_football_logos(self, include_fcs: bool = True, force_download: bool = False) -> Tuple[int, int]:
"""Download all NCAA football team logos including FCS teams."""
logger.info(f"Starting comprehensive NCAA football logo download (FCS: {include_fcs})")
# Use the comprehensive NCAA football endpoint
league = 'ncaa_fb_all'
logo_dir = self.get_logo_directory(league)
if not self.ensure_logo_directory(logo_dir):
logger.error(f"Failed to create logo directory for {league}")
return 0, 0
# Fetch team data
data = self.fetch_teams_data(league)
if not data:
logger.error(f"Failed to fetch team data for {league}")
return 0, 0
# Extract teams
teams = self.extract_teams_from_data(data, league)
if not teams:
logger.warning(f"No teams found for {league}")
return 0, 0
# Filter teams based on FCS inclusion
if not include_fcs:
teams = [team for team in teams if team.get('category') == 'FBS']
logger.info(f"Filtered to FBS teams only: {len(teams)} teams")
# Download missing logos
downloaded_count = 0
failed_count = 0
for team in teams:
abbreviation = team['abbreviation']
display_name = team['display_name']
logo_url = team['logo_url']
category = team.get('category', 'Unknown')
conference = team.get('conference', 'Unknown')
# Create filename
filename = f"{self.normalize_abbreviation(abbreviation)}.png"
filepath = Path(logo_dir) / filename
# Skip if already exists and not forcing download
if filepath.exists() and not force_download:
logger.debug(f"Skipping {display_name} ({category}, {conference}): {filename} already exists")
continue
# Download logo
if self.download_logo(logo_url, filepath, display_name):
downloaded_count += 1
logger.info(f"Downloaded {display_name} ({category}, {conference}) -> {filename}")
else:
failed_count += 1
logger.warning(f"Failed to download {display_name} ({category}, {conference})")
# Small delay to be respectful to the API
time.sleep(0.1)
logger.info(f"Comprehensive NCAA football logo download complete: {downloaded_count} downloaded, {failed_count} failed")
return downloaded_count, failed_count
def download_missing_logo_for_team(self, team_abbreviation: str, league: str, team_name: str = None) -> bool:
"""Download a specific team's logo if it's missing."""
logo_dir = self.get_logo_directory(league)
if not self.ensure_logo_directory(logo_dir):
return False
filename = f"{self.normalize_abbreviation(team_abbreviation)}.png"
filepath = Path(logo_dir) / filename
# Return True if logo already exists
if filepath.exists():
logger.debug(f"Logo already exists for {team_abbreviation}")
return True
# Fetch team data to find the logo URL
data = self.fetch_teams_data(league)
if not data:
return False
teams = self.extract_teams_from_data(data, league)
# Find the specific team with improved matching
target_team = None
normalized_search = self.normalize_abbreviation(team_abbreviation)
# First try exact match
for team in teams:
if team['abbreviation'].upper() == team_abbreviation.upper():
target_team = team
break
# If not found, try normalized match
if not target_team:
for team in teams:
normalized_team_abbr = self.normalize_abbreviation(team['abbreviation'])
if normalized_team_abbr == normalized_search:
target_team = team
break
# If still not found, try partial matching for common variations
if not target_team:
search_variations = self._get_team_name_variations(team_abbreviation)
for team in teams:
team_variations = self._get_team_name_variations(team['abbreviation'])
if any(var in team_variations for var in search_variations):
target_team = team
logger.info(f"Found team {team_abbreviation} as {team['abbreviation']} ({team['display_name']})")
break
if not target_team:
logger.warning(f"Team {team_abbreviation} not found in {league} data")
return False
# Download the logo
success = self.download_logo(target_team['logo_url'], filepath, target_team['display_name'])
if success:
time.sleep(0.1) # Small delay
return success
def download_all_missing_logos(self, leagues: List[str] = None, force_download: bool = False) -> Dict[str, Tuple[int, int]]:
"""Download missing logos for all specified leagues."""
if leagues is None:
leagues = list(self.API_ENDPOINTS.keys())
results = {}
total_downloaded = 0
total_failed = 0
for league in leagues:
if league not in self.API_ENDPOINTS:
logger.warning(f"Skipping unknown league: {league}")
continue
downloaded, failed = self.download_missing_logos_for_league(league, force_download)
results[league] = (downloaded, failed)
total_downloaded += downloaded
total_failed += failed
logger.info(f"Overall logo download results: {total_downloaded} downloaded, {total_failed} failed")
return results
def create_placeholder_logo(self, team_abbreviation: str, logo_dir: str, team_name: str = None) -> bool:
"""Create a placeholder logo when real logo cannot be downloaded."""
try:
# Ensure the logo directory exists
if not self.ensure_logo_directory(logo_dir):
logger.error(f"Failed to create logo directory: {logo_dir}")
return False
filename = f"{self.normalize_abbreviation(team_abbreviation)}.png"
filepath = Path(logo_dir) / filename
# Check if we can write to the directory
try:
# Test write permissions by creating a temporary file
test_file = filepath.parent / "test_write.tmp"
test_file.touch()
test_file.unlink() # Remove the test file
except PermissionError:
logger.error(f"Permission denied: Cannot write to directory {logo_dir}")
return False
except Exception as e:
logger.error(f"Directory access error for {logo_dir}: {e}")
return False
# Create a simple placeholder logo
logo = Image.new('RGBA', (64, 64), (100, 100, 100, 255)) # Gray background
draw = ImageDraw.Draw(logo)
# Try to load a font, fallback to default
try:
font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 12)
except:
try:
font = ImageFont.load_default()
except:
font = None
# Draw team abbreviation
text = team_abbreviation
if font:
# Center the text
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (64 - text_width) // 2
y = (64 - text_height) // 2
draw.text((x, y), text, font=font, fill=(255, 255, 255, 255))
else:
# Fallback without font
draw.text((16, 24), text, fill=(255, 255, 255, 255))
logo.save(filepath)
logger.info(f"Created placeholder logo for {team_abbreviation} at {filepath}")
return True
except Exception as e:
logger.error(f"Failed to create placeholder logo for {team_abbreviation}: {e}")
return False
def convert_image_to_rgba(self, filepath: Path) -> bool:
"""Convert an image file to RGBA format to avoid PIL warnings."""
try:
with Image.open(filepath) as img:
if img.mode != 'RGBA':
# Convert to RGBA
converted_img = img.convert('RGBA')
converted_img.save(filepath, 'PNG')
logger.debug(f"Converted {filepath.name} from {img.mode} to RGBA")
return True
else:
logger.debug(f"{filepath.name} is already in RGBA format")
return True
except Exception as e:
logger.error(f"Failed to convert {filepath.name} to RGBA: {e}")
return False
def convert_all_logos_to_rgba(self, league: str) -> Tuple[int, int]:
"""Convert all logos in a league directory to RGBA format."""
logo_dir = Path(self.get_logo_directory(league))
if not logo_dir.exists():
logger.warning(f"Logo directory does not exist: {logo_dir}")
return 0, 0
converted_count = 0
failed_count = 0
for logo_file in logo_dir.glob("*.png"):
if self.convert_image_to_rgba(logo_file):
converted_count += 1
else:
failed_count += 1
logger.info(f"Converted {converted_count} logos to RGBA format for {league}, {failed_count} failed")
return converted_count, failed_count
# Convenience function for easy integration
def download_missing_logo(team_abbreviation: str, league: str, team_name: str = None, create_placeholder: bool = True) -> bool:
"""
Convenience function to download a missing team logo.
Args:
team_abbreviation: Team abbreviation (e.g., 'UGA', 'BAMA', 'TA&M')
league: League identifier (e.g., 'ncaa_fb', 'nfl')
team_name: Optional team name for logging
create_placeholder: Whether to create a placeholder if download fails
Returns:
True if logo exists or was successfully downloaded, False otherwise
"""
downloader = LogoDownloader()
# Check if logo already exists
logo_dir = downloader.get_logo_directory(league)
filename = f"{downloader.normalize_abbreviation(team_abbreviation)}.png"
filepath = Path(logo_dir) / filename
if filepath.exists():
logger.debug(f"Logo already exists for {team_abbreviation} ({league})")
return True
# Try to download the real logo first
logger.info(f"Attempting to download logo for {team_abbreviation} ({team_name or 'Unknown'}) from {league}")
success = downloader.download_missing_logo_for_team(team_abbreviation, league, team_name)
if not success and create_placeholder:
logger.info(f"Creating placeholder logo for {team_abbreviation} ({team_name or 'Unknown'})")
# Create placeholder as fallback
success = downloader.create_placeholder_logo(team_abbreviation, logo_dir, team_name)
if success:
logger.info(f"Successfully handled logo for {team_abbreviation} ({team_name or 'Unknown'})")
else:
logger.warning(f"Failed to download or create logo for {team_abbreviation} ({team_name or 'Unknown'})")
return success
def download_all_logos_for_league(league: str, force_download: bool = False) -> Tuple[int, int]:
"""
Convenience function to download all missing logos for a league.
Args:
league: League identifier (e.g., 'ncaa_fb', 'nfl')
force_download: Whether to re-download existing logos
Returns:
Tuple of (downloaded_count, failed_count)
"""
downloader = LogoDownloader()
return downloader.download_missing_logos_for_league(league, force_download)

View File

@@ -29,12 +29,14 @@ class BaseMLBManager:
def __init__(self, config: Dict[str, Any], display_manager, cache_manager: CacheManager):
self.config = config
self.display_manager = display_manager
# Store reference to config instead of creating new ConfigManager
self.config_manager = None # Not used in this class
self.mlb_config = config.get('mlb', {})
self.show_odds = self.mlb_config.get("show_odds", False)
self.favorite_teams = self.mlb_config.get('favorite_teams', [])
self.show_records = self.mlb_config.get('show_records', False)
self.cache_manager = cache_manager
self.odds_manager = OddsManager(self.cache_manager, self.config)
self.odds_manager = OddsManager(self.cache_manager, self.config_manager)
self.logger = logging.getLogger(__name__)
# Logo handling

View File

@@ -6,7 +6,7 @@ import json
import os
from io import BytesIO
import requests
from typing import Union
from typing import Union, Dict, Any, Optional
from PIL import Image, ImageEnhance
import queue # Added import
@@ -15,6 +15,14 @@ from .spotify_client import SpotifyClient
from .ytm_client import YTMClient
# 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
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
@@ -313,6 +321,10 @@ class MusicManager:
try:
response = requests.get(url, timeout=5) # 5-second timeout for image download
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 = Image.open(img_data)

View File

@@ -43,10 +43,11 @@ class BaseNBAManager:
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager):
self.display_manager = display_manager
self.config_manager = ConfigManager()
# Store reference to config instead of creating new ConfigManager
self.config_manager = None # Not used in this class
self.config = config
self.cache_manager = cache_manager
self.odds_manager = OddsManager(self.cache_manager, self.config)
self.odds_manager = OddsManager(self.cache_manager, None)
self.logger = logging.getLogger(__name__)
self.nba_config = config.get("nba_scoreboard", {})
self.is_enabled = self.nba_config.get("enabled", False)
@@ -63,13 +64,9 @@ class BaseNBAManager:
# Set logging level to INFO to reduce noise
self.logger.setLevel(logging.INFO)
# Get display dimensions from config
display_config = config.get("display", {})
hardware_config = display_config.get("hardware", {})
cols = hardware_config.get("cols", 64)
chain = hardware_config.get("chain_length", 1)
self.display_width = int(cols * chain)
self.display_height = hardware_config.get("rows", 32)
# Get display dimensions from matrix
self.display_width = self.display_manager.matrix.width
self.display_height = self.display_manager.matrix.height
# Cache for loaded logos
self._logo_cache = {}
@@ -79,7 +76,8 @@ class BaseNBAManager:
def _get_timezone(self):
try:
return pytz.timezone(self.config_manager.get_timezone())
timezone_str = self.config.get('timezone', 'UTC')
return pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
return pytz.utc

View File

@@ -24,12 +24,14 @@ class BaseNCAABaseballManager:
def __init__(self, config: Dict[str, Any], display_manager, cache_manager: CacheManager):
self.config = config
self.display_manager = display_manager
# Store reference to config instead of creating new ConfigManager
self.config_manager = None # Not used in this class
self.ncaa_baseball_config = config.get('ncaa_baseball_scoreboard', {})
self.show_odds = self.ncaa_baseball_config.get('show_odds', False)
self.show_records = self.ncaa_baseball_config.get('show_records', False)
self.favorite_teams = self.ncaa_baseball_config.get('favorite_teams', [])
self.cache_manager = cache_manager
self.odds_manager = OddsManager(self.cache_manager, self.config)
self.odds_manager = OddsManager(self.cache_manager, self.config_manager)
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.DEBUG) # Set logger level to DEBUG

View File

@@ -11,6 +11,7 @@ from src.display_manager import DisplayManager
from src.cache_manager import CacheManager # Keep CacheManager import
from src.config_manager import ConfigManager
from src.odds_manager import OddsManager
from src.logo_downloader import download_missing_logo
import pytz
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
@@ -53,6 +54,7 @@ class BaseNCAAFBManager: # Renamed class
self.logo_dir = self.ncaa_fb_config.get("logo_dir", "assets/sports/ncaa_fbs_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)
self.season_cache_duration = self.ncaa_fb_config.get("season_cache_duration_seconds", 86400) # 24 hours default
# Number of games to show (instead of time-based windows)
self.recent_games_to_show = self.ncaa_fb_config.get("recent_games_to_show", 5) # Show last 5 games
@@ -91,22 +93,66 @@ class BaseNCAAFBManager: # Renamed class
self.logger.setLevel(logging.INFO)
display_config = config.get("display", {})
hardware_config = display_config.get("hardware", {})
cols = hardware_config.get("cols", 64)
chain = hardware_config.get("chain_length", 1)
self.display_width = int(cols * chain)
self.display_height = hardware_config.get("rows", 32)
self.display_width = self.display_manager.matrix.width
self.display_height = self.display_manager.matrix.height
self._logo_cache = {}
# Initialize team rankings cache
self._team_rankings_cache = {}
self._rankings_cache_timestamp = 0
self._rankings_cache_duration = 3600 # Cache rankings for 1 hour
self.logger.info(f"Initialized NCAAFB manager with display dimensions: {self.display_width}x{self.display_height}")
self.logger.info(f"Logo directory: {self.logo_dir}")
self.logger.info(f"Display modes - Recent: {self.recent_enabled}, Upcoming: {self.upcoming_enabled}, Live: {self.live_enabled}")
def _fetch_team_rankings(self) -> Dict[str, int]:
"""Fetch current team rankings from ESPN API."""
current_time = time.time()
# Check if we have cached rankings that are still valid
if (self._team_rankings_cache and
current_time - self._rankings_cache_timestamp < self._rankings_cache_duration):
return self._team_rankings_cache
try:
rankings_url = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings"
response = self.session.get(rankings_url, headers=self.headers, timeout=30)
response.raise_for_status()
data = response.json()
rankings = {}
rankings_data = data.get('rankings', [])
if rankings_data:
# Use the first ranking (usually AP Top 25)
first_ranking = rankings_data[0]
teams = first_ranking.get('ranks', [])
for team_data in teams:
team_info = team_data.get('team', {})
team_abbr = team_info.get('abbreviation', '')
current_rank = team_data.get('current', 0)
if team_abbr and current_rank > 0:
rankings[team_abbr] = current_rank
# Cache the results
self._team_rankings_cache = rankings
self._rankings_cache_timestamp = current_time
self.logger.debug(f"Fetched rankings for {len(rankings)} teams")
return rankings
except Exception as e:
self.logger.error(f"Error fetching team rankings: {e}")
return {}
def _get_timezone(self):
try:
return pytz.timezone(self.config_manager.get_timezone())
timezone_str = self.config.get('timezone', 'UTC')
return pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
return pytz.utc
@@ -313,39 +359,8 @@ class BaseNCAAFBManager: # Renamed class
draw.text((x + dx, y + dy), text, font=font, fill=outline_color)
draw.text((x, y), text, font=font, fill=fill)
def _download_team_logo(self, team_id):
# Base API URL with placeholder for team ID
API_URL_TEMPLATE = "http://site.api.espn.com/apis/site/v2/sports/football/college-football/teams/{}"
url = API_URL_TEMPLATE.format(team_id)
response = requests.get(url)
if response.status_code != 200:
print(f"Failed to fetch data for team ID {team_id}. Status code: {response.status_code}")
return
data = response.json()
team = data.get("team", {})
logos = team.get("logos", [])
if logos:
logo_url = logos[0].get("href")
if logo_url:
try:
img_data = requests.get(logo_url).content
file_path = f"{self.logo_dir}/{team_id}.png"
with open(file_path, "wb") as f:
f.write(img_data)
print(f"Saved logo for {team_id} as {file_path}")
except Exception as e:
print(f"Error downloading logo for {team_id}: {e}")
else:
print(f"No logo URL found for team ID {team_id}")
else:
print(f"No logos found for team ID {team_id}")
def _load_and_resize_logo(self, team_abbrev: str) -> Optional[Image.Image]:
"""Load and resize a team logo, with caching."""
def _load_and_resize_logo(self, team_abbrev: str, team_name: str = None) -> Optional[Image.Image]:
"""Load and resize a team logo, with caching and automatic download if missing."""
if team_abbrev in self._logo_cache:
return self._logo_cache[team_abbrev]
@@ -353,20 +368,22 @@ class BaseNCAAFBManager: # Renamed class
self.logger.debug(f"Logo path: {logo_path}")
try:
# Try to download team logo
# Try to download missing logo first
if not os.path.exists(logo_path):
self.logger.warning(f"Logo not found for {team_abbrev} at {logo_path}. Attempting to download.")
self._download_team_logo(team_abbrev)
# Check to make sure logo was able to be downloaded and saved. If not, create a placeholder.
if not os.path.exists(logo_path):
self.logger.warning(f"Error occured donwloading logo for {team_abbrev} at {logo_path}. Creating 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"Logo not found for {team_abbrev} at {logo_path}. Attempting to download.")
# Try to download the logo from ESPN API
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.")
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}")
logo = Image.open(logo_path)
if logo.mode != 'RGBA':
@@ -775,8 +792,8 @@ class NCAAFBLiveManager(BaseNCAAFBManager): # Renamed class
overlay = Image.new('RGBA', (self.display_width, self.display_height), (0, 0, 0, 0))
draw_overlay = ImageDraw.Draw(overlay) # Draw text elements on overlay first
home_logo = self._load_and_resize_logo(game["home_abbr"])
away_logo = self._load_and_resize_logo(game["away_abbr"])
home_logo = self._load_and_resize_logo(game["home_abbr"], game.get("home_team_name"))
away_logo = self._load_and_resize_logo(game["away_abbr"], game.get("away_team_name"))
if not home_logo or not away_logo:
self.logger.error(f"[NCAAFB] Failed to load logos for live game: {game.get('id')}") # Changed log prefix
@@ -1002,8 +1019,8 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
overlay = Image.new('RGBA', (self.display_width, self.display_height), (0, 0, 0, 0))
draw_overlay = ImageDraw.Draw(overlay)
home_logo = self._load_and_resize_logo(game["home_abbr"])
away_logo = self._load_and_resize_logo(game["away_abbr"])
home_logo = self._load_and_resize_logo(game["home_abbr"], game.get("home_team_name"))
away_logo = self._load_and_resize_logo(game["away_abbr"], game.get("away_team_name"))
if not home_logo or not away_logo:
self.logger.error(f"[NCAAFB Recent] Failed to load logos for game: {game.get('id')}") # Changed log prefix
@@ -1046,29 +1063,72 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
if 'odds' in game and game['odds']:
self._draw_dynamic_odds(draw_overlay, game['odds'], self.display_width, self.display_height)
# Draw records if enabled
if self.show_records:
# Draw records or rankings if enabled
if self.show_records or self.show_ranking:
try:
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
except IOError:
record_font = ImageFont.load_default()
away_record = game.get('away_record', '')
home_record = game.get('home_record', '')
# Get team abbreviations
away_abbr = game.get('away_abbr', '')
home_abbr = game.get('home_abbr', '')
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
if away_record:
away_record_x = 0
self._draw_text_with_outline(draw_overlay, away_record, (away_record_x, record_y), record_font)
# Display away team info
if away_abbr:
if self.show_ranking:
# Show ranking if available
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
away_text = ''
else:
# Show record only if show_records is enabled
if self.show_records:
away_text = game.get('away_record', '')
else:
away_text = ''
if away_text:
away_record_x = 0
self._draw_text_with_outline(draw_overlay, away_text, (away_record_x, record_y), record_font)
if home_record:
home_record_bbox = draw_overlay.textbbox((0,0), home_record, font=record_font)
home_record_width = home_record_bbox[2] - home_record_bbox[0]
home_record_x = self.display_width - home_record_width
self._draw_text_with_outline(draw_overlay, home_record, (home_record_x, record_y), record_font)
# Display home team info
if home_abbr:
if self.show_ranking:
# Show ranking if available
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
home_text = ''
else:
# Show record only if show_records is enabled
if self.show_records:
home_text = game.get('home_record', '')
else:
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._draw_text_with_outline(draw_overlay, home_text, (home_record_x, record_y), record_font)
# Composite and display
main_img = Image.alpha_composite(main_img, overlay)
@@ -1234,8 +1294,8 @@ class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class
overlay = Image.new('RGBA', (self.display_width, self.display_height), (0, 0, 0, 0))
draw_overlay = ImageDraw.Draw(overlay)
home_logo = self._load_and_resize_logo(game["home_abbr"])
away_logo = self._load_and_resize_logo(game["away_abbr"])
home_logo = self._load_and_resize_logo(game["home_abbr"], game.get("home_team_name"))
away_logo = self._load_and_resize_logo(game["away_abbr"], game.get("away_team_name"))
if not home_logo or not away_logo:
self.logger.error(f"[NCAAFB Upcoming] Failed to load logos for game: {game.get('id')}") # Changed log prefix
@@ -1284,29 +1344,72 @@ class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class
if 'odds' in game and game['odds']:
self._draw_dynamic_odds(draw_overlay, game['odds'], self.display_width, self.display_height)
# Draw records if enabled
if self.show_records:
# Draw records or rankings if enabled
if self.show_records or self.show_ranking:
try:
record_font = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6)
except IOError:
record_font = ImageFont.load_default()
away_record = game.get('away_record', '')
home_record = game.get('home_record', '')
# Get team abbreviations
away_abbr = game.get('away_abbr', '')
home_abbr = game.get('home_abbr', '')
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
if away_record:
away_record_x = 0
self._draw_text_with_outline(draw_overlay, away_record, (away_record_x, record_y), record_font)
# Display away team info
if away_abbr:
if self.show_ranking:
# Show ranking if available
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
away_text = ''
else:
# Show record only if show_records is enabled
if self.show_records:
away_text = game.get('away_record', '')
else:
away_text = ''
if away_text:
away_record_x = 0
self._draw_text_with_outline(draw_overlay, away_text, (away_record_x, record_y), record_font)
if home_record:
home_record_bbox = draw_overlay.textbbox((0,0), home_record, font=record_font)
home_record_width = home_record_bbox[2] - home_record_bbox[0]
home_record_x = self.display_width - home_record_width
self._draw_text_with_outline(draw_overlay, home_record, (home_record_x, record_y), record_font)
# Display home team info
if home_abbr:
if self.show_ranking:
# Show ranking if available
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
home_text = ''
else:
# Show record only if show_records is enabled
if self.show_records:
home_text = game.get('home_record', '')
else:
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._draw_text_with_outline(draw_overlay, home_text, (home_record_x, record_y), record_font)
# Composite and display
main_img = Image.alpha_composite(main_img, overlay)

View File

@@ -13,6 +13,14 @@ from src.config_manager import ConfigManager
from src.odds_manager import OddsManager
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
ESPN_NCAAMB_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard"
@@ -35,10 +43,11 @@ class BaseNCAAMBasketballManager:
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager):
self.display_manager = display_manager
self.config_manager = ConfigManager()
# Store reference to config instead of creating new ConfigManager
self.config_manager = None # Not used in this class
self.config = config
self.cache_manager = cache_manager
self.odds_manager = OddsManager(self.cache_manager, self.config)
self.odds_manager = OddsManager(self.cache_manager, None)
self.logger = logging.getLogger(__name__)
self.ncaam_basketball_config = config.get("ncaam_basketball_scoreboard", {})
self.is_enabled = self.ncaam_basketball_config.get("enabled", False)
@@ -55,13 +64,9 @@ class BaseNCAAMBasketballManager:
# Set logging level to INFO to reduce noise
self.logger.setLevel(logging.INFO)
# Get display dimensions from config
display_config = config.get("display", {})
hardware_config = display_config.get("hardware", {})
cols = hardware_config.get("cols", 64)
chain = hardware_config.get("chain_length", 1)
self.display_width = int(cols * chain)
self.display_height = hardware_config.get("rows", 32)
# Get display dimensions from matrix
self.display_width = self.display_manager.matrix.width
self.display_height = self.display_manager.matrix.height
# Cache for loaded logos
self._logo_cache = {}
@@ -99,7 +104,8 @@ class BaseNCAAMBasketballManager:
def _get_timezone(self):
try:
return pytz.timezone(self.config_manager.get_timezone())
timezone_str = self.config.get('timezone', 'UTC')
return pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
return pytz.utc
@@ -328,6 +334,9 @@ class BaseNCAAMBasketballManager:
response.raise_for_status()
data = response.json()
# Increment API counter for sports data
increment_api_counter('sports', 1)
if use_cache:
self.cache_manager.set(cache_key, data)

View File

@@ -29,9 +29,10 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class NewsManager:
def __init__(self, config: Dict[str, Any], display_manager):
def __init__(self, config: Dict[str, Any], display_manager, config_manager=None):
self.config = config
self.config_manager = ConfigManager()
# Use provided config_manager or create a new one if none provided
self.config_manager = config_manager or ConfigManager()
self.display_manager = display_manager
self.news_config = config.get('news_manager', {})
self.last_update = time.time() # Initialize to current time

View File

@@ -37,10 +37,11 @@ class BaseNFLManager: # Renamed class
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager):
self.display_manager = display_manager
self.config_manager = ConfigManager()
# Store reference to config instead of creating new ConfigManager
self.config_manager = None # Not used in this class
self.config = config
self.cache_manager = cache_manager
self.odds_manager = OddsManager(self.cache_manager, self.config)
self.odds_manager = OddsManager(self.cache_manager, None)
self.logger = logging.getLogger(__name__)
self.nfl_config = config.get("nfl_scoreboard", {}) # Changed config key
self.is_enabled = self.nfl_config.get("enabled", False)
@@ -62,12 +63,8 @@ class BaseNFLManager: # Renamed class
self.logger.setLevel(logging.INFO)
display_config = config.get("display", {})
hardware_config = display_config.get("hardware", {})
cols = hardware_config.get("cols", 64)
chain = hardware_config.get("chain_length", 1)
self.display_width = int(cols * chain)
self.display_height = hardware_config.get("rows", 32)
self.display_width = self.display_manager.matrix.width
self.display_height = self.display_manager.matrix.height
self._logo_cache = {}
@@ -87,7 +84,8 @@ class BaseNFLManager: # Renamed class
def _get_timezone(self):
try:
return pytz.timezone(self.config_manager.get_timezone())
timezone_str = self.config.get('timezone', 'UTC')
return pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
return pytz.utc

View File

@@ -42,10 +42,11 @@ class BaseNHLManager:
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager):
self.display_manager = display_manager
self.config_manager = ConfigManager()
# Store reference to config instead of creating new ConfigManager
self.config_manager = None # Not used in this class
self.config = config
self.cache_manager = cache_manager
self.odds_manager = OddsManager(self.cache_manager, self.config)
self.odds_manager = OddsManager(self.cache_manager, None)
self.logger = logging.getLogger(__name__)
self.nhl_config = config.get("nhl_scoreboard", {})
self.is_enabled = self.nhl_config.get("enabled", False)
@@ -63,13 +64,9 @@ class BaseNHLManager:
# Set logging level to DEBUG to see all messages
self.logger.setLevel(logging.DEBUG)
# Get display dimensions from config
display_config = config.get("display", {})
hardware_config = display_config.get("hardware", {})
cols = hardware_config.get("cols", 64)
chain = hardware_config.get("chain_length", 1)
self.display_width = int(cols * chain)
self.display_height = hardware_config.get("rows", 32)
# Get display dimensions from matrix
self.display_width = self.display_manager.matrix.width
self.display_height = self.display_manager.matrix.height
# Cache for loaded logos
self._logo_cache = {}
@@ -107,7 +104,8 @@ class BaseNHLManager:
def _get_timezone(self):
try:
return pytz.timezone(self.config_manager.get_timezone())
timezone_str = self.config.get('timezone', 'UTC')
return pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
return pytz.utc

View File

@@ -1,13 +1,22 @@
import requests
import time
import logging
import requests
import json
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from src.cache_manager import CacheManager
from src.config_manager import ConfigManager
from typing import Optional, List, Dict, Any
import pytz
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:
def __init__(self, cache_manager: CacheManager, config_manager: ConfigManager):
def __init__(self, cache_manager: CacheManager, config_manager=None):
self.cache_manager = cache_manager
self.config_manager = config_manager
self.logger = logging.getLogger(__name__)
@@ -31,6 +40,9 @@ class OddsManager:
response = requests.get(url, timeout=10)
response.raise_for_status()
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)}")
odds_data = self._extract_espn_data(raw_data)

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More