This commit is contained in:
Foufure13
2025-02-19 17:47:19 +01:00
parent fc2547c45c
commit ddbac35391
21 changed files with 470 additions and 140 deletions
+246 -99
View File
@@ -11,6 +11,9 @@ import shutil
import datetime
import glob
import threading
import asyncio
from urllib.parse import urlparse
from pytmi import Client
# import keyboard
from pynput import keyboard
from threading import Thread, Semaphore
@@ -27,6 +30,10 @@ import pty
console = Console()
def sleep_control(time_sleep, running):
if running:
time.sleep(time_sleep)
def hprint(color, texte):
timestamp = datetime.datetime.now().strftime("%Hh %Mm %Ss")
console.print("[bold "+color+"] ["+timestamp+"] "+texte+" [/bold "+color+"]")
@@ -35,6 +42,30 @@ def sprint(script_name,color, texte):
timestamp = datetime.datetime.now().strftime("%Hh %Mm %Ss")
console.print("[bold "+color+"] ["+timestamp+"] ("+script_name+") "+texte+" [/bold "+color+"]")
def debug_print(TAG, texte_print, type_debug,script_name):
timestamp = datetime.datetime.now().strftime("%Hh %Mm %Ss")
type_color = "blue"
# more color
# DARKCYAN
if TAG == "e": # Erreur
type_color = "red"
console.print("[bold "+type_color+"] ["+timestamp+"] ("+script_name+") "+texte_print+" [/bold "+type_color+"]")
elif TAG == "w": # Warning
type_color = "yellow"
console.print("[bold "+type_color+"] ["+timestamp+"] ("+script_name+") "+texte_print+" [/bold "+type_color+"]")
elif TAG == "g": # Succes
type_color = "green"
console.print("[bold "+type_color+"] ["+timestamp+"] ("+script_name+") "+texte_print+" [/bold "+type_color+"]")
elif TAG == "i": # Info
type_color = "cyan"
console.print("[bold "+type_color+"] ["+timestamp+"] ("+script_name+") "+texte_print+" [/bold "+type_color+"]")
elif TAG == "v" and type_debug != "Info": # Verbos
type_color = "magenta"
console.print("[bold "+type_color+"] ["+timestamp+"] ("+script_name+") "+texte_print+" [/bold "+type_color+"]")
elif TAG == "d" and type_debug != "Verbos" and type_debug != "Info": # Debug
type_color = "blue"
console.print("[bold "+type_color+"] ["+timestamp+"] ("+script_name+") "+texte_print+" [/bold "+type_color+"]")
def del_file(dossier, file):
motif = os.path.join(dossier, file)
@@ -42,6 +73,15 @@ def del_file(dossier, file):
os.remove(file)
hprint("",f"file deleted : {file}")
def del_pathfile(file_path):
try:
os.remove(file_path)
hprint("green", "Fichier Suprimé : " +file_path)
except Exception as e:
hprint("red", "del_file Error : "+ str(e))
# Supprimer le fichier après le traitement
def get_value_json(var_name, config):
return config.get(var_name)
@@ -176,6 +216,10 @@ storage = PersistentStorage()
################### RecordTwitch ########################
class RecordTwitch:
def __init__(self, channel_name, record_time):
@@ -184,6 +228,7 @@ class RecordTwitch:
self.record_time = record_time
self.max_timerecordfile = 30
self.running = True
self.type_debug = "Debug"
self.script_name = "twitch_record"
if (self.record_time == -1):
self.record_time = 9999
@@ -239,12 +284,12 @@ class RecordTwitch:
def loop_run(self, intervalle):#boucle pour déplacer les fichier fini enregistrement
time.sleep(30) # attente début du script complet et enregistrement
time.sleep(20) # attente début du script complet et enregistrement
try:
while self.running:
self.verif_record_move()
sprint(self.script_name,"yellow", f"wait {intervalle}s for next scan file completed.")
time.sleep(intervalle)
# sprint(self.script_name,"yellow", f"wait {intervalle}s for next scan file completed.")
sleep_control(intervalle,self.running)
#except KeyboardInterrupt:
except Exception as e:
sprint(self.script_name,"red", "STOP loop_run Error : "+ str(e))
@@ -253,7 +298,8 @@ class RecordTwitch:
def verif_record_move(self, dir_inrecord="in_record", dir_record="record"):
sprint(self.script_name,"green", "start verif_record_move")
# sprint(self.script_name,"green", "start verif_record_move")
debug_print("v", "Verif_record_move Start", self.type_debug, self.script_name)
if not os.path.exists(dir_record):
os.makedirs(dir_record)
sprint(self.script_name,"green", "création du dossier : "+dir_record)
@@ -267,25 +313,29 @@ class RecordTwitch:
chemin_destination = os.path.join(dir_record, fileto_move)
# move file
shutil.move(chemin_source, chemin_destination)
sprint(self.script_name,"green",f"File moved: {fileto_move}")
else:
sprint(self.script_name,"yellow","Not enough files to compare.")
# sprint(self.script_name,"green",f"File moved: {fileto_move}")
debug_print("d", f"File moved: {fileto_move}", self.type_debug, self.script_name)
def compteur(self ):
seconds = 0
loop = 0
def compteur(self):
self.seconds = 0
self.loop = 0
while self.running:
time.sleep(1) # Attend une seconde
if (seconds < 10):
print(f"\033[94mRecording time: 0{seconds}s | file : {loop}\033[0m", end='\r', flush=True) # Réinitialise la ligne à chaque fois
else:
print(f"\033[94mRecording time: {seconds}s | file : {loop}\033[0m", end='\r', flush=True) # Réinitialise la ligne à chaque fois
seconds += 1
if seconds == self.max_timerecordfile:
loop +=1
seconds = 0 # Réinitialise le compteur après 60 seconds
# if (self.seconds < 10):
# print(f"\033[94mRecording time: 0{self.seconds}s | file : {self.loop}\033[0m", end='\r', flush=True) # Réinitialise la ligne à chaque fois
# else:
# print(f"\033[94mRecording time: {self.seconds}s | file : {self.loop}\033[0m", end='\r', flush=True) # Réinitialise la ligne à chaque fois
self.seconds += 1
if self.seconds == self.max_timerecordfile:
self.loop +=1
self.seconds = 0 # Réinitialise le compteur après 60 seconds
def get_seconde_compteur(self):
return self.seconds
def get_loop_compteur(self):
return self.loop
def record_audio(self):
output_directory = "record"
@@ -293,8 +343,6 @@ class RecordTwitch:
os.makedirs(output_directory, exist_ok=True)
os.makedirs(in_record_directory, exist_ok=True)
timestamp_complet = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
timestamp = datetime.datetime.now().strftime("%Y-%m-%d")
@@ -328,7 +376,7 @@ class RecordTwitch:
loop_run = Thread(target=self.loop_run, args=(20,), daemon=True)
loop_run.start()
#loop_run.join() # Wait for the recording to finish
sprint(self.script_name,"cyan","Enregistrement terminé, le programme va se terminer.")
# sprint(self.script_name,"cyan","Enregistrement terminé, le programme va se terminer.")
@@ -361,10 +409,11 @@ class RecordTwitch:
class Subtitle_translation:
def __init__(self, pathjson):
self.script_name = "translation"
self.type_debug = "Debug"
self.pathjson = pathjson
self.reload_json()
self.dir_whisperX = "whisperX"
self.filename_memory = "subtitle_" +get_current_date()
self.filename_memory = "subtitle_data"
self.subtitle = {}
self.all_subtitle = ""
self.is_running = True
@@ -372,8 +421,8 @@ class Subtitle_translation:
self.language = get_value_json("language", self.config)
self.dir_record = "record"
def stop(self):
sprint(self.script_name,"yellow",f"Arrêt loop subtitle")
def stop(self):
sprint(self.script_name,"red",f"Arrêt loop subtitle")
self.is_running = False
if self.loop_thread.is_alive():
# self.loop_thread.join() # Attend la fin du thread pour un arrêt propre
@@ -390,17 +439,15 @@ class Subtitle_translation:
for file in os.listdir(self.dir_record):
file_path = os.path.join(self.dir_record, file)
if os.path.isfile(file_path):
sprint(self.script_name,"blue",f"File find -> transcribe.")
# sprint(self.script_name,"blue",f"File find -> transcribe.")
debug_print("d", "Creation subtitle : "+file_path, self.type_debug, self.script_name)
# Exécuter une fonction sur le fichier
self.auto_create_subtitle(file)
# Supprimer le fichier après le traitement
os.remove(file_path)
print(f"Fichier {file_path} supprimé.")
del_pathfile(file_path)
def remove_repetitions(self, message):
sprint(self.script_name,"yellow", "remove_repetitions start for: \n" +message)
# sprint(self.script_name,"yellow", "remove_repetitions start for: \n" +message)
words = message.split() # Découpe le texte en mots
seen = set() # Pour suivre les mots déjà rencontrés
result = [] # Stocke les mots sans répétition
@@ -408,11 +455,10 @@ class Subtitle_translation:
if word not in seen:
result.append(word)
seen.add(word)
sprint(self.script_name,"green", "remove_repetitions cleaned text : \n" +" ".join(result))
# sprint(self.script_name,"green", "remove_repetitions cleaned text : \n" +" ".join(result))
return " ".join(result)## str this switch
def print_allsubtitle(self):
os.system('clear')
sprint(self.script_name,"yellow", "\n\n All subtitle Print\n ")
for key, text in self.subtitle.items():
sprint(self.script_name,"yellow", " time "+ str(key)+" = "+ text+" \n\n")
@@ -431,18 +477,14 @@ class Subtitle_translation:
def auto_create_subtitle(self,file):
sprint(self.script_name,"green",f"start auto_create_subtitle")
# debug_print("v", "auto_create_subtitle Start", self.type_debug,self.script_name)
del_file("","*.txt")
del_file("","*.srt")
del_file("","*.vtt")
del_file("","*.tsv")
del_file("","*.json")
# subtitle_directory = "subtitle"
# os.makedirs(subtitle_directory, exist_ok=True)
# os.chdir(subtitle_directory)
del_file("","*.json")
speak_found = ""
sprint(self.script_name,"green", "translation : "+file+" ")
# sprint(self.script_name,"green", "translation : "+file+" ")
# whisperx --language fr --compute_type float32 ../record/final_output_audio.mp3
# record_twitch = ['whisperx', '--language',self.language, '--compute_type','float32', '../'+self.dir_record+"/"+file] #ancienne version
# record_twitch = ['whisper', '--language',self.language, '../'+self.dir_record+"/"+file,"--device","cuda"]
@@ -460,30 +502,26 @@ class Subtitle_translation:
speak_found = self.remove_repetitions(speak_found)
self.all_subtitle += speak_found+"\n"
current_time = get_current_time()
# if self.subtitle:
self.subtitle[str(current_time)] = speak_found
storage.write(self.filename_memory, str(current_time), speak_found)
sprint(self.script_name,"yellow", "parole du streamer : \n" +speak_found)
# attention a No active speech found in audio
# os.chdir("../")
sprint(self.script_name,"green","finish create_subtitle")
def main_loop(self):
time.sleep(45)
time.sleep(25)
sprint(self.script_name,"green", "start main boucle_traitement record")
try:
while self.is_running:
sprint(self.script_name,"blue",f"wait {self.loop_timer}s loop main translation.")
# Attend 'intervalle' secondes avant la prochaine itération
# debug_print("v", "main_loop record Start", self.type_debug,self.script_name)
while self.is_running:
try:
# sprint(self.script_name,"blue",f"wait {self.loop_timer}s loop main translation.")
self.verif_file_transcribe()
time.sleep(self.loop_timer)
# except KeyboardInterrupt:
# print("Arrêt du script.")
except Exception as e:
sprint(self.script_name,"red", "STOP main_loop Error : "+ str(e))
# #console.print("[bold green]Enregistrement terminé, le programme va se terminer.[/bold green]")
sleep_control(self.loop_timer,self.is_running)
except Exception as e:
sprint(self.script_name,"red", "STOP main_loop Error : "+ str(e))
#console.print("[bold green]Enregistrement terminé, le programme va se terminer.[/bold green]")
def start_main_loop(self):
"""Lance dans un thread pour ne pas bloquer le code principal."""
@@ -525,8 +563,9 @@ class Subtitle_translation:
class IA_generator:
def __init__(self,pathjson):
self.type_debug = "Debug"
self.script_name = "IA_generator"
self.filename_memory = "IA_generator_" +get_current_date()
self.filename_memory = "IA_generator"
self.pathjson = pathjson
self.reload_json()
self.subtitle = {}
@@ -541,7 +580,7 @@ class IA_generator:
self.bad_answer = get_value_json("bad_answer", self.config)
def stop(self):
sprint(self.script_name,"yellow",f"Arrêt loop subtitle")
sprint(self.script_name,"red",f"Arrêt loop subtitle")
self.ia_running = False
if self.loop_thread.is_alive():
# self.loop_thread.join() # Attend la fin du thread pour un arrêt propre
@@ -568,7 +607,6 @@ class IA_generator:
return ''.join(values[-n:])
def print_allgeneration(self):
os.system('clear')
sprint(self.script_name,"yellow", "\n\n Print All response_generation \n ")
for key, text in self.response_generation.items():
sprint(self.script_name,"yellow", " key = "+ str(key)+" = "+ text+" \n\n")
@@ -580,9 +618,25 @@ class IA_generator:
return ""
# Récupère la dernière clé et valeur
key, last_generation = list(self.response_generation.items())[-1]
sprint(self.script_name,"magenta", f"Dernier response_generation key : {key} = {last_generation}")
# sprint(self.script_name,"magenta", f"Dernier response_generation key : {key} = {last_generation}")
return last_generation
def get_last_subtitle(self):
data = storage.read("subtitle_data")
if data: # Vérifie si data n'est pas vide
# Trie les clés et récupère la dernière
sorted_keys = sorted(data.keys())
last_key = sorted_keys[-1]
self.streamer_word = data[last_key]
# sprint(self.script_name,"blue", "get_last_subtitle : " + data[last_key])
else:
self.generation_text = ""
def clear_response(self, text):
# Vérifie si ":" est dans la chaîne de caractères
if ":" in text:
@@ -593,7 +647,9 @@ class IA_generator:
return text
def imagine_response(self):
sprint(self.script_name,"green",f"start imagine_response\n")
# sprint(self.script_name,"green",f"start imagine_response\n")
debug_print("v", "start imagine_response", self.type_debug,self.script_name)
# sprint(self.script_name,"magenta",f"streamer_word : \n"+str(self.streamer_word))
# sprint(self.script_name,"magenta",f"streamer_word : \n"+str(len(self.streamer_word[0])))
if str(self.streamer_word) == "":
@@ -602,20 +658,27 @@ class IA_generator:
prompt_gpt = ['tgpt','-q','-w', '"'+self.list_prompt[self.index_prompt]+' "'+ str(self.streamer_word)+'"']
sprint(self.script_name,"DARKCYAN","index preprompt : "+str(self.index_prompt)+"\n")
sprint(self.script_name,"DARKCYAN","\nPrompt demandé : "+str(prompt_gpt)+"\n")
# sprint(self.script_name,"DARKCYAN","index preprompt : "+str(self.index_prompt)+"\n")
# sprint(self.script_name,"DARKCYAN","\nPrompt demandé : "+str(prompt_gpt)+"\n")
process = subprocess.run(prompt_gpt, capture_output=True, text=True, check=False)
text_generation = process.stdout.replace("'", "").replace('"', "").replace("\n", "")
# sprint(self.script_name,"CYAN","réponse imaginé sans formatage : \n"+text_generation+"\n\n") #debug
textfinal = self.clear_response(text_generation)
# nombre_de_mots = len(textfinal.split())
nombre_de_mots = len(textfinal.split())
#TODO amélioration condition de génération
# if nombre_de_mots>100:
# sprint(self.script_name,"red","réponse prompt trop grande : "+str(textfinal))
# self.change_prompt()
# self.imagine_response()
# return
# nombre_de_caracteres = len(textfinal)
self.last_streamer_word = self.streamer_word
sprint(self.script_name,"CYAN","réponse imaginé : \n"+textfinal+"\n\n")
# sprint(self.script_name,"CYAN","réponse imaginé : \n"+textfinal+"\n\n")
debug_print("d", "Réponse imaginé : \n"+textfinal, self.type_debug,self.script_name)
self.change_prompt()
key = get_current_time()
self.response_generation[str(key)] = textfinal
storage.write(self.filename_memory, str(key), textfinal) #ecrire dans la mémoire fichier
@@ -628,22 +691,28 @@ class IA_generator:
return ''.join(values[-n:])
def main_ask(self, streamer_word_text):
sprint(self.script_name,"blue", "imagine_response start")
# sprint(self.script_name,"blue", "imagine_response start")
debug_print("i", "PRINCIPAL START imagine_response", self.type_debug,self.script_name)
self.streamer_word = streamer_word_text
imagine_response_thread = Thread(target=self.imagine_response)
imagine_response_thread.start()
def main_loop_ia(self):
sprint(self.script_name,"blue", "main_loop_ia imagine_response start")
time.sleep(20)
# sprint(self.script_name,"blue", "main_loop_ia imagine_response start")
# debug_print("v", "main_loop_ia imagine_response start", self.type_debug,self.script_name)
try:
while self.ia_running:
time.sleep(self.loop_timer_ia)
sprint(self.script_name,"yellow",f"wait {self.loop_timer_ia}s main_loop_ia imagine_response.")
# sprint(self.script_name,"yellow",f"wait {self.loop_timer_ia}s main_loop_ia imagine_response.")
self.get_last_subtitle()
if(self.last_streamer_word != self.streamer_word):
self.imagine_response()
self.last_streamer_word = self.streamer_word
else:
sprint(self.script_name,"yellow",f"génération déja créer")
# sprint(self.script_name,"yellow",f"génération déja créer")
debug_print("d", "génération déja créer", self.type_debug,self.script_name)
sleep_control(self.loop_timer_ia,self.ia_running)
# except KeyboardInterrupt:
# print("Arrêt du script.")
except Exception as e:
@@ -674,8 +743,35 @@ class IA_generator:
# class messageTwitch:
# def __init__(self, channel_name, pseudo, token):
# self.channel_name = channel_name
# self.pseudo = pseudo
# self.token = token
# async def send_message_to_twitch_stream(self, message):
# parsed_url = urlparse(self.channel_name)
# channel = parsed_url.path.lstrip('/')
# pseudo = self.tw_acc_pseudo
# token = self.tw_acc_token
# message = self.ram_msgnow
# async with Client() as client:
# try:
# print("Tentative de login")
# await client.login_oauth(token, pseudo)
# print("Tentative de join")
# await client.join(channel)
# print("Tentative d'envoi de message")
# await client.send_message(message)
# print("Message envoyé avec succès.")
# sleep(45) # Utilisez asyncio.sleep pour éviter de bloquer l'event loop
# await client.part(channel)
# print("Déconnecté.")
# except Exception as e:
# print(f"Erreur lors de l'interaction avec Twitch: {type(e).__name__}, {e}")
@@ -695,6 +791,7 @@ class messageTwitch:
self.path_file_config_user = config_user
self.channel_name = channel_name
self.indexuser = 0
self.type_debug = "Debug"
with open(self.path_file_config_user, 'r') as file:
self.userjson = json.load(file)
self.totaluser = len(self.userjson)
@@ -703,6 +800,7 @@ class messageTwitch:
self.tw_acc_token = get_value_json_list(self.indexuser, "tw_acc_token", self.userjson)
self.charactere = get_value_json_list(self.indexuser, "charactere", self.userjson)
self.ram_msgnow = ""
self.generation_text = ""
self.last_respond_word = ""
@@ -718,8 +816,9 @@ class messageTwitch:
def change_user(self):
# commented for pausing
debug_print("v", "Changement User Twitch", self.type_debug, self.script_name)
if (self.totaluser != 1): # si la liste ne fait pas que 1 de taille
if(self.totaluser > self.indexuser): # si la taille de liste est plus grande que lindex
if(self.totaluser-1 > self.indexuser): # si la taille de liste est plus grande que lindex
self.indexuser = self.indexuser + 1
else :
self.indexuser = 0
@@ -729,14 +828,6 @@ class messageTwitch:
self.charactere = get_value_json_list(self.indexuser, "charactere", self.userjson)
def set_user(self,index_user):
self.indexuser = index_user
self.tw_acc_pseudo = get_value_json_list(self.indexuser, "tw_acc_pseudo", self.userjson)
self.tw_acc_token = get_value_json_list(self.indexuser, "tw_acc_token", self.userjson)
self.charactere = get_value_json_list(self.indexuser, "charactere", self.userjson)
def conversation(self):
user1 = 1
user2 = 2
@@ -757,44 +848,100 @@ class messageTwitch:
def send_message(self, Message_text):
hprint("cyan","start send_message")
try:
command = '-pseudo "'+self.tw_acc_pseudo+'" -token "'+self.tw_acc_token+'" -twitchname "'+self.channel_name+'" -message " '+self.charactere+' '+Message_text+'"'
message_tosend = 'python send_message.py '+ str(command)
debug_print("d", f"send message = "+str(message_tosend), self.type_debug, self.script_name)
sprint(self.script_name,"cyan",f"send message = "+str(message_tosend))
subprocess.run(message_tosend, shell=True)
command = '-pseudo "'+self.tw_acc_pseudo+'" -token "'+self.tw_acc_token+'" -twitchname "'+self.channel_name+'" -message " '+self.charactere+' '+Message_text+'"'
message_tosend = 'python send_message.py '+ str(command)
hprint("cyan",f"send message = "+str(message_tosend))
subprocess.run(message_tosend, shell=True)
self.last_respond_word = Message_text
hprint("cyan",f"Finnish send_message message")
# self.ram_msgnow = Message_text
# await self.send_message_to_twitch_stream()
# asyncio.run(self.send_message_to_twitch_stream())
self.last_respond_word = Message_text
# sprint(self.script_name,"cyan",f"Finnish send_message message")
debug_print("g", "Finnish send_message message", self.type_debug,self.script_name)
except Exception as e:
sprint(self.script_name,"red", "send_message Error : "+ str(e))
async def send_message_to_twitch_stream(self):
parsed_url = urlparse(self.channel_name)
channel = parsed_url.path.lstrip('/')
pseudo = self.tw_acc_pseudo
token = self.tw_acc_token
message = self.ram_msgnow
async with Client() as client:
try:
print("Tentative de login")
await client.login_oauth(token, pseudo)
# while self.message_running:
print("Tentative de join")
await client.join(channel)
print("Tentative d'envoi de message")
await client.send_message(message)
print("Message envoyé avec succès.")
sleep_control(25,self.message_running)
await client.part(channel)
print("disconnect.")
except Exception as e:
print(f"Erreur lors de l'interaction avec Twitch: {type(e).__name__}, {e}")
def get_last_generation(self):
data = storage.read("IA_generator_" +get_current_date())
# Trie les clés et récupère la dernière
sorted_keys = sorted(data.keys())
last_key = sorted_keys[-1]
self.generation_text = data[last_key]
data = storage.read("IA_generator")
if data: # Vérifie si data n'est pas vide
# Trie les clés et récupère la dernière
sorted_keys = sorted(data.keys())
last_key = sorted_keys[-1]
self.generation_text = data[last_key]
# sprint(self.script_name,"blue", "get_last_generation : " + data[last_key])
else:
debug_print("e", "get_last_generation = No generation: ", self.type_debug, self.script_name)
# sprint(self.script_name,"red", "get_last_generation = No generation: ")
self.generation_text = ""
def start_main_loop_respond(self):
time.sleep(20) #wait starting script
sprint(self.script_name,"blue", "main_loop_ia imagine_response start")
time.sleep(40) #wait starting script
sprint(self.script_name,"blue", "main_loop_respond start")
try:
while self.message_running:
time.sleep(20)
self.get_last_generation()
if (self.generation_text == ""):
sprint(self.script_name,"yellow",f"pas encore de génération")
sleep_control(45,self.message_running)
continue
if(self.last_respond_word != self.generation_text):
sprint(self.script_name,"green",f"\n\n ENVOIE MESSAGE : \n "+self.generation_text)
# sprint(self.script_name,"green",f"\n\n ENVOIE MESSAGE : \n "+self.generation_text)
debug_print("v", "Début envoie message : "+self.generation_text, self.type_debug, self.script_name)
self.send_message(self.generation_text) # envoie de message
self.last_respond_word = self.generation_text # mise a jour du dernier message envoyé
self.change_user() # cahngement de user
else:
sprint(self.script_name,"yellow",f"génération déja créer")
except KeyboardInterrupt:
print("Arrêt du script.")
# sprint(self.script_name,"yellow",f"génération déja envoyé")
debug_print("d", "génération déja envoyé", self.type_debug, self.script_name)
sleep_control(20,self.message_running)
except Exception as e:
sprint(self.script_name,"red", "STOP main_loop_respond Error : "+ str(e))
def start_loop_respond(self):
"""Lance dans un thread pour ne pas bloquer le code principal."""
self.message_running = True