diff --git a/PYTHON APPS/DownTube/Exceptions.py b/PYTHON APPS/DownTube/Exceptions.py new file mode 100644 index 00000000..aae18fbc --- /dev/null +++ b/PYTHON APPS/DownTube/Exceptions.py @@ -0,0 +1,18 @@ +class Downtube_Error(Exception): + """ This is the base exception class for all other user defined exception(classes) """ + pass + +class Link_Error(Downtube_Error): + """ When there is no Link is given as input """ + pass + +class InvalidLink(Downtube_Error): + """ When the ink URL isn't a youtube link """ + pass + +class DirectoryError(Downtube_Error): + """ when No directory is given """ + pass +class ResolutionError(Downtube_Error): + """ when no resolution is givenn """ + pass \ No newline at end of file diff --git a/PYTHON APPS/DownTube/Functionality.py b/PYTHON APPS/DownTube/Functionality.py new file mode 100644 index 00000000..868e27a6 --- /dev/null +++ b/PYTHON APPS/DownTube/Functionality.py @@ -0,0 +1,138 @@ +import re +from tkinter.constants import END +import downtube as dt +import Exceptions as ex +from tkinter import messagebox as msg +from tkinter import filedialog as fd +import urllib.request +import pytube as pt +from pytube import YouTube + + +def clear(link, get_link): + """ Clears the Entry box widget """ + download_link = link.get() + if download_link == "": + msg.showwarning( + title= "Warning", + message= "Warning: The input field is already empty", + ) + else : + get_link.delete(0,last= len(download_link)) + +def browse_folder(get_dir): + global dir_path + dir_path = fd.askdirectory() + get_dir.delete(0, END) + get_dir.configure(fg= "black") + get_dir.insert(0, dir_path) + if dir_path == "": + get_dir.configure(fg= "grey") + get_dir.insert(0, "Choose a folder") + +def check_connectivity(link): # Referred website: codespeedy (https://www.codespeedy.com/how-to-check-the-internet-connection-in-python/) + """ Checks for internet connectivity or valid youtube link """ + try: + urllib.request.urlopen(link) + except: + msg.showerror( + title= "Network Error", + message= "Connectivity issue found", + detail= "Check you internet connectivity \nor\n the link might not be correct" + ) + +def dwn(download_link, video_resolution, directory): + """ Downloads the video """ + yt = pt.YouTube(download_link) + try: + if video_resolution == "": + raise ex.ResolutionError + except ex.ResolutionError: + msg.showerror(title= "Downtube", + message= "No video resolution found" + ) + if video_resolution == "360p": + try: + video = yt.streams.filter(progressive= True, file_extension= "mp4", res= "360p", type= "video").first() + if video == None: + raise AttributeError + else: + return video + #video.download(output_path= directory) + except AttributeError: + msg.showinfo(title= "Downtube", + message= "The video resolution is not available to be downloaded ", + detail= "Try downloading with other resolution" + ) + elif video_resolution == "720p": + try: + video = yt.streams.filter(progressive= True, file_extension= "mp4", res= "720p", type= "video").first() + if video == None: + raise AttributeError + else: + return video + #video.download(output_path= directory) + except AttributeError: + msg.showinfo(title= "Downtube", + message= "The video resolution is not available to be downloaded ", + detail= "Try downloading with other resolution" + ) + +def clear_inputs(get_link, get_resolution): + input_of_get_link = get_link.get() + input_of_get_resolution = get_resolution.get() + get_link.delete(0,last= len(input_of_get_link)) + get_resolution.delete(0,last= len(input_of_get_resolution)) + + + +def download_bt(download_link, get_link, video_resolution, directory, get_dir, resolution_box ): + """ All the process for downloading will be done here """ + verify_YT = re.search(r"youtube.com", download_link) # Verifying youtube link or not + + + try: + """ Checks for youtube link and rasie error if link is not valid or not found """ + if download_link == "": + raise ex.Link_Error + elif verify_YT == None: + raise ex.InvalidLink + else: + check_connectivity(download_link) + get_link.delete(0,last= len(download_link)) + try: + if directory == "": + raise ex.DirectoryError + else: + vid = dwn(download_link, video_resolution, directory) + vid.download(directory) #type: ignore + clear_inputs(get_dir, resolution_box) + except ex.DirectoryError: + msg.showerror(title= "Directory error", + message= "Directory Error: No Directory is found", + detail= "Browse or enter the directory to save the file" + ) + except ex.Link_Error: + msg.showerror( + title= "Link Error", + message= "Youtube link Not Found", + detail= "You've not given any link in the \"Link section\"" + ) + except ex.InvalidLink: + msg.showerror( + title= "Invalid link", + message= "Error: Invalid link", + detail= "The given link is invalid or doesn't belongs to youtube" + ) + + try: + if directory == "Choose a folder" : + raise ex.DirectoryError + except ex.DirectoryError: + msg.showerror( + title = "Directory Error", + message="Error: No directory is given to save the file" + ) + +if __name__=='__main__': + dt.main() diff --git a/PYTHON APPS/DownTube/Props/YT_icon_ico_64x64.ico b/PYTHON APPS/DownTube/Props/YT_icon_ico_64x64.ico new file mode 100644 index 00000000..7b5b87ab Binary files /dev/null and b/PYTHON APPS/DownTube/Props/YT_icon_ico_64x64.ico differ diff --git a/PYTHON APPS/DownTube/Props/YT_icon_png_64x64.png b/PYTHON APPS/DownTube/Props/YT_icon_png_64x64.png new file mode 100644 index 00000000..b94e9c78 Binary files /dev/null and b/PYTHON APPS/DownTube/Props/YT_icon_png_64x64.png differ diff --git a/PYTHON APPS/DownTube/Props/sample_GUI.jpg b/PYTHON APPS/DownTube/Props/sample_GUI.jpg new file mode 100644 index 00000000..868c3c0d Binary files /dev/null and b/PYTHON APPS/DownTube/Props/sample_GUI.jpg differ diff --git a/PYTHON APPS/DownTube/README.md b/PYTHON APPS/DownTube/README.md new file mode 100644 index 00000000..8de17885 --- /dev/null +++ b/PYTHON APPS/DownTube/README.md @@ -0,0 +1,39 @@ +# DownTube + +## Introduction + +**DownTube** is a piece of GUI based program which downloads Youtube videos either in 360p or 720p resolution. Very simple User Interface. + +--- + +## Packages + +Refer [requirements.txt](requirements.txt) for python packages used to develop this program. + +--- + +## Features + +**Features of this application:** + +* Just paste the complete URL(***from search bar of the browser***) of the video you want to download. + +* You can store the downloaded video whereever you want(***just click browse button***) + +* Choose the available resolution(either 360p or 720p). + +--- + +## Information to users + +* If videos aren't downloaded, Try to update or reinstall `pytube` package. Since pytube had issues in downloading the videos from YouTube in the past. + +* If this program shows not responding in windows, kindly don't close the application. Since this the sign of downloading the videos. + +* This program was tested on Windows machines and works completely fine. But it may or may not work on other operating system platforms like(Linux or MacOS, etc.,). + +--- + +## Sample GUI + +![sample UI](/PYTHON%20APPS/DownTube/Props/sample_GUI.jpg) diff --git a/PYTHON APPS/DownTube/downtube.py b/PYTHON APPS/DownTube/downtube.py new file mode 100644 index 00000000..51cb9c95 --- /dev/null +++ b/PYTHON APPS/DownTube/downtube.py @@ -0,0 +1,101 @@ +import tkinter as tk +from tkinter import ttk +from tkinter.constants import RAISED + +# Importing the user defined module. +import Functionality as fn + +def main(): + + # Root window + main_window = tk.Tk() + main_window.title("Downtube") # Title of the window + main_window.minsize(600, 400) # Default Window size + main_window.rowconfigure(0,weight=1) # Rowconfiguration of root window in order to expand widgets, when window is is resized. + main_window.columnconfigure(0, weight= 1) # Rowconfiguration of root window in order to expand widgets, when window is is resized. + main_window.rowconfigure(1,weight=1) # Rowconfiguration of root window in order to expand widgets, when window is is resized. ### + main_window.columnconfigure(1, weight= 1) # Rowconfiguration of root window in order to expand widgets, when window is is resized. ### + main_window.configure(bg= "white") # Background color for root window + +# Parent Frame widgets: + + # Input frame widget. + main_frame = tk.Frame(main_window, borderwidth= 2,bg= "white") + main_frame.grid(row= 0, column= 0, columnspan= 3, rowspan= 4) + + # Configuration of row and column of "main_frame" in order to expand widgets, when window is is resized. + main_frame.columnconfigure(0, weight= 1) + main_frame.columnconfigure(1, weight= 1) + main_frame.columnconfigure(2, weight= 1) + main_frame.rowconfigure(0, weight= 1) + main_frame.rowconfigure(1, weight= 1) + main_frame.rowconfigure(2, weight= 1) + main_frame.rowconfigure(3, weight= 1) + +# main_frame widgets + + # Display label indicating --> 'paste the youtube link'. + linke_label = tk.Label(main_frame, text= "Paste the YouTube link", width= 40, borderwidth= 1, anchor= "w", bg= "white") + linke_label.grid(row= 0, column= 1, sticky="WE", pady= 2) + + # Display label indicating --> 'Browse to save the file'. + linke_label = tk.Label(main_frame, text= "Browse to save the file", bg= "white", width= 40, anchor= "w") + linke_label.grid(row= 2, column= 1) + + # Display label indicating --> 'Choose the resolution'. + resolution_lb = tk.Label( + main_frame, text= "Choose the resolution", width= 15, + height= 1, anchor= "w", bg= "white" + ) + resolution_lb.grid(row=4, column= 1, pady=2, sticky= "we") + + + # Entry Widget --> Getting youtube link. + link = tk.StringVar() + get_link = tk.Entry(main_frame, textvariable= link, bg= "white") + get_link.grid(row= 1, column= 1, sticky= "wE", pady= 2) + + # Entry Widget --> Getting Directory to save file. + directory = tk.StringVar() + get_dir = tk.Entry(main_frame,textvariable= directory, bg= "white", fg= "grey") + get_dir.grid(row= 3, column= 1, sticky= "wE") + get_dir.insert(0, "Choose a folder") + + # Combo box Widget --> Shows the options(Resolution) available. + my_string_var = tk.StringVar() + resolution_box = ttk.Combobox( + main_frame, textvariable=my_string_var, + values=["360p", "720p"]) + resolution_box.grid(row=5, column= 1, pady=2, sticky= "we") + + # Button widget --> Clear the Input field of youtube entry widget + clear_bt = tk.Button( + main_frame, text= "Clear", + width= 10, height= 1, + bg= "grey", + command= lambda: fn.clear(link, get_link) + ) + clear_bt.grid(row= 1, column= 2, pady= 2) + + # Button widget --> Opens file explorer to save the file + temp_bt = tk.Button( + main_frame, text= "Browse", + relief= RAISED, width= 10, + height= 1, bg= "grey", + command= lambda :fn.browse_folder(get_dir) + ) + temp_bt.grid(row=3, column= 2, pady=2) + + # Button widget --> Downloads the video + download_file = tk.Button( + main_frame, text= "Download", + relief= RAISED, width= 10, + height= 1, bg= "grey", anchor= "center", + command= lambda :fn.download_bt(link.get(), get_link, resolution_box.get(), directory.get(), get_dir, resolution_box) + ) + download_file.grid(row=5, column= 2, pady=2) + + main_window.mainloop() + +if __name__ == '__main__': + main() diff --git a/PYTHON APPS/DownTube/requirements.txt b/PYTHON APPS/DownTube/requirements.txt new file mode 100644 index 00000000..0ff08030 Binary files /dev/null and b/PYTHON APPS/DownTube/requirements.txt differ