2024-11-15 14:07:58 +01:00
import yt_dlp
from PIL import Image
import requests
from io import BytesIO
from tkinter import *
from PIL import Image , ImageTk
2024-11-15 21:19:33 +01:00
from tkinter . ttk import Progressbar
import threading
import queue
2024-11-16 00:00:01 +01:00
from tkinter . messagebox import askyesno
import os
2024-11-15 14:07:58 +01:00
2024-11-16 00:00:01 +01:00
res_directory = ' res '
def add_url ( url = False , dowload_playlist = False ) :
2024-11-15 14:07:58 +01:00
2024-11-15 21:19:33 +01:00
#Ajoute un classe pour intercepter les logs
class YTDLLogger :
"""
Logger personnalisé pour capturer et afficher les messages de yt - dlp .
"""
def debug ( self , msg ) :
2024-11-16 00:00:01 +01:00
# Affiche les messages de debug
2024-11-15 21:19:33 +01:00
ProgresseLabel . config ( text = msg )
2024-11-15 14:07:58 +01:00
2024-11-15 21:19:33 +01:00
def warning ( self , msg ) :
2024-11-16 00:00:01 +01:00
# Affiche les avertissements
ProgresseLabel . config ( text = f " WARNING: { msg } " )
2024-11-15 14:07:58 +01:00
2024-11-15 21:19:33 +01:00
def error ( self , msg ) :
2024-11-16 00:00:01 +01:00
# Affiche les erreurs
ProgresseLabel . config ( text = f " ERROR: { msg } " )
#Ajoute une fonction pour ajouter une vidéo a la liste
def add_movies ( info , is_playlist = False ) :
title = info . get ( ' title ' , ' Titre indisponible ' )
view_count = info . get ( ' view_count ' , None )
subtitlte = (
info . get ( ' artist ' , info . get ( ' uploader ' , ' Artiste indisponible ' ) )
+ ' · '
+ ' Nombre de vues indisponible ' if view_count is None else ' {:,} ' . format ( view_count ) . replace ( ' , ' , ' ' )
+ ' vues '
)
thumbnail_url = info . get ( ' thumbnail ' )
if is_playlist :
title = ' [Playlist] ' + title
subtitlte + = ' · ' + str ( info . get ( ' playlist_count ' , ' Nombre de vidéo indisponible ' ) ) + ' vidéos '
try :
thumbnail_url = info . get ( ' thumbnails ' ) [ 0 ] . get ( ' url ' )
except TypeError :
thumbnail_url = False
# Création d'un cadre pour chaque vidéo
cadre = Frame ( scrollable_frame )
cadre . pack ( fill = X , padx = 10 , pady = 5 )
if thumbnail_url :
response = requests . get ( thumbnail_url )
image = Image . open ( BytesIO ( response . content ) )
width , height = image . size
# Découper l'image en un carré centré
square_size = min ( width , height )
left = ( width - square_size ) / / 2
top = ( height - square_size ) / / 2
# Calculer les coordonnées du coin inférieur droit du carré
right = left + square_size
bottom = top + square_size
# Découper l'image carrée
image = image . crop ( ( left , top , right , bottom ) )
else :
image = Image . open ( os . path . join ( os . path . join ( res_directory , " img " ) , ' jaquette_default.png ' ) )
image . thumbnail ( ( 50 , 50 ) )
# Charger l'image et l'afficher
image = ImageTk . PhotoImage ( image )
image_label = Label ( cadre , image = image )
image_label . image = image # Préserver la référence pour éviter le garbage collection
image_label . pack ( side = LEFT , padx = 5 )
# Ajouter les titres
texte_frame = Frame ( cadre )
texte_frame . pack ( side = LEFT , padx = 10 , fill = BOTH , expand = True )
label_texte1 = Label ( texte_frame , text = title , font = ( " Arial " , 12 , " bold " ) )
label_texte1 . pack ( anchor = " w " )
label_texte2 = Label ( texte_frame , text = subtitlte , font = ( " Arial " , 10 ) )
label_texte2 . pack ( anchor = " w " )
# Ajouter un bouton "Supprimer"
menu_button = Button ( cadre , text = " Supprimer " , relief = FLAT , command = cadre . destroy )
menu_button . pack ( side = RIGHT )
2024-11-15 21:19:33 +01:00
2024-11-16 00:00:01 +01:00
#Récuperer l'url si non fournie comme argument
if not url :
url = EntryURL . get ( )
if ' &list= ' in url :
if askyesno ( ' Playlist ' , ' Attention, vous vous apretez à ajouter une playlist générer automatiquement par youtube, Pour garder uniquement la vidéo que vous regardiez, appuyer sur oui, si vous voulez télecharger la playlist, appuyer sur non. ' ) :
url = url . split ( ' &list= ' ) [ 0 ]
2024-11-15 19:08:15 +01:00
# Effacer l'entrée URL
EntryURL . delete ( 0 , END )
2024-11-15 14:07:58 +01:00
2024-11-15 21:19:33 +01:00
ydl_opts = {
' quiet ' : True , # Supprime la sortie standard (redirigée vers le logger)
2024-11-16 00:00:01 +01:00
' logger ' : YTDLLogger ( ) ,
' extract_flat ' : True , # Utilise le logger personnalisé
2024-11-15 21:19:33 +01:00
}
2024-11-16 00:00:01 +01:00
if dowload_playlist :
ydl_opts [ ' extract_flat ' ] = False
2024-11-15 21:19:33 +01:00
# Queue pour recuperer les donnes
q = queue . Queue ( )
# Fonction qui exécute le téléchargement dans un thread séparé
def process_download ( ) :
try :
with yt_dlp . YoutubeDL ( ydl_opts ) as ydl :
info = ydl . extract_info ( url , download = False ) # Extraire les métadonnées sans télécharger
q . put ( info ) # Mettre l'info dans la queue pour être récupérée par le thread principal
except Exception as e :
q . put ( f " Erreur : { str ( e ) } " ) # Mettre l'erreur dans la queue
finally :
progress_windows . after ( 2000 , progress_windows . destroy ) # Fermer la fenêtre de progression après 2s
# Fonction pour vérifier la queue et mettre à jour l'interface
def check_queue ( ) :
try :
# Essayer de récupérer l'info sans bloquer
info = q . get_nowait ( )
if isinstance ( info , dict ) : # Si l'info est un dictionnaire (métadonnées)
ProgresseLabel . config ( text = f " Dowloading complet " )
2024-11-16 00:00:01 +01:00
if info . get ( ' _type ' , None ) == ' playlist ' : #Si c'est une playlist
if dowload_playlist : # Vérifie si l'utilisateur a déja répondus a la question suivante
for video in info . get ( ' entries ' ) :
add_movies ( video )
elif askyesno ( ' Playlist ' , f " L ' URL que vous avez fourni est une playlist, voulez-vous l ' ajouter en tant que playlist ou en plusieurs vidéos ? \n Attention, en fonction du nombre de vidéos (ici { str ( info . get ( ' playlist_count ' , ' Nombre de vidéo indisponible ' ) ) } ), cette opération peut prendre du temps \n \n (Oui -> playliste / Non -> Vidéos) " ) : #Demande a l'utilisateur si on ajoute chaque vidéo séparément
add_movies ( info , True )
else :
add_url ( url , True )
else :
add_movies ( info )
2024-11-15 21:19:33 +01:00
else : # Si c'est une erreur
ProgresseLabel . config ( text = info )
progress_windows . after ( 2000 , progress_windows . destroy ) # Fermer après 2s
except queue . Empty : # Si la queue est vide, vérifier à nouveau
progress_windows . after ( 100 , check_queue )
# Création de la fenêtre de progression
progress_windows = Toplevel ( root )
# Barre de progression et label
progressBarMetahdonne = Progressbar ( progress_windows , orient = HORIZONTAL , length = 400 , mode = ' indeterminate ' )
progressBarMetahdonne . pack ( side = TOP )
progressBarMetahdonne . start ( )
ProgresseLabel = Label ( progress_windows , text = " Téléchargement " )
ProgresseLabel . pack ( side = BOTTOM )
# Démarrer le téléchargement dans un thread séparé
thread = threading . Thread ( target = process_download , daemon = True )
thread . start ( )
# Lancer la vérification périodique de la queue
check_queue ( )
# Mainloop Tkinter
root . mainloop ( )
2024-11-15 14:07:58 +01:00
# Fenêtre principale
root = Tk ( )
2024-11-15 19:08:15 +01:00
root . title ( " Téléchargeur YouTube " )
root . geometry ( " 1000x500 " )
2024-11-15 14:07:58 +01:00
2024-11-15 19:08:15 +01:00
# Cadre pour la barre d'URL
2024-11-15 14:07:58 +01:00
EntryFrame = Frame ( root )
2024-11-15 19:08:15 +01:00
EntryFrame . pack ( fill = " x " , padx = 5 , pady = 2.5 )
2024-11-15 14:07:58 +01:00
EntryURL = Entry ( EntryFrame )
EntryURL . pack ( side = LEFT , fill = " x " , expand = True , padx = 2.5 )
2024-11-16 00:00:01 +01:00
EntryURL . bind ( " <Return> " , lambda event = None : add_url ( ) )
2024-11-15 14:07:58 +01:00
EntryButton = Button ( EntryFrame , text = " Ajouter " , command = add_url )
EntryButton . pack ( side = RIGHT , padx = 3.5 )
2024-11-15 19:08:15 +01:00
# LabelFrame pour lister les vidéos
MoviesList = LabelFrame ( root , text = " Vidéos à télécharger " , relief = GROOVE )
2024-11-15 14:07:58 +01:00
MoviesList . pack ( fill = " both " , expand = True , padx = 5 , pady = 2.5 )
2024-11-15 19:08:15 +01:00
# Canvas pour le défilement
MoviesCanva = Canvas ( MoviesList , highlightthickness = 0 )
MoviesCanva . pack ( side = " left " , fill = " both " , expand = True )
# Barre de défilement verticale
scrollbar = Scrollbar ( MoviesList , orient = " vertical " , command = MoviesCanva . yview )
scrollbar . pack ( side = " right " , fill = " y " )
MoviesCanva . configure ( yscrollcommand = scrollbar . set )
# Frame pour les vidéos à l'intérieur du Canvas
scrollable_frame = Frame ( MoviesCanva )
canvas_frame = MoviesCanva . create_window ( ( 0 , 0 ) , window = scrollable_frame , anchor = " nw " )
# Ajuster la taille du Canvas en fonction du contenu
def on_frame_configure ( event ) :
MoviesCanva . configure ( scrollregion = MoviesCanva . bbox ( " all " ) )
scrollable_frame . bind ( " <Configure> " , on_frame_configure )
2024-11-15 14:07:58 +01:00
2024-11-15 19:08:15 +01:00
# Ajuster la largeur du Canvas au redimensionnement
MoviesCanva . bind ( " <Configure> " , lambda e : MoviesCanva . itemconfig ( canvas_frame , width = e . width ) )
2024-11-15 14:07:58 +01:00
root . mainloop ( )