v1.9.8.5 Refactor controls mapping to use dynamic translations and update language files for improved localization + fix xbox conversionafter extraction
This commit is contained in:
@@ -19,7 +19,7 @@ from display import (
|
||||
from language import handle_language_menu_events, _
|
||||
from network import test_internet, download_rom, is_1fichier_url, download_from_1fichier, check_for_updates
|
||||
from controls import handle_controls, validate_menu_state, process_key_repeats, get_emergency_controls
|
||||
from controls_mapper import load_controls_config, save_controls_config, map_controls, draw_controls_mapping, ACTIONS
|
||||
from controls_mapper import load_controls_config, save_controls_config, map_controls, draw_controls_mapping, get_actions
|
||||
from utils import (
|
||||
detect_non_pc, load_sources, check_extension_before_download, extract_zip_data,
|
||||
play_random_music, load_accessibility_settings, load_music_config
|
||||
@@ -639,7 +639,7 @@ async def main():
|
||||
logger.debug("Initialisation de config.controls_config avec un dictionnaire vide")
|
||||
|
||||
# Forcer l'affichage de l'interface de mappage des contrôles
|
||||
action = ACTIONS[0]
|
||||
action = get_actions()[0]
|
||||
draw_controls_mapping(screen, action, None, True, 0.0)
|
||||
pygame.display.flip()
|
||||
logger.debug("Interface de mappage des contrôles affichée")
|
||||
|
||||
@@ -4,7 +4,7 @@ import sys
|
||||
import logging
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "1.9.8.4"
|
||||
app_version = "1.9.8.5"
|
||||
|
||||
def get_application_root():
|
||||
"""Détermine le dossier de l'application de manière portable."""
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
import logging
|
||||
import config
|
||||
import language
|
||||
from config import CONTROLS_CONFIG_PATH
|
||||
from display import draw_gradient
|
||||
import xml.etree.ElementTree as ET
|
||||
@@ -13,23 +14,37 @@ logger = logging.getLogger(__name__)
|
||||
CONTROLS_CONFIG_PATH = os.path.join(config.SAVE_FOLDER, "controls.json")
|
||||
|
||||
# Actions internes de RGSX à mapper
|
||||
ACTIONS = [
|
||||
{"name": "confirm", "display": "Confirmer", "description": "Valider (Recommandé: Entrée, A/Croix)"},
|
||||
{"name": "cancel", "display": "Annuler", "description": "Annuler/Retour (Recommandé: Retour Arrière, B/Rond)"},
|
||||
{"name": "up", "display": "Haut", "description": "Naviguer vers le haut"},
|
||||
{"name": "down", "display": "Bas", "description": "Naviguer vers le bas"},
|
||||
{"name": "left", "display": "Gauche", "description": "Naviguer à gauche"},
|
||||
{"name": "right", "display": "Droite", "description": "Naviguer à droite"},
|
||||
{"name": "start", "display": "Start", "description": "Menu pause / Paramètres (Recommandé: Start, AltGr)"},
|
||||
{"name": "filter", "display": "Filtrer", "description": "Ouvrir filtre (Recommandé: F, Select)"},
|
||||
{"name": "page_up", "display": "Page Précédente", "description": "Page précédente/Défilement Rapide Haut (Recommandé: PageUp, LB/L1)"},
|
||||
{"name": "page_down", "display": "Page Suivante", "description": "Page suivante/Défilement Rapide Bas (Recommandé: PageDown, RB/R1)"},
|
||||
{"name": "history", "display": "Historique", "description": "Ouvrir l'historique (Recommandé: H, Y/Carré)"},
|
||||
{"name": "progress", "display": "Progression", "description": "Historique : Effacer la liste (Recommandé: X/Triangle)"},
|
||||
{"name": "delete", "display": "Supprimer", "description": "Mode Fitre : Supprimer caractère en mode recherche (Recommandé: DEL, LT/L2)"},
|
||||
{"name": "space", "display": "Espace", "description": "Mode Filtre : Ajouter espace (Recommandé: Espace, RT/R2)"},
|
||||
|
||||
# Actions internes de RGSX à mapper (labels et descriptions traduits dynamiquement)
|
||||
ACTION_DEFS = [
|
||||
{"name": "confirm"},
|
||||
{"name": "cancel"},
|
||||
{"name": "up"},
|
||||
{"name": "down"},
|
||||
{"name": "left"},
|
||||
{"name": "right"},
|
||||
{"name": "start"},
|
||||
{"name": "filter"},
|
||||
{"name": "page_up"},
|
||||
{"name": "page_down"},
|
||||
{"name": "history"},
|
||||
{"name": "progress"},
|
||||
{"name": "delete"},
|
||||
{"name": "space"},
|
||||
]
|
||||
|
||||
def get_actions(lang=None):
|
||||
"""Retourne la liste des actions avec labels/descriptions traduits selon la langue courante."""
|
||||
actions = []
|
||||
for a in ACTION_DEFS:
|
||||
name = a["name"]
|
||||
display = language.get_text(f"controls_action_{name}", name.capitalize())
|
||||
description = language.get_text(f"controls_desc_{name}", "")
|
||||
actions.append({"name": name, "display": display, "description": description})
|
||||
return actions
|
||||
|
||||
# ...existing code...
|
||||
|
||||
# Mappage des valeurs SDL vers les constantes Pygame
|
||||
SDL_TO_PYGAME_KEY = {
|
||||
1073741906: pygame.K_UP, # Flèche Haut
|
||||
@@ -335,10 +350,11 @@ def map_controls(screen):
|
||||
held_hats = {}
|
||||
held_mouse_buttons = set()
|
||||
|
||||
while current_action_index < len(ACTIONS):
|
||||
actions = get_actions()
|
||||
while current_action_index < len(actions):
|
||||
if config.needs_redraw:
|
||||
progress = min(input_held_time / HOLD_DURATION, 1.0) if current_input else 0.0
|
||||
draw_controls_mapping(screen, ACTIONS[current_action_index], last_input_name, current_input is not None, progress)
|
||||
draw_controls_mapping(screen, actions[current_action_index], last_input_name, current_input is not None, progress)
|
||||
pygame.display.flip()
|
||||
config.needs_redraw = False
|
||||
|
||||
@@ -450,7 +466,7 @@ def map_controls(screen):
|
||||
if current_input:
|
||||
input_held_time += delta_time
|
||||
if input_held_time >= HOLD_DURATION:
|
||||
action_name = ACTIONS[current_action_index]["name"]
|
||||
action_name = actions[current_action_index]["name"]
|
||||
|
||||
# Sauvegarder avec la structure attendue par controls.py
|
||||
if current_input["type"] == "key":
|
||||
@@ -522,22 +538,24 @@ def draw_controls_mapping(screen, action, last_input, waiting_for_input, hold_pr
|
||||
border_width = 4
|
||||
shadow_offset = 8
|
||||
|
||||
# Titre principal
|
||||
title_text = "Configuration des contrôles"
|
||||
# Titre principal (traduction)
|
||||
title_text = language.get_text("controls_mapping_title", "Configuration des contrôles")
|
||||
title_surface = config.title_font.render(title_text, True, (255, 255, 255))
|
||||
title_rect = title_surface.get_rect(center=(config.screen_width // 2, 80))
|
||||
screen.blit(title_surface, title_rect)
|
||||
|
||||
# Instructions
|
||||
instruction_text = "Maintenez pendant 3s pour configurer :"
|
||||
description_text = action['description']
|
||||
# Instructions (traduction)
|
||||
instruction_text = language.get_text("controls_mapping_instruction", "Maintenez pendant 3s pour configurer :")
|
||||
description_text = action.get('description', '')
|
||||
instruction_surface = config.small_font.render(instruction_text, True, (255, 255, 255))
|
||||
description_surface = config.font.render(description_text, True, (200, 200, 200))
|
||||
instruction_width, instruction_height = instruction_surface.get_size()
|
||||
description_width, description_height = description_surface.get_size()
|
||||
|
||||
# Input détecté
|
||||
input_text = last_input or (f"En attente d'une touche ou bouton..." if waiting_for_input else "Appuyez sur une touche ou un bouton")
|
||||
# Input détecté (traduction)
|
||||
waiting_text = language.get_text("controls_mapping_waiting", "En attente d'une touche ou bouton...")
|
||||
press_text = language.get_text("controls_mapping_press", "Appuyez sur une touche ou un bouton")
|
||||
input_text = last_input or (waiting_text if waiting_for_input else press_text)
|
||||
input_surface = config.small_font.render(input_text, True, (0, 255, 0) if last_input else (255, 255, 255))
|
||||
input_width, input_height = input_surface.get_size()
|
||||
|
||||
|
||||
@@ -1250,23 +1250,15 @@ def draw_pause_menu(screen, selected_option):
|
||||
# Menu aide contrôles
|
||||
def draw_controls_help(screen, previous_state):
|
||||
"""Affiche la liste des contrôles avec un style moderne."""
|
||||
# Définir les noms d'actions traduits en dehors des f-strings pour éviter les problèmes de syntaxe
|
||||
start_text = _("controls_action_start")
|
||||
progress_text = _("controls_action_progress")
|
||||
up_text = _("controls_action_up")
|
||||
down_text = _("controls_action_down")
|
||||
filter_text = _("controls_action_filter")
|
||||
history_text = _("controls_action_history")
|
||||
delete_text = _("controls_action_delete")
|
||||
space_text = _("controls_action_space")
|
||||
|
||||
# Catégories de contrôles
|
||||
nav_text = _("controls_navigation")
|
||||
pages_text = _("controls_pages")
|
||||
confirm_select_text = _("controls_confirm_select")
|
||||
cancel_back_text = _("controls_cancel_back")
|
||||
history_text = _("controls_history")
|
||||
clear_history_text = _("controls_clear_history")
|
||||
clear_history_text = _("controls_action_delete_history")
|
||||
filter_search_text = _("controls_filter_search")
|
||||
|
||||
control_categories = {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"controls_mapping_title": "Steuerung konfigurieren",
|
||||
"controls_mapping_instruction": "3 Sekunden halten zum Konfigurieren:",
|
||||
"controls_mapping_waiting": "Warte auf eine Taste oder einen Button...",
|
||||
"controls_mapping_press": "Drücke eine Taste oder einen Button",
|
||||
"welcome_message": "Willkommen bei RGSX",
|
||||
"disclaimer_line1": "Es ist gefährlich, allein zu gehen, nimm alles, was du brauchst!",
|
||||
"disclaimer_line2": "Aber lade nur Spiele herunter,",
|
||||
@@ -92,7 +96,7 @@
|
||||
"controls_action_right": "Rechts",
|
||||
"controls_action_page_up": "Vorherige Seite",
|
||||
"controls_action_page_down": "Nächste Seite",
|
||||
"controls_action_progress": "Fortschritt",
|
||||
"controls_action_delete_history": "Verlauf leeren",
|
||||
"controls_action_history": "Verlauf",
|
||||
"controls_action_filter": "Filtern",
|
||||
"controls_action_delete": "Löschen",
|
||||
@@ -107,7 +111,7 @@
|
||||
"controls_desc_right": "Nach rechts navigieren",
|
||||
"controls_desc_page_up": "Vorherige Seite/Schnelles Scrollen nach oben (z.B.: BildAuf, LB)",
|
||||
"controls_desc_page_down": "Nächste Seite/Schnelles Scrollen nach unten (z.B.: BildAb, RB)",
|
||||
"controls_desc_progress": "Fortschritt anzeigen (z.B.: X)",
|
||||
"controls_desc_delete_history": "Verlauf löschen (z.B.: X)",
|
||||
"controls_desc_history": "Verlauf öffnen (z.B.: H, Y)",
|
||||
"controls_desc_filter": "Filter öffnen (z.B.: F, Select)",
|
||||
"controls_desc_delete": "Zeichen löschen (z.B.: LT, Entf)",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"controls_mapping_title": "Controls configuration",
|
||||
"controls_mapping_instruction": "Hold for 3s to configure:",
|
||||
"controls_mapping_waiting": "Waiting for a key or button...",
|
||||
"controls_mapping_press": "Press a key or button",
|
||||
"welcome_message": "Welcome to RGSX",
|
||||
"disclaimer_line1": "It's dangerous to go alone, take all you need!",
|
||||
"disclaimer_line2": "But only download games",
|
||||
@@ -92,7 +96,7 @@
|
||||
"controls_action_right": "Right",
|
||||
"controls_action_page_up": "Previous Page",
|
||||
"controls_action_page_down": "Next Page",
|
||||
"controls_action_progress": "Progress",
|
||||
"controls_action_delete_history": "Clear History",
|
||||
"controls_action_history": "History",
|
||||
"controls_action_filter": "Filter",
|
||||
"controls_action_delete": "Delete",
|
||||
@@ -107,7 +111,7 @@
|
||||
"controls_desc_right": "Navigate right",
|
||||
"controls_desc_page_up": "Previous page/Fast scroll up (e.g. PageUp, LB)",
|
||||
"controls_desc_page_down": "Next page/Fast scroll down (e.g. PageDown, RB)",
|
||||
"controls_desc_progress": "View progress (e.g. X)",
|
||||
"controls_desc_delete_history": "Clear History (e.g. X)",
|
||||
"controls_desc_history": "Open history (e.g. H, Y)",
|
||||
"controls_desc_filter": "Open filter (e.g. F, Select)",
|
||||
"controls_desc_delete": "Delete character (e.g. LT, Delete)",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"controls_mapping_title": "Configuración de controles",
|
||||
"controls_mapping_instruction": "Mantén pulsado 3s para configurar:",
|
||||
"controls_mapping_waiting": "Esperando una tecla o botón...",
|
||||
"controls_mapping_press": "Pulsa una tecla o botón",
|
||||
"welcome_message": "Bienvenido a RGSX",
|
||||
"disclaimer_line1": "¡Es peligroso ir solo, toma todo lo que necesites!",
|
||||
"disclaimer_line2": "Pero solo descarga juegos",
|
||||
@@ -92,7 +96,7 @@
|
||||
"controls_action_right": "Derecha",
|
||||
"controls_action_page_up": "Página anterior",
|
||||
"controls_action_page_down": "Página siguiente",
|
||||
"controls_action_progress": "Progreso",
|
||||
"controls_action_delete_history": "Vaciar Historial",
|
||||
"controls_action_history": "Historial",
|
||||
"controls_action_filter": "Filtrar",
|
||||
"controls_action_delete": "Eliminar",
|
||||
@@ -107,7 +111,7 @@
|
||||
"controls_desc_right": "Navegar a derecha",
|
||||
"controls_desc_page_up": "Página anterior/Desplazamiento rápido arriba (ej: RePág, LB)",
|
||||
"controls_desc_page_down": "Página siguiente/Desplazamiento rápido abajo (ej: AvPág, RB)",
|
||||
"controls_desc_progress": "Ver progreso (ej: X)",
|
||||
"controls_desc_delete_history": "Borrar Historial (ej: X)",
|
||||
"controls_desc_history": "Abrir historial (ej: H, Y)",
|
||||
"controls_desc_filter": "Abrir filtro (ej: F, Select)",
|
||||
"controls_desc_delete": "Eliminar carácter (ej: LT, Supr)",
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"controls_action_right": "Droite",
|
||||
"controls_action_page_up": "Page Précédente",
|
||||
"controls_action_page_down": "Page Suivante",
|
||||
"controls_action_progress": "Progression",
|
||||
"controls_action_delete_history": "Vider Historique",
|
||||
"controls_action_history": "Historique",
|
||||
"controls_action_filter": "Filtrer",
|
||||
"controls_action_delete": "Supprimer",
|
||||
@@ -107,7 +107,7 @@
|
||||
"controls_desc_right": "Naviguer à droite",
|
||||
"controls_desc_page_up": "Page précédente/Défilement Rapide Haut (ex: PageUp, LB)",
|
||||
"controls_desc_page_down": "Page suivante/Défilement Rapide Bas (ex: PageDown, RB)",
|
||||
"controls_desc_progress": "Voir progression (ex: X)",
|
||||
"controls_desc_delete_history": "Effacer Historique (ex: X)",
|
||||
"controls_desc_history": "Ouvrir l'historique (ex: H, Y)",
|
||||
"controls_desc_filter": "Ouvrir filtre (ex: F, Select)",
|
||||
"controls_desc_delete": "Supprimer caractère (ex: LT, Suppr)",
|
||||
@@ -176,5 +176,9 @@
|
||||
"utils_permission_denied": "Permission refusée lors de l'extraction: {0}",
|
||||
"utils_extraction_failed": "Échec de l'extraction: {0}",
|
||||
"utils_unrar_unavailable": "Commande unrar non disponible",
|
||||
"utils_rar_list_failed": "Échec de la liste des fichiers RAR: {0}"
|
||||
"utils_rar_list_failed": "Échec de la liste des fichiers RAR: {0}",
|
||||
"controls_mapping_title": "Controls configuration",
|
||||
"controls_mapping_instruction": "Hold for 3s to configure:",
|
||||
"controls_mapping_waiting": "Waiting for a key or button...",
|
||||
"controls_mapping_press": "Press a key or button"
|
||||
}
|
||||
@@ -354,65 +354,76 @@ def extract_zip(zip_path, dest_dir, url):
|
||||
zip_ref.testzip() # Vérifier l'intégrité de l'archive
|
||||
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
|
||||
logger.info(f"Taille totale à extraire: {total_size} octets")
|
||||
if total_size == 0:
|
||||
logger.warning("ZIP vide ou ne contenant que des dossiers")
|
||||
return True, "ZIP vide extrait avec succès"
|
||||
lock = threading.Lock()
|
||||
# Lister les ISO avant extraction
|
||||
iso_before = set()
|
||||
for root, dirs, files in os.walk(dest_dir):
|
||||
for file in files:
|
||||
if file.lower().endswith('.iso'):
|
||||
iso_before.add(os.path.abspath(os.path.join(root, file)))
|
||||
|
||||
extracted_size = 0
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
chunk_size = 2048 # Réduire pour plus de mises à jour
|
||||
last_save_time = time.time()
|
||||
save_interval = 0.5 # Sauvegarder toutes les 0.5 secondes
|
||||
for info in zip_ref.infolist():
|
||||
if info.is_dir():
|
||||
continue
|
||||
file_path = os.path.join(dest_dir, info.filename)
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
|
||||
file_size = info.file_size
|
||||
file_extracted = 0
|
||||
while True:
|
||||
chunk = source.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
dest.write(chunk)
|
||||
file_extracted += len(chunk)
|
||||
extracted_size += len(chunk)
|
||||
current_time = time.time()
|
||||
with lock:
|
||||
# Vérifier si config.history est une liste avant d'itérer
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
# Vérifier si l'entrée a les clés nécessaires et correspond à notre téléchargement
|
||||
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
|
||||
# Chercher par URL si disponible
|
||||
if "url" in entry and entry["url"] == url:
|
||||
# Calculer le pourcentage correctement et le limiter entre 0 et 100
|
||||
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
progress_percent = max(0, min(100, progress_percent))
|
||||
|
||||
entry["status"] = "Extracting"
|
||||
entry["progress"] = progress_percent
|
||||
entry["message"] = "Extraction en cours"
|
||||
|
||||
if current_time - last_save_time >= save_interval:
|
||||
save_history(config.history)
|
||||
last_save_time = current_time
|
||||
# logger.debug(f"Extraction en cours: {info.filename}, file_extracted={file_extracted}/{file_size}, total_extracted={extracted_size}/{total_size}, progression={progress_percent:.1f}%")
|
||||
|
||||
config.needs_redraw = True
|
||||
break
|
||||
os.chmod(file_path, 0o644)
|
||||
# Vérifier si c'est un dossier xbox et le traiter si nécessaire
|
||||
xbox_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), "xbox")
|
||||
if dest_dir == xbox_dir:
|
||||
success, error_msg = handle_xbox(dest_dir)
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
for root, dirs, files in os.walk(dest_dir):
|
||||
for dir_name in dirs:
|
||||
os.chmod(os.path.join(root, dir_name), 0o755)
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.testzip() # Vérifier l'intégrité de l'archive
|
||||
total_size = sum(info.file_size for info in zip_ref.infolist() if not info.is_dir())
|
||||
logger.info(f"Taille totale à extraire: {total_size} octets")
|
||||
if total_size == 0:
|
||||
logger.warning("ZIP vide ou ne contenant que des dossiers")
|
||||
return True, "ZIP vide extrait avec succès"
|
||||
|
||||
extracted_size = 0
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
chunk_size = 2048 # Réduire pour plus de mises à jour
|
||||
last_save_time = time.time()
|
||||
save_interval = 0.5 # Sauvegarder toutes les 0.5 secondes
|
||||
for info in zip_ref.infolist():
|
||||
if info.is_dir():
|
||||
continue
|
||||
file_path = os.path.join(dest_dir, info.filename)
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with zip_ref.open(info) as source, open(file_path, 'wb') as dest:
|
||||
file_size = info.file_size
|
||||
file_extracted = 0
|
||||
while True:
|
||||
chunk = source.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
dest.write(chunk)
|
||||
file_extracted += len(chunk)
|
||||
extracted_size += len(chunk)
|
||||
current_time = time.time()
|
||||
with lock:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
|
||||
if "url" in entry and entry["url"] == url:
|
||||
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
progress_percent = max(0, min(100, progress_percent))
|
||||
entry["status"] = "Extracting"
|
||||
entry["progress"] = progress_percent
|
||||
entry["message"] = "Extraction en cours"
|
||||
if current_time - last_save_time >= save_interval:
|
||||
save_history(config.history)
|
||||
last_save_time = current_time
|
||||
config.needs_redraw = True
|
||||
break
|
||||
os.chmod(file_path, 0o644)
|
||||
# Vérifier si c'est un dossier xbox et le traiter si nécessaire
|
||||
xbox_dir = os.path.join(os.path.dirname(os.path.dirname(config.APP_FOLDER)), "xbox")
|
||||
if dest_dir == xbox_dir:
|
||||
# Lister les ISO après extraction
|
||||
iso_after = set()
|
||||
for root, dirs, files in os.walk(dest_dir):
|
||||
for file in files:
|
||||
if file.lower().endswith('.iso'):
|
||||
iso_after.add(os.path.abspath(os.path.join(root, file)))
|
||||
new_isos = list(iso_after - iso_before)
|
||||
if new_isos:
|
||||
success, error_msg = handle_xbox(dest_dir, new_isos)
|
||||
if not success:
|
||||
return False, error_msg
|
||||
else:
|
||||
logger.warning("Aucun nouvel ISO détecté après extraction pour conversion Xbox.")
|
||||
# On ne retourne pas d'erreur fatale ici, on continue
|
||||
|
||||
try:
|
||||
os.remove(zip_path)
|
||||
@@ -657,7 +668,7 @@ def handle_ps3(dest_dir):
|
||||
return True, None
|
||||
|
||||
|
||||
def handle_xbox(dest_dir):
|
||||
def handle_xbox(dest_dir, iso_files):
|
||||
"""Gère la conversion des fichiers Xbox extraits."""
|
||||
logger.debug(f"Traitement spécifique Xbox dans: {dest_dir}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user