Home > OS >  Trouble with progressbar(tkinter) in a download file
Trouble with progressbar(tkinter) in a download file

Time:03-22

I'm starting in python and developing a youtube video/audio downloader for my wife. It is already functional, but I wanted to improve the look of it by placing a download progress bar, which can be determined or indeterminate. I've searched several places, even rolled over stackoverflow, but i couldn't find the solution for myself. Maybe my code's a little messed up, but I'm working on it.

As I did two separate programs, I will post only the video downloader. The audio contains a few more lines, just changing the type of file I want from the link and converting it to mp3, since the tkinter downloads in mp4 only audio. Then I'm going to integrate the two into one.

from pytube import YouTube
from tkinter import *
from threading import Thread


def threading():
    t1 = Thread(target=downloader)
    t1.start()


def threading2():
    t1 = Thread(target=downloader2)
    t1.start()


def threading3():
    t1 = Thread(target=downloader3)
    t1.start()


def threading4():
    t1 = Thread(target=downloader4)
    t1.start()


def threading5():
    t1 = Thread(target=downloader5)
    t1.start()




def downloader():
    url = YouTube(str(inp.get()))
    video = url.streams.get_highest_resolution()
    video.download()
    Label(janela, text='Baixado', font='arial 15').grid(column=0, row=9)


def downloader2():
    url = YouTube(str(inp2.get()))
    video = url.streams.get_highest_resolution()
    video.download()
    Label(janela, text='Baixado', font='arial 15').grid(column=0, row=9)


def downloader3():
    url = YouTube(str(inp3.get()))
    video = url.streams.get_highest_resolution()
    video.download()
    Label(janela, text='Baixado', font='arial 15').grid(column=0, row=9)


def downloader4():
    url = YouTube(str(inp4.get()))
    video = url.streams.get_highest_resolution()
    video.download()
    Label(janela, text='Baixado', font='arial 15').grid(column=0, row=9)


def downloader5():
    url = YouTube(str(inp5.get()))
    video = url.streams.get_highest_resolution()
    video.download()
    Label(janela, text='Baixado', font='arial 15').grid(column=0, row=9)


janela = Tk()
janela.title('God Of YouTube Downloader - No AD Version')
janela.geometry('480x500')
janela.resizable(0, 0)
janela.config(background='#dde')
inp = StringVar()
inp2 = StringVar()
inp3 = StringVar()
inp4 = StringVar()
inp5 = StringVar()
imgLogo = PhotoImage(file='youtube.gif')



imagem_logo = Label(janela, image=imgLogo).grid(column=0, row=0, pady=20)

cole_aqui = Label(janela, text='Cole aqui, seu link!', font='arial 15 bold', background='#dde')
cole_aqui.grid(column=0, row=1, pady=5)

entrada_download = Entry(janela, width=50, textvariable=inp)
entrada_download.grid(column=0, row=2, padx=82, pady=3)

entrada_download2 = Entry(janela, width=50, textvariable=inp2)
entrada_download2.grid(column=0, row=4, padx=82, pady=3)

entrada_download3 = Entry(janela, width=50, textvariable=inp3)
entrada_download3.grid(column=0, row=5, padx=82, pady=3)

entrada_download4 = Entry(janela, width=50, textvariable=inp4)
entrada_download4.grid(column=0, row=6, padx=82, pady=3)

entrada_download5 = Entry(janela, width=50, textvariable=inp5)
entrada_download5.grid(column=0, row=7, padx=82, pady=3)

botao_download = Button(janela, text='Baixar', width=20,
                        command=lambda: [threading(), threading2(), threading3(), threading4(), threading5()])
botao_download.grid(column=0, row=8, pady=20)

identif = Label(janela, text='@God Of Python', font='arial 8 bold').place(x=380, y=470)


janela.mainloop()

CodePudding user response:

I created example which uses Label to display how many bytes remained to download.

enter image description here

If you get file length then you could calculate it as percentage.


YouTube() can use on_progress_callback and on_complete_callback to execute functions during downloading and they can be used to display progress.

These functions runs in new threads and they can't update widgets directly (it generate error) so I use Queue() to send values from these functions to main thread. And main thread uses root.after() to execute update_status() every 100ms. And this function gets information from queue and update labels.

Every thread gets number to later send it back and update_status() which label to update

And similar way it could work with widgets Progressbar.


For test I added few links directly in code - so I did't have to put them manually in every run.

Because I use for-loops and list so I reduced code but still have 5 Entries, and can download 5 files at the same time. If I would use range(10) then I could download 10 files.

Originally I created all_threads to keep all threads and use is_alive() to check finished threads but now I don't use this list.

I tried to keep only elements which are really important in example - so I removed fonts, colors, images.

from pytube import YouTube
import tkinter as tk  # PEP8: `import *` is not preferred
from threading import Thread
from queue import Queue

# --- functions ---

def update_status():
    # check if there are new messages in queue and update correct label
    while not queue.empty():
        number, text = queue.get()
        print('[update_status]:', number, text)
        all_labels[number]['text'] = text

    root.after(100, update_status)  # run again after 100ms
    
def on_progress(number, queue, stream, chunk, bytes_remaining):
    print('[on_progress] bytes_remaining:', bytes_remaining)
    queue.put( (number, bytes_remaining) )

def on_complete(number, queue, stream, file_path):
    print('[on_complete] file_path:', file_path)
    queue.put( (number, file_path) )

def download(url, number, queue):
    print('[download] started:', url)
    queue.put( (number, 'started') )
    
    yt = YouTube(url,
                 on_progress_callback=lambda *args:on_progress(number, queue, *args),
                 on_complete_callback=lambda *args:on_complete(number, queue, *args),
                 )
    video = yt.streams.get_highest_resolution()
    video.download()
    
    all_threads[number] = None  # inform that finished

    print('[download] finished:', url)
    #queue.put( (number, 'finished') )
    
def start_threads():
    for number, (t, entry) in enumerate(zip(all_threads, all_entries)):
        url = entry.get().strip()
        
        if url:
            print('[start_threads]:', number, 'starting')
            t = Thread(target=download, args=(url, number, queue))
            t.start()
        else:
            print('[start_threads]:', number, 'empty')
            t = None
            
        all_threads[number] = t

# --- main ---

all_threads = [None] * 5  # to have all values at start
all_entries = []
all_labels  = []

root = tk.Tk()

queue = Queue()

for n in range(5):
    entry = tk.Entry(root, width=50)
    entry.grid(column=0, row=n)
    all_entries.append(entry)

    label = tk.Label(root, width=15, text='-')
    label.grid(column=1, row=n)
    all_labels.append(label)
    
button = tk.Button(root, text='Start', command=start_threads)
button.grid(column=0, row=n 1, columnspan=2)

# for test I put some links so I don't have to do it manually
all_entries[0].insert('end', 'https://www.youtube.com/watch?v=aqz-KE-bpKQ')
all_entries[1].insert('end', 'https://www.youtube.com/watch?v=bNfYUsDSrOs')
all_entries[2].insert('end', 'https://www.youtube.com/watch?v=W7LWny6c4wI')
all_entries[3].insert('end', 'https://www.youtube.com/watch?v=A8LRxIANzQs')

update_status()

root.mainloop()
  • Related