Le projet Save Videos est une application web inspiré des plugins yt-dlp et X_Media_download pour permettre aux utilisateurs de sauvegarder, organiser et visualiser des vidéos provenant de plateformes comme YouTube et X (anciennement Twitter). Conçue avec un accent sur la simplicité d’utilisation et la sécurité, cette plateforme répond aux besoins des individus souhaitant archiver du contenu multimédia de manière fiable et privée. L’application intègre des fonctionnalités avancées tout en respectant les principes de confidentialité et de protection des données. Ce résumé présente les fonctionnalités principales, les mesures de sécurité implémentées, ainsi que les perspectives d’évolution futures.
Fonctionnalités Actuelles
L’application offre un ensemble de fonctionnalités essentielles pour une gestion efficace des vidéos :
Inscription et Connexion Sécurisées : Les utilisateurs peuvent créer un compte avec un nom d’utilisateur, un email et un mot de passe haché. Une vérification par code de confirmation envoyé par email est requise pour activer le compte, garantissant l’authenticité des adresses fournies.
Ajout et Édition de Vidéos : Possibilité d’ajouter des vidéos via URL (YouTube ou X) ou téléversement local (formats MP4, AVI, MKV). Les catégories multiples (séparées par virgules), commentaires et miniatures sont supportés. Une option permet de copier localement les vidéos pour éviter la censure.
Visualisation et Gestion : Une page dédiée affiche les vidéos en mode inline (iframe pour YouTube, tag video pour locales). Les utilisateurs peuvent éditer, supprimer ou rendre publiques leurs vidéos. Un système de catégories facilite la navigation.
Téléchargement Sécurisé : Les vidéos locales sont servies via un script dédié qui vérifie les permissions, empêchant les accès non autorisés.
Support Multimédia : Intégration de miniatures automatiques pour YouTube/X, et analyse antivirus (ClamAV) pour les uploads.
Vidéo publique , apparaît sur la home page , vidéo privé n’apparaît que sur la page de son utilisateur
Importation par playlist youtube
Importation de toutes les vidéos publiques d’un user youtube
Ces fonctionnalités sont accessibles via une interface responsive, stylisée avec Tailwind CSS, assurant une expérience utilisateur fluide sur tous les appareils.
Mesures de Sécurité Mises en Place
La sécurité est au cœur du projet, avec des implémentations robustes pour protéger les données et prévenir les attaques courantes :
Protection contre les Attaques CSRF : Tous les formulaires (inscription, connexion, ajout/édition de vidéos) incluent un jeton CSRF généré dynamiquement et validé côté serveur.
CAPTCHA Open Source : Utilisation de Securimage pour empêcher les inscriptions et connexions automatisées par bots, sans dépendance à des services externes comme Google.
Vérification par Email : Les nouveaux comptes nécessitent une confirmation via un code à 6 chiffres envoyé par email, avec expiration après une heure.
Contrôle d’Accès aux Fichiers : Les vidéos uploadées ne sont plus accessibles directement ; un script PHP vérifie l’authentification et les permissions avant de servir le contenu.
Analyse Antivirus : Tous les fichiers téléversés sont scannés avec ClamAV avant stockage.
Hachage des Mots de Passe : Utilisation de `password_hash` pour stocker les mots de passe de manière sécurisée.
Validation des Entrées : Filtrage strict des URLs, emails et fichiers pour prévenir les injections et les uploads malveillants.
Ces mesures alignent l’application sur les standards OWASP, réduisant significativement les risques d’exploitation.
Fonctionnalités Futures Envisagées
Pour étendre les capacités de l’application et renforcer sa robustesse, plusieurs améliorations sont prévues :
Améliorations Cryptographiques : Stockage des secrets (comme les mots de passe de base de données) via variables d’environnement, et enforcement de HTTPS pour les sessions.
Rate Limiting : Limitation des tentatives de connexion/inscription pour contrer les attaques par force brute.
Gestion Avancée des Uploads : Vérification du type MIME réel et quotas de stockage par utilisateur.
Fonctionnalités Utilisateur : Recherche avancée des vidéos, partage sécurisé, et intégration de notifications par email pour les mises à jour.
Mises à Jour Automatisées : Système de monitoring des dépendances pour détecter et patcher les vulnérabilités.
Headers de Sécurité HTTP : Ajout de CSP, HSTS et X-Frame-Options pour prévenir les attaques comme le clickjacking.
Amélioration du design
Ajout d’autres plateformes
Extension pour firefox : qui permettra de capturer l’URL d’une vidéo sur YouTube ou X et de l’envoyer directement à ce serveur
Ces évolutions viseront à maintenir l’application à jour face aux menaces émergentes, tout en améliorant l’expérience utilisateur.
Conclusion
Le projet Save Videos représente une solution pratique et sécurisée pour l’archivage de vidéos, avec un focus sur la confidentialité et la facilité d’utilisation. Grâce aux fonctionnalités actuelles et aux mesures de sécurité robustes, il offre une base solide pour les utilisateurs. Les développements futurs renforceront encore sa résilience, en faisant un outil indispensable pour la gestion multimédia.
x_downdl.py , script python permettant de télécharger des videos poster sur t sur X.com
Extrayant l’ID du post à partir d’une URL.
Récupérant un « guest token » pour accéder à l’API sans compte.
Analysant les données du post pour identifier les images et vidéos.
Téléchargeant les fichiers en qualité optimale (images en original, vidéos avec le bitrate le plus élevé).
Un script Python robuste pour analyser des images de disquette .dsk (Amstrad CPC), détecter le répertoire, reconstituer la liste des fichiers et générer automatiquement un cat.txt lisible. Gère les formats variés, les disques protégés et les images corrompues.
Blackjack : le but est d’avoir une main plus proche de 21 que le croupier sans dépasser 21.
Chaque carte a une valeur dans le comptage :
Cartes basses (2 à 6) = +1
Cartes neutres (7 à 9) = 0
Cartes hautes (10, figures, as) = -1
Si le reste du paquet est riche en cartes hautes (10, as), le joueur a plus de chances de faire blackjack et de gagner.
Si le reste est riche en cartes basses, le croupier a un avantage plus important.
L’astuce : augmenter les mises quand le paquet est favorable, et miser peu quand il ne l’est pas.
import randomimport loggingimport tkinter as tkfrom tkinter import messageboxfrom tkinter import ttkfrom collections import dequeimport osimport sysimport argparse# -------------------------# Import optionnels# -------------------------# PIL (images de cartes)try:from PIL import Image, ImageTk PIL_AVAILABLE =TrueexceptException: PIL_AVAILABLE =False# Matplotlib (graphique)try:import matplotlib.pyplot as plt MATPLOTLIB_AVAILABLE =TrueexceptException: MATPLOTLIB_AVAILABLE =False# -------------------------# Configuration du logging# -------------------------logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s] %(message)s')logger = logging.getLogger('BlackjackTrainer')# Mode debug détaillé (comportement et prints supplémentaires)DEBUG_MODE =False# -------------------------# Constantes et utilitaires# -------------------------RANKS =['A','2','3','4','5','6','7','8','9','10','J','Q','K']SUITS =['S','H','D','C']# Valeur blackjack nominale (A peut être 11 ou 1 selon l'ajustement)VALUE_MAP ={'A':11,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'10':10,'J':10,'Q':10,'K':10}# Hi-Lo count mappingHI_LO_MAP ={'2':1,'3':1,'4':1,'5':1,'6':1,'7':0,'8':0,'9':0,'10':-1,'J':-1,'Q':-1,'K':-1,'A':-1}# Nom du dossier d'imagesCARD_IMAGE_DIR ='cards'CARD_IMAGE_SIZE =(72,96)# taille d'affichage (largeur, hauteur)# Nombre max de slots (réduit pour éviter clipping)MAX_CARD_SLOTS =6# -------------------------# Fonctions utilitaires# -------------------------defbuild_shoe(num_decks):"""Construit une 'shoe' (paquet multiple) sous forme deque de tuples (rank, suit).""" shoe =[]for _ inrange(num_decks):for s in SUITS:for r in RANKS: shoe.append((r, s)) random.shuffle(shoe)returndeque(shoe)defcard_value(card):"""Retourne la valeur du rang (A=11 par défaut, on ajustera après).""" r, _ = cardreturn VALUE_MAP[r]defcard_str(card):"""Représentation texte d'une carte (ex: 'A♠').""" r, s = card suit_symbol ={'S':'♠','H':'♥','D':'♦','C':'♣'}[s]returnf"{r}{suit_symbol}"defhi_lo_value(card):"""Valeur Hi-Lo d'une carte donnée (int).""" r, _ = cardreturn HI_LO_MAP[r]# -------------------------# Conseiller basique (Basic Strategy simplifiée)# -------------------------defbasic_strategy_advice(player_hand,dealer_upcard):""" Fournit un conseil simple: 'Hit' ou 'Stand' (et 'Double' si applicable). C'est une version simplifiée pour l'entraînement.""" total =sum(card_value(c)for c in player_hand) aces =sum(1for c in player_hand if c[0]=='A') soft =Falseif aces and total <=21: soft =True dealer_rank = dealer_upcard[0] dealer_val =10if dealer_rank in['10','J','Q','K']else(11if dealer_rank=='A'elseint(dealer_rank))if soft:if total >=19:return'Stand'if total ==18:if dealer_val in[9,10,11]:return'Hit'return'Stand'return'Hit'else:if total >=17:return'Stand'if total <=11:return'Hit'if12<= total <=16:if2<= dealer_val <=6:return'Stand'else:return'Hit'return'Hit'# -------------------------# Classe principale GUI# -------------------------classBlackjackTrainerGUI:def__init__(self,master,num_decks=6,base_bet=10):self.master = master master.title('Blackjack Trainer - Hi-Lo')# Paramètresself.num_decks = num_decksself.base_bet = base_betself.player_money =1000self.min_bet = base_betself.max_bet =10000self.debug = DEBUG_MODE# Delais configurables (ms)self.dealer_draw_delay_ms =700self.initial_dealer_start_delay_ms =150self.end_round_delay_ms =200self.player_end_delay_ms =800# pour afficher la carte du joueur avant résolution# Shoe et compteself.shoe =build_shoe(self.num_decks)self.running_count =0self.hands_played =0self.history_money =[self.player_money]self.history_count =[self.running_count]# Chargement images si possibleself.card_images ={}if PIL_AVAILABLE:self._load_card_images()else: logger.info('Pillow non disponible: affichage texte des cartes')# Construire UIself._build_ui()# Forcer une taille minimale pour éviter clipping des slotstry:self.master.minsize(480,260)exceptException:pass# Démarrer la première mainself.new_round()# -------------------------# Chargement images# -------------------------def_load_card_images(self): dir_path = CARD_IMAGE_DIRifnot os.path.isdir(dir_path): logger.warning(f"Dossier d'images '{dir_path}' introuvable. Utilisation du texte.")return loaded =0for r in RANKS:for s in SUITS: fname =f"{r}{s}.png" path = os.path.join(dir_path, fname)if os.path.isfile(path):try: im = Image.open(path).convert('RGBA') im = im.resize(CARD_IMAGE_SIZE, Image.LANCZOS)self.card_images[(r,s)]= ImageTk.PhotoImage(im) loaded +=1exceptExceptionas e: logger.warning(f"Erreur chargement image {path}: {e}")else:ifself.debug: logger.debug(f"Image non trouvée: {path}") logger.info(f"Images chargées: {loaded} cartes")# -------------------------# UI Builder# -------------------------def_build_ui(self):# Top info top = ttk.Frame(self.master,padding=8) top.pack(fill='x')self.info_var = tk.StringVar() info_label = ttk.Label(top,textvariable=self.info_var,font=('Courier',11)) info_label.pack(side='left',padx=6)# Buttons btn_frame = ttk.Frame(self.master,padding=6) btn_frame.pack(fill='x')self.hit_btn = ttk.Button(btn_frame,text='Hit',command=self.hit)self.stand_btn = ttk.Button(btn_frame,text='Stand',command=self.stand)self.new_btn = ttk.Button(btn_frame,text='Nouvelle main',command=self.new_round)if MATPLOTLIB_AVAILABLE:self.graph_btn = ttk.Button(btn_frame,text='Graphiques',command=self.show_graph)else:# bouton désactivé si matplotlib absentself.graph_btn = ttk.Button(btn_frame,text='Graphiques (requires matplotlib)',state='disabled',command=self._graph_unavailable)self.debug_btn = ttk.Button(btn_frame,text='Toggle Debug',command=self.toggle_debug)for w in(self.hit_btn,self.stand_btn,self.new_btn,self.graph_btn,self.debug_btn): w.pack(side='left',padx=4)# Board frames board = ttk.Frame(self.master,padding=6) board.pack(fill='both',expand=True) dealer_frame = ttk.LabelFrame(board,text='Croupier') dealer_frame.pack(fill='x',pady=4)# ici on crée des slots fixes pour le croupierself.dealer_canvas = ttk.Frame(dealer_frame)self.dealer_canvas.pack(fill='x',padx=6,pady=6)self.dealer_slots =[]for _ inrange(MAX_CARD_SLOTS): lbl = tk.Label(self.dealer_canvas,text='',borderwidth=1,relief='solid',width=8,height=4,padx=6,pady=6,bg='white',fg='black',anchor='center') lbl.pack(side='left',padx=3)self.dealer_slots.append(lbl) player_frame = ttk.LabelFrame(board,text='Joueur') player_frame.pack(fill='x',pady=4)self.player_canvas = ttk.Frame(player_frame)self.player_canvas.pack(fill='x',padx=6,pady=6)self.player_slots =[]for _ inrange(MAX_CARD_SLOTS): lbl = tk.Label(self.player_canvas,text='',borderwidth=1,relief='solid',width=8,height=4,padx=6,pady=6,bg='white',fg='black',anchor='center') lbl.pack(side='left',padx=3)self.player_slots.append(lbl)# Advice advice_frame = ttk.Frame(self.master,padding=6) advice_frame.pack(fill='x')self.advice_var = tk.StringVar() advice_label = ttk.Label(advice_frame,textvariable=self.advice_var,font=('Arial',11,'italic')) advice_label.pack(side='left')# Bet control bet_frame = ttk.Frame(self.master,padding=6) bet_frame.pack(fill='x') ttk.Label(bet_frame,text='Mise:').pack(side='left')self.bet_var = tk.IntVar(value=self.base_bet)self.bet_spin = ttk.Spinbox(bet_frame,from_=self.min_bet,to=self.max_bet,textvariable=self.bet_var,width=8)self.bet_spin.pack(side='left',padx=6)# -------------------------# Mécanique de jeu# -------------------------def_draw_from_shoe(self):"""Tire une carte et gère le reshuffle quand la shoe est entamée."""# Remélanger si il reste moins de 25% de la shoe (paramètre configurable)iflen(self.shoe)<int(0.25*52*self.num_decks): logger.info('Reshuffle: pas assez de cartes, reshuffle')self.shoe =build_shoe(self.num_decks)self.running_count =0 card =self.shoe.popleft()# Mettre à jour running countself.running_count +=hi_lo_value(card)ifself.debug: logger.debug(f'Draw card: {card_str(card)} | hi-lo {hi_lo_value(card)} | running_count {self.running_count}')self.history_count.append(self.running_count)return carddef_update_info(self):"""Met à jour la barre d'information en haut (argent, compte, true count, total joueur).""" player_total =self._hand_value(self.player_hand)ifhasattr(self,'player_hand')else0 true_count =self._true_count() suggested_bet =self._suggest_bet() info_text =(f"Argent: {self.player_money} "f"Compte (running): {self.running_count} "f"True Count: {true_count:.2f} "f"Mise suggérée: {suggested_bet} "f"Main joueur: {player_total}")self.info_var.set(info_text)def_update_advice(self):"""Met à jour le conseil de base affiché."""ifhasattr(self,'player_hand')andhasattr(self,'dealer_hand')andlen(self.dealer_hand)>0: advice =basic_strategy_advice(self.player_hand,self.dealer_hand[0])self.advice_var.set(f"Conseil (basic): {advice}")else:self.advice_var.set("")defnew_round(self):"""Commence une nouvelle main: distribue deux cartes au joueur et au croupier."""# Réactiver contrôles (garantie)self._enable_controls()ifself.player_money <=0: messagebox.showwarning('Fin',"Vous n'avez plus d'argent. Fin de la session.")return# Distributionself.player_hand =[self._draw_from_shoe(),self._draw_from_shoe()]self.dealer_hand =[self._draw_from_shoe(),self._draw_from_shoe()]# récupérer la mise **courante** choisie par l'utilisateurself.current_bet =int(self.bet_var.get())self.hands_played +=1ifself.debug: logger.debug(f'New round: player {self.player_hand}, dealer {self.dealer_hand}, bet {self.current_bet}') logger.debug(f'Dealer hand ranks: {[c[0]for c inself.dealer_hand]}')# Afficher la main du croupier avec la seconde carte cachée (placeholder)self._render_cards(hide_dealer_second=True)# Forcer le rendu immédiat de l'UItry:self.master.update_idletasks()exceptException:passself._update_info()self._update_advice()def_render_cards(self,hide_dealer_second=False):"""Met à jour les slots existants au lieu de recréer des widgets."""# Dealerfor i inrange(MAX_CARD_SLOTS): lbl =self.dealer_slots[i]if i <len(self.dealer_hand):# cas de la seconde carte cachéeif i ==1and hide_dealer_second: lbl.config(text='[??]',image='',borderwidth=1,relief='solid',bg='white',fg='black') lbl.image =Noneelse: card =self.dealer_hand[i]if card inself.card_images: lbl.config(image=self.card_images[card],text='',borderwidth=0,relief='flat',bg='white') lbl.image =self.card_images[card]else: lbl.config(text=card_str(card),image='',borderwidth=1,relief='solid',bg='white',fg='black') lbl.image =Noneelse:# slot vide lbl.config(text='',image='',borderwidth=0,relief='flat',bg='white') lbl.image =None# Playerfor i inrange(MAX_CARD_SLOTS): lbl =self.player_slots[i]if i <len(self.player_hand): card =self.player_hand[i]if card inself.card_images: lbl.config(image=self.card_images[card],text='',borderwidth=0,relief='flat',bg='white') lbl.image =self.card_images[card]else: lbl.config(text=card_str(card),image='',borderwidth=1,relief='solid',bg='white',fg='black') lbl.image =Noneelse: lbl.config(text='',image='',borderwidth=0,relief='flat',bg='white') lbl.image =None# Debug: log what est affiché dans les slotsifself.debug:try: dealer_texts =[self.dealer_slots[i].cget('text')for i inrange(min(MAX_CARD_SLOTS,max(1,len(self.dealer_hand)+1)))] logger.debug(f"Rendering dealer slots (texts): {dealer_texts}")exceptException:pass# Forcer rafraîchissement UItry:self.master.update_idletasks()exceptException:passdefhit(self):"""Le joueur tire une carte."""ifnotself._controls_enabled():returnself.player_hand.append(self._draw_from_shoe())ifself.debug: logger.debug(f'Player hits, hand now: {[card_str(c)for c inself.player_hand]}') total =self._hand_value(self.player_hand)# Toujours afficher la main complèteself._render_cards(hide_dealer_second=True)self._update_info()self._update_advice()# Si la main est finie (21 ou bust), attendre un petit délai avant de résoudre pour que l'utilisateur voie la carteif total >=21:self._disable_controls()self.master.after(self.player_end_delay_ms,self.resolve_round)defstand(self):"""Le joueur reste: croupier joue selon règle (stand sur soft 17)."""self._render_cards(hide_dealer_second=False)self._update_info()self._disable_controls()self.master.after(self.initial_dealer_start_delay_ms,self._dealer_play)def_dealer_play(self):ifself._hand_value(self.dealer_hand)<17:self.dealer_hand.append(self._draw_from_shoe())ifself.debug: logger.debug(f'Dealer draws: {card_str(self.dealer_hand[-1])}')self._render_cards(hide_dealer_second=False)self._update_info()self.master.after(self.dealer_draw_delay_ms,self._dealer_play)else:self.master.after(self.end_round_delay_ms,self._end_dealer_and_resolve)def_end_dealer_and_resolve(self):self._enable_controls()self.resolve_round()defresolve_round(self): player_total =self._hand_value(self.player_hand) dealer_total =self._hand_value(self.dealer_hand) bet =self.current_bet player_blackjack =(len(self.player_hand)==2and player_total ==21) dealer_blackjack =(len(self.dealer_hand)==2and dealer_total ==21)if player_blackjack andnot dealer_blackjack: payout =int(1.5* bet)self.player_money += payout result_text =f'Blackjack! Vous gagnez {payout}.'elif dealer_blackjack andnot player_blackjack:self.player_money -= bet result_text ='Le croupier a Blackjack. Vous perdez.'else:if player_total >21:self.player_money -= bet result_text ='Bust! Vous perdez.'elif dealer_total >21or player_total > dealer_total:self.player_money += bet result_text ='Vous gagnez.'elif player_total == dealer_total: result_text ='Push (égalité).'else:self.player_money -= bet result_text ='Vous perdez.'self.history_money.append(self.player_money) message =(f"{result_text}\nArgent restant: {self.player_money}\n"f"Compte running: {self.running_count} | True Count: {self._true_count():.2f}") messagebox.showinfo('Résultat', message)self.new_round()# -------------------------# Calculs & stratégies# -------------------------def_hand_value(self,hand): total =sum(card_value(c)for c in hand) aces =sum(1for c in hand if c[0]=='A')while total >21and aces: total -=10 aces -=1return totaldef_true_count(self): decks_rem =max(1.0,len(self.shoe)/52.0)returnself.running_count / decks_remdef_suggest_bet(self):"""Retourne la mise recommandée sans modifier la Spinbox de l'utilisateur.""" tc =self._true_count()if tc <=1: bet =self.base_betelse: level =int(tc) bet =min(self.player_money,self.base_bet * level)return bet# -------------------------# Contrôles utilitaires# -------------------------def_disable_controls(self):for w in(self.hit_btn,self.stand_btn,self.new_btn,self.graph_btn,self.debug_btn):try: w.state(['disabled'])exceptException:try: w.config(state='disabled')exceptException:passtry:self.bet_spin.config(state='disabled')exceptException:passdef_enable_controls(self):for w in(self.hit_btn,self.stand_btn,self.new_btn,self.graph_btn,self.debug_btn):try: w.state(['!disabled'])exceptException:try: w.config(state='normal')exceptException:passtry:self.bet_spin.config(state='normal')exceptException:passdef_controls_enabled(self):try:return'disabled'notinself.hit_btn.state()exceptException:try:returnself.hit_btn['state']!='disabled'exceptException:returnTrue# -------------------------# Graphiques & utilitaires# -------------------------defshow_graph(self):ifnot MATPLOTLIB_AVAILABLE: messagebox.showwarning('Graphiques indisponibles','matplotlib n\'est pas installé dans cet environnement.')return fig, ax1 = plt.subplots(figsize=(10,5)) ax1.plot(self.history_money,label='Argent du joueur') ax1.set_xlabel('Mains jouées') ax1.set_ylabel('Argent') ax1.legend(loc='upper left') ax2 = ax1.twinx() ax2.plot(self.history_count,label='Running Count',linestyle='--') ax2.set_ylabel('Running Count') ax2.legend(loc='upper right') plt.title('Historique: Argent et Running Count') plt.grid(True) plt.show()def_graph_unavailable(self): messagebox.showinfo('Graphiques désactivés','La fonctionnalité de graphiques nécessite matplotlib.\nInstallez-le via "pip install matplotlib" si vous voulez activer cette fonction.')deftoggle_debug(self):self.debug =notself.debug logger.setLevel(logging.DEBUG ifself.debug else logging.INFO) logger.info(f'Debug mode set to {self.debug}')# -------------------------# Tests unitaires (non-GUI)# -------------------------defrun_unit_tests():print('Running unit tests...') shoe =build_shoe(2)assertlen(shoe)==52*2,'build_shoe: taille incorrecte'assertcard_value(('A','S'))==11assertcard_value(('K','H'))==10asserthi_lo_value(('2','C'))==1asserthi_lo_value(('A','D'))==-1 advice =basic_strategy_advice([('Q','H'),('4','D')],('6','S'))assert advice =='Stand',f'Expected Stand, got {advice}' advice2 =basic_strategy_advice([('A','H'),('6','D')],('10','S'))assert advice2 =='Hit',f'Expected Hit, got {advice2}' advice3 =basic_strategy_advice([('10','H'),('9','D')],('7','C'))assert advice3 =='Stand',f'Expected Stand, got {advice3}'print('All non-GUI tests passed.')# -------------------------# Main# -------------------------if __name__ =='__main__': parser = argparse.ArgumentParser(description='Blackjack Trainer GUI') parser.add_argument('--test',action='store_true',help='Run non-GUI unit tests and exit') args = parser.parse_args()if args.test:run_unit_tests() sys.exit(0)try: root = tk.Tk()exceptExceptionas e:print('Impossible de démarrer l\'interface graphique:', e) sys.exit(1) gui =BlackjackTrainerGUI(root,num_decks=6,base_bet=10) root.mainloop()
Markdown and LaTeX Support: Full support for rich text and mathematical expressions.
RAG Integration: Advanced document interaction with Retrieval Augmented Generation, including local and remote document access.
Web Browsing: Integrate web content directly into chats.
Prompt Presets: Easy access to preset conversation starters.
RLHF Annotation: Feedback tools for training models using thumbs up/down and textual feedback.
Conversation Tagging: Simplifies searching and organizing chat records.
Model Management: Download, delete, or update Ollama models directly from the UI.
Multiple Model Support: Switch between different models for varied interactions.
Multimodal Interactions: Supports image generation and other multimodal functionalities.
Modelfile Builder: Create and customize modelfiles easily.
Collaborative Chat: Group conversations with multiple models.
Local Chat Sharing: Generate and share chat links for collaboration.
Voice Input and Text-to-Speech: Interact with models using voice; customize TTS settings.
Advanced Parameters Control: Customize conversation settings with detailed parameters.
OpenAI API Integration: Supports integration with OpenAI and other compatible APIs.
Multi-User Management: Administer user roles and access through a comprehensive admin panel.
Security Enhancements: Features like model whitelisting, role-based access, and backend reverse proxy support.
Multilingual Support: Available in multiple languages with ongoing expansion efforts.
Continuous Updates: Regular enhancements and new features.
# installer python 3.11 si pas déjà fait
sudo pacman -Syu python311
# créer un venv avec python 3.11
python3.11 -m venv ~/open-webui-venv
# activer le venv pour fish
source ~/open-webui-venv/bin/activate.fish
# vérifier que pip et python sont du venv
which python
which pip
# installer open-webui dans le venv
pip install open-webui
#Démarer open web-ui , part défaut port 8080
open-webui serve
#si besoin de spécifier autre port
open-webui serve --port 3000
Script python pour télécharger le contenu d’une archive en fonction de l’id sur Internet Archive,
❯ python3 iamuz2.py --help
usage: iamuz2.py [-h] [--id ID] [--debug] [--selftest]
Télécharge des MP3/OGG depuis archive.org
options:
-h, --help show this help message and exit
--id ID Identifiant Archive.org
--debug Mode debug
--selftest Test du script
~
Création automatique des dossiers avec dest.parent.mkdir(parents=True, exist_ok=True).
Détection des fichiers audio basée sur l’extension .mp3 ou .ogg.
Logs détaillés pour toutes les étapes : récupération, téléchargement, erreurs.
--selftest pour vérifier que le script peut créer des dossiers correctement.
#!/usr/bin/env python3importosimportsysimportrequestsimportargparsefrompathlibimportPathfromtimeimporttimeDOWNLOAD_DIR=Path("downloads")deflog_debug(msg):print(f"[DEBUG] {msg}")deflog_info(msg):print(f"[INFO] {msg}")deflog_warn(msg):print(f"[WARN] {msg}")deffetch_metadata(identifier,session):url=f"https://archive.org/metadata/{identifier}"log_debug(f"Récupération des métadonnées pour {identifier} depuis {url}")try:response=session.get(url)response.raise_for_status() return response.json()exceptrequests.RequestExceptionase:log_warn(f"Impossible de récupérer les métadonnées: {e}")returnNonedeffind_audio_files(metadata):files=metadata.get("files", [])audio_files= [f["name"] for f in files iff["name"].lower().endswith((".mp3", ".ogg"))]log_info(f"{len(audio_files)} fichier(s) audio trouvé(s)")for f in audio_files:log_debug(f" → {f}")returnaudio_filesdefdownload_file(session,identifier,file_name):url=f"https://archive.org/download/{identifier}/{file_name}"dest=DOWNLOAD_DIR/file_namedest.parent.mkdir(parents=True, exist_ok=True)ifdest.exists():log_debug(f"Fichier déjà existant, skipping: {dest}")returnlog_info(f"Téléchargement de {file_name}")try:response=session.get(url,stream=True)response.raise_for_status() total_size = int(response.headers.get("content-length",0))downloaded=0start_time=time()withopen(dest,"wb") asf:for chunk in response.iter_content(chunk_size=8192):ifchunk:f.write(chunk)downloaded+=len(chunk)iftotal_size>0:percent=downloaded/total_size*100elapsed=time() -start_timespeed=downloaded/1024/elapsedifelapsed>0else0print(f"\r{percent:6.2f}% | {downloaded/1024:.2f} KB / {total_size/1024:.2f} KB | {speed:.2f} KB/s",end="")print() # nouvelle ligne après téléchargement log_info(f"Téléchargé avec succès: {dest}")exceptrequests.HTTPErrorase:log_warn(f"Erreur HTTP lors du téléchargement de {file_name}: {e}")exceptExceptionase:log_warn(f"Erreur lors du téléchargement de {file_name}: {e}")defprocess_identifier(identifier,session):log_debug(f"Début traitement pour {identifier}")metadata=fetch_metadata(identifier,session)ifnotmetadata:returnaudio_files=find_audio_files(metadata)ifnotaudio_files:log_warn(f"Aucun fichier audio trouvé pour {identifier}")returnfor f in audio_files:download_file(session,identifier,f)defmain():parser=argparse.ArgumentParser(description="Télécharge des MP3/OGG depuis archive.org")parser.add_argument("--id",help="Identifiant Archive.org",required=False)parser.add_argument("--debug",action="store_true",help="Mode debug")parser.add_argument("--selftest",action="store_true",help="Test du script")args=parser.parse_args()session=requests.Session()ifargs.selftest:log_info("Selftest: Vérification de la structure de téléchargement...") (DOWNLOAD_DIR/"test_folder").mkdir(parents=True, exist_ok=True)log_info("Selftest terminé avec succès")returnifnotargs.id:log_warn("Aucun identifiant fourni. Exemple pour tester metadata:")print(" python3 iamuz.py --id LAROCCA13CLASSICS02 --debug")returnprocess_identifier(args.id,session)if__name__=="__main__":main()
~
❯ python3 iamuz2.py --id GLOBE_AFTER_CLUB
[DEBUG] Début traitement pour GLOBE_AFTER_CLUB
[DEBUG] Récupération des métadonnées pour GLOBE_AFTER_CLUB depuis https://archive.org/metadata/GLOBE_AFTER_CLUB
[INFO] 60 fichier(s) audio trouvé(s)
[DEBUG] → globe - after club/CD1/1.01. Energiya - Straight Kickin' (Original Mix).mp3
[DEBUG] → globe - after club/CD1/1.01. Energiya - Straight Kickin' (Original Mix).ogg
[DEBUG] → globe - after club/CD1/1.02. The Regulators - Berlin.mp3
[DEBUG] → globe - after club/CD1/1.02. The Regulators - Berlin.ogg
[DEBUG] → globe - after club/CD1/1.03. Groovezone - Eisbaer (Ice Remix).mp3
[DEBUG] → globe - after club/CD1/1.03. Groovezone - Eisbaer (Ice Remix).ogg
[DEBUG] → globe - after club/CD1/1.04. Moonman - First Light (Hole In One Mix).mp3
[DEBUG] → globe - after club/CD1/1.04. Moonman - First Light (Hole In One Mix).ogg
[DEBUG] → globe - after club/CD1/1.05. Team Deep - Spang.mp3
[DEBUG] → globe - after club/CD1/1.05. Team Deep - Spang.ogg
[DEBUG] → globe - after club/CD1/1.06. Da Hool - Freakstyle.mp3
[DEBUG] → globe - after club/CD1/1.06. Da Hool - Freakstyle.ogg
[DEBUG] → globe - after club/CD1/1.07. Afrowax - English 101 (Al Farist & A. Wooden Remix).mp3
[DEBUG] → globe - after club/CD1/1.07. Afrowax - English 101 (Al Farist & A. Wooden Remix).ogg
[DEBUG] → globe - after club/CD1/1.08. Silent Breed - Sync In.mp3
[DEBUG] → globe - after club/CD1/1.08. Silent Breed - Sync In.ogg
[DEBUG] → globe - after club/CD1/1.09. Antic - Pulse.mp3
[DEBUG] → globe - after club/CD1/1.09. Antic - Pulse.ogg
[DEBUG] → globe - after club/CD1/1.10. Cold Turkey - Freakstyle.mp3
[DEBUG] → globe - after club/CD1/1.10. Cold Turkey - Freakstyle.ogg
[DEBUG] → globe - after club/CD1/1.11. Snitzer & Mc Coy vs. Humate - Oh My Darling I Love You (Heavy Mix).mp3
[DEBUG] → globe - after club/CD1/1.11. Snitzer & Mc Coy vs. Humate - Oh My Darling I Love You (Heavy Mix).ogg
[DEBUG] → globe - after club/CD1/1.12. V.O.O.D.I. Traxx - Natural Trail (Club Quake vs. V.O.O.D.I. Mix).mp3
[DEBUG] → globe - after club/CD1/1.12. V.O.O.D.I. Traxx - Natural Trail (Club Quake vs. V.O.O.D.I. Mix).ogg
[DEBUG] → globe - after club/CD1/1.13. DJ Ablaze - One More.mp3
[DEBUG] → globe - after club/CD1/1.13. DJ Ablaze - One More.ogg
[DEBUG] → globe - after club/CD1/1.14. Chemistry - Outerspace.mp3
[DEBUG] → globe - after club/CD1/1.14. Chemistry - Outerspace.ogg
[DEBUG] → globe - after club/CD1/1.15. Yves Deruyter ft Zolex - The Limit.mp3
[DEBUG] → globe - after club/CD1/1.15. Yves Deruyter ft Zolex - The Limit.ogg
[DEBUG] → globe - after club/CD2/2.01. Insider - Saurian.mp3
[DEBUG] → globe - after club/CD2/2.01. Insider - Saurian.ogg
[DEBUG] → globe - after club/CD2/2.02. Loophole - Frontcut.mp3
[DEBUG] → globe - after club/CD2/2.02. Loophole - Frontcut.ogg
[DEBUG] → globe - after club/CD2/2.03. Chiapet - Tick Tock (Apocalypse Now Mix).mp3
[DEBUG] → globe - after club/CD2/2.03. Chiapet - Tick Tock (Apocalypse Now Mix).ogg
[DEBUG] → globe - after club/CD2/2.04. LSG - Netherworld (DJ Randy's Smoke Free Remix).mp3
[DEBUG] → globe - after club/CD2/2.04. LSG - Netherworld (DJ Randy's Smoke Free Remix).ogg
[DEBUG] → globe - after club/CD2/2.05. Literon - How Little Space.mp3
[DEBUG] → globe - after club/CD2/2.05. Literon - How Little Space.ogg
[DEBUG] → globe - after club/CD2/2.06. The Juice - Iron Man.mp3
[DEBUG] → globe - after club/CD2/2.06. The Juice - Iron Man.ogg
[DEBUG] → globe - after club/CD2/2.07. Deniro - Mind Of Man (Remix).mp3
[DEBUG] → globe - after club/CD2/2.07. Deniro - Mind Of Man (Remix).ogg
[DEBUG] → globe - after club/CD2/2.08. DJ Misjah - Karin's Paradox.mp3
[DEBUG] → globe - after club/CD2/2.08. DJ Misjah - Karin's Paradox.ogg
[DEBUG] → globe - after club/CD2/2.09. Dimitri Gelders - Cycle Mode.mp3
[DEBUG] → globe - after club/CD2/2.09. Dimitri Gelders - Cycle Mode.ogg
[DEBUG] → globe - after club/CD2/2.10. The X Factor - The Soul Shelter (Mr Marvin Native Percussion Mix).mp3
[DEBUG] → globe - after club/CD2/2.10. The X Factor - The Soul Shelter (Mr Marvin Native Percussion Mix).ogg
[DEBUG] → globe - after club/CD2/2.11. Speedy J - Pannik.mp3
[DEBUG] → globe - after club/CD2/2.11. Speedy J - Pannik.ogg
[DEBUG] → globe - after club/CD2/2.12. The Blunted Boy Wonder - Metropolis.mp3
[DEBUG] → globe - after club/CD2/2.12. The Blunted Boy Wonder - Metropolis.ogg
[DEBUG] → globe - after club/CD2/2.13. Sinus - The Blob (J Jam Remix).mp3
[DEBUG] → globe - after club/CD2/2.13. Sinus - The Blob (J Jam Remix).ogg
[DEBUG] → globe - after club/CD2/2.14. Innerdrive - Beat On My Drum.mp3
[DEBUG] → globe - after club/CD2/2.14. Innerdrive - Beat On My Drum.ogg
[DEBUG] → globe - after club/CD2/2.15. Tyrome - Bounce.mp3
[DEBUG] → globe - after club/CD2/2.15. Tyrome - Bounce.ogg
[INFO] Téléchargement de globe - after club/CD1/1.01. Energiya - Straight Kickin' (Original Mix).mp3
100.00% | 4143.65 KB / 4143.65 KB | 1090.96 KB/s
[INFO] Téléchargé avec succès: downloads/globe - after club/CD1/1.01. Energiya - Straight Kickin' (Original Mix).mp3
[INFO] Téléchargement de globe - after club/CD1/1.01. Energiya - Straight Kickin' (Original Mix).ogg
100.00% | 2986.52 KB / 2986.52 KB | 3810.46 KB/ss
[INFO] Téléchargé avec succès: downloads/globe - after club/CD1/1.01. Energiya - Straight Kickin' (Original Mix).ogg
[INFO] Téléchargement de globe - after club/CD1/1.02. The Regulators - Berlin.mp3
100.00% | 5578.72 KB / 5578.72 KB | 380.89 KB/s
[INFO] Téléchargé avec succès: downloads/globe - after club/CD1/1.02. The Regulators - Berlin.mp3
[INFO] Téléchargement de globe - after club/CD1/1.02. The Regulators - Berlin.ogg
100.00% | 3806.59 KB / 3806.59 KB | 2314.54 KB/s
[INFO] Téléchargé avec succès: downloads/globe - after club/CD1/1.02. The Regulators - Berlin.ogg