├── Utils ├── __init__.py ├── utils.py ├── helpers.py └── tkutils.py ├── Images ├── logo.ico ├── Youtube.png ├── home_screen.PNG ├── search_result.PNG ├── video_compiling.PNG ├── download_complete.PNG └── file_downloading.PNG ├── requirements.txt ├── README.md └── main.py /Utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Images/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/logo.ico -------------------------------------------------------------------------------- /Images/Youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/Youtube.png -------------------------------------------------------------------------------- /Images/home_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/home_screen.PNG -------------------------------------------------------------------------------- /Images/search_result.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/search_result.PNG -------------------------------------------------------------------------------- /Images/video_compiling.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/video_compiling.PNG -------------------------------------------------------------------------------- /Images/download_complete.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/download_complete.PNG -------------------------------------------------------------------------------- /Images/file_downloading.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mabdullahadeel/Youtube-Video-Downloader/HEAD/Images/file_downloading.PNG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2020.12.5 2 | chardet==3.0.4 3 | decorator==4.4.2 4 | idna==2.10 5 | imageio==2.9.0 6 | imageio-ffmpeg==0.4.2 7 | moviepy==1.0.3 8 | numpy==1.19.4 9 | Pillow==8.0.1 10 | proglog==0.1.9 11 | pytube==10.0.0 12 | requests==2.25.0 13 | tqdm==4.54.1 14 | typing-extensions==3.7.4.3 15 | urllib3==1.26.2 16 | -------------------------------------------------------------------------------- /Utils/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains all the code for interacting with the YouTube video download related stuff 3 | """ 4 | import pytube 5 | 6 | 7 | def search(url): 8 | video = pytube.YouTube(url=url) 9 | streams = video.streams 10 | 11 | return { 12 | "streams": [stream for stream in streams 13 | if stream.mime_type == "video/mp4" 14 | or stream.mime_type == "audio/mp4" 15 | or stream.mime_type == "audio/mp3" 16 | ], 17 | "thumbnail_url": video.thumbnail_url, 18 | "title": video.title, 19 | "originalStream": streams 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Youtube-Video-Downloader 2 |
This repository holds the code for a custom desktop application that downloads and Edit a YouTube Video provided the URL. 3 | The main features include that the user can download the video in any of the available resolution and if the required "codec" 4 | for the video is not available, the program downloads the the audio and video separately 5 | and compile it to make the final desired video and save it to the user defined location if user has defined else it finds the 6 | download folder on the machine and saves the file there. The interface (UI) is build using tkinter.
7 |

Home Screen

8 | 9 | ![home_screen](Images/home_screen.PNG) 10 | 11 |

Search Results

12 | 13 | ![search_result](Images/search_result.PNG) 14 | 15 |

File Downloading

16 | 17 | ![search_result](Images/file_downloading.PNG) 18 | 19 |

File Downloading

20 | 21 | ![video_cmpiling](Images/video_compiling.PNG) 22 | 23 |

Process Finished

24 | 25 | ![search_result](Images/download_complete.PNG) 26 | 27 | ## Setup Directories 28 | ```cmd 29 | cd/you_desired_directory/ 30 | mkdir any_name 31 | ``` 32 | ## Setting up Virtual Environment 33 | ```bash 34 | python -m virtualenv env | python -m venv env (if virtualenv is not installed) 35 | env\Scripts\activate (windows) | source env/bin/activate (Mac, Linux) 36 | git clone -commit hash 37 | 38 | # installing dependencies 39 | pip install -r requirements.txt 40 | ``` 41 | 42 | #### You are set to develop amazing new features 🙂 43 | 44 | ### Main Dependencies 45 | 46 | * pytube 47 | * pymovie 48 | -------------------------------------------------------------------------------- /Utils/helpers.py: -------------------------------------------------------------------------------- 1 | import pytube 2 | from tkinter import * 3 | from tkinter import ttk 4 | 5 | 6 | def show_image(image_url): 7 | from PIL import Image, ImageTk 8 | import requests 9 | from io import BytesIO 10 | 11 | url = image_url 12 | response = requests.get(url) 13 | raw_data = response.content 14 | response.close() 15 | im = Image.open(BytesIO(raw_data)) 16 | im = im.resize((100, 80), Image.ANTIALIAS) 17 | photo = ImageTk.PhotoImage(im) 18 | return photo 19 | 20 | 21 | def combine_audio(vidname, audname, outname, fps=30): 22 | import moviepy.editor as mpe 23 | my_clip = mpe.VideoFileClip(vidname) 24 | audio_background = mpe.AudioFileClip(audname) 25 | final_clip = my_clip.set_audio(audio_background) 26 | final_clip.write_videofile(outname, fps=fps, logger=None) 27 | 28 | 29 | # Grabs the file path for Download on Any Machine 30 | def file_path(): 31 | import os 32 | home = os.path.expanduser('~') 33 | download_path = os.path.join(home, 'Downloads') 34 | return download_path 35 | 36 | 37 | class ProgressBar: 38 | def __init__(self, root_frame, length, fileSize=None): 39 | self.root_frame = root_frame 40 | self.length = length 41 | self.fileSize = fileSize 42 | 43 | # Creating the progress bar 44 | self.progressbar_frame = LabelFrame(self.root_frame, borderwidth=0, highlightthickness=0) 45 | self.progressbar_frame.configure(background="#2b2929") 46 | self.progressbar = ttk.Progressbar(self.progressbar_frame, orient=HORIZONTAL, length=self.length, mode="determinate") 47 | self.label = Label(self.progressbar_frame, text="Downloaded", bg="#2b2929", fg="white", font=('bold', 14)) 48 | 49 | def place_progressbar(self, Row, Column, columnSpan): 50 | self.progressbar_frame.grid(row=Row, column=Column, columnspan=columnSpan, padx=5, pady=(10, 30), sticky=W) 51 | # Placing internal Frame Components 52 | self.label.grid(row=0, column=0, pady=15, sticky=W) 53 | self.progressbar.grid(row=0, column=1, sticky=W, padx=(150, 10)) 54 | 55 | def step(self, increment_value): 56 | self.progressbar.config(value=increment_value) 57 | # updating the frame to update the progressbar in real time 58 | self.progressbar_frame.update_idletasks() 59 | 60 | def progress_Check(self, stream: pytube.Stream, chunk: bytes, bytes_remaining: int): 61 | # Gets the percentage of the file that has been downloaded. 62 | file_size = self.fileSize 63 | percent = (100 * (int(file_size) - int(bytes_remaining))) / file_size 64 | self.step(increment_value=percent) 65 | # print("{:00.0f}% downloaded".format(percent)) 66 | 67 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from tkinter import messagebox 3 | from tkinter.font import Font 4 | from PIL import ImageTk 5 | from Utils.utils import * 6 | from Utils.tkutils import * 7 | import threading 8 | 9 | # Window Setting 10 | root = Tk() 11 | root.iconbitmap("Images/logo.ico") 12 | root.title("AU YT Downloader") 13 | 14 | width = 900 15 | height = 550 16 | root.minsize(810, 650) 17 | root.configure(background="#2b2929") 18 | # Placing the screen in the center of the screen 19 | screen_width = root.winfo_screenwidth() 20 | screen_height = root.winfo_screenheight() 21 | root.geometry(f"{width}x{height}+{int((screen_width / 2) - (width / 2))}+{int((screen_height / 2) - (height / 2))}") 22 | 23 | # Responsive Screen Configuration 24 | n_rows = 3 25 | n_columns = 2 26 | for i in range(n_rows): 27 | root.grid_rowconfigure(i, weight=1) 28 | for i in range(n_columns): 29 | root.grid_columnconfigure(i, weight=1) 30 | 31 | # Global Font Configurations 32 | heading_font = Font( 33 | family="Helvetica", 34 | size=20, weight="bold", slant="roman", underline=0, overstrike=0 35 | ) 36 | 37 | 38 | def thread_search(): 39 | if url_entry.get(): 40 | search_button['state'] = DISABLED 41 | threading.Thread(target=search_video).start() 42 | else: 43 | messagebox.showerror('Required Fields', 'Please Provide a valid URL') 44 | 45 | 46 | def search_video(): 47 | video_url = url_entry.get() 48 | if video_url: 49 | res = search(url=video_url) 50 | 51 | # Creating the Frame from the available data to download the video 52 | show_result_frame(root_window=root, 53 | available_stream=res["streams"], 54 | originalStream=res["originalStream"], 55 | thumbnail_url=res["thumbnail_url"], 56 | title=res["title"], 57 | video_url=video_url, 58 | search_button=search_button, 59 | url_entry=url_entry) 60 | 61 | 62 | from PIL import Image 63 | image = ImageTk.PhotoImage(Image.open("images/Youtube.png")) 64 | imageLabel = Label(root, image=image, bg="#2b2929") 65 | imageLabel.grid(row=0, column=0, sticky=E) 66 | heading = Label(root, text='Youtube Video Downloader', font=heading_font, bg="#2b2929", fg="white") 67 | heading.grid(row=0, column=1, padx=0) 68 | 69 | url_label = Label(root, text='Video URL', font=('bold', 14), bg="#2b2929", fg="white") 70 | url_label.grid(row=1, column=0, padx=0) 71 | url_text = StringVar() 72 | url_entry = Entry(root, textvariable=url_text, width=55, borderwidth=1, font=('bold', 14)) 73 | url_entry.grid(row=1, column=1) 74 | 75 | search_button = Button(root, text='Search', bg='#5872a8', command=thread_search, 76 | fg='#ffffff', font=('bold', 13), width=20) 77 | search_button.grid(row=2, column=0, columnspan=2, pady=10) 78 | 79 | 80 | root.mainloop() -------------------------------------------------------------------------------- /Utils/tkutils.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains all the helper functions use to build UI using tkinter 3 | """ 4 | import shutil 5 | import os 6 | import threading 7 | from tkinter import * 8 | from tkinter import ttk 9 | from tkinter import filedialog, messagebox 10 | import pytube 11 | import random 12 | import Utils.helpers as helperFunctions 13 | 14 | 15 | def show_result_frame(root_window, *args, **kwargs): 16 | result_frame = LabelFrame(root_window) 17 | result_frame.configure(background="#2b2929") 18 | result_frame.grid(row=3, column=0, columnspan=2, rowspan=4, padx=(2, 2), pady=(0, 10)) 19 | 20 | resolution_options = {} 21 | 22 | for stream in kwargs["available_stream"]: 23 | if stream.type == "video": 24 | if f"Video {stream.resolution}" in resolution_options: 25 | resolution_options.update([(f"Video {stream.resolution}-{random.randint(0, 100)}", int(stream.itag))]) 26 | else: 27 | resolution_options.update([(f"Video {stream.resolution}", int(stream.itag))]) 28 | if stream.type == "audio": 29 | resolution_options.update([(f"Audio {stream.abr}", int(stream.itag))]) 30 | 31 | def thread_download_video(): 32 | if resolution_dropdown.get(): 33 | threading.Thread(target=download_video).start() 34 | else: 35 | messagebox.showerror('Required Fields', 'Please select required resolution.') 36 | 37 | def select_folder(): 38 | path = filedialog.askdirectory(initialdir=helperFunctions.file_path(), title="Select Folder") 39 | if path: 40 | file_path_info.config(text=f'file will be saved to {path}. Press "Select Folder" to change') 41 | global f_file_path, r_file_path 42 | f_file_path = path 43 | r_file_path = f_file_path.replace("/", "\\") 44 | 45 | def try_another(): 46 | kwargs["url_entry"].delete(0, END) 47 | result_frame.destroy() 48 | kwargs["search_button"]["state"] = NORMAL 49 | 50 | def download_video(): 51 | if resolution_dropdown.get(): 52 | # Disabling the essential buttons to prevent running multiple threads 53 | download_button["state"] = DISABLED 54 | kwargs["search_button"]["state"] = DISABLED 55 | another_url_button["state"] = DISABLED 56 | essential_messages.config(text="Downloading the video...........") 57 | file_name_assigned = file_name_entry.get() 58 | try: 59 | video_progressBar.fileSize = pytube.YouTube(url=kwargs["video_url"])\ 60 | .streams.get_by_itag(resolution_options[f"{resolution_dropdown.get()}"]).filesize 61 | streamQuery = pytube.YouTube(url=kwargs["video_url"], 62 | on_progress_callback=video_progressBar.progress_Check)\ 63 | .streams.get_by_itag(resolution_options[f"{resolution_dropdown.get()}"]) 64 | if streamQuery.audio_codec and streamQuery.audio_codec == "mp4a.40.2": 65 | path_to_file = streamQuery.download(output_path=r_file_path 66 | if "r_file_path" in globals() else helperFunctions.file_path(), 67 | filename=file_name_assigned if file_name_assigned else streamQuery.title) 68 | essential_messages.config(text="Done!") 69 | 70 | user_selection = messagebox.askquestion('Download Successful', 71 | f"Video has been downloaded to {path_to_file}. Do you want to play it?", 72 | icon='info', parent=root_window) 73 | if user_selection == 'yes': 74 | os.system('"%s"' % path_to_file) 75 | # Enabling the search button for next search 76 | kwargs["search_button"]["state"] = NORMAL 77 | download_button["state"] = NORMAL 78 | another_url_button["state"] = NORMAL 79 | else: 80 | essential_messages.config(text="Creating and Compiling the Video for you! Please Wait....") 81 | streamQuery.download(filename=f"Video_0mpOlokeLYrCsQ64hk5K", output_path="cached/") 82 | # Downloading the audio separately 83 | pytube.YouTube(url=kwargs["video_url"], 84 | on_progress_callback=video_progressBar.progress_Check)\ 85 | .streams.get_by_itag(140).download(filename="Audio_0mpOlokeLYrCsQ64hk5K", output_path="cached/") 86 | # Merging the audio and video 87 | master_path = r_file_path if "r_file_path" in globals() else helperFunctions.file_path() 88 | f_path = ('"%s"' % master_path) 89 | save_path = f"{f_path}/{file_name_assigned if file_name_assigned else streamQuery.title}.mp4" 90 | helperFunctions.combine_audio( 91 | vidname="cached/Video_0mpOlokeLYrCsQ64hk5K.mp4", 92 | audname="cached/Audio_0mpOlokeLYrCsQ64hk5K.mp4", 93 | outname=save_path, 94 | fps=30 95 | ) 96 | # Emptying / Removing the cached folder 97 | if os.path.exists('cached/'): 98 | shutil.rmtree("cached") 99 | essential_messages.config(text="Done!") 100 | # Enabling the search button for next search 101 | kwargs["search_button"]["state"] = NORMAL 102 | download_button["state"] = NORMAL 103 | another_url_button["state"] = NORMAL 104 | 105 | user_selection = messagebox.askquestion('Download Successful', 106 | f"Video has been downloaded to {save_path}. Do you want to play it?", 107 | icon='info', parent=root_window) 108 | if user_selection == 'yes': 109 | os.system(save_path) 110 | except AttributeError: 111 | download_file = pytube.YouTube(url=kwargs["video_url"], 112 | on_progress_callback=video_progressBar.progress_Check)\ 113 | .streams.get_highest_resolution() 114 | download_file.download(output_path=f"{r_file_path if 'r_file_path' in globals() else helperFunctions.file_path()}") 115 | # Enabling the search button for next search 116 | kwargs["search_button"]["state"] = NORMAL 117 | download_button["state"] = NORMAL 118 | another_url_button["state"] = NORMAL 119 | 120 | thumbnail = helperFunctions.show_image(image_url=kwargs["thumbnail_url"]) 121 | thumbnail_label = Label(result_frame, image=thumbnail) 122 | thumbnail_label.image = thumbnail 123 | thumbnail_label.grid(row=0, column=0, sticky=W) 124 | 125 | title_frame = LabelFrame(result_frame, borderwidth=0, highlightthickness=0) 126 | title_frame.configure(background="#2b2929") 127 | title_frame.grid(row=0, column=1, padx=(150, 10), pady=(10, 40), sticky=W) 128 | 129 | title = Label(title_frame, text=kwargs["title"], font=("bold", 12), bg="#2b2929", fg="white", wraplength=500) 130 | title.grid(row=0, column=0, sticky=W) 131 | 132 | resolution_label = Label(result_frame, text="Resolution", font=('bold', 14), bg="#2b2929", fg="white") 133 | resolution_label.grid(row=1, column=0, pady=8, sticky=W) 134 | resolution_dropdown_value = StringVar() 135 | resolution_dropdown = ttk.Combobox(result_frame, textvariable=resolution_dropdown_value, width=33, font=('bold', 14)) 136 | resolution_dropdown['values'] = [k for k in resolution_options] 137 | resolution_dropdown.grid(row=1, column=1, pady=8, padx=(150, 10), sticky=W) 138 | 139 | file_name = Label(result_frame, text='File Name', font=('bold', 14), bg="#2b2929", fg="white") 140 | file_name.grid(row=2, column=0, pady=8, sticky=W) 141 | file_name_text = StringVar() 142 | file_name_entry = Entry(result_frame, textvariable=file_name_text, width=35, borderwidth=1, font=('bold', 14)) 143 | file_name_entry.grid(row=2, column=1, pady=8, sticky=W, padx=(150, 10)) 144 | 145 | download_button = Button(result_frame, text='Download', bg='#5872a8', command=thread_download_video, 146 | fg='#ffffff', font=('bold', 13), width=18) 147 | download_button.grid(row=3, column=1, sticky=W, padx=(150, 10)) 148 | 149 | file_path_button = Button(result_frame, text='Select Folder', bg='#5872a8', command=select_folder, 150 | fg='#ffffff', font=('bold', 13), width=18) 151 | file_path_button.grid(row=3, column=1, sticky=W, padx=(340, 10)) 152 | 153 | another_url_button = Button(result_frame, text='Try Another', bg='#5872a8', command=try_another, 154 | fg='red', font=('bold', 13), width=18) 155 | another_url_button.grid(row=4, column=1, sticky=W, padx=(150, 10), pady=8) 156 | 157 | file_path_info = Label(result_frame, 158 | text=f'file will be saved to {helperFunctions.file_path()}. Press "Select Folder" to change', 159 | font=('normal', 7), bg="#2b2929", fg="white", wraplength=500) 160 | file_path_info.grid(row=5, column=1, pady=8, sticky=W, padx=(150, 0)) 161 | 162 | essential_messages = Label(result_frame, 163 | text="", 164 | font=('normal', 10), bg="#2b2929", fg="red", wraplength=600) 165 | essential_messages.grid(row=7, column=1, pady=8, columnspan=2) 166 | 167 | video_progressBar = helperFunctions.ProgressBar(root_frame=result_frame, length=395) 168 | video_progressBar.place_progressbar(Row=6, Column=0, columnSpan=2) 169 | 170 | --------------------------------------------------------------------------------