diff --git a/assets/sports/ncaa_fbs_logos/american/American_Athletic_Conference_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/AAC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/American_Athletic_Conference_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/AAC.png diff --git a/assets/sports/ncaa_fbs_logos/acc/Atlantic_Coast_Conference_Acc_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/ACC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/Atlantic_Coast_Conference_Acc_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/ACC.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/AFA.png b/assets/sports/ncaa_fbs_logos/AFA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/AFA.png rename to assets/sports/ncaa_fbs_logos/AFA.png diff --git a/assets/sports/ncaa_fbs_logos/mac/AKR.png b/assets/sports/ncaa_fbs_logos/AKR.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/AKR.png rename to assets/sports/ncaa_fbs_logos/AKR.png diff --git a/assets/sports/ncaa_fbs_logos/sec/ALA.png b/assets/sports/ncaa_fbs_logos/ALA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/ALA.png rename to assets/sports/ncaa_fbs_logos/ALA.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/APP.png b/assets/sports/ncaa_fbs_logos/APP.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/APP.png rename to assets/sports/ncaa_fbs_logos/APP.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/ARIZ.png b/assets/sports/ncaa_fbs_logos/ARIZ.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/ARIZ.png rename to assets/sports/ncaa_fbs_logos/ARIZ.png diff --git a/assets/sports/ncaa_fbs_logos/sec/ARK.png b/assets/sports/ncaa_fbs_logos/ARK.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/ARK.png rename to assets/sports/ncaa_fbs_logos/ARK.png diff --git a/assets/sports/ncaa_fbs_logos/american/ARMY.png b/assets/sports/ncaa_fbs_logos/ARMY.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/ARMY.png rename to assets/sports/ncaa_fbs_logos/ARMY.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/ARST.png b/assets/sports/ncaa_fbs_logos/ARST.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/ARST.png rename to assets/sports/ncaa_fbs_logos/ARST.png diff --git a/assets/sports/ncaa_fbs_logos/sec/AUB.png b/assets/sports/ncaa_fbs_logos/AUB.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/AUB.png rename to assets/sports/ncaa_fbs_logos/AUB.png diff --git a/assets/sports/ncaa_fbs_logos/mac/BALL.png b/assets/sports/ncaa_fbs_logos/BALL.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/BALL.png rename to assets/sports/ncaa_fbs_logos/BALL.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/BAY.png b/assets/sports/ncaa_fbs_logos/BAY.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/BAY.png rename to assets/sports/ncaa_fbs_logos/BAY.png diff --git a/assets/sports/ncaa_fbs_logos/acc/BC.png b/assets/sports/ncaa_fbs_logos/BC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/BC.png rename to assets/sports/ncaa_fbs_logos/BC.png diff --git a/assets/sports/ncaa_fbs_logos/mac/BGSU.png b/assets/sports/ncaa_fbs_logos/BGSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/BGSU.png rename to assets/sports/ncaa_fbs_logos/BGSU.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/Boise_State_Broncos_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/BSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/Boise_State_Broncos_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/BSU.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/BUFF.png b/assets/sports/ncaa_fbs_logos/BUFF.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/BUFF.png rename to assets/sports/ncaa_fbs_logos/BUFF.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/BYU.png b/assets/sports/ncaa_fbs_logos/BYU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/BYU.png rename to assets/sports/ncaa_fbs_logos/BYU.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/Big_12_Conference_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Big_12_Conference_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/Big_12_Conference_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Big_12_Conference_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/Big_Ten_Conference_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Big_Ten_Conference_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/Big_Ten_Conference_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Big_Ten_Conference_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/acc/California_Golden_Bears_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/CAL.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/California_Golden_Bears_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/CAL.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/CCU.png b/assets/sports/ncaa_fbs_logos/CCU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/CCU.png rename to assets/sports/ncaa_fbs_logos/CCU.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/CIN.png b/assets/sports/ncaa_fbs_logos/CIN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/CIN.png rename to assets/sports/ncaa_fbs_logos/CIN.png diff --git a/assets/sports/ncaa_fbs_logos/acc/CLEM.png b/assets/sports/ncaa_fbs_logos/CLEM.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/CLEM.png rename to assets/sports/ncaa_fbs_logos/CLEM.png diff --git a/assets/sports/ncaa_fbs_logos/american/CLT.png b/assets/sports/ncaa_fbs_logos/CLT.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/CLT.png rename to assets/sports/ncaa_fbs_logos/CLT.png diff --git a/assets/sports/ncaa_fbs_logos/mac/CMU.png b/assets/sports/ncaa_fbs_logos/CMU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/CMU.png rename to assets/sports/ncaa_fbs_logos/CMU.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/COLO.png b/assets/sports/ncaa_fbs_logos/COLO.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/COLO.png rename to assets/sports/ncaa_fbs_logos/COLO.png diff --git a/assets/sports/ncaa_fbs_logos/fbs-independents/CONN.png b/assets/sports/ncaa_fbs_logos/CONN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/fbs-independents/CONN.png rename to assets/sports/ncaa_fbs_logos/CONN.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/Conference_Usa_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Conference_Usa_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/Conference_Usa_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Conference_Usa_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/acc/DUKE.png b/assets/sports/ncaa_fbs_logos/DUKE.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/DUKE.png rename to assets/sports/ncaa_fbs_logos/DUKE.png diff --git a/assets/sports/ncaa_fbs_logos/fbs-independents/Division_I_Fbs_Independents.png b/assets/sports/ncaa_fbs_logos/Division_I_Fbs_Independents.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/fbs-independents/Division_I_Fbs_Independents.png rename to assets/sports/ncaa_fbs_logos/Division_I_Fbs_Independents.png diff --git a/assets/sports/ncaa_fbs_logos/american/ECU.png b/assets/sports/ncaa_fbs_logos/ECU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/ECU.png rename to assets/sports/ncaa_fbs_logos/ECU.png diff --git a/assets/sports/ncaa_fbs_logos/mac/EMU.png b/assets/sports/ncaa_fbs_logos/EMU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/EMU.png rename to assets/sports/ncaa_fbs_logos/EMU.png diff --git a/assets/sports/ncaa_fbs_logos/american/FAU.png b/assets/sports/ncaa_fbs_logos/FAU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/FAU.png rename to assets/sports/ncaa_fbs_logos/FAU.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/Fiu_Panthers.png b/assets/sports/ncaa_fbs_logos/FIU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/Fiu_Panthers.png rename to assets/sports/ncaa_fbs_logos/FIU.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/FRES.png b/assets/sports/ncaa_fbs_logos/FRES.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/FRES.png rename to assets/sports/ncaa_fbs_logos/FRES.png diff --git a/assets/sports/ncaa_fbs_logos/acc/FLA.png b/assets/sports/ncaa_fbs_logos/FSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/FLA.png rename to assets/sports/ncaa_fbs_logos/FSU.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/GASO.png b/assets/sports/ncaa_fbs_logos/GASO.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/GASO.png rename to assets/sports/ncaa_fbs_logos/GASO.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/GAST.png b/assets/sports/ncaa_fbs_logos/GAST.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/GAST.png rename to assets/sports/ncaa_fbs_logos/GAST.png diff --git a/assets/sports/ncaa_fbs_logos/acc/GT.png b/assets/sports/ncaa_fbs_logos/GT.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/GT.png rename to assets/sports/ncaa_fbs_logos/GT.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/HOU.png b/assets/sports/ncaa_fbs_logos/HOU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/HOU.png rename to assets/sports/ncaa_fbs_logos/HOU.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/ILL.png b/assets/sports/ncaa_fbs_logos/ILL.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/ILL.png rename to assets/sports/ncaa_fbs_logos/ILL.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/IOWA.png b/assets/sports/ncaa_fbs_logos/IOWA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/IOWA.png rename to assets/sports/ncaa_fbs_logos/IOWA.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/IOWA.png b/assets/sports/ncaa_fbs_logos/ISU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/IOWA.png rename to assets/sports/ncaa_fbs_logos/ISU.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/IU.png b/assets/sports/ncaa_fbs_logos/IU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/IU.png rename to assets/sports/ncaa_fbs_logos/IU.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/DUKE.png b/assets/sports/ncaa_fbs_logos/JMU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/DUKE.png rename to assets/sports/ncaa_fbs_logos/JMU.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/JVST.png b/assets/sports/ncaa_fbs_logos/JVST.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/JVST.png rename to assets/sports/ncaa_fbs_logos/JVST.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/KENN.png b/assets/sports/ncaa_fbs_logos/KENN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/KENN.png rename to assets/sports/ncaa_fbs_logos/KENN.png diff --git a/assets/sports/ncaa_fbs_logos/mac/KENT.png b/assets/sports/ncaa_fbs_logos/KENT.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/KENT.png rename to assets/sports/ncaa_fbs_logos/KENT.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/KSU.png b/assets/sports/ncaa_fbs_logos/KSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/KSU.png rename to assets/sports/ncaa_fbs_logos/KSU.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/KU.png b/assets/sports/ncaa_fbs_logos/KU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/KU.png rename to assets/sports/ncaa_fbs_logos/KU.png diff --git a/assets/sports/ncaa_fbs_logos/american/LAC.png b/assets/sports/ncaa_fbs_logos/LAC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/LAC.png rename to assets/sports/ncaa_fbs_logos/LAC.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/LAF.png b/assets/sports/ncaa_fbs_logos/LAF.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/LAF.png rename to assets/sports/ncaa_fbs_logos/LAF.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/LIB.png b/assets/sports/ncaa_fbs_logos/LIB.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/LIB.png rename to assets/sports/ncaa_fbs_logos/LIB.png diff --git a/assets/sports/ncaa_fbs_logos/acc/LOU.png b/assets/sports/ncaa_fbs_logos/LOU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/LOU.png rename to assets/sports/ncaa_fbs_logos/LOU.png diff --git a/assets/sports/ncaa_fbs_logos/sec/LSU.png b/assets/sports/ncaa_fbs_logos/LSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/LSU.png rename to assets/sports/ncaa_fbs_logos/LSU.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/LT.png b/assets/sports/ncaa_fbs_logos/LT.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/LT.png rename to assets/sports/ncaa_fbs_logos/LT.png diff --git a/assets/sports/ncaa_fbs_logos/acc/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/Loodibee_Web_X2_White.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/Loodibee_Web_X2_White.png rename to assets/sports/ncaa_fbs_logos/Loodibee_Web_X2_White.png diff --git a/assets/sports/ncaa_fbs_logos/fbs-independents/MASS.png b/assets/sports/ncaa_fbs_logos/MASS.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/fbs-independents/MASS.png rename to assets/sports/ncaa_fbs_logos/MASS.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/MD.png b/assets/sports/ncaa_fbs_logos/MD.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/MD.png rename to assets/sports/ncaa_fbs_logos/MD.png diff --git a/assets/sports/ncaa_fbs_logos/american/MEM.png b/assets/sports/ncaa_fbs_logos/MEM.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/MEM.png rename to assets/sports/ncaa_fbs_logos/MEM.png diff --git a/assets/sports/ncaa_fbs_logos/acc/MIAMI.png b/assets/sports/ncaa_fbs_logos/MIAMI.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/MIAMI.png rename to assets/sports/ncaa_fbs_logos/MIAMI.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/MICH.png b/assets/sports/ncaa_fbs_logos/MICH.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/MICH.png rename to assets/sports/ncaa_fbs_logos/MICH.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/MINN.png b/assets/sports/ncaa_fbs_logos/MINN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/MINN.png rename to assets/sports/ncaa_fbs_logos/MINN.png diff --git a/assets/sports/ncaa_fbs_logos/sec/MISS.png b/assets/sports/ncaa_fbs_logos/MISS.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/MISS.png rename to assets/sports/ncaa_fbs_logos/MISS.png diff --git a/assets/sports/ncaa_fbs_logos/sec/MIZ.png b/assets/sports/ncaa_fbs_logos/MIZ.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/MIZ.png rename to assets/sports/ncaa_fbs_logos/MIZ.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/MRSH.png b/assets/sports/ncaa_fbs_logos/MRSH.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/MRSH.png rename to assets/sports/ncaa_fbs_logos/MRSH.png diff --git a/assets/sports/ncaa_fbs_logos/sec/MSST.png b/assets/sports/ncaa_fbs_logos/MSST.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/MSST.png rename to assets/sports/ncaa_fbs_logos/MSST.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/MTSU.png b/assets/sports/ncaa_fbs_logos/MTSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/MTSU.png rename to assets/sports/ncaa_fbs_logos/MTSU.png diff --git a/assets/sports/ncaa_fbs_logos/mac/MIAMI.png b/assets/sports/ncaa_fbs_logos/MU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/MIAMI.png rename to assets/sports/ncaa_fbs_logos/MU.png diff --git a/assets/sports/ncaa_fbs_logos/mac/Mid_American_Conference_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Mid_American_Conference_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/Mid_American_Conference_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Mid_American_Conference_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/Mountain_West_Conference_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Mountain_West_Conference_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/Mountain_West_Conference_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Mountain_West_Conference_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/american/NAVY.png b/assets/sports/ncaa_fbs_logos/NAVY.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/NAVY.png rename to assets/sports/ncaa_fbs_logos/NAVY.png diff --git a/assets/sports/ncaa_fbs_logos/acc/ND.png b/assets/sports/ncaa_fbs_logos/ND.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/ND.png rename to assets/sports/ncaa_fbs_logos/ND.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/Nebraska_Cornhuskers_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/NEB.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/Nebraska_Cornhuskers_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/NEB.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/NEV.png b/assets/sports/ncaa_fbs_logos/NEV.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/NEV.png rename to assets/sports/ncaa_fbs_logos/NEV.png diff --git a/assets/sports/ncaa_fbs_logos/mac/ILL.png b/assets/sports/ncaa_fbs_logos/NIU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/ILL.png rename to assets/sports/ncaa_fbs_logos/NIU.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/NMSU.png b/assets/sports/ncaa_fbs_logos/NMSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/NMSU.png rename to assets/sports/ncaa_fbs_logos/NMSU.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/NW.png b/assets/sports/ncaa_fbs_logos/NW.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/NW.png rename to assets/sports/ncaa_fbs_logos/NW.png diff --git a/assets/sports/ncaa_fbs_logos/mac/BUFF.png b/assets/sports/ncaa_fbs_logos/NYBU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/BUFF.png rename to assets/sports/ncaa_fbs_logos/NYBU.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/ODU.png b/assets/sports/ncaa_fbs_logos/ODU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/ODU.png rename to assets/sports/ncaa_fbs_logos/ODU.png diff --git a/assets/sports/ncaa_fbs_logos/mac/OHIO.png b/assets/sports/ncaa_fbs_logos/OHIO.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/OHIO.png rename to assets/sports/ncaa_fbs_logos/OHIO.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/OKST.png b/assets/sports/ncaa_fbs_logos/OKST.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/OKST.png rename to assets/sports/ncaa_fbs_logos/OKST.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/Oregon_Ducks_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/ORE.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/Oregon_Ducks_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/ORE.png diff --git a/assets/sports/ncaa_fbs_logos/pac-12/ORST.png b/assets/sports/ncaa_fbs_logos/ORST.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/pac-12/ORST.png rename to assets/sports/ncaa_fbs_logos/ORST.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/OHIO.png b/assets/sports/ncaa_fbs_logos/OSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/OHIO.png rename to assets/sports/ncaa_fbs_logos/OSU.png diff --git a/assets/sports/ncaa_fbs_logos/sec/OU.png b/assets/sports/ncaa_fbs_logos/OU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/OU.png rename to assets/sports/ncaa_fbs_logos/OU.png diff --git a/assets/sports/ncaa_fbs_logos/acc/Pitt_Panthers_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/PITT.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/Pitt_Panthers_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/PITT.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/PSU.png b/assets/sports/ncaa_fbs_logos/PSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/PSU.png rename to assets/sports/ncaa_fbs_logos/PSU.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/Purdue_Boilermakers_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/PUR.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/Purdue_Boilermakers_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/PUR.png diff --git a/assets/sports/ncaa_fbs_logos/pac-12/Pac_12_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Pac_12_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/pac-12/Pac_12_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Pac_12_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/american/RICE.png b/assets/sports/ncaa_fbs_logos/RICE.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/RICE.png rename to assets/sports/ncaa_fbs_logos/RICE.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/RUTG.png b/assets/sports/ncaa_fbs_logos/RUTG.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/RUTG.png rename to assets/sports/ncaa_fbs_logos/RUTG.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/ALA.png b/assets/sports/ncaa_fbs_logos/SA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/ALA.png rename to assets/sports/ncaa_fbs_logos/SA.png diff --git a/assets/sports/ncaa_fbs_logos/sec/SC.png b/assets/sports/ncaa_fbs_logos/SC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/SC.png rename to assets/sports/ncaa_fbs_logos/SC.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/SDSU.png b/assets/sports/ncaa_fbs_logos/SDSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/SDSU.png rename to assets/sports/ncaa_fbs_logos/SDSU.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/HOU.png b/assets/sports/ncaa_fbs_logos/SHSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/HOU.png rename to assets/sports/ncaa_fbs_logos/SHSU.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/San_Jose_State_Spartans_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/SJSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/San_Jose_State_Spartans_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/SJSU.png diff --git a/assets/sports/ncaa_fbs_logos/acc/Smu_Mustang_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/SMU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/Smu_Mustang_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/SMU.png diff --git a/assets/sports/ncaa_fbs_logos/acc/STAN.png b/assets/sports/ncaa_fbs_logos/STAN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/STAN.png rename to assets/sports/ncaa_fbs_logos/STAN.png diff --git a/assets/sports/ncaa_fbs_logos/acc/SYR.png b/assets/sports/ncaa_fbs_logos/SYR.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/SYR.png rename to assets/sports/ncaa_fbs_logos/SYR.png diff --git a/assets/sports/ncaa_fbs_logos/sec/Southeastern_Conference_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Southeastern_Conference_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/Southeastern_Conference_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Southeastern_Conference_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/Sun_Belt_Conference_2020_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/Sun_Belt_Conference_2020_Logo_300X300.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/Sun_Belt_Conference_2020_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/Sun_Belt_Conference_2020_Logo_300X300.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/Tcu_Horned_Frogs_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/TCU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/Tcu_Horned_Frogs_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/TCU.png diff --git a/assets/sports/ncaa_fbs_logos/american/TEM.png b/assets/sports/ncaa_fbs_logos/TEM.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/TEM.png rename to assets/sports/ncaa_fbs_logos/TEM.png diff --git a/assets/sports/ncaa_fbs_logos/sec/TENN.png b/assets/sports/ncaa_fbs_logos/TENN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/TENN.png rename to assets/sports/ncaa_fbs_logos/TENN.png diff --git a/assets/sports/ncaa_fbs_logos/american/TEX.png b/assets/sports/ncaa_fbs_logos/TEX.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/TEX.png rename to assets/sports/ncaa_fbs_logos/TEX.png diff --git a/assets/sports/ncaa_fbs_logos/american/TLSA.png b/assets/sports/ncaa_fbs_logos/TLSA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/TLSA.png rename to assets/sports/ncaa_fbs_logos/TLSA.png diff --git a/assets/sports/ncaa_fbs_logos/mac/TOL.png b/assets/sports/ncaa_fbs_logos/TOL.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/TOL.png rename to assets/sports/ncaa_fbs_logos/TOL.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/Troy_Trojans_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/TROY.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/Troy_Trojans_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/TROY.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/TEX.png b/assets/sports/ncaa_fbs_logos/TTU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/TEX.png rename to assets/sports/ncaa_fbs_logos/TTU.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/TEX.png b/assets/sports/ncaa_fbs_logos/TXSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/TEX.png rename to assets/sports/ncaa_fbs_logos/TXSU.png diff --git a/assets/sports/ncaa_fbs_logos/american/UAB.png b/assets/sports/ncaa_fbs_logos/UAB.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/UAB.png rename to assets/sports/ncaa_fbs_logos/UAB.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/UCF.png b/assets/sports/ncaa_fbs_logos/UCF.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/UCF.png rename to assets/sports/ncaa_fbs_logos/UCF.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/UCLA.png b/assets/sports/ncaa_fbs_logos/UCLA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/UCLA.png rename to assets/sports/ncaa_fbs_logos/UCLA.png diff --git a/assets/sports/ncaa_fbs_logos/sec/FLA.png b/assets/sports/ncaa_fbs_logos/UF.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/FLA.png rename to assets/sports/ncaa_fbs_logos/UF.png diff --git a/assets/sports/ncaa_fbs_logos/sec/UGA.png b/assets/sports/ncaa_fbs_logos/UGA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/UGA.png rename to assets/sports/ncaa_fbs_logos/UGA.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/Hawaii_Rainbow_Warriors_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/UH.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/Hawaii_Rainbow_Warriors_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/UH.png diff --git a/assets/sports/ncaa_fbs_logos/sec/UK.png b/assets/sports/ncaa_fbs_logos/UK.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/UK.png rename to assets/sports/ncaa_fbs_logos/UK.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/UL.png b/assets/sports/ncaa_fbs_logos/UL.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/UL.png rename to assets/sports/ncaa_fbs_logos/UL.png diff --git a/assets/sports/ncaa_fbs_logos/acc/UNC.png b/assets/sports/ncaa_fbs_logos/UNC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/UNC.png rename to assets/sports/ncaa_fbs_logos/UNC.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/UNLV.png b/assets/sports/ncaa_fbs_logos/UNLV.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/UNLV.png rename to assets/sports/ncaa_fbs_logos/UNLV.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/UNM.png b/assets/sports/ncaa_fbs_logos/UNM.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/UNM.png rename to assets/sports/ncaa_fbs_logos/UNM.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/USC.png b/assets/sports/ncaa_fbs_logos/USC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/USC.png rename to assets/sports/ncaa_fbs_logos/USC.png diff --git a/assets/sports/ncaa_fbs_logos/american/FLA.png b/assets/sports/ncaa_fbs_logos/USF.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/FLA.png rename to assets/sports/ncaa_fbs_logos/USF.png diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/Southern_Miss_Golden_Eagles_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/USM.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sun-belt/Southern_Miss_Golden_Eagles_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/USM.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/USU.png b/assets/sports/ncaa_fbs_logos/USU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/USU.png rename to assets/sports/ncaa_fbs_logos/USU.png diff --git a/assets/sports/ncaa_fbs_logos/sec/TEX.png b/assets/sports/ncaa_fbs_logos/UT.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/TEX.png rename to assets/sports/ncaa_fbs_logos/UT.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/UTAH.png b/assets/sports/ncaa_fbs_logos/UTAH.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/UTAH.png rename to assets/sports/ncaa_fbs_logos/UTAH.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/Utep_Miners.png b/assets/sports/ncaa_fbs_logos/UTEP.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/Utep_Miners.png rename to assets/sports/ncaa_fbs_logos/UTEP.png diff --git a/assets/sports/ncaa_fbs_logos/acc/UVA.png b/assets/sports/ncaa_fbs_logos/UVA.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/UVA.png rename to assets/sports/ncaa_fbs_logos/UVA.png diff --git a/assets/sports/ncaa_fbs_logos/sec/VAN.png b/assets/sports/ncaa_fbs_logos/VAN.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/sec/VAN.png rename to assets/sports/ncaa_fbs_logos/VAN.png diff --git a/assets/sports/ncaa_fbs_logos/acc/WAKE.png b/assets/sports/ncaa_fbs_logos/WAKE.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/acc/WAKE.png rename to assets/sports/ncaa_fbs_logos/WAKE.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/WASH.png b/assets/sports/ncaa_fbs_logos/WASH.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/WASH.png rename to assets/sports/ncaa_fbs_logos/WASH.png diff --git a/assets/sports/ncaa_fbs_logos/big-ten/WISC.png b/assets/sports/ncaa_fbs_logos/WISC.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-ten/WISC.png rename to assets/sports/ncaa_fbs_logos/WISC.png diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/UK.png b/assets/sports/ncaa_fbs_logos/WKU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/conference-usa/UK.png rename to assets/sports/ncaa_fbs_logos/WKU.png diff --git a/assets/sports/ncaa_fbs_logos/mac/MICH.png b/assets/sports/ncaa_fbs_logos/WMU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mac/MICH.png rename to assets/sports/ncaa_fbs_logos/WMU.png diff --git a/assets/sports/ncaa_fbs_logos/pac-12/WASH.png b/assets/sports/ncaa_fbs_logos/WSU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/pac-12/WASH.png rename to assets/sports/ncaa_fbs_logos/WSU.png diff --git a/assets/sports/ncaa_fbs_logos/big-12/UVA.png b/assets/sports/ncaa_fbs_logos/WVU.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/big-12/UVA.png rename to assets/sports/ncaa_fbs_logos/WVU.png diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/Wyoming_Cowboys_Logo_300X300.png b/assets/sports/ncaa_fbs_logos/WYO.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/mountain-west/Wyoming_Cowboys_Logo_300X300.png rename to assets/sports/ncaa_fbs_logos/WYO.png diff --git a/assets/sports/ncaa_fbs_logos/american/Wichita_State_Shockers.png b/assets/sports/ncaa_fbs_logos/Wichita_State_Shockers.png similarity index 100% rename from assets/sports/ncaa_fbs_logos/american/Wichita_State_Shockers.png rename to assets/sports/ncaa_fbs_logos/Wichita_State_Shockers.png diff --git a/assets/sports/ncaa_fbs_logos/american/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/american/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/american/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/big-12/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/big-12/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/big-12/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/big-ten/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/big-ten/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/big-ten/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/conference-usa/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/conference-usa/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/conference-usa/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/fbs-independents/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/fbs-independents/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/fbs-independents/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/fbs-independents/ND.png b/assets/sports/ncaa_fbs_logos/fbs-independents/ND.png deleted file mode 100644 index 97bd8fcc..00000000 Binary files a/assets/sports/ncaa_fbs_logos/fbs-independents/ND.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/mac/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/mac/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/mac/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/mountain-west/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/mountain-west/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/mountain-west/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/pac-12/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/pac-12/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/pac-12/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/sec/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/sec/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/sec/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/assets/sports/ncaa_fbs_logos/sun-belt/Loodibee_Web_X2_White.png b/assets/sports/ncaa_fbs_logos/sun-belt/Loodibee_Web_X2_White.png deleted file mode 100644 index fa38d0be..00000000 Binary files a/assets/sports/ncaa_fbs_logos/sun-belt/Loodibee_Web_X2_White.png and /dev/null differ diff --git a/config/config.json b/config/config.json index cad40ba0..267457dc 100644 --- a/config/config.json +++ b/config/config.json @@ -41,6 +41,9 @@ "nfl_live": 30, "nfl_recent": 15, "nfl_upcoming": 15, + "ncaa_fb_live": 30, + "ncaa_fb_recent": 15, + "ncaa_fb_upcoming": 15, "calendar": 30, "youtube": 20, "mlb_live": 30, @@ -147,6 +150,24 @@ "recent_game_duration": 30, "upcoming_game_duration": 30 }, + "ncaa_fb_scoreboard": { + "enabled": false, + "test_mode": false, + "update_interval_seconds": 3600, + "live_update_interval": 15, + "past_fetch_days": 7, + "future_fetch_days": 7, + "favorite_teams": [], + "logo_dir": "assets/sports/ncaa_fbs_logos", + "display_modes": { + "ncaa_fb_live": true, + "ncaa_fb_recent": true, + "ncaa_fb_upcoming": true + }, + "live_game_duration": 30, + "recent_game_duration": 15, + "upcoming_game_duration": 15 + }, "youtube": { "enabled": true, "update_interval": 3600 diff --git a/src/display_controller.py b/src/display_controller.py index 80ab84d7..7a8a24fa 100644 --- a/src/display_controller.py +++ b/src/display_controller.py @@ -22,6 +22,7 @@ from src.nba_managers import NBALiveManager, NBARecentManager, NBAUpcomingManage from src.mlb_manager import MLBLiveManager, MLBRecentManager, MLBUpcomingManager from src.soccer_managers import SoccerLiveManager, SoccerRecentManager, SoccerUpcomingManager from src.nfl_managers import NFLLiveManager, NFLRecentManager, NFLUpcomingManager +from src.ncaa_fb_managers import NCAAFBLiveManager, NCAAFBRecentManager, NCAAFBUpcomingManager from src.youtube_display import YouTubeDisplay from src.calendar_manager import CalendarManager from src.text_display import TextDisplay @@ -130,6 +131,21 @@ class DisplayController: self.nfl_upcoming = None logger.info("NFL managers initialized in %.3f seconds", time.time() - nfl_time) + # Initialize NCAA FB managers if enabled + ncaa_fb_time = time.time() + ncaa_fb_enabled = self.config.get('ncaa_fb_scoreboard', {}).get('enabled', False) + ncaa_fb_display_modes = self.config.get('ncaa_fb_scoreboard', {}).get('display_modes', {}) + + if ncaa_fb_enabled: + self.ncaa_fb_live = NCAAFBLiveManager(self.config, self.display_manager) if ncaa_fb_display_modes.get('ncaa_fb_live', True) else None + self.ncaa_fb_recent = NCAAFBRecentManager(self.config, self.display_manager) if ncaa_fb_display_modes.get('ncaa_fb_recent', True) else None + self.ncaa_fb_upcoming = NCAAFBUpcomingManager(self.config, self.display_manager) if ncaa_fb_display_modes.get('ncaa_fb_upcoming', True) else None + else: + self.ncaa_fb_live = None + self.ncaa_fb_recent = None + self.ncaa_fb_upcoming = None + logger.info("NCAA FB managers initialized in %.3f seconds", time.time() - ncaa_fb_time) + # Track MLB rotation state self.mlb_current_team_index = 0 self.mlb_showing_recent = True @@ -176,6 +192,12 @@ class DisplayController: if self.nfl_upcoming: self.available_modes.append('nfl_upcoming') # nfl_live is handled separately + # Add NCAA FB display modes if enabled + if ncaa_fb_enabled: + if self.ncaa_fb_recent: self.available_modes.append('ncaa_fb_recent') + if self.ncaa_fb_upcoming: self.available_modes.append('ncaa_fb_upcoming') + # ncaa_fb_live is handled separately + # Set initial display to first available mode (clock) self.current_mode_index = 0 self.current_display_mode = self.available_modes[0] if self.available_modes else 'none' @@ -205,6 +227,12 @@ class DisplayController: self.nfl_favorite_teams = self.config.get('nfl_scoreboard', {}).get('favorite_teams', []) self.in_nfl_rotation = False + # Add NCAA FB rotation state + self.ncaa_fb_current_team_index = 0 + self.ncaa_fb_showing_recent = True + self.ncaa_fb_favorite_teams = self.config.get('ncaa_fb_scoreboard', {}).get('favorite_teams', []) + self.in_ncaa_fb_rotation = False + # Update display durations to include all modes self.display_durations = self.config['display'].get('display_durations', {}) # Add defaults for soccer if missing @@ -231,7 +259,10 @@ class DisplayController: 'soccer_upcoming': 20, 'nfl_live': 30, # Added NFL durations 'nfl_recent': 30, - 'nfl_upcoming': 30 + 'nfl_upcoming': 30, + 'ncaa_fb_live': 30, # Added NCAA FB durations + 'ncaa_fb_recent': 15, + 'ncaa_fb_upcoming': 15 } # Merge loaded durations with defaults for key, value in default_durations.items(): @@ -245,6 +276,7 @@ class DisplayController: logger.info(f"MLB Favorite teams: {self.mlb_favorite_teams}") logger.info(f"Soccer Favorite teams: {self.soccer_favorite_teams}") # Log Soccer teams logger.info(f"NFL Favorite teams: {self.nfl_favorite_teams}") # Log NFL teams + logger.info(f"NCAA FB Favorite teams: {self.ncaa_fb_favorite_teams}") # Log NCAA FB teams # Removed redundant NHL/MLB init time logs def get_current_duration(self) -> int: @@ -296,6 +328,11 @@ class DisplayController: if self.nfl_recent: self.nfl_recent.update() if self.nfl_upcoming: self.nfl_upcoming.update() + # Update NCAA FB managers + if self.ncaa_fb_live: self.ncaa_fb_live.update() + if self.ncaa_fb_recent: self.ncaa_fb_recent.update() + if self.ncaa_fb_upcoming: self.ncaa_fb_upcoming.update() + def _check_live_games(self) -> tuple[bool, str]: """ Check if there are any live games available. @@ -308,7 +345,8 @@ class DisplayController: return True, 'soccer' if self.nfl_live and self.nfl_live.live_games: - return True, 'nfl' + logger.debug("NFL live games available") + return True, 'nfl_live' if self.nhl_live and self.nhl_live.live_games: return True, 'nhl' @@ -319,6 +357,15 @@ class DisplayController: if self.mlb_live and self.mlb_live.live_games: return True, 'mlb' + if 'ncaa_fb_scoreboard' in self.config and self.config['ncaa_fb_scoreboard'].get('enabled', False): + if self.ncaa_fb_live and self.ncaa_fb_live.live_games: + logger.debug("NCAA FB live games available") + return True, 'ncaa_fb_live' + # Add more sports checks here (e.g., MLB, Soccer) + if 'mlb' in self.config and self.config['mlb'].get('enabled', False): + if self.mlb_live and self.mlb_live.live_games: + return True, 'mlb_live' + return False, None def _get_team_games(self, team: str, sport: str = 'nhl', is_recent: bool = True) -> bool: @@ -353,6 +400,15 @@ class DisplayController: manager_recent = self.soccer_recent manager_upcoming = self.soccer_upcoming games_list_attr = 'games_list' if is_recent else 'upcoming_games' # Soccer uses games_list/upcoming_games + elif sport == 'nfl': + manager_recent = self.nfl_recent + manager_upcoming = self.nfl_upcoming + elif sport == 'ncaa_fb': # Add NCAA FB case + manager_recent = self.ncaa_fb_recent + manager_upcoming = self.ncaa_fb_upcoming + else: + logger.warning(f"Unsupported sport '{sport}' for team game check") + return False manager = manager_recent if is_recent else manager_upcoming @@ -390,6 +446,14 @@ class DisplayController: favorite_teams = self.soccer_favorite_teams manager_recent = self.soccer_recent manager_upcoming = self.soccer_upcoming + elif sport == 'nfl': + favorite_teams = self.nfl_favorite_teams + manager_recent = self.nfl_recent + manager_upcoming = self.nfl_upcoming + elif sport == 'ncaa_fb': # Add NCAA FB case + favorite_teams = self.ncaa_fb_favorite_teams + manager_recent = self.ncaa_fb_recent + manager_upcoming = self.ncaa_fb_upcoming return bool(favorite_teams and (manager_recent or manager_upcoming)) @@ -427,6 +491,34 @@ class DisplayController: self.soccer_current_team_index = (self.soccer_current_team_index + 1) % len(self.soccer_favorite_teams) self.soccer_showing_recent = True # Reset to recent for the new team # Maybe try finding game for the *new* team immediately? Optional. + elif sport == 'nfl': + if not self.nfl_favorite_teams: return + current_team = self.nfl_favorite_teams[self.nfl_current_team_index] + # Try to find games for current team (recent first) + found_games = self._get_team_games(current_team, 'nfl', self.nfl_showing_recent) + if not found_games: + # Try opposite type (upcoming/recent) + self.nfl_showing_recent = not self.nfl_showing_recent + found_games = self._get_team_games(current_team, 'nfl', self.nfl_showing_recent) + + if not found_games: + # Move to next team if no games found for current one + self.nfl_current_team_index = (self.nfl_current_team_index + 1) % len(self.nfl_favorite_teams) + self.nfl_showing_recent = True # Reset to recent for the new team + elif sport == 'ncaa_fb': # Add NCAA FB case + if not self.ncaa_fb_favorite_teams: return + current_team = self.ncaa_fb_favorite_teams[self.ncaa_fb_current_team_index] + # Try to find games for current team (recent first) + found_games = self._get_team_games(current_team, 'ncaa_fb', self.ncaa_fb_showing_recent) + if not found_games: + # Try opposite type (upcoming/recent) + self.ncaa_fb_showing_recent = not self.ncaa_fb_showing_recent + found_games = self._get_team_games(current_team, 'ncaa_fb', self.ncaa_fb_showing_recent) + + if not found_games: + # Move to next team if no games found for current one + self.ncaa_fb_current_team_index = (self.ncaa_fb_current_team_index + 1) % len(self.ncaa_fb_favorite_teams) + self.ncaa_fb_showing_recent = True # Reset to recent for the new team def run(self): """Run the display controller, switching between displays.""" @@ -636,6 +728,13 @@ class DisplayController: self.text_display.display() # Assumes text handles its own drawing display_updated = True + elif self.current_display_mode == 'ncaa_fb_recent' and self.ncaa_fb_recent: + self.ncaa_fb_recent.display(force_clear=self.force_clear) + display_updated = True + elif self.current_display_mode == 'ncaa_fb_upcoming' and self.ncaa_fb_upcoming: + self.ncaa_fb_upcoming.display(force_clear=self.force_clear) + display_updated = True + # Reset force_clear only if a display method was actually called if display_updated: self.force_clear = False diff --git a/src/ncaa_fb_managers.py b/src/ncaa_fb_managers.py new file mode 100644 index 00000000..085542ee --- /dev/null +++ b/src/ncaa_fb_managers.py @@ -0,0 +1,1039 @@ +import os +import time +import logging +import requests +import json +from typing import Dict, Any, Optional, List +from PIL import Image, ImageDraw, ImageFont +from pathlib import Path +from datetime import datetime, timedelta, timezone +from src.display_manager import DisplayManager +from src.cache_manager import CacheManager # Keep CacheManager import + +# Constants +ESPN_NCAAFB_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard" # Changed URL for NCAA FB + +# Configure logging to match main configuration +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s.%(msecs)03d - %(levelname)s:%(name)s:%(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) + +# Re-add CacheManager definition temporarily until it's confirmed where it lives +class CacheManager: + """Manages caching of ESPN API responses.""" + _instance = None + _cache = {} + _cache_timestamps = {} + + def __new__(cls): + if cls._instance is None: + cls._instance = super(CacheManager, cls).__new__(cls) + return cls._instance + + @classmethod + def get(cls, key: str, max_age: int = 60) -> Optional[Dict]: + """ + Get data from cache if it exists and is not stale. + Args: + key: Cache key (usually the date string) + max_age: Maximum age of cached data in seconds + Returns: + Cached data if valid, None if missing or stale + """ + if key not in cls._cache: + return None + + timestamp = cls._cache_timestamps.get(key, 0) + if time.time() - timestamp > max_age: + # Data is stale, remove it + del cls._cache[key] + del cls._cache_timestamps[key] + return None + + return cls._cache[key] + + @classmethod + def set(cls, key: str, data: Dict) -> None: + """ + Store data in cache with current timestamp. + Args: + key: Cache key (usually the date string) + data: Data to cache + """ + cls._cache[key] = data + cls._cache_timestamps[key] = time.time() + + @classmethod + def clear(cls) -> None: + """Clear all cached data.""" + cls._cache.clear() + cls._cache_timestamps.clear() + + +class BaseNCAAFBManager: # Renamed class + """Base class for NCAA FB managers with common functionality.""" # Updated docstring + # Class variables for warning tracking + _no_data_warning_logged = False + _last_warning_time = 0 + _warning_cooldown = 60 # Only log warnings once per minute + _shared_data = None + _last_shared_update = 0 + cache_manager = CacheManager() + logger = logging.getLogger('NCAAFB') # Changed logger name + + def __init__(self, config: Dict[str, Any], display_manager: DisplayManager): + self.display_manager = display_manager + self.config = config + self.ncaa_fb_config = config.get("ncaa_fb_scoreboard", {}) # Changed config key + self.is_enabled = self.ncaa_fb_config.get("enabled", False) + self.test_mode = self.ncaa_fb_config.get("test_mode", False) + self.logo_dir = self.ncaa_fb_config.get("logo_dir", "assets/sports/ncaa_fbs_logos") # Changed logo dir + self.update_interval = self.ncaa_fb_config.get("update_interval_seconds", 60) + self.last_update = 0 + self.current_game = None + self.fonts = self._load_fonts() + self.favorite_teams = self.ncaa_fb_config.get("favorite_teams", []) + self.past_fetch_days = self.ncaa_fb_config.get("past_fetch_days", 7) + self.future_fetch_days = self.ncaa_fb_config.get("future_fetch_days", 7) + + self.logger.setLevel(logging.DEBUG) + + 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._logo_cache = {} + + 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}") + + @classmethod + def _fetch_shared_data(cls, past_days: int, future_days: int, date_str: str = None) -> Optional[Dict]: + """Fetch and cache data for all managers to share.""" + current_time = time.time() + + if cls._shared_data and (current_time - cls._last_shared_update) < 300: + return cls._shared_data + + try: + cache_key = date_str if date_str else 'today_ncaafb' # Changed cache key prefix + cached_data = cls.cache_manager.get(cache_key, max_age=300) + if cached_data: + cls.logger.info(f"[NCAAFB] Using cached data for {cache_key}") + cls._shared_data = cached_data + cls._last_shared_update = current_time + return cached_data + + url = ESPN_NCAAFB_SCOREBOARD_URL # Use NCAA FB URL + params = {} + if date_str: + params['dates'] = date_str + + response = requests.get(url, params=params) + response.raise_for_status() + data = response.json() + cls.logger.info(f"[NCAAFB] Successfully fetched data from ESPN API") + + cls.cache_manager.set(cache_key, data) + cls._shared_data = data + cls._last_shared_update = current_time + + if not date_str: + today = datetime.now(timezone.utc).date() + dates_to_fetch = [] + # Generate dates from past_days ago to future_days ahead + for i in range(-past_days, future_days + 1): + fetch_dt = today + timedelta(days=i) + dates_to_fetch.append(fetch_dt.strftime('%Y%m%d')) + + cls.logger.info(f"[NCAAFB] Fetching data for dates: {dates_to_fetch}") + + all_events = [] + # Fetch data for each date (excluding today if already fetched) + for fetch_date in dates_to_fetch: + if fetch_date == today.strftime('%Y%m%d') and cache_key == 'today_ncaafb': # Skip today if already fetched initially + continue + + date_cache_key = f"{fetch_date}_ncaafb" # Changed cache key suffix + cached_date_data = cls.cache_manager.get(date_cache_key, max_age=300) + if cached_date_data: + cls.logger.info(f"[NCAAFB] Using cached data for date {fetch_date}") + if "events" in cached_date_data: + all_events.extend(cached_date_data["events"]) + continue + + params['dates'] = fetch_date + response = requests.get(url, params=params) + response.raise_for_status() + date_data = response.json() + if date_data and "events" in date_data: + all_events.extend(date_data["events"]) + cls.logger.info(f"[NCAAFB] Fetched {len(date_data['events'])} events for date {fetch_date}") + cls.cache_manager.set(date_cache_key, date_data) + + if all_events: + if "events" not in data: data["events"] = [] # Ensure 'events' key exists + data["events"].extend(all_events) + cls.logger.info(f"[NCAAFB] Combined {len(data['events'])} total events from all dates") + cls._shared_data = data + cls._last_shared_update = current_time + + return data + except requests.exceptions.RequestException as e: + cls.logger.error(f"[NCAAFB] Error fetching data from ESPN: {e}") + return None + + def _fetch_data(self, date_str: str = None) -> Optional[Dict]: + """Fetch data using shared data mechanism or direct fetch for live.""" + # Check if the instance is NCAAFBLiveManager + if isinstance(self, NCAAFBLiveManager): # Changed class name + try: + url = ESPN_NCAAFB_SCOREBOARD_URL # Use NCAA FB URL + params = {} + if date_str: + params['dates'] = date_str + + response = requests.get(url, params=params) + response.raise_for_status() + data = response.json() + self.logger.info(f"[NCAAFB] Successfully fetched live game data from ESPN API") + return data + except requests.exceptions.RequestException as e: + self.logger.error(f"[NCAAFB] Error fetching live game data from ESPN: {e}") + return None + else: + # For non-live games, use the shared cache + return self._fetch_shared_data(self.past_fetch_days, self.future_fetch_days, date_str) + + def _load_fonts(self): + """Load fonts used by the scoreboard.""" + fonts = {} + try: + fonts['score'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 10) + fonts['time'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) + fonts['team'] = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8) + fonts['status'] = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6) # Using 4x6 for status + fonts['detail'] = ImageFont.truetype("assets/fonts/4x6-font.ttf", 6) # Added detail font + logging.info("[NCAAFB] Successfully loaded fonts") # Changed log prefix + except IOError: + logging.warning("[NCAAFB] Fonts not found, using default PIL font.") # Changed log prefix + fonts['score'] = ImageFont.load_default() + fonts['time'] = ImageFont.load_default() + fonts['team'] = ImageFont.load_default() + fonts['status'] = ImageFont.load_default() + fonts['detail'] = ImageFont.load_default() + return fonts + + def _draw_text_with_outline(self, draw, text, position, font, fill=(255, 255, 255), outline_color=(0, 0, 0)): + """Draw text with a black outline for better readability.""" + x, y = position + for dx, dy in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]: + draw.text((x + dx, y + dy), text, font=font, fill=outline_color) + draw.text((x, y), text, font=font, fill=fill) + + def _load_and_resize_logo(self, team_abbrev: str) -> Optional[Image.Image]: + """Load and resize a team logo, with caching.""" + if team_abbrev in self._logo_cache: + return self._logo_cache[team_abbrev] + + logo_path = os.path.join(self.logo_dir, f"{team_abbrev}.png") + self.logger.debug(f"Logo path: {logo_path}") + + try: + # Create placeholder if logo doesn't exist (useful for testing) + if not os.path.exists(logo_path): + self.logger.warning(f"Logo not found 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}") + + logo = Image.open(logo_path) + if logo.mode != 'RGBA': + logo = logo.convert('RGBA') + + max_width = int(self.display_width * 1.5) + max_height = int(self.display_height * 1.5) + logo.thumbnail((max_width, max_height), Image.Resampling.LANCZOS) + self._logo_cache[team_abbrev] = logo + return logo + + except Exception as e: + self.logger.error(f"Error loading logo for {team_abbrev}: {e}", exc_info=True) + return None + + def _extract_game_details(self, game_event: Dict) -> Optional[Dict]: + """Extract relevant game details from ESPN NCAA FB API response.""" + # --- THIS METHOD MAY NEED ADJUSTMENTS FOR NCAA FB API DIFFERENCES --- + if not game_event: return None + + try: + competition = game_event["competitions"][0] + status = competition["status"] + competitors = competition["competitors"] + game_date_str = game_event["date"] + + start_time_utc = None + try: + start_time_utc = datetime.fromisoformat(game_date_str.replace("Z", "+00:00")) + except ValueError: + logging.warning(f"[NCAAFB] Could not parse game date: {game_date_str}") + + home_team = next((c for c in competitors if c.get("homeAway") == "home"), None) + away_team = next((c for c in competitors if c.get("homeAway") == "away"), None) + + if not home_team or not away_team: + self.logger.warning(f"[NCAAFB] Could not find home or away team in event: {game_event.get('id')}") + return None + + game_time, game_date = "", "" + if start_time_utc: + local_time = start_time_utc.astimezone() + game_time = local_time.strftime("%-I:%M %p") + game_date = local_time.strftime("%-m/%-d") + + # --- Football Specific Details (Likely same for NFL/NCAAFB) --- + situation = competition.get("situation") + down_distance_text = "" + if situation and status["type"]["state"] == "in": + down = situation.get("down") + distance = situation.get("distance") + if down and distance is not None: + down_str = {1: "1st", 2: "2nd", 3: "3rd", 4: "4th"}.get(down, f"{down}th") + dist_str = f"& {distance}" if distance > 0 else "& Goal" + down_distance_text = f"{down_str} {dist_str}" + elif situation.get("isRedZone"): + down_distance_text = "Red Zone" # Simplified if down/distance not present but in redzone + + # Format period/quarter + period = status.get("period", 0) + period_text = "" + if status["type"]["state"] == "in": + if period == 0: period_text = "Start" # Before kickoff + elif period == 1: period_text = "Q1" + elif period == 2: period_text = "Q2" + elif period == 3: period_text = "HALF" # Halftime is usually period 3 in API + elif period == 4: period_text = "Q3" + elif period == 5: period_text = "Q4" + elif period > 5: period_text = "OT" # Assuming OT starts at period 6+ + elif status["type"]["state"] == "halftime": # Check explicit halftime state + period_text = "HALF" + elif status["type"]["state"] == "post": + if period > 5 : period_text = "Final/OT" + else: period_text = "Final" + elif status["type"]["state"] == "pre": + period_text = game_time # Show time for upcoming + + # Timeouts (assuming max 3 per half, not carried over well in standard API) + # API often provides 'timeouts' directly under team, but reset logic is tricky + # We might need to simplify this or just use a fixed display if API is unreliable + home_timeouts = home_team.get("timeouts", 3) # Default to 3 if not specified + away_timeouts = away_team.get("timeouts", 3) # Default to 3 if not specified + + + details = { + "id": game_event.get("id"), + "start_time_utc": start_time_utc, + "status_text": status["type"]["shortDetail"], # e.g., "Final", "7:30 PM", "Q1 12:34" + "period": period, + "period_text": period_text, # Formatted quarter/status + "clock": status.get("displayClock", "0:00"), + "is_live": status["type"]["state"] == "in", + "is_final": status["type"]["state"] == "post", + "is_upcoming": status["type"]["state"] == "pre", + "is_halftime": status["type"]["state"] == "halftime", # Added halftime check + "home_abbr": home_team["team"]["abbreviation"], + "home_score": home_team.get("score", "0"), + "home_logo_path": os.path.join(self.logo_dir, f"{home_team['team']['abbreviation']}.png"), + "home_timeouts": home_timeouts, + "away_abbr": away_team["team"]["abbreviation"], + "away_score": away_team.get("score", "0"), + "away_logo_path": os.path.join(self.logo_dir, f"{away_team['team']['abbreviation']}.png"), + "away_timeouts": away_timeouts, + "game_time": game_time, + "game_date": game_date, + "down_distance_text": down_distance_text, # Added Down/Distance + "possession": situation.get("possession") if situation else None, # ID of team with possession + } + + # Basic validation (can be expanded) + if not details['home_abbr'] or not details['away_abbr']: + self.logger.warning(f"[NCAAFB] Missing team abbreviation in event: {details['id']}") + return None + + self.logger.debug(f"[NCAAFB] Extracted: {details['away_abbr']}@{details['home_abbr']}, Status: {status['type']['name']}, Live: {details['is_live']}, Final: {details['is_final']}, Upcoming: {details['is_upcoming']}") + + # Logo validation (optional but good practice) + for team in ["home", "away"]: + logo_path = details[f"{team}_logo_path"] + # No need to check file existence here, _load_and_resize_logo handles it + + return details + except Exception as e: + # Log the problematic event structure if possible + logging.error(f"[NCAAFB] Error extracting game details: {e} from event: {game_event.get('id')}", exc_info=True) + return None + + def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None: + """Placeholder draw method - subclasses should override.""" + # This base method will be simple, subclasses provide specifics + try: + img = Image.new('RGB', (self.display_width, self.display_height), (0, 0, 0)) + draw = ImageDraw.Draw(img) + status = game.get("status_text", "N/A") + self._draw_text_with_outline(draw, status, (2, 2), self.fonts['status']) + self.display_manager.image.paste(img, (0, 0)) + # Don't call update_display here, let subclasses handle it after drawing + except Exception as e: + self.logger.error(f"Error in base _draw_scorebug_layout: {e}", exc_info=True) + + + def display(self, force_clear: bool = False) -> None: + """Common display method for all NCAA FB managers""" # Updated docstring + if not self.is_enabled: # Check if module is enabled + return + + if not self.current_game: + current_time = time.time() + if not hasattr(self, '_last_warning_time'): + self._last_warning_time = 0 + if current_time - getattr(self, '_last_warning_time', 0) > 300: + self.logger.warning(f"[NCAAFB] No game data available to display in {self.__class__.__name__}") + setattr(self, '_last_warning_time', current_time) + return + + try: + self._draw_scorebug_layout(self.current_game, force_clear) + # display_manager.update_display() should be called within subclass draw methods + # or after calling display() in the main loop. Let's keep it out of the base display. + except Exception as e: + self.logger.error(f"[NCAAFB] Error during display call in {self.__class__.__name__}: {e}", exc_info=True) + + +class NCAAFBLiveManager(BaseNCAAFBManager): # Renamed class + """Manager for live NCAA FB games.""" # Updated docstring + def __init__(self, config: Dict[str, Any], display_manager: DisplayManager): + super().__init__(config, display_manager) + self.update_interval = self.ncaa_fb_config.get("live_update_interval", 15) + self.no_data_interval = 300 + self.last_update = 0 + self.logger.info("Initialized NCAAFB Live Manager") + self.live_games = [] + self.current_game_index = 0 + self.last_game_switch = 0 + self.game_display_duration = self.ncaa_fb_config.get("live_game_duration", 20) + self.last_display_update = 0 + self.last_log_time = 0 + self.log_interval = 300 + + if self.test_mode: + # More detailed test game for NCAA FB + self.current_game = { + "id": "testNCAAFB001", + "home_abbr": "UGA", "away_abbr": "AUB", # NCAA Examples + "home_score": "28", "away_score": "21", + "period": 4, "period_text": "Q4", "clock": "01:15", + "down_distance_text": "2nd & 5", "possession": "UGA_ID", # Placeholder ID + "home_timeouts": 1, "away_timeouts": 2, + "home_logo_path": os.path.join(self.logo_dir, "UGA.png"), + "away_logo_path": os.path.join(self.logo_dir, "AUB.png"), + "is_live": True, "is_final": False, "is_upcoming": False, "is_halftime": False, + "status_text": "Q4 01:15" + } + self.live_games = [self.current_game] + logging.info("[NCAAFB] Initialized NCAAFBLiveManager with test game: AUB vs UGA") # Updated log message + else: + logging.info("[NCAAFB] Initialized NCAAFBLiveManager in live mode") # Updated log message + + def update(self): + """Update live game data.""" + if not self.is_enabled: return + current_time = time.time() + interval = self.no_data_interval if not self.live_games and not self.test_mode else self.update_interval + + if current_time - self.last_update >= interval: + self.last_update = current_time + + if self.test_mode: + # Simulate clock running down in test mode + if self.current_game and self.current_game["is_live"]: + try: + minutes, seconds = map(int, self.current_game["clock"].split(':')) + seconds -= 1 + if seconds < 0: + seconds = 59 + minutes -= 1 + if minutes < 0: + # Simulate end of quarter/game + if self.current_game["period"] < 5: # Assuming 5 is Q4 end + self.current_game["period"] += 1 + # Update period_text based on new period + if self.current_game["period"] == 3: self.current_game["period_text"] = "HALF" + elif self.current_game["period"] == 5: self.current_game["period_text"] = "Q4" + # Reset clock for next quarter (e.g., 15:00) + minutes, seconds = 15, 0 + else: + # Simulate game end + self.current_game["is_live"] = False + self.current_game["is_final"] = True + self.current_game["period_text"] = "Final" + minutes, seconds = 0, 0 + self.current_game["clock"] = f"{minutes:02d}:{seconds:02d}" + # Simulate down change occasionally + if seconds % 15 == 0: + self.current_game["down_distance_text"] = f"{['1st','2nd','3rd','4th'][seconds % 4]} & {seconds % 10 + 1}" + self.current_game["status_text"] = f"{self.current_game['period_text']} {self.current_game['clock']}" + + # Display update handled by main loop or explicit call if needed immediately + # self.display(force_clear=True) # Only if immediate update is desired here + + except ValueError: + self.logger.warning("[NCAAFB] Test mode: Could not parse clock") # Changed log prefix + # No actual display call here, let main loop handle it + else: + # Fetch live game data + data = self._fetch_data() + new_live_games = [] + if data and "events" in data: + for event in data["events"]: + details = self._extract_game_details(event) + if details and (details["is_live"] or details["is_halftime"]): # Include halftime as 'live' display + if not self.favorite_teams or ( + details["home_abbr"] in self.favorite_teams or + details["away_abbr"] in self.favorite_teams + ): + new_live_games.append(details) + + # Log changes or periodically + should_log = ( + current_time - self.last_log_time >= self.log_interval or + len(new_live_games) != len(self.live_games) or + any(g1['id'] != g2.get('id') for g1, g2 in zip(self.live_games, new_live_games)) or # Check if game IDs changed + (not self.live_games and new_live_games) # Log if games appeared + ) + + if should_log: + if new_live_games: + self.logger.info(f"[NCAAFB] Found {len(new_live_games)} live/halftime games for fav teams.") # Changed log prefix + for game in new_live_games: + self.logger.info(f" - {game['away_abbr']}@{game['home_abbr']} ({game.get('status_text', 'N/A')})") + else: + self.logger.info("[NCAAFB] No live/halftime games found for favorite teams.") # Changed log prefix + self.last_log_time = current_time + + + # Update game list and current game + if new_live_games: + # Check if the games themselves changed, not just scores/time + new_game_ids = {g['id'] for g in new_live_games} + current_game_ids = {g['id'] for g in self.live_games} + + if new_game_ids != current_game_ids: + self.live_games = sorted(new_live_games, key=lambda g: g.get('start_time_utc') or datetime.now(timezone.utc)) # Sort by start time + # Reset index if current game is gone or list is new + if not self.current_game or self.current_game['id'] not in new_game_ids: + self.current_game_index = 0 + self.current_game = self.live_games[0] if self.live_games else None + self.last_game_switch = current_time + else: + # Find current game's new index if it still exists + try: + self.current_game_index = next(i for i, g in enumerate(self.live_games) if g['id'] == self.current_game['id']) + self.current_game = self.live_games[self.current_game_index] # Update current_game with fresh data + except StopIteration: # Should not happen if check above passed, but safety first + self.current_game_index = 0 + self.current_game = self.live_games[0] + self.last_game_switch = current_time + + else: + # Just update the data for the existing games + temp_game_dict = {g['id']: g for g in new_live_games} + self.live_games = [temp_game_dict.get(g['id'], g) for g in self.live_games] # Update in place + if self.current_game: + self.current_game = temp_game_dict.get(self.current_game['id'], self.current_game) + + # Display update handled by main loop based on interval + + else: + # No live games found + if self.live_games: # Were there games before? + self.logger.info("[NCAAFB] Live games previously showing have ended or are no longer live.") # Changed log prefix + self.live_games = [] + self.current_game = None + self.current_game_index = 0 + + else: + # Error fetching data or no events + if self.live_games: # Were there games before? + self.logger.warning("[NCAAFB] Could not fetch update; keeping existing live game data for now.") # Changed log prefix + else: + self.logger.warning("[NCAAFB] Could not fetch data and no existing live games.") # Changed log prefix + self.current_game = None # Clear current game if fetch fails and no games were active + + # Handle game switching (outside test mode check) + if not self.test_mode and len(self.live_games) > 1 and (current_time - self.last_game_switch) >= self.game_display_duration: + self.current_game_index = (self.current_game_index + 1) % len(self.live_games) + self.current_game = self.live_games[self.current_game_index] + self.last_game_switch = current_time + self.logger.info(f"[NCAAFB] Switched live view to: {self.current_game['away_abbr']}@{self.current_game['home_abbr']}") # Changed log prefix + # Force display update via flag or direct call if needed, but usually let main loop handle + + def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None: + """Draw the detailed scorebug layout for a live NCAA FB game.""" # Updated docstring + try: + main_img = Image.new('RGBA', (self.display_width, self.display_height), (0, 0, 0, 255)) + 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"]) + + 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 + # Draw placeholder text if logos fail + draw_final = ImageDraw.Draw(main_img.convert('RGB')) + self._draw_text_with_outline(draw_final, "Logo Error", (5,5), self.fonts['status']) + self.display_manager.image.paste(main_img.convert('RGB'), (0, 0)) + self.display_manager.update_display() + return + + center_y = self.display_height // 2 + + # Draw logos (shifted slightly more inward than NHL perhaps) + home_x = self.display_width - home_logo.width + 10 #adjusted from 18 # Adjust position as needed + home_y = center_y - (home_logo.height // 2) + main_img.paste(home_logo, (home_x, home_y), home_logo) + + away_x = -10 #adjusted from 18 # Adjust position as needed + away_y = center_y - (away_logo.height // 2) + main_img.paste(away_logo, (away_x, away_y), away_logo) + + # --- Draw Text Elements on Overlay --- + # Scores (centered, slightly above bottom) + home_score = str(game.get("home_score", "0")) + away_score = str(game.get("away_score", "0")) + score_text = f"{away_score}-{home_score}" + score_width = draw_overlay.textlength(score_text, font=self.fonts['score']) + score_x = (self.display_width - score_width) // 2 + score_y = (self.display_height // 2) - 3 #centered #from 14 # Position score higher + self._draw_text_with_outline(draw_overlay, score_text, (score_x, score_y), self.fonts['score']) + + # Period/Quarter and Clock (Top center) + period_clock_text = f"{game.get('period_text', '')} {game.get('clock', '')}".strip() + if game.get("is_halftime"): period_clock_text = "Halftime" # Override for halftime + + status_width = draw_overlay.textlength(period_clock_text, font=self.fonts['time']) + status_x = (self.display_width - status_width) // 2 + status_y = 1 # Position at top + self._draw_text_with_outline(draw_overlay, period_clock_text, (status_x, status_y), self.fonts['time']) + + # Down & Distance (Below Period/Clock) + down_distance = game.get("down_distance_text", "") + if down_distance and game.get("is_live"): # Only show if live and available + dd_width = draw_overlay.textlength(down_distance, font=self.fonts['detail']) + dd_x = (self.display_width - dd_width) // 2 + dd_y = (self.display_height)- 7 #score_y + 12 # Below the status/clock line + self._draw_text_with_outline(draw_overlay, down_distance, (dd_x, dd_y), self.fonts['detail'], fill=(200, 200, 0)) # Yellowish text + + # Timeouts (Bottom corners) - 3 small bars per team + timeout_bar_width = 4 + timeout_bar_height = 2 + timeout_spacing = 1 + timeout_y = self.display_height - timeout_bar_height - 1 # Bottom edge + + # Away Timeouts (Bottom Left) + away_timeouts_remaining = game.get("away_timeouts", 0) + for i in range(3): + to_x = 2 + i * (timeout_bar_width + timeout_spacing) + color = (255, 255, 255) if i < away_timeouts_remaining else (80, 80, 80) # White if available, gray if used + draw_overlay.rectangle([to_x, timeout_y, to_x + timeout_bar_width, timeout_y + timeout_bar_height], fill=color, outline=(0,0,0)) + + # Home Timeouts (Bottom Right) + home_timeouts_remaining = game.get("home_timeouts", 0) + for i in range(3): + to_x = self.display_width - 2 - timeout_bar_width - (2-i) * (timeout_bar_width + timeout_spacing) + color = (255, 255, 255) if i < home_timeouts_remaining else (80, 80, 80) # White if available, gray if used + draw_overlay.rectangle([to_x, timeout_y, to_x + timeout_bar_width, timeout_y + timeout_bar_height], fill=color, outline=(0,0,0)) + + # Composite the text overlay onto the main image + main_img = Image.alpha_composite(main_img, overlay) + main_img = main_img.convert('RGB') # Convert for display + + # Display the final image + self.display_manager.image.paste(main_img, (0, 0)) + self.display_manager.update_display() # Update display here for live + + except Exception as e: + self.logger.error(f"Error displaying live NCAAFB game: {e}", exc_info=True) # Changed log prefix + + # Inherits display() method from BaseNCAAFBManager, which calls the overridden _draw_scorebug_layout + + +class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class + """Manager for recently completed NCAA FB games.""" # Updated docstring + def __init__(self, config: Dict[str, Any], display_manager: DisplayManager): + super().__init__(config, display_manager) + self.recent_games = [] # Store all fetched recent games initially + self.games_list = [] # Filtered list for display (favorite teams) + self.current_game_index = 0 + self.last_update = 0 + self.update_interval = 300 # Check for recent games every 5 mins + self.last_game_switch = 0 + self.game_display_duration = 15 # Display each recent game for 15 seconds + self.logger.info(f"Initialized NCAAFBRecentManager with {len(self.favorite_teams)} favorite teams") # Changed log prefix + + def update(self): + """Update recent games data.""" + if not self.is_enabled: return + current_time = time.time() + if current_time - self.last_update < self.update_interval: + return + + self.last_update = current_time # Update time even if fetch fails + try: + data = self._fetch_data() # Uses shared cache + if not data or 'events' not in data: + self.logger.warning("[NCAAFB Recent] No events found in shared data.") # Changed log prefix + if not self.games_list: self.current_game = None # Clear display if no games were showing + return + + events = data['events'] + # self.logger.info(f"[NCAAFB Recent] Processing {len(events)} events from shared data.") # Changed log prefix + + # Process games and filter for final & within window & favorite teams + processed_games = [] + for event in events: + game = self._extract_game_details(event) + # Filter criteria: must be final, within time window + if game and game['is_final'] and game.get('is_within_window', True): # Assume within window if key missing, check logic + processed_games.append(game) + + # Filter for favorite teams + if self.favorite_teams: + team_games = [game for game in processed_games + if game['home_abbr'] in self.favorite_teams or + game['away_abbr'] in self.favorite_teams] + else: + team_games = processed_games # Show all recent games if no favorites defined + + # Sort by game time, most recent first + team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.min.replace(tzinfo=timezone.utc), reverse=True) + + # Check if the list of games to display has changed + new_game_ids = {g['id'] for g in team_games} + current_game_ids = {g['id'] for g in self.games_list} + + if new_game_ids != current_game_ids: + self.logger.info(f"[NCAAFB Recent] Found {len(team_games)} final games within window for display.") # Changed log prefix + self.games_list = team_games + # Reset index if list changed or current game removed + if not self.current_game or not self.games_list or self.current_game['id'] not in new_game_ids: + self.current_game_index = 0 + self.current_game = self.games_list[0] if self.games_list else None + self.last_game_switch = current_time # Reset switch timer + else: + # Try to maintain position if possible + try: + self.current_game_index = next(i for i, g in enumerate(self.games_list) if g['id'] == self.current_game['id']) + self.current_game = self.games_list[self.current_game_index] # Update data just in case + except StopIteration: + self.current_game_index = 0 + self.current_game = self.games_list[0] + self.last_game_switch = current_time + + elif self.games_list: + # List content is same, just update data for current game + self.current_game = self.games_list[self.current_game_index] + + + if not self.games_list: + self.logger.info("[NCAAFB Recent] No relevant recent games found to display.") # Changed log prefix + self.current_game = None # Ensure display clears if no games + + except Exception as e: + self.logger.error(f"[NCAAFB Recent] Error updating recent games: {e}", exc_info=True) # Changed log prefix + # Don't clear current game on error, keep showing last known state + # self.current_game = None # Decide if we want to clear display on error + + def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None: + """Draw the layout for a recently completed NCAA FB game.""" # Updated docstring + try: + main_img = Image.new('RGBA', (self.display_width, self.display_height), (0, 0, 0, 255)) + 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"]) + + 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 + # Draw placeholder text if logos fail (similar to live) + draw_final = ImageDraw.Draw(main_img.convert('RGB')) + self._draw_text_with_outline(draw_final, "Logo Error", (5,5), self.fonts['status']) + self.display_manager.image.paste(main_img.convert('RGB'), (0, 0)) + self.display_manager.update_display() + return + + center_y = self.display_height // 2 + + home_x = self.display_width - home_logo.width + 18 + home_y = center_y - (home_logo.height // 2) + main_img.paste(home_logo, (home_x, home_y), home_logo) + + away_x = -18 + away_y = center_y - (away_logo.height // 2) + main_img.paste(away_logo, (away_x, away_y), away_logo) + + # Draw Text Elements on Overlay + # Final Scores (Centered, same position as live) + home_score = str(game.get("home_score", "0")) + away_score = str(game.get("away_score", "0")) + score_text = f"{away_score} - {home_score}" + score_width = draw_overlay.textlength(score_text, font=self.fonts['score']) + score_x = (self.display_width - score_width) // 2 + score_y = self.display_height - 14 + self._draw_text_with_outline(draw_overlay, score_text, (score_x, score_y), self.fonts['score']) + + # "Final" text (Top center) + status_text = game.get("period_text", "Final") # Use formatted period text (e.g., "Final/OT") or default "Final" + status_width = draw_overlay.textlength(status_text, font=self.fonts['time']) + status_x = (self.display_width - status_width) // 2 + status_y = 1 + self._draw_text_with_outline(draw_overlay, status_text, (status_x, status_y), self.fonts['time']) + + # Composite and display + main_img = Image.alpha_composite(main_img, overlay) + main_img = main_img.convert('RGB') + self.display_manager.image.paste(main_img, (0, 0)) + self.display_manager.update_display() # Update display here + + except Exception as e: + self.logger.error(f"[NCAAFB Recent] Error displaying recent game: {e}", exc_info=True) # Changed log prefix + + def display(self, force_clear=False): + """Display recent games, handling switching.""" + if not self.is_enabled or not self.games_list: + # If disabled or no games, ensure display might be cleared by main loop if needed + # Or potentially clear it here? For now, rely on main loop/other managers. + if not self.games_list and self.current_game: + self.current_game = None # Clear internal state if list becomes empty + return + + try: + current_time = time.time() + + # Check if it's time to switch games + if len(self.games_list) > 1 and current_time - self.last_game_switch >= self.game_display_duration: + self.current_game_index = (self.current_game_index + 1) % len(self.games_list) + self.current_game = self.games_list[self.current_game_index] + self.last_game_switch = current_time + force_clear = True # Force redraw on switch + self.logger.debug(f"[NCAAFB Recent] Switched to game index {self.current_game_index}") # Changed log prefix + + if self.current_game: + self._draw_scorebug_layout(self.current_game, force_clear) + # update_display() is called within _draw_scorebug_layout for recent + + except Exception as e: + self.logger.error(f"[NCAAFB Recent] Error in display loop: {e}", exc_info=True) # Changed log prefix + + +class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class + """Manager for upcoming NCAA FB games.""" # Updated docstring + def __init__(self, config: Dict[str, Any], display_manager: DisplayManager): + super().__init__(config, display_manager) + self.upcoming_games = [] # Store all fetched upcoming games initially + self.games_list = [] # Filtered list for display (favorite teams) + self.current_game_index = 0 + self.last_update = 0 + self.update_interval = 300 # Check for upcoming games every 5 mins + self.last_log_time = 0 + self.log_interval = 300 + self.last_warning_time = 0 + self.warning_cooldown = 300 + self.last_game_switch = 0 + self.game_display_duration = 15 # Display each upcoming game for 15 seconds + self.logger.info(f"Initialized NCAAFBUpcomingManager with {len(self.favorite_teams)} favorite teams") # Changed log prefix + + def update(self): + """Update upcoming games data.""" + if not self.is_enabled: return + current_time = time.time() + if current_time - self.last_update < self.update_interval: + return + + self.last_update = current_time + try: + data = self._fetch_data() # Uses shared cache + if not data or 'events' not in data: + self.logger.warning("[NCAAFB Upcoming] No events found in shared data.") # Changed log prefix + if not self.games_list: self.current_game = None + return + + events = data['events'] + # self.logger.info(f"[NCAAFB Upcoming] Processing {len(events)} events from shared data.") # Changed log prefix + + processed_games = [] + for event in events: + game = self._extract_game_details(event) + # Filter criteria: must be upcoming ('pre' state) and within time window + if game and game['is_upcoming'] and game.get('is_within_window', True): # Assume within window if key missing, check logic + processed_games.append(game) + + # Filter for favorite teams + if self.favorite_teams: + team_games = [game for game in processed_games + if game['home_abbr'] in self.favorite_teams or + game['away_abbr'] in self.favorite_teams] + else: + team_games = processed_games # Show all upcoming if no favorites + + # Sort by game time, earliest first + team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.max.replace(tzinfo=timezone.utc)) + + # Log changes or periodically + should_log = ( + current_time - self.last_log_time >= self.log_interval or + len(team_games) != len(self.games_list) or + any(g1['id'] != g2.get('id') for g1, g2 in zip(self.games_list, team_games)) or + (not self.games_list and team_games) + ) + + # Check if the list of games to display has changed + new_game_ids = {g['id'] for g in team_games} + current_game_ids = {g['id'] for g in self.games_list} + + if new_game_ids != current_game_ids: + self.logger.info(f"[NCAAFB Upcoming] Found {len(team_games)} upcoming games within window for display.") # Changed log prefix + self.games_list = team_games + if not self.current_game or not self.games_list or self.current_game['id'] not in new_game_ids: + self.current_game_index = 0 + self.current_game = self.games_list[0] if self.games_list else None + self.last_game_switch = current_time + else: + try: + self.current_game_index = next(i for i, g in enumerate(self.games_list) if g['id'] == self.current_game['id']) + self.current_game = self.games_list[self.current_game_index] + except StopIteration: + self.current_game_index = 0 + self.current_game = self.games_list[0] + self.last_game_switch = current_time + + elif self.games_list: + self.current_game = self.games_list[self.current_game_index] # Update data + + if not self.games_list: + self.logger.info("[NCAAFB Upcoming] No relevant upcoming games found to display.") # Changed log prefix + self.current_game = None + + if should_log and not self.games_list: + # Log favorite teams only if no games are found and logging is needed + self.logger.debug(f"[NCAAFB Upcoming] Favorite teams: {self.favorite_teams}") # Changed log prefix + self.logger.debug(f"[NCAAFB Upcoming] Total upcoming games before filtering: {len(processed_games)}") # Changed log prefix + self.last_log_time = current_time + elif should_log: + self.last_log_time = current_time + + + except Exception as e: + self.logger.error(f"[NCAAFB Upcoming] Error updating upcoming games: {e}", exc_info=True) # Changed log prefix + # self.current_game = None # Decide if clear on error + + def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None: + """Draw the layout for an upcoming NCAA FB game.""" # Updated docstring + try: + main_img = Image.new('RGBA', (self.display_width, self.display_height), (0, 0, 0, 255)) + 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"]) + + 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 + draw_final = ImageDraw.Draw(main_img.convert('RGB')) + self._draw_text_with_outline(draw_final, "Logo Error", (5,5), self.fonts['status']) + self.display_manager.image.paste(main_img.convert('RGB'), (0, 0)) + self.display_manager.update_display() + return + + center_y = self.display_height // 2 + + home_x = self.display_width - home_logo.width + 18 + home_y = center_y - (home_logo.height // 2) + main_img.paste(home_logo, (home_x, home_y), home_logo) + + away_x = -18 + away_y = center_y - (away_logo.height // 2) + main_img.paste(away_logo, (away_x, away_y), away_logo) + + # Draw Text Elements on Overlay + game_date = game.get("game_date", "") + game_time = game.get("game_time", "") + + # "Next Game" at the top (use smaller status font) + status_text = "Next Game" + status_width = draw_overlay.textlength(status_text, font=self.fonts['status']) + status_x = (self.display_width - status_width) // 2 + status_y = 1 # Changed from 2 + self._draw_text_with_outline(draw_overlay, status_text, (status_x, status_y), self.fonts['status']) + + # Date text (centered, below "Next Game") + date_width = draw_overlay.textlength(game_date, font=self.fonts['time']) + date_x = (self.display_width - date_width) // 2 + # Adjust Y position to stack date and time nicely + date_y = center_y - 7 # Raise date slightly + self._draw_text_with_outline(draw_overlay, game_date, (date_x, date_y), self.fonts['time']) + + # Time text (centered, below Date) + time_width = draw_overlay.textlength(game_time, font=self.fonts['time']) + time_x = (self.display_width - time_width) // 2 + time_y = date_y + 9 # Place time below date + self._draw_text_with_outline(draw_overlay, game_time, (time_x, time_y), self.fonts['time']) + + # Composite and display + main_img = Image.alpha_composite(main_img, overlay) + main_img = main_img.convert('RGB') + self.display_manager.image.paste(main_img, (0, 0)) + self.display_manager.update_display() # Update display here + + except Exception as e: + self.logger.error(f"[NCAAFB Upcoming] Error displaying upcoming game: {e}", exc_info=True) # Changed log prefix + + def display(self, force_clear=False): + """Display upcoming games, handling switching.""" + if not self.is_enabled: return + + if not self.games_list: + if self.current_game: self.current_game = None # Clear state if list empty + current_time = time.time() + # Log warning periodically if no games found + if current_time - self.last_warning_time > self.warning_cooldown: + self.logger.info("[NCAAFB Upcoming] No upcoming games found for favorite teams to display.") # Changed log prefix + self.last_warning_time = current_time + return # Skip display update + + try: + current_time = time.time() + + # Check if it's time to switch games + if len(self.games_list) > 1 and current_time - self.last_game_switch >= self.game_display_duration: + self.current_game_index = (self.current_game_index + 1) % len(self.games_list) + self.current_game = self.games_list[self.current_game_index] + self.last_game_switch = current_time + force_clear = True # Force redraw on switch + self.logger.debug(f"[NCAAFB Upcoming] Switched to game index {self.current_game_index}") # Changed log prefix + + if self.current_game: + self._draw_scorebug_layout(self.current_game, force_clear) + # update_display() is called within _draw_scorebug_layout for upcoming + + except Exception as e: + self.logger.error(f"[NCAAFB Upcoming] Error in display loop: {e}", exc_info=True) # Changed log prefix \ No newline at end of file