├── 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 | 
10 |
11 | Search Results
12 |
13 | 
14 |
15 | File Downloading
16 |
17 | 
18 |
19 | File Downloading
20 |
21 | 
22 |
23 | Process Finished
24 |
25 | 
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 |
--------------------------------------------------------------------------------