@@ -171,8 +171,16 @@ def get_skin_html(skin_name):
171171 return None
172172
173173
174- def list_available_skins ():
175- """Liste tous les skins disponibles avec leurs informations"""
174+ def list_available_skins (force_refresh = False ):
175+ """Liste tous les skins disponibles avec leurs informations (avec cache)"""
176+ global _skins_list_cache
177+
178+ # Utiliser le cache s'il est valide (cache de 60 secondes ou jusqu'à invalidation)
179+ if (not force_refresh
180+ and _skins_list_cache ["skins" ] is not None
181+ and (time .time () - _skins_list_cache ["last_update" ]) < 60 ):
182+ return _skins_list_cache ["skins" ]
183+
176184 skins_dir = Path ("skins" )
177185 available_skins = []
178186
@@ -212,11 +220,17 @@ def list_available_skins():
212220
213221 available_skins .append (skin_info )
214222
223+ # Mettre à jour le cache
224+ _skins_list_cache ["skins" ] = available_skins
225+ _skins_list_cache ["last_update" ] = time .time ()
226+
215227 return available_skins
216228
217229
218230def set_active_skin (skin_name ):
219231 """Change le skin actif et le sauvegarde dans la configuration"""
232+ global _active_skin_cache
233+
220234 skin_config_file = Path ("config" ) / "active_skin.json"
221235
222236 # Vérifier que le skin existe
@@ -232,6 +246,10 @@ def set_active_skin(skin_name):
232246 with open (skin_config_file , 'w' , encoding = 'utf-8' ) as f :
233247 json .dump (config , f , indent = 2 , ensure_ascii = False )
234248
249+ # Invalider le cache du skin
250+ _active_skin_cache ["skin_id" ] = None
251+ _active_skin_cache ["html_content" ] = None
252+
235253 print (f"[OK] Skin actif change pour : { skin_name } " )
236254 return True , f"Skin changé pour : { skin_name } "
237255 except Exception as e :
@@ -272,6 +290,28 @@ def set_active_skin(skin_name):
272290# Event pour le graceful shutdown
273291shutdown_event = threading .Event ()
274292
293+ # ============================================================================
294+ # CACHE SYSTÈME
295+ # ============================================================================
296+
297+ # Cache pour le thumbnail (évite de re-encoder en base64 si la piste n'a pas changé)
298+ _thumbnail_cache = {
299+ "track_key" : None , # (title, artist, album) pour identifier la piste
300+ "thumbnail" : "" # Le thumbnail en base64
301+ }
302+
303+ # Cache pour le skin actif (évite de relire le fichier à chaque requête)
304+ _active_skin_cache = {
305+ "skin_id" : None ,
306+ "html_content" : None
307+ }
308+
309+ # Cache pour la liste des skins
310+ _skins_list_cache = {
311+ "skins" : None ,
312+ "last_update" : 0
313+ }
314+
275315# ============================================================================
276316# FILTRE MÉDIA
277317# ============================================================================
@@ -320,6 +360,8 @@ def is_app_allowed(app_id: str) -> bool:
320360
321361async def get_media_info () -> Optional [Dict ]:
322362 """Récupère les informations de la piste en cours depuis Windows Media API"""
363+ global _thumbnail_cache
364+
323365 try :
324366 sessions = await MediaManager .request_async ()
325367 current_session = sessions .get_current_session ()
@@ -339,45 +381,60 @@ async def get_media_info() -> Optional[Dict]:
339381 playback_info = current_session .get_playback_info ()
340382 timeline_props = current_session .get_timeline_properties ()
341383
342- # Récupérer la pochette d'album
343- thumbnail_base64 = ""
344- if info .thumbnail : # type: ignore[union-attr]
345- try :
346- thumb_stream_ref = info .thumbnail # type: ignore[union-attr]
347- thumb_read_buffer = await thumb_stream_ref .open_read_async ()
348-
349- buffer = Buffer (thumb_read_buffer .size )
350- await thumb_read_buffer .read_async (
351- buffer ,
352- buffer .capacity ,
353- InputStreamOptions .READ_AHEAD
354- )
355-
356- reader = DataReader .from_buffer (buffer )
357- byte_array = bytearray (buffer .length )
358- reader .read_bytes (byte_array )
359-
360- thumbnail_base64 = "data:image/jpeg;base64," + base64 .b64encode (byte_array ).decode ('utf-8' )
361- except Exception as e :
362- # Pas grave si la pochette n'est pas disponible
363- pass
384+ # Extraire les infos de base
385+ title = info .title or "Unknown Title" # type: ignore[union-attr]
386+ artist = info .artist or "Unknown Artist" # type: ignore[union-attr]
387+ album = info .album_title or "" # type: ignore[union-attr]
388+
389+ # Clé unique pour identifier la piste
390+ track_key = (title , artist , album )
391+
392+ # Utiliser le cache du thumbnail si la piste n'a pas changé
393+ if _thumbnail_cache ["track_key" ] == track_key and _thumbnail_cache ["thumbnail" ]:
394+ thumbnail_base64 = _thumbnail_cache ["thumbnail" ]
395+ else :
396+ # Nouvelle piste, récupérer la pochette
397+ thumbnail_base64 = ""
398+ if info .thumbnail : # type: ignore[union-attr]
399+ try :
400+ thumb_stream_ref = info .thumbnail # type: ignore[union-attr]
401+ thumb_read_buffer = await thumb_stream_ref .open_read_async ()
402+
403+ buffer = Buffer (thumb_read_buffer .size )
404+ await thumb_read_buffer .read_async (
405+ buffer ,
406+ buffer .capacity ,
407+ InputStreamOptions .READ_AHEAD
408+ )
409+
410+ reader = DataReader .from_buffer (buffer )
411+ byte_array = bytearray (buffer .length )
412+ reader .read_bytes (byte_array )
413+
414+ thumbnail_base64 = "data:image/jpeg;base64," + base64 .b64encode (byte_array ).decode ('utf-8' )
415+ except Exception :
416+ pass
417+
418+ # Mettre à jour le cache
419+ _thumbnail_cache ["track_key" ] = track_key
420+ _thumbnail_cache ["thumbnail" ] = thumbnail_base64
364421
365422 # Convertir les temps (timedelta) en secondes
366423 position_seconds = int (timeline_props .position .total_seconds ()) if timeline_props .position else 0
367424 duration_seconds = int (timeline_props .end_time .total_seconds ()) if timeline_props .end_time else 0
368425
369426 return {
370- "title" : info . title or "Unknown Title" , # type: ignore[union-attr]
371- "artist" : info . artist or "Unknown Artist" , # type: ignore[union-attr]
372- "album" : info . album_title or "" , # type: ignore[union-attr]
427+ "title" : title ,
428+ "artist" : artist ,
429+ "album" : album ,
373430 "thumbnail" : thumbnail_base64 ,
374431 "is_playing" : playback_info .playback_status == 4 , # 4 = Playing
375432 "position" : position_seconds ,
376433 "duration" : duration_seconds ,
377434 "source_app" : source_app_id
378435 }
379436
380- except Exception as e :
437+ except Exception :
381438 # Pas de musique en cours ou erreur
382439 return None
383440
@@ -647,15 +704,28 @@ def update_track_info():
647704
648705@app .route ('/' )
649706def index ():
650- """Page d'accueil avec l'overlay - charge le skin actif"""
707+ """Page d'accueil avec l'overlay - charge le skin actif (avec cache)"""
708+ global _active_skin_cache
709+
651710 active_skin = load_active_skin ()
711+
712+ # Utiliser le cache si le skin n'a pas changé
713+ if (_active_skin_cache ["skin_id" ] == active_skin
714+ and _active_skin_cache ["html_content" ] is not None ):
715+ return _active_skin_cache ["html_content" ]
716+
717+ # Charger le nouveau skin
652718 skin_html = get_skin_html (active_skin )
653719
654720 # Si le skin n'est pas trouvé, utiliser le template par défaut
655721 if skin_html is None :
656722 print (f"[WARN] Skin { active_skin } introuvable, utilisation du template par defaut" )
657723 return render_template_string (OVERLAY_HTML )
658724
725+ # Mettre à jour le cache
726+ _active_skin_cache ["skin_id" ] = active_skin
727+ _active_skin_cache ["html_content" ] = skin_html
728+
659729 return skin_html
660730
661731
0 commit comments