예제 #1
0
파일: test.py 프로젝트: varung/ffpyplayer
class PlayerApp(App):

    def __init__(self, **kwargs):
        super(PlayerApp, self).__init__(**kwargs)
        self.texture = None
        self.size = (0, 0)
        self.next_frame = None
        self._done = False
        self._lock = RLock()
        self._thread = Thread(target=self._next_frame, name='Next frame')
        self._trigger = Clock.create_trigger(self.redraw)
        self._force_refresh = False

    def build(self):
        self.root = Root()
        return self.root

    def on_start(self):
        self.callback_ref = WeakMethod(self.callback)
        filename = sys.argv[1]
        logging.info('ffpyplayer: Playing file "{}"'.format(filename))
        # try ff_opts = {'vf':'edgedetect'} http://ffmpeg.org/ffmpeg-filters.html
        ff_opts = {}
        self.ffplayer = MediaPlayer(filename, callback=self.callback_ref,
                                    loglevel=log_level, ff_opts=ff_opts)
        self._thread.start()
        self.keyboard = Window.request_keyboard(None, self.root)
        self.keyboard.bind(on_key_down=self.on_keyboard_down)

    def resize(self):
        if self.ffplayer:
            w, h = self.ffplayer.get_metadata()['src_vid_size']
            if not h:
                return
            lock = self._lock
            lock.acquire()
            if self.root.image.width < self.root.image.height * w / float(h):
                self.ffplayer.set_size(-1, self.root.image.height)
            else:
                self.ffplayer.set_size(self.root.image.width, -1)
            lock.release()
            logging.debug('ffpyplayer: Resized video.')

    def update_pts(self, *args):
        if self.ffplayer:
            self.root.seek.value = self.ffplayer.get_pts()

    def on_keyboard_down(self, keyboard, keycode, text, modifiers):
        if not self.ffplayer:
            return False
        lock = self._lock
        ctrl = 'ctrl' in modifiers
        if keycode[1] == 'p' or keycode[1] == 'spacebar':
            logging.info('Toggled pause.')
            self.ffplayer.toggle_pause()
        elif keycode[1] == 'r':
            logging.debug('ffpyplayer: Forcing a refresh.')
            self._force_refresh = True
        elif keycode[1] == 'v':
            logging.debug('ffpyplayer: Changing video stream.')
            lock.acquire()
            self.ffplayer.request_channel('video',
                                          'close' if ctrl else 'cycle')
            lock.release()
            Clock.unschedule(self.update_pts)
            if ctrl:    # need to continue updating pts, since video is disabled.
                Clock.schedule_interval(self.update_pts, 0.05)
        elif keycode[1] == 'a':
            logging.debug('ffpyplayer: Changing audio stream.')
            lock.acquire()
            self.ffplayer.request_channel('audio',
                                          'close' if ctrl else 'cycle')
            lock.release()
        elif keycode[1] == 't':
            logging.debug('ffpyplayer: Changing subtitle stream.')
            lock.acquire()
            self.ffplayer.request_channel('subtitle',
                                          'close' if ctrl else 'cycle')
            lock.release()
        elif keycode[1] == 'right':
            logging.debug('ffpyplayer: Seeking forward by 10s.')
            self.ffplayer.seek(10.)
        elif keycode[1] == 'left':
            logging.debug('ffpyplayer: Seeking back by 10s.')
            self.ffplayer.seek(-10.)
        elif keycode[1] == 'up':
            logging.debug('ffpyplayer: Increasing volume.')
            self.ffplayer.set_volume(self.ffplayer.get_volume() + 0.01)
            self.root.volume.value = self.ffplayer.get_volume()
        elif keycode[1] == 'down':
            logging.debug('ffpyplayer: Decreasing volume.')
            self.ffplayer.set_volume(self.ffplayer.get_volume() - 0.01)
            self.root.volume.value = self.ffplayer.get_volume()
        return True

    def touch_down(self, touch):
        if self.root.seek.collide_point(*touch.pos) and self.ffplayer:
            pts = ((touch.pos[0] - self.root.volume.width) /
            self.root.seek.width * self.ffplayer.get_metadata()['duration'])
            logging.debug('ffpyplayer: Seeking to {}.'.format(pts))
            self.ffplayer.seek(pts, relative=False)
            self._force_refresh = True
            return True
        return False

    def callback(self, selector, value):
        if self.ffplayer is None:
            return
        if selector == 'quit':
            logging.debug('ffpyplayer: Quitting.')
            def close(*args):
                self._done = True
                self.ffplayer = None
            Clock.schedule_once(close, 0)
        # called from internal thread, it typically reads forward
        elif selector == 'display_sub':
            self.display_subtitle(*value)

    def _next_frame(self):
        ffplayer = self.ffplayer
        sleep = time.sleep
        trigger = self._trigger
        while not self._done:
            force = self._force_refresh
            if force:
                self._force_refresh = False
            frame, val = ffplayer.get_frame(force_refresh=force)

            if val == 'eof':
                logging.debug('ffpyplayer: Got eof.')
                sleep(1 / 30.)
            elif val == 'paused':
                logging.debug('ffpyplayer: Got paused.')
                sleep(1 / 30.)
            else:
                if frame:
                    logging.debug('ffpyplayer: Next frame: {}.'.format(val))
                    sleep(val)
                    self.next_frame = frame
                    trigger()
                else:
                    val = val if val else (1 / 30.)
                    logging.debug('ffpyplayer: Schedule next frame check: {}.'
                                  .format(val))
                    sleep(val)

    def redraw(self, dt=0, force_refresh=False):
        if not self.ffplayer:
            return
        if self.next_frame:
            img, pts = self.next_frame
            if img.get_size() != self.size or self.texture is None:
                self.root.image.canvas.remove_group(str(self)+'_display')
                self.texture = Texture.create(size=img.get_size(),
                                              colorfmt='rgb')
                # by adding 'vf':'vflip' to the player initialization ffmpeg
                # will do the flipping
                self.texture.flip_vertical()
                self.texture.add_reload_observer(self.reload_buffer)
                self.size = img.get_size()
                logging.debug('ffpyplayer: Creating new image texture of '
                              'size: {}.'.format(self.size))
            self.texture.blit_buffer(img.to_memoryview()[0])
            self.root.image.texture = None
            self.root.image.texture = self.texture
            self.root.seek.value = pts
            logging.debug('ffpyplayer: Blitted new frame with time: {}.'
                          .format(pts))

        if self.root.seek.value:
            self.root.seek.max = self.ffplayer.get_metadata()['duration']

    def display_subtitle(self, text, fmt, pts, t_start, t_end):
        pass # fmt is text (unformatted), or ass (formatted subs)

    def reload_buffer(self, *args):
        logging.debug('ffpyplayer: Reloading buffer.')
        frame = self.next_frame
        if not frame:
            return
        self.texture.blit_buffer(frame[0].to_memoryview()[0], colorfmt='rgb',
                                 bufferfmt='ubyte')
예제 #2
0
class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.geometry("640x420")
        self.winfo_toplevel().title("YouTube downloader")
        #Create icon from base64 string
        icon_file = io.BytesIO(base64.b64decode(icon))
        img = Image.open(icon_file, mode='r')
        self.master.iconphoto(True, ImageTk.PhotoImage(image=img))

        self.video_link = tk.StringVar()  #Link to the youtube video
        self.download_path = tk.StringVar()  #Folder to download videos to
        self.video_folder = tk.StringVar(
        )  #Represents the folder to play videos from
        self.selected_video = 0  #Current playing video, idx
        self.playlist = []  #Array of video's to play
        self.downloadLeft = [0, 0]
        self.download_count = None  #Download count text widget
        self.video_list = None
        self.video_embed = None
        #Styles
        self.mainBgColor = "#121212"
        self.labelBgColor = "#1E1E1E"
        self.fontColor = "white"
        self.btnHighlight = "#FF00FF"

        #Video player
        self.video_player = None
        #Create blank image for video player
        img_str = io.BytesIO(base64.b64decode(blank))
        img = Image.open(img_str, mode='r')
        self.blank_img = ImageTk.PhotoImage(image=img)

        #Play/Stop video buttons
        self.playButton = None
        self.stopButton = None
        self.pauseButton = None
        self.playlistChanged = False  #Flag for seeing whether playlist has changed
        self.songChanged = False  #Flag for seeing if user pressed next button
        self.playback_buttons_frame = None  #Bind the playback buttons frame for swapping buttons inside of it
        self.isPlaying = False  #Flag used seeing if video is being streamed (Used in next/prev song, not in thread termination!)
        self.curVolume = 50
        self.curBassBoost = 0  #Maybe used one day
        self.now_playing = ""

        self.master.resizable(False, False)
        self.create_widgets()
        self.fps = 30

    def create_widgets(self):
        self.master.config(bg=self.mainBgColor)

        ##DOWNLOADING
        download_frame = LabelFrame(self.master, bg=self.mainBgColor, width=20)
        download_frame.grid(row=0, column=0, pady=10)

        #Input/Link frame
        input_frame = LabelFrame(download_frame, bg=self.mainBgColor, bd=0)
        input_frame.grid(row=0, column=0, padx=5)

        link_lable = tk.Label(input_frame,
                              text="YouTube link: ",
                              width=10,
                              bg=self.mainBgColor,
                              fg=self.fontColor)
        link_lable.grid(row=1, column=0, pady=5, padx=20)

        self.master.linkText = tk.Entry(input_frame,
                                        width=54,
                                        textvariable=self.video_link,
                                        bg=self.labelBgColor,
                                        fg=self.fontColor)
        self.master.linkText.grid(row=1, column=1, padx=2)

        #destination frame (for better button positioning)
        destination_frame = LabelFrame(download_frame,
                                       bg=self.mainBgColor,
                                       bd=0)
        destination_frame.grid(row=2, column=0)

        destination_label = tk.Label(destination_frame,
                                     text="Destination: ",
                                     width=10,
                                     bg=self.mainBgColor,
                                     fg=self.fontColor)
        destination_label.grid(row=0, column=0, padx=20)

        self.master.destinationText = tk.Entry(destination_frame,
                                               width=40,
                                               textvariable=self.download_path,
                                               bg=self.labelBgColor,
                                               fg=self.fontColor)
        self.master.destinationText.grid(row=0, column=1, padx=(4, 0))

        browse_B = tk.Button(destination_frame,
                             text="Browse",
                             command=self.BrowseDestination,
                             width=10,
                             bg=self.labelBgColor,
                             fg=self.fontColor,
                             activebackground=self.btnHighlight,
                             activeforeground="black")
        browse_B.grid(row=0, column=2, padx=4)

        #Download frame
        download_btn_frame = LabelFrame(download_frame,
                                        bg=self.mainBgColor,
                                        bd=0)
        download_btn_frame.grid(row=3, column=0)

        download_b = tk.Button(download_btn_frame,
                               text="Download",
                               command=self.Download,
                               width=20,
                               bg=self.labelBgColor,
                               fg="white",
                               activebackground=self.btnHighlight,
                               activeforeground="black")
        download_b.grid(row=0, column=0)

        self.download_count = tk.Text(download_btn_frame,
                                      width=22,
                                      height=1,
                                      bg=self.mainBgColor,
                                      fg="white",
                                      bd=0)
        self.download_count.grid(row=0, column=1, padx=5)
        self.download_count.insert(tk.END, "Download status: ")
        self.download_count.insert(tk.END, self.downloadLeft[0])
        self.download_count.insert(tk.END, " / ")
        self.download_count.insert(tk.END, self.downloadLeft[1])

        ##VIDEO PLAYER

        #Playback buttons and volume sliders container
        playback_container_frame = LabelFrame(self.master,
                                              bg=self.mainBgColor,
                                              bd=0)
        playback_container_frame.grid(row=1, column=0, pady=5)

        #Container that has bottom and top frame for playback buttons
        buttons_container = LabelFrame(playback_container_frame,
                                       bg=self.mainBgColor,
                                       bd=0)
        buttons_container.grid(row=0, column=0)

        #Top frame (Empty)
        top_frame = LabelFrame(buttons_container,
                               bg=self.mainBgColor,
                               height=22,
                               bd=0)
        top_frame.grid(row=0, column=0)

        self.playback_buttons_frame = LabelFrame(buttons_container,
                                                 bg=self.mainBgColor,
                                                 bd=0)
        self.playback_buttons_frame.grid(row=1,
                                         column=0,
                                         padx=(50, 0),
                                         pady=(8, 0))

        prevVid = tk.Button(self.playback_buttons_frame,
                            text="Prev",
                            command=self.PreviousVideo,
                            width=10,
                            bg=self.labelBgColor,
                            fg="white",
                            activebackground=self.btnHighlight,
                            activeforeground="black")

        prevVid.grid(row=0, column=0)

        self.playButton = tk.Button(self.playback_buttons_frame,
                                    text="Play",
                                    command=self.PlayVideo,
                                    width=10,
                                    bg=self.labelBgColor,
                                    fg="white",
                                    activebackground=self.btnHighlight,
                                    activeforeground="black")
        self.playButton.grid(row=0, column=1)

        nextVid = tk.Button(self.playback_buttons_frame,
                            text="Next",
                            command=self.NextVideo,
                            width=10,
                            bg=self.labelBgColor,
                            fg="white",
                            activebackground=self.btnHighlight,
                            activeforeground="black")
        nextVid.grid(row=0, column=2)

        self.pauseButton = tk.Button(self.playback_buttons_frame,
                                     text="Pause",
                                     command=self.PauseVideo,
                                     width=10,
                                     bg=self.labelBgColor,
                                     fg="white",
                                     activebackground=self.btnHighlight,
                                     activeforeground="black")

        self.pauseButton.grid(row=0, column=3)

        #Volume slider and bass EQ
        eq_frame = LabelFrame(playback_container_frame,
                              bg=self.mainBgColor,
                              bd=0)
        eq_frame.grid(row=0, column=1, padx=50)

        volumeText = tk.Text(eq_frame,
                             width=10,
                             height=1,
                             bg=self.mainBgColor,
                             fg="white",
                             bd=0)
        volumeText.tag_configure("center", justify="center")
        volumeText.insert("1.0", "Volume")
        volumeText.tag_add("center", "1.0", "end")
        volumeText.grid(row=0, column=0, padx=(30, 0))
        volume_slider = Scale(eq_frame,
                              from_=0,
                              to=100,
                              orient=tk.HORIZONTAL,
                              bg=self.mainBgColor,
                              bd=0,
                              fg="white",
                              troughcolor=self.labelBgColor,
                              highlightbackground=self.mainBgColor,
                              activebackground="#FF00FF",
                              command=self.VolumeSlider,
                              length=150)

        volume_slider.grid(row=1, column=0, padx=(30, 0))
        volume_slider.set(self.curVolume)

        #EQ/Bass boost slider for future use
        #TODO:: Re-compile ffpyplayer module with custom sdl_audio_callback function call, which will take use DSP for changing the pitch of the audio
        #Bind this slider to callback the sdl_audio_callback custom function
        # bassText = tk.Text(eq_frame, width=10, height=1, bg=self.mainBgColor, fg="white", bd=0)
        # bassText.insert(tk.END, "Bass boost")
        # bassText.grid(row=0, column=1)
        # bass_slider = Scale(eq_frame, from_=0, to=100, orient=tk.HORIZONTAL,
        #                     bg=self.mainBgColor, bd=0, fg="white",
        #                     troughcolor=self.labelBgColor, highlightbackground=self.mainBgColor,
        #                     activebackground="#FF00FF")
        # bass_slider.grid(row=1, column=1)
        # bass_slider.set(self.curBassBoost)

        #Video stream frame
        video_player_frame = LabelFrame(self.master,
                                        bg=self.labelBgColor,
                                        bd=1)
        video_player_frame.grid(row=2, column=0, padx=17)

        self.video_embed = tk.Label(video_player_frame,
                                    text="Video",
                                    image=self.blank_img,
                                    bg=self.labelBgColor)
        self.video_embed.grid(row=0, column=0)

        self.now_playing = Text(video_player_frame,
                                width=50,
                                height=1,
                                bg=self.mainBgColor,
                                fg="white")
        self.now_playing.insert(tk.END, "Now playing: ")
        self.now_playing.grid(row=1, column=0)

        #Video queue frame
        queue_frame = LabelFrame(video_player_frame,
                                 bg=self.labelBgColor,
                                 bd=0)
        queue_frame.grid_rowconfigure(0, weight=0)
        queue_frame.grid_columnconfigure(0, weight=1)
        queue_frame.grid(row=0, column=1)

        queue_buttons = LabelFrame(queue_frame, bg=self.labelBgColor)
        queue_buttons.grid(row=0, column=0)

        browse_In = tk.Button(queue_buttons,
                              text="Browse",
                              command=self.BrowseInputFolder,
                              width=10,
                              bg=self.labelBgColor,
                              fg=self.fontColor,
                              bd=1,
                              activebackground=self.btnHighlight,
                              activeforeground="black")
        browse_In.grid(row=0, column=1)

        playAll = tk.Button(queue_buttons,
                            text="Select all",
                            command=self.SelectAll,
                            width=10,
                            bg=self.labelBgColor,
                            fg=self.fontColor,
                            bd=1,
                            activebackground=self.btnHighlight,
                            activeforeground="black")
        playAll.grid(row=0, column=2)

        self.video_list = tk.Listbox(queue_frame,
                                     font=("Helvetica", 12),
                                     selectmode=tk.EXTENDED,
                                     exportselection=0,
                                     height=9,
                                     bg=self.labelBgColor,
                                     fg=self.fontColor,
                                     bd=0,
                                     selectbackground=self.btnHighlight)
        self.video_list.grid(row=1, column=0)
        self.video_list.bind("<<ListboxSelect>>", self.listbox_sel_callback)
        #self.video_list.bind('<Double-Button>', self.PlayVideo)            #Double clicking video causes thread exceptions for some reason

        scrollbar = Scrollbar(queue_frame,
                              orient="vertical",
                              command=self.video_list.yview,
                              bg=self.labelBgColor,
                              highlightcolor=self.btnHighlight,
                              bd=0)
        self.video_list.config(yscrollcommand=scrollbar.set)
        scrollbar.grid(row=1, column=1, sticky='ns')

        self.curVolume = 50
        #Search bar for videos
        # search_bar = tk.Entry(queue_frame, bd=0, )
        # search_bar.grid(row=2,column=0)

    def listbox_sel_callback(self, event):
        self.playlist = []
        indices = self.video_list.curselection()
        for i in indices:
            self.playlist.append(self.video_list.get(i))
        self.playlistChanged = True

    def BrowseInputFolder(self):
        video_dir = filedialog.askdirectory(initialdir="C:\\YoutubeVideos")
        self.video_folder.set(video_dir)
        self.video_list.delete(0, tk.END)
        for root, dirs, files in os.walk(self.video_folder.get()):
            for filename in files:
                self.video_list.insert(tk.END, filename)

    def PlayVideo(self):
        global stop_thread
        stop_thread = True
        time.sleep(0.05)  #Dangerous way of waiting for thread lol
        stop_thread = False
        self.isPlaying = True
        self.playlistChanged = False

        # if self.selected_video >= 0 and self.selected_video < len(self.playlist):
        self.start_videostream()
        #self.video_player.set_volume(float(self.curVolume)/100)
        thread = threading.Thread(target=self.Video_data_stream)
        thread.daemon = 1
        thread.start()
        self.playButton.grid_forget()
        self.stopButton = tk.Button(self.playback_buttons_frame,
                                    text="Stop",
                                    command=self.StopVideo,
                                    width=10,
                                    bg="#FF00FF",
                                    fg="black")
        self.stopButton.grid(row=0, column=1)

        #Change the "now playing"
        self.changeNowPlaying()

    def changeNowPlaying(self):
        self.now_playing.delete("1.0", tk.END)
        self.now_playing.insert(tk.END, "Now playing: ")
        if self.isPlaying:
            self.now_playing.insert(tk.END, self.playlist[self.selected_video])

    def StopVideo(self):
        global stop_thread
        global pause_thread
        self.isPlaying = False
        stop_thread = True
        pause_thread = True
        self.PauseVideo()

        self.stopButton.grid_forget()
        self.playButton = tk.Button(self.playback_buttons_frame,
                                    text="Play",
                                    command=self.PlayVideo,
                                    width=10,
                                    bg=self.labelBgColor,
                                    fg=self.fontColor)
        self.playButton.grid(row=0, column=1)
        self.changeNowPlaying()

    def PauseVideo(self):
        global pause_thread
        if pause_thread:
            #Why isn't this done in play/stop aswell lol
            self.pauseButton.config(text="Pause",
                                    bg=self.labelBgColor,
                                    fg="white")
            pause_thread = False
            self.video_player.set_pause(False)
        else:
            self.pauseButton.config(text="Unpause",
                                    bg=self.btnHighlight,
                                    fg="black")
            pause_thread = True
            self.video_player.set_pause(True)

    def start_videostream(self):
        #Start new instance of player
        if self.video_player:
            self.video_player.close_player()
        cVol = float(self.curVolume) / 100
        print(cVol)
        self.video_player = MediaPlayer(self.video_folder.get() + "\\" +
                                        self.playlist[self.selected_video],
                                        ff_opts={
                                            'paused': True,
                                            'volume': 0.03
                                        })
        self.video_player.set_size(400, 200)
        #while not self.video_player:
        #    continue
        time.sleep(0.1)
        if self.video_player:
            self.video_player.set_volume(cVol)
        self.video_player.set_pause(False)

    def NextVideo(self):
        if self.isPlaying == False:
            return
        #Destroy current player if there's one
        self.video_player.close_player()

        #Inform the video stream that video was changed
        self.songChanged = True

        #If playlist was changed, reset the index to 0
        if self.playlistChanged:
            self.selected_video = 0
            self.playlistChanged = False
        #Other wise just increment idx or start from 0 idx
        elif self.selected_video < len(self.playlist) - 1:
            self.selected_video += 1
        else:
            self.selected_video = 0

        self.start_videostream()
        #self.video_player.set_volume(float(self.curVolume)/100)
        self.changeNowPlaying()

    def PreviousVideo(self):
        if self.isPlaying == False:
            return

        #Destroy current player if there's one
        self.video_player.close_player()

        self.songChanged = True

        if self.playlistChanged:
            self.selected_video = 0
            self.playlistChanged = False
        elif self.selected_video > 0:
            self.selected_video -= 1
        else:
            self.selected_video = len(self.playlist) - 1

        self.start_videostream()
        self.changeNowPlaying()

    def SelectAll(self):
        #Select every line in listbox / Every video from list
        for i in range(0, self.video_list.size()):
            self.video_list.selection_set(i)
        #Since manual selection doesn't call callback functions, just add them to playlist manually
        self.playlist = []
        indices = self.video_list.curselection()
        for i in indices:
            self.playlist.append(self.video_list.get(i))
        self.playlistChanged = True

    def VolumeSlider(self, value):
        if self.video_player:
            self.video_player.set_volume(float(value) / 100)
        self.curVolume = value

    def Video_data_stream(self):
        global stop_thread
        global pause_thread
        stop_thread = False
        pause_thread = False

        #Start video/audio stream
        #todo:: len(self.playlist will change)
        while True:
            try:

                frame, val = self.video_player.get_frame()
                if val == 'eof':
                    self.video_player.close_player()
                    self.NextVideo()  #Increment the video index
                    self.video_player.set_volume(float(self.curVolume) / 100)
                    #If we still have videos left in playlist, play another one
                    # if self.selected_video < len(self.playlist):
                    #     self.video_player = MediaPlayer(self.video_folder.get() + "\\" + self.playlist[self.selected_video])
                    #     self.video_player.set_size(400, 200)
                elif frame is None:
                    time.sleep(0.01)
                else:
                    image, t = frame
                    w, h = image.get_size()
                    img = np.asarray(image.to_bytearray()[0]).reshape(h, w, 3)
                    the_frame = ImageTk.PhotoImage(Image.fromarray(img))
                    self.video_embed.config(image=the_frame)
                    self.video_embed.image = the_frame
                    if stop_thread:
                        self.video_player.close_player()
                        #Reset the embed image
                        self.video_embed.config(image=self.blank_img)
                        return
                    while pause_thread:
                        #Do nothing
                        if stop_thread:
                            pause_thread = False
                            return
                        continue
                    if val <= 1:
                        time.sleep(val)
            except:
                #Exception (e.g outside thread changes to player can cause exception)
                continue

    def BrowseDestination(self):
        download_directory = filedialog.askdirectory(
            initialdir="C:\\YoutubeVideos")
        self.download_path.set(download_directory)

    def Download(self):
        self.Update_Download_Status()

        link = self.video_link.get()
        download_folder = self.download_path.get()
        if "list" in link:
            playlist = Playlist(link)
            thread = threading.Thread(target=self.Download_Playlist,
                                      args=(
                                          playlist,
                                          download_folder,
                                      ))
            thread.daemon = 1
            thread.start()
        else:
            thread = threading.Thread(target=self.Download_Single,
                                      args=(
                                          link,
                                          download_folder,
                                      ))
            thread.daemon = 1
            thread.start()

    def Download_Playlist(self, playlist, folder):
        self.downloadLeft = [0, len(playlist.video_urls)]
        for url in playlist.video_urls:
            self.Update_Download_Status()
            try:
                getVideo = YouTube(url)
                video_stream_buffer = getVideo.streams.first()
                video_stream_buffer.download(folder)
                self.downloadLeft[0] += 1
            except:
                if self.downloadLeft[1] > 0:
                    self.downloadLeft[1] -= 1
                continue
        self.Update_Download_Status()
        messagebox.showinfo("Download complete!",
                            "Downloaded videos from playlist to:\n" + folder)

    def Download_Single(self, link, folder):
        self.downloadLeft = [0, 1]
        try:
            self.Update_Download_Status()
            getVideo = YouTube(link)

            video_stream_buffer = getVideo.streams.first()
            video_stream_buffer.download(folder)
            self.downloadLeft = [1, 1]
            messagebox.showinfo("Download complete!",
                                "Downloaded video to:\n" + folder)
        except:
            messagebox.showinfo("Download failed!",
                                "Video not available:\n" + folder)
        self.Update_Download_Status()

    def Update_Download_Status(self):
        self.download_count.delete('1.0', tk.END)
        self.download_count.insert(tk.END, "Download status: ")
        self.download_count.insert(tk.END, self.downloadLeft[0])
        self.download_count.insert(tk.END, " / ")
        self.download_count.insert(tk.END, self.downloadLeft[1])