diff --git a/README.md b/README.md index 70cb3e0..ddfd33b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-

Youtube-DL GUI v1.0.81

+

Youtube-DL GUI v1.0.9

Download videos freely with this simple GUI.
diff --git a/Youtube-DL GUI.exe b/Youtube-DL GUI.exe new file mode 100644 index 0000000..de18c8a Binary files /dev/null and b/Youtube-DL GUI.exe differ diff --git a/download.zip b/download.zip index 4e59a61..dd15898 100644 Binary files a/download.zip and b/download.zip differ diff --git a/exe.zip b/exe.zip index 8c548e1..faad4be 100644 Binary files a/exe.zip and b/exe.zip differ diff --git a/main/main.py b/main/main.py index 56dda2d..2661015 100644 --- a/main/main.py +++ b/main/main.py @@ -30,7 +30,7 @@ import logging import webbrowser -__version__ = '1.0.81' +__version__ = '1.0.9' ## Main Dictionary video_ops = { @@ -1701,35 +1701,45 @@ def download_apply(self): video_ops.pop('max_filesize') self.input_entry2.delete(0, END) - if len(str(self.var_11.get())) <= 0: - video_ops.update(playliststart=None) - video_ops.pop('playliststart') - self.input_entry3.delete(0, END) - else: - if str(self.var_11.get()).isnumeric(): - video_ops.update(playliststart=self.var_11.get()) - else: - if self.count == 1: - messagebox.showerror("???", "Error on Start Playlist section.\nYou can only use integers.", parent=self.download_win) - self.count += 1 + try: + if len(str(self.var_11.get())) <= 0: video_ops.update(playliststart=None) video_ops.pop('playliststart') self.input_entry3.delete(0, END) - - if len(str(self.var_12.get())) <= 0: - video_ops.update(playlistend=None) - video_ops.pop('playlistend') - self.input_entry4.delete(0, END) - else: - if str(self.var_12.get()).isnumeric(): - video_ops.update(playlistend=self.var_12.get()) else: - if self.count == 1: - messagebox.showerror("???", "Error on End Playlist section.\nYou can only use integers.", parent=self.download_win) - self.count += 1 + if str(self.var_11.get()).isnumeric(): + video_ops.update(playliststart=self.var_11.get()) + else: + if self.count == 1: + messagebox.showerror("???", "Error on Start Playlist section.\nYou can only use integers.", parent=self.download_win) + self.count += 1 + video_ops.update(playliststart=None) + video_ops.pop('playliststart') + self.input_entry3.delete(0, END) + except TclError: + video_ops.update(playliststart=None) + video_ops.pop('playliststart') + self.input_entry3.delete(0, END) + + try: + if len(str(self.var_12.get())) <= 0: video_ops.update(playlistend=None) video_ops.pop('playlistend') self.input_entry4.delete(0, END) + else: + if str(self.var_12.get()).isnumeric(): + video_ops.update(playlistend=self.var_12.get()) + else: + if self.count == 1: + messagebox.showerror("???", "Error on End Playlist section.\nYou can only use integers.", parent=self.download_win) + self.count += 1 + video_ops.update(playlistend=None) + video_ops.pop('playlistend') + self.input_entry4.delete(0, END) + except TclError: + video_ops.update(playlistend=None) + video_ops.pop('playlistend') + self.input_entry4.delete(0, END) if self.var_13.get() == '4.2M': video_ops.update(ratelimit=None) @@ -2664,7 +2674,6 @@ def __init__(self): self._index = 0 self.win_count = 1 self.terminate_count = 1 - self._driver = None self._downloadError = yt.utils.DownloadError self._FFmpegPostProcessorError = postprocessor.ffmpeg.FFmpegPostProcessorError @@ -2780,7 +2789,7 @@ def my_hook(d): thread.wait(1.5) if d['status'] == 'downloading': t.delete_lines() - print('\nFILE: %s' % d['filename'], 'SPEED: %s' % d['_speed_str'], 'PERCENT: %s' % d['_percent_str'], 'ETA: %s' % d['_eta_str'], sep='\n') + print('\nFILE: %s' % title1, 'SPEED: %s' % d['_speed_str'], 'PERCENT: %s' % d['_percent_str'], 'ETA: %s' % d['_eta_str'], sep='\n') ''' _speed_str _percent_str -- these are all the more accurate-like strings rather than just "eta". They give more information, and are also located in source code. @@ -2793,7 +2802,7 @@ def download(self): An error is a rare occurance now thanks to the update in v0.7.6 BETA. """ global max_downloads - global floatnum, part_type, _url, _url_holder + global floatnum, part_type, _url, _url_holder, title1 floatnum = "3.0" part_type = "-- VIDEO --" _url = url_box.get("1.0", END).split(sep="\n") # split() will seperate all strings containing a space or new line @@ -2842,6 +2851,12 @@ def download(self): elif len(_url) == 1: self.kill_button() _url_holder = next(_url_iterator) + ydler = yt.YoutubeDL({'no_color': True, 'quiet': True}) + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + extract = ydler.extract_info(_url_holder, download=False) + title1 = extract['title'] + download_call.undo() ydl.download([_url_holder]) if ext_btn_var.get() == "MP4" \ or ext_btn_var.get() == "WEBM"\ @@ -2936,6 +2951,12 @@ def download(self): print("Download [{}] starting\n".format(index)) thread = threading.Event() thread.wait(wait_time) + ydler = yt.YoutubeDL({'no_color': True, 'quiet': True}) + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + extract = ydler.extract_info(_url_holder, download=False) + title1 = extract['title'] + download_call.undo() ydl.download([_url_holder]) if ext_btn_var.get() == "MP4"\ or ext_btn_var.get() == "WEBM"\ @@ -3040,7 +3061,12 @@ def download(self): try: if len(_url) == 1: self.kill_button() - _url_holder = next(_url_iterator) + ydler = yt.YoutubeDL({'no_color': True, 'quiet': True}) + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + extract = ydler.extract_info(_url_holder, download=False) + title1 = extract['title'] + download_call.undo() ydl.download([_url_holder]) if ext_btn_var.get() == "MP4" \ or ext_btn_var.get() == "WEBM"\ @@ -3127,7 +3153,6 @@ def download(self): print("[info] Maximum number of downloaded files reached!\n\n") self.quit_win() else: - _url_holder = next(_url_iterator) if index >= 1: self._delete_lines() print("\n\nDownload [{}] starting\n".format(index)) @@ -3135,6 +3160,12 @@ def download(self): print("Download [{}] starting\n".format(index)) thread = threading.Event() thread.wait(wait_time) + ydler = yt.YoutubeDL({'no_color': True, 'quiet': True}) + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + extract = ydler.extract_info(_url_holder, download=False) + title1 = extract['title'] + download_call.undo() ydl.download([_url_holder]) if ext_btn_var.get() == "MP4"\ or ext_btn_var.get() == "WEBM"\ @@ -3208,6 +3239,7 @@ def download(self): print("\nDownload [{}] completed\n".format(index)) thread = threading.Event() thread.wait(0.5) + _url_holder = next(_url_iterator) if quality_btn_var.get() != "NONE" \ and audio_btn_var.get() != "NONE": part_type = '-- VIDEO --' @@ -3243,7 +3275,7 @@ def download(self): print("\nDownload COMPLETE!\n") self.quit_win() - def open_selenium(self): + def open_sel_gui(self): """ A fun feature to use when your browsing youtube or other sites. """ @@ -3284,57 +3316,60 @@ def add_border(win, height, width, bg=None, bd=None, text=None, font=None, label add_label(self.selenium_win, "Open = Open Selenium.", '#cbdbfc', fg="#80200f", x=1, y=200) add_label(self.selenium_win, "Close - Close down this window.", '#cbdbfc', fg="#80200f", x=1, y=220) def open_selenium_thread(): - def open_selenium_function(): + def open_selenium(): try: + global driver open_sel.configure(state=DISABLED) if sel_detail['browser'] == 'Firefox': if sel_detail['profile'] != "": if sel_detail['path'] != "": self._profile = webdriver.FirefoxProfile(sel_detail['profile']) - self._driver = webdriver.Firefox(executable_path=PATH, firefox_profile=self._profile) + driver = webdriver.Firefox(executable_path=PATH, firefox_profile=self._profile) else: self._profile = webdriver.FirefoxProfile(sel_detail['profile']) - self._driver = webdriver.Firefox(firefox_profile=self._profile) + driver = webdriver.Firefox(firefox_profile=self._profile) else: if sel_detail['path'] != "": - self._driver = webdriver.Firefox(executable_path=PATH) + driver = webdriver.Firefox(executable_path=PATH) else: - self._driver = webdriver.Firefox() + driver = webdriver.Firefox() if sel_detail['browser'] == 'Chrome': if sel_detail['path'] != "": - self._driver = webdriver.Chrome(executable_path=PATH) + driver = webdriver.Chrome(executable_path=PATH) else: - self._driver = webdriver.Chrome() + driver = webdriver.Chrome() if sel_detail['browser'] == 'Safari': if sel_detail['path'] != "": - self._driver = webdriver.Safari(executable_path=PATH) + driver = webdriver.Safari(executable_path=PATH) else: - self._driver = webdriver.Safari() + driver = webdriver.Safari() if sel_detail['browser'] == 'Opera': if sel_detail['path'] != "": - self._driver = webdriver.Opera(executable_path=PATH) + driver = webdriver.Opera(executable_path=PATH) else: - self._driver = webdriver.Opera() + driver = webdriver.Opera() if sel_detail['browser'] == 'Edge': if sel_detail['path'] != "": - self._driver = webdriver.Edge(executable_path=PATH) + driver = webdriver.Edge(executable_path=PATH) else: - self._driver = webdriver.Edge() + driver = webdriver.Edge() if sel_detail['browser'] == 'Internet Explorer': if sel_detail['path'] != "": - self._driver = webdriver.Ie(executable_path=PATH) + driver = webdriver.Ie(executable_path=PATH) else: - self._driver = webdriver.Ie() - self._driver.get(sel_detail['link']) - self._driver.maximize_window() + driver = webdriver.Ie() + + if sel_detail['link'] != "": + driver.get(sel_detail['link']) + driver.maximize_window() except Exception as exc: logger.exception("ERROR: An error occured while opening selenium: %s" % exc) - sel_thread = threading.Timer(0.2, open_selenium_function) + sel_thread = threading.Timer(0.2, open_selenium) sel_thread.start() def execute_urls_function(): open_sel.configure(state=NORMAL) - download_call.on_get_urls() + self.get_urls() style1.configure('selenium.TButton', width=10) @@ -3349,32 +3384,40 @@ def execute_urls_function(): def get_urls(self): try: + handle = driver.window_handles[0] self._index = 0 - self._driver.switch_to.window(self._driver.window_handles[self._index]) - for i in range(len(self._driver.window_handles)): - if str(self._driver.current_url).startswith(tuple(all_extractors.pack_extractors)): - url_box.insert("1.0", self._driver.current_url + "\n") - self._index += 1 + driver.switch_to.window(driver.window_handles[0]) + for i in range(len(driver.window_handles)): + if str(driver.current_url).startswith(tuple(all_extractors.pack_extractors)): + url_box.insert("1.0", driver.current_url + "\n") try: - self._driver.switch_to.window(self._driver.window_handles[self._index]) + self._index += 1 + driver.switch_to.window(driver.window_handles[self._index]) except IndexError: pass - self._index = 0 - self._driver.switch_to.window(self._driver.window_handles[self._index]) - except AttributeError as exc: - url_box.delete("1.0", END) - url_box.insert("1.0", "Selenium is not open, therefore no URLS detected.") - logger.exception("Not able to detect Selenium. error: %s" % exc) - + driver.switch_to.window(handle) + + # self._index = 0 + # self._driver.switch_to.window(self._driver.window_handles[self._index]) + # for i in range(len(self._driver.window_handles)): + # if str(self._driver.current_url).startswith(tuple(all_extractors.pack_extractors)): + # url_box.insert("1.0", self._driver.current_url + "\n") + # self._index += 1 + # try: + # self._driver.switch_to.window(self._driver.window_handles[self._index]) + # except IndexError: + # pass + # self._index = 0 + # self._driver.switch_to.window(self._driver.window_handles[self._index]) except WebDriverException as exc: url_box.delete("1.0", END) url_box.insert("1.0", "Selenium is not open, therefore no URLS detected.") - logger.exception("Selenium was not able to detect any valid URLS. error: %s" % exc) + logger.exception("Selenium was not able to detect any valid URLS. error:\n %s" % exc) except Exception as exc: url_box.delete("1.0", END) url_box.insert("1.0", "Selenium is not open, therefore no URLS detected.") - logger.exception("Selenium was not able to detect any valid URLS. error: %s" % exc) + logger.exception("Selenium was not able to detect any valid URLS. error:\n %s" % exc) # Threading @@ -3391,14 +3434,10 @@ def on_download(self): download_thread = threading.Thread(target=self.download) download_thread.start() - def on_selenium(self): - selenium_thread = threading.Thread(target=self.open_selenium) + def on_sel_gui(self): + selenium_thread = threading.Thread(target=self.open_sel_gui) selenium_thread.start() - def on_get_urls(self): - get_urls_thread = threading.Thread(target=self.get_urls) - get_urls_thread.start() - class MyLogger: @@ -3406,7 +3445,10 @@ class MyLogger: def debug(msg): now = threading.Event() now.wait(0.30) - print(msg) + if msg.__contains__('ETA'): + pass + else: + print(msg) @staticmethod def warning(msg): @@ -3433,7 +3475,7 @@ def error(msg): edit_format = ttk.Button(root, text=" Edit Formats ", style='some.TButton', state=DISABLED, command=do.edit_format_btns) edit_format.place(x=435, y=375) -detect_btn = ttk.Button(root, text=" Detect URLS ", style='some.TButton', state=DISABLED, command=download_call.on_selenium) +detect_btn = ttk.Button(root, text=" Detect URLS ", style='some.TButton', state=DISABLED, command=download_call.on_sel_gui) detect_btn.place(x=435, y=340) clear_btn = ttk.Button(root, text=" Clear ", style='some.TButton', state=DISABLED, command=do.clear_url_box) diff --git a/main/update_preview.py b/main/update_preview.py new file mode 100644 index 0000000..1d79a6f --- /dev/null +++ b/main/update_preview.py @@ -0,0 +1,307 @@ +from tkinter import * +from tkinter import ttk +import threading +import os +from PIL import Image, ImageTk +import youtube_dl as yt + +def my_hook(d): + global status, merged + + if kill_event.is_set(): + print("\nTerminated") + delthread = threading.Timer(3.0, lambda: os.remove(d['tmpfilename'])) + delthread.start() + raise ConnectionAbortedError + + if pause_event.is_set(): + print("\nPaused") + raise ConnectionAbortedError + + if d['status'] == 'finished': + if merged: + status = 'Finished' + update_dict() + + else: + status = 'Post Processing' + merged = True + update_dict() + + treeview.item(row, text=meta['title']) + treeview.set(row, 'ext', f'.{meta["ext"].lower()}') + treeview.set(row, 'size', d['_total_bytes_str']) + treeview.set(row, 'percent', '100.0%') + treeview.set(row, 'eta', '0:00') + treeview.set(row, 'speed', 'N/A') + treeview.set(row, 'status', status) + thread_event = threading.Event() + thread_event.wait(1.50) + + if d['status'] == 'downloading': + if status != 'Downloading': + status = 'Downloading' + update_dict() + treeview.item(row, text=meta['title']) + treeview.set(row, 'ext', f'.{meta["ext"].lower()}') + treeview.set(row, 'size', d['_total_bytes_str']) + treeview.set(row, 'percent', d['_percent_str']) + treeview.set(row, 'eta', d['_eta_str']) + treeview.set(row, 'speed', d['_speed_str']) + treeview.set(row, 'status', status) + + if d['status'] == 'error': + if status != 'Error': + status = 'Error' + update_dict() + treeview.item(row, text=meta['title']) + treeview.set(row, 'ext', f'.{meta["ext"].lower()}') + treeview.set(row, 'size', '-') + treeview.set(row, 'percent', '-') + treeview.set(row, 'eta', '-') + treeview.set(row, 'speed', '-') + treeview.set(row, 'status', status) + + +class MyLogger: + + @staticmethod + def debug(msg): + thread_event = threading.Event() + thread_event.wait(0.25) + + @staticmethod + def warning(msg): + if msg == "ERROR: unable to download video data: ": + pass + else: + global status + if status != 'Error': + status = 'Error' + update_dict() + treeview.item(row, text=meta['title']) + treeview.set(row, 'ext', f'.{meta["ext"].lower()}') + treeview.set(row, 'size', '-') + treeview.set(row, 'percent', '-') + treeview.set(row, 'eta', '-') + treeview.set(row, 'speed', '-') + treeview.set(row, 'status', status) + print(msg) + + @staticmethod + def error(msg): + if msg == "ERROR: unable to download video data: ": + pass + else: + global status + if status != 'Error': + status = 'Error' + update_dict() + treeview.item(row, text=meta['title']) + treeview.set(row, 'ext', f'.{meta["ext"].lower()}') + treeview.set(row, 'size', '-') + treeview.set(row, 'percent', '-') + treeview.set(row, 'eta', '-') + treeview.set(row, 'speed', '-') + treeview.set(row, 'status', status) + print(msg) + +root = Tk() +root.title("Learning Tkinter - TreeView") +root.configure(bg='#ededed', bd=5) +root.geometry("1000x280") +root.minsize(1000, 280) # can set the minimum size +root.maxsize(1000, 560) # can set the maximum size + +def resize(event): + try: + height = root.winfo_height() + terminate_btn.place(x=920, y=height-50) + pause_btn.place(x=500, y=height-50) + except: + pass + +root.bind("", resize) + +treeview = ttk.Treeview(root) +treeview.grid(padx=29) + +treeview.config(height=10) + +treeview.config(columns=('ext', 'size', 'percent', 'eta', 'speed', 'status')) +treeview.column('#0', width=280, anchor=W) +treeview.column('ext', width=100, anchor=W) +treeview.column('size', width=100, anchor=W) +treeview.column('percent', width=100, anchor=W) +treeview.column('eta', width=100, anchor=W) +treeview.column('speed', width=100, anchor=W) +treeview.column('status', width=150, anchor=W) + +treeview.heading('#0', text="Title", anchor=W) +treeview.heading('ext', text="Extension", anchor=W) +treeview.heading('size', text="Size", anchor=W) +treeview.heading('percent', text="Percent", anchor=W) +treeview.heading('eta', text="ETA", anchor=W) +treeview.heading('speed', text="Speed", anchor=W) +treeview.heading('status', text="Status", anchor=W) + +status = 'Queued' +row = 'URL1' +merged = False +rows = ['URL1'] + +row_dict = { + "URL1": status +} + +treeview.insert("", '0', row, text="-") +treeview.set(row, 'ext', '-') +treeview.set(row, 'size', '-') +treeview.set(row, 'percent', '0.0%') +treeview.set(row, 'eta', '-') +treeview.set(row, 'speed', '-') +treeview.set(row, 'status', status) + +count = 1 +def callback(event): + global count + def remove(i): + treeview.selection_remove(i) + def assign(): + global count + count = 1 + if count == 1: + count = 2 + for i in row_dict: + if row_dict.get(i) == 'Terminated': + remove(i) + if row_dict.get(i) == 'Queued': + remove(i) + thread = threading.Timer(0.2, assign) # using threading to stop this function from looping + thread.start() + +def update_dict(): + row_dict.update(URL1=status) + print(row_dict) + +def pause_thread(): + def pause(): + if treeview.selection() == (): + pass + else: + global resume_btn, resume_img, status + + status = 'Paused' + update_dict() + treeview.set(row, 'status', status) + pause_btn.destroy() + resume_img = ImageTk.PhotoImage(Image.open('images/#resume_32px.png')) + resume_btn = ttk.Button(root, image=resume_img, command=resume_thread) + resume_btn.place(x=500, y=root.winfo_height()-50) + + pause_event.set() + + pause_thread = threading.Thread(target=pause) + pause_thread.start() + +def resume_thread(): + def resume(): + if treeview.selection() == (): + pass + else: + global pause_btn, pause_img, status + + status = 'Resuming' + update_dict() + treeview.set(row, 'status', status) + resume_btn.destroy() + pause_img = ImageTk.PhotoImage(Image.open('images/#pause_32px.png')) + pause_btn = ttk.Button(root, image=pause_img, command=pause_thread) + pause_btn.place(x=500, y=root.winfo_height()-50) + + resume_event.set() + + if resume_event.is_set(): + pause_event.clear() + resume_event.clear() + download_ytdl() + + resume_thread = threading.Thread(target=resume) + resume_thread.start() + +def terminate_thread(): + def terminate(): + if treeview.selection() == (): + pass + else: + global status + + status = 'Terminated' + treeview.item(row, text='TERMINATED') + treeview.set(row, 'ext', f'N/A') + treeview.set(row, 'size', 'N/A') + treeview.set(row, 'percent', '0.0%') + treeview.set(row, 'eta', '0:00') + treeview.set(row, 'speed', 'N/A') + treeview.set(row, 'status', status) + update_dict() + treeview.selection_remove(row) + + kill_event.set() + + terminate_thread = threading.Thread(target=terminate) + terminate_thread.start() + +treeview.bind("<>", callback) + +treeview.config(selectmode='browse') # allow 1 at a time to be selected + +terminate_img = ImageTk.PhotoImage(Image.open('images/#stop_32px.png')) +terminate_btn = ttk.Button(root, image=terminate_img, command=terminate_thread) +terminate_btn.place(x=920, y=230) + +pause_img = ImageTk.PhotoImage(Image.open('images/#pause_32px.png')) +pause_btn = ttk.Button(root, image=pause_img, command=pause_thread) +pause_btn.place(x=500, y=230) + +pause_event = threading.Event() +resume_event = threading.Event() +kill_event = threading.Event() + +video_ops = { + 'outtmpl': 'R:/Downloaded Videos/%(title)s.%(ext)s', + 'format': 'bestvideo[height<=360,width<=640]+bestaudio/best[abr<=1441]', + 'ext': 'mkv', + 'merge_output_format': 'mkv', + 'quiet': True, + 'progress_hooks': [my_hook], + 'logger': MyLogger() +} + +link = 'https://www.youtube.com/watch?v=QglaLzo_aPk' + +def download_ytdl(): + with yt.YoutubeDL(video_ops) as ydl: + global meta, status + + if status == 'Queued': + meta = ydl.extract_info(link, download=False) + + status = 'Pre Processing' + update_dict() + treeview.item(row, text=link) + treeview.set(row, 'status', status) + + thread_event = threading.Event() + thread_event.wait(1.00) + + try: + ydl.download([link]) + except yt.utils.DownloadError as exc: + if status == 'Terminated': + kill_event.clear() + +yt_thread = threading.Timer(5.0, download_ytdl) +yt_thread.start() + +mainloop() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d7f15e7..c76f921 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ youtube-dl selenium requests -Pillow (Python Image Libary) cryptography +Pillow (Python Image Libary) ------------------------------------ -ffmpeg is needed in command line. +ffmpeg is needed in command line. \ No newline at end of file diff --git a/version.txt b/version.txt index cc863fd..e5a4a5e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.81 \ No newline at end of file +1.0.9 \ No newline at end of file