Confused by complex code? Let our AI-powered Code Explainer demystify it for you. Try it out!
YouTube is a great platform where you can find tons of great video resources, be it programming, entrepreneurship, movies, etc. It is a platform that also enables users to live stream and watch videos online. You might run into a situation where you want to have some of your favorite YouTube videos on your local computer. To do this, you will need a video downloader for downloading these videos.
If you have ever fancied the idea of building your own YouTube video downloader, then this article is for you; it will show you step-by-step how to build a YouTube video downloader using Python. This article has been split into two sections. In the first section, we will focus on building a command line version, and in the second section, we will build an advanced graphical user interface (GUI) version.
If you want to download only audio from YouTube videos, then this tutorial should be helpful.
Table of contents:
We need to install the primary library, which is pytube
:
$ pip install pytube
Let us create two new Python files and name them as follows:
Feel free to name them what you prefer, but just make sure the names are meaningful.
Let's start building the command line version of the app. Open the youtube_downloader_cli.py
file and do the following import:
from pytube import YouTube
Here we are importing the YouTube object from pytube
, according to pytube
’s documentation, it is simply a lightweight library for downloading YouTube videos. That’s all we needed for our imports. Now let us move on to creating a function that will handle the actual video download; we will call it video_downloader()
. Below the import, paste these lines of code:
# the function takes the video url as an argument
def video_downloader(video_url):
# passing the url to the YouTube object
my_video = YouTube(video_url)
# downloading the video in high resolution
my_video.streams.get_highest_resolution().download()
# return the video title
return my_video.title
Let us break the code down so that we are on the same page; the function takes the video_url
as the argument, this video_url
is then passed to the YouTube()
object.
From YouTube()
, we can access video information like streams
, title
, thumbnail_url
, channel_id
, etc., this object is assigned to a variable called my_video
. So to download the video, we use this set of functions at once, streams.get_highest_resolution().download()
; this will download the video in the highest resolution, and finally, we are returning the title of the video.
The pytube
library comes with lots of options for downloading your videos. If you want to download a video of the lowest resolutions, use the:
YouTube(url).streams.first().download()
Or, if you want to specify the video resolution, you can use the below code:
YouTube(url).streams.filter(res="Your_Resolution").first().download()
Just replace the placeholder Your_Resolution
with your preferred resolution e.g. 144p, 240p, 360p, 480p, 720p, etc.
Let us make the app prompt the user to enter the video URL. Just below the function, paste the following code:
# the try statement will run if there are no errors
try:
# getting the url from the user
youtube_link = input('Enter the YouTube link:')
print(f'Downloading your Video, please wait.......')
# passing the url to the function
video = video_downloader(youtube_link)
# printing the video title
print(f'"{video}" downloaded successfully!!')
#the except will catch ValueError, URLError, RegexMatchError and simalar
except:
print(f'Failed to download video\nThe '\
'following might be the causes\n->No internet '\
'connection\n->Invalid video link')
In the code snippet, we have a try/except
block, inside the try
statement, we have the code for getting the URL from the user using the input()
function, and this URL is passed to the video_downloader()
function to download the video. Inside the except
statement, we are just printing a message after catching errors.
Now that we have a complete working code, let us test it, copy the URL from any YouTube video and run:
$ python youtube_downloader_cli.py
You will be prompted to provide the video URL, and after entering it, you will get this output:
Enter the YouTube link:https://youtu.be/i9TlSFrsh4Q
Downloading your Video, please wait.......
"Find your next Python or ML job with my new job board!" downloaded succussfully!!
If you check in your project’s folder, you will find an mp4 file:
Now re-run the app, and this time, try to provide an invalid link:
Enter the YouTube link:this is my link
Downloading your Video, please wait.......
Failed to download video
The following might be the causes
->No internet connection
->Invalid video link
Our YouTube video downloader command line version is working perfectly!
In this section, we will build the GUI version of the YouTube video downloader app. This is what we are going to be building:
So without further ado, let us get started with our designing process. The first thing that we will do is import the required libraries, so do the following:
from tkinter import *
from tkinter import ttk
from pytube import YouTube
from tkinter.messagebox import showinfo, showerror
import threading
In the code above, we are importing everything from tkinter
, we are also importing ttk
from tkinter
, ttk
is a module that provides styles for the widgets, from pytube
we are importing the YouTube
object, this will give us access to all the functions available in pytube
.
We are also importing the showinfo
and showerror
message boxes, which are popups for displaying messages to the user, and finally, we are importing threading
; this will help our app’s tasks (downloading the video, running the window, and searching for video resolutions) run concurrently.
Now that the imports are taken care of, it is now time we create the main window for the app; just below the imports, paste these lines of code:
from tkinter import *
from tkinter import ttk
from pytube import YouTube
from tkinter.messagebox import showinfo, showerror, askokcancel
import threading
# creates the window using Tk() fucntion
window = Tk()
# creates title for the window
window.title('YouTube Video Downloader')
# dimensions and position of the window
window.geometry('500x460+430+180')
# makes the window non-resizable
window.resizable(height=FALSE, width=FALSE)
# runs the window infinitely
window.mainloop()
Let us break this code down. To create the main window, we are using the tkinter
built-in Tk()
class. After creating the window, we create its title using the title()
function; then, we give it dimensions and position.
For the height
and width
we are using 500x460, and for the position that is horizontal and vertical, we are using 430+180, and to make the window non-resizable, we are setting height
and width
to FALSE
, and finally, we are calling the mainloop()
function, which allows the window to run infinitely until the user clicks the close button.
Now that the window is created, we need to create a container that will contain all the other widgets, so we will use the Canvas
widget. Below this line of code:
window.resizable(height=FALSE, width=FALSE)
Paste this code:
# creates the canvas for containing all the widgets
canvas = Canvas(window, width=500, height=400)
canvas.pack()
In the code, we are creating a canvas by placing it inside the main window; inside the main window, it will take up a width
of 500 and a height
of 400 like this:
Our container for the widgets is now ready. Let us now start adding widgets to it. The first one will be the YouTube logo. Make sure the logo is located in the same folder as youtube_downloader_ui.py
file:
To create the logo, paste these lines of code:
# loading the logo
logo = PhotoImage(file='youtubelogo.png')
# creates dimensions of the logo
logo = logo.subsample(10, 10)
# adding the logo to the canvas
canvas.create_image(250, 80, image=logo)
In the code snippet, we are, first of all, loading the logo using the PhotoImage()
function, we are then giving the logo dimensions using the subsample()
function, and finally, we are adding the logo to the canvas
using the function create_image()
function, we are positioning the logo 250 horizontally and 80 vertically.
Let us test to see if the logo has been added successfully to the canvas
. This is the output:
The app is taking its shape; we will now define the styles for the widgets that we will be adding just in a moment; just below the logo code, paste this code:
"""Styles for the widgets"""
# style for the label
label_style = ttk.Style()
label_style.configure('TLabel', foreground='#000000', font=('OCR A Extended', 15))
# style for the entry
entry_style = ttk.Style()
entry_style.configure('TEntry', font=('Dotum', 15))
# style for the button
button_style = ttk.Style()
button_style.configure('TButton', foreground='#000000', font='DotumChe')
Let’s break down the code snippet above; we are creating styles for the widgets; we are using the ttk
module for this.
We are creating a style object using the ttk.Style()
class, so to add styles to the object, we will use the configure()
function, this function takes the style name; in our case, we have TLabel
, TButton
, and TEntry
as style names, foreground
, and font()
are the other arguments the Style()
class is taking.
In our widgets, we will use the style names to refer to or point to the style we want to use. Now that the styles are taken care of, we will now create and add the first two widgets to the canvas, the label and the entry. Below the styles, add this code:
# creating a ttk label
url_label = ttk.Label(window, text='Enter Video URL:', style='TLabel')
# creating a ttk entry
url_entry = ttk.Entry(window, width=76, style='TEntry')
# adding the label to the canvas
canvas.create_window(114, 200, window=url_label)
# adding the entry to the canvas
canvas.create_window(250, 230, window=url_entry)
To create a styled label, we are using a ttk.Label()
class that comes from the ttk
module, this function takes window
, text
, and the style
as arguments, and to create a styled entry, we are using the ttk.Entry()
class, also has arguments, window
, width
, and style
.
Now to add these widgets to the canvas
we are using the create_window()
canvas function, takes two integers, in our case, 114 and 200 for the label and 250 and 230 for the entry; all these are for positioning the widget.
Running the app, we will get this output:
Now we should add the resolution label and the Combobox
that will hold the video resolutions; the two will come below the entry widget; add this code:
# creating resolution label
resolution_label = Label(window, text='Resolution:')
# adding the label to the canvas
canvas.create_window(50, 260, window=resolution_label)
# creating a combobox to hold the video resolutions
video_resolution = ttk.Combobox(window, width=10)
# adding the combobox to the canvas
canvas.create_window(60, 280, window=video_resolution)
We are creating two widgets; the first one is the label for displaying the text Resolution
, something worth noticing here, this label is not a ttk
label. After the label, we create the actual Combobox
, it takes window
, and width
as arguments, and finally, we add all these to the canvas
as usual. With the above code added, this is what we get:
Currently, the Combobox
is empty, and something worth your attention about the Combobox
; you can enter values as well, just in case the value you want is not in the Combobox
.
Since we want to search for video resolutions, we will add a button for the search feature. To do that, add the following code below the Combobox
:
# creating a button for searching resolutions
search_resolution = ttk.Button(window, text='Search Resolution')
# adding the button to the canvas
canvas.create_window(85, 315, window=search_resolution)
With the above code added, this is the output:
Let us finish off the GUI design by adding our last three widgets, the empty label, the progress bar, and the Download Video
button; the empty label and the progress bar will be used for displaying the video download progress. After the Search Resolution button, add this code:
# creating the empty label for displaying download progress
progress_label = Label(window, text='')
# adding the label to the canvas
canvas.create_window(240, 360, window=progress_label)
# creating a progress bar to display progress
progress_bar = ttk.Progressbar(window, orient=HORIZONTAL, length=450, mode='determinate')
# adding the progress bar to the canvas
canvas.create_window(250, 380, window=progress_bar)
# creating the button
download_button = ttk.Button(window, text='Download Video', style='TButton')
# adding the button to the canvas
canvas.create_window(240, 410, window=download_button)
Here we are creating an empty label, a progress bar, and a styled button using the Label()
, ttk.Progressbar()
and ttk.Button()
functions, to the progress bar before adding it to the canvas, we are passing the window
, orient
, length
, and mode
as arguments.
To the progress_label
, we are adding window
and an empty text, and to the button widget, we are also passing the arguments window
, text
, and style
then we add all these widgets to the canvas
.
This is what we get after adding the empty label, the progress bar, and the button:
Congrats on successfully designing the app’s GUI!
Now it is time we implement the search video resolution functionality. For this, we need to create a function for doing that, so after the imports, paste this code:
# function for searching video resolutions
def searchResolution():
# getting video url from entry
video_link = url_entry.get()
# checking if the video link is empty
if video_link == '':
showerror(title='Error', message='Provide the video link please!')
# if video link not empty search resolution
else:
try:
# creating a YouTube object
video = YouTube(video_link)
# an empty list that will hold all the video resolutions
resolutions = []
# looping through the video streams
for i in video.streams.filter(file_extension='mp4'):
# adding the video resolutions to the resolutions list
resolutions.append(i.resolution)
# adding the resolutions to the combobox
video_resolution['values'] = resolutions
# when search is complete notify the user
showinfo(title='Search Complete', message='Check the Combobox for the available video resolutions')
# catch any errors if they occur
except:
# notify the user if errors are caught
showerror(title='Error', message='An error occurred while searching for video resolutions!\n'\
'Below might be the causes\n->Unstable internet connection\n->Invalid link')
Let us break the code down so that we are on the same page. First of all, we get the video link from the entry. Then we check if the link entry is empty. If it’s empty, we notify the user to provide the link. Otherwise, we create a YouTube()
object; then an empty resolutions
list containing all the video resolutions.
Then we have a for
loop that’s looping through all the video resolutions filtered by file_extension
argument, then all these video resolutions are added to the resolutions
list via this line of code:
resolutions.append(i.resolution)
And to add all the video streams to the empty Combobox
we created earlier; we are using this code:
video_resolution['values'] = resolutions
After the search is complete, we notify the user by the showinfo
popup. And finally, in the except
statement, we are catching all the errors, and the message will be displayed using the showerror
popup.
We want to run the searchResolution()
function in a separate thread, so to do that, we will create another function that will point to this function and run it as a thread. Below the searchResolution()
function, paste this code:
# the function to run the searchResolution function as a thread
def searchThread():
t1 = threading.Thread(target=searchResolution)
t1.start()
The above code creates searchThread()
function, and inside it, we are creating a t1
thread whose target function is the searchResolution()
, and then the thread is started.
So to test this feature, we will have to bind the searchThread()
function to the Search Resolution button, edit the search_resolution
button code and make it look like this:
search_resolution = ttk.Button(window, text='Search Resolution', command=searchThread)
We are using the command
argument to let the button know which function to trigger when the user clicks it. The function we are pointing to is the searchThread()
, run the app paste the video URL, and click the Search Resolution button. The first output is this:
And the second output will be this after clicking the Combobox
:
Now we have managed to add all the searched video resolutions to the Combobox
.
Related: How to Make a YouTube Audio Downloader in Python.
In this final part of the article, we will implement the video download functionality. Just above the searchResolution()
function, paste this code:
# the function to download the video
def download_video():
# the try statement to excute the download the video code
try:
# getting video url from entry
video_link = url_entry.get()
# getting video resolution from Combobox
resolution = video_resolution.get()
# checking if the entry and combobox is empty
if resolution == '' and video_link == '':
# display error message when combobox is empty
showerror(title='Error', message='Please enter both the video URL and resolution!!')
# checking if the resolution is empty
elif resolution == '':
# display error message when combobox is empty
showerror(title='Error', message='Please select a video resolution!!')
# checking if the comboxbox value is None
elif resolution == 'None':
# display error message when combobox value is None
showerror(title='Error', message='None is an invalid video resolution!!\n'\
'Please select a valid video resolution')
# else let's download the video
else:
# this try statement will run if the resolution exists for the video
try:
# this function will track the video download progress
def on_progress(stream, chunk, bytes_remaining):
# the total size of the video
total_size = stream.filesize
# this function will get the size of the video
def get_formatted_size(total_size, factor=1024, suffix='B'):
# looping through the units
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
if total_size < factor:
return f"{total_size:.2f}{unit}{suffix}"
total_size /= factor
# returning the formatted video size
return f"{total_size:.2f}Y{suffix}"
# getting the formatted video size calling the function
formatted_size = get_formatted_size(total_size)
# the size downloaded after the start
bytes_downloaded = total_size - bytes_remaining
# the percentage downloaded after the start
percentage_completed = round(bytes_downloaded / total_size * 100)
# updating the progress bar value
progress_bar['value'] = percentage_completed
# updating the empty label with the percentage value and formatted file size
progress_label.config(text=str(percentage_completed) + '%, File size:' + formatted_size)
# updating the main window of the app
window.update()
# creating the YouTube object and passing the the on_progress function
video = YouTube(video_link, on_progress_callback=on_progress)
# downlaoding the actual video
video.streams.filter(res=resolution).first().download()
# popup for dispalying the video downlaoded success message
showinfo(title='Download Complete', message='Video has been downloaded successfully.')
# ressetting the progress bar and the progress label
progress_label.config(text='')
progress_bar['value'] = 0
# the except will run when the resolution is not available or invalid
except:
showerror(title='Download Error', message='Failed to download video for this resolution')
# ressetting the progress bar and the progress label
progress_label.config(text='')
progress_bar['value'] = 0
# the except statement to catch errors, URLConnectError, RegMatchError
except:
# popup for displaying the error message
showerror(title='Download Error', message='An error occurred while trying to ' \
'download the video\nThe following could ' \
'be the causes:\n->Invalid link\n->No internet connection\n'\
'Make sure you have stable internet connection and the video link is valid')
# ressetting the progress bar and the progress label
progress_label.config(text='')
progress_bar['value'] = 0
In this code, we are creating a function called download_video()
and inside the function, we have an outer try/except
block and an inner try/except
block that’s wrapped inside the else
statement. So we are getting the video URL and resolution from the entry and the Combobox
respectively using the get()
function, then we are doing some data validation using the if
and elif
statements and show error windows accordingly.
If the input is validated, here we have the inner try/except
block, and inside the try
statement, we have a progress tracking function called on_progress()
whose arguments are stream
, chunk
, and bytes_remaining
.
Inside this function, we calculate the bytes downloaded and the percentage completed, which is then added to the progress bar and the empty progress label. Finally, we are updating the window with the update()
function.
The get_formatted_size()
function inside the on_progress()
function calculates and returns the video size in the format KBs, MBs, GBs, TBs, etc. It was grabbed from this tutorial.
Just outside the on_progress()
function, we are creating the YouTube()
object, which takes the video URL and the on_progress()
function as arguments, then the actual video download is handled by this line of code:
video.streams.filter(res=resolution).first().download()
After the video is downloaded successfully, the success message pops up, and if they are errors like a broken internet connection, the except
statement will handle all this and notify the user of the errors as well, and both the progress bar and progress label are reset.
Finally, we have the final outer except
statement, this will handle errors like invalid video URL, no internet connection, etc., and again reset both the progress bar and the progress label.
After taking care of the download_video()
function, we will again create another function that will execute this function as a thread, so below the searchThread()
function paste this code:
# the function to run the download_video function as a thread
def downloadThread():
t2 = threading.Thread(target=download_video)
t2.start()
Here we are doing the same thing as in the searchThread()
function, we are creating a thread whose target is download_video()
function, then the thread is started using the start()
function.
Now to download the video, we need to bind the downloadThread()
function with the Download Video button, so to do that, add the command
argument, and this command must point to the downloadThread()
function as below:
download_button = ttk.Button(window, text='Download Video', style='TButton', command=downloadThread)
So this is what it means when the user clicks the Download Video button, the downloadThread()
function will be triggered and start the download_video()
function as a thread, this means that tasks will run without conflicts. This comes in handy because the app will run smoothly without glitches like the app not responding.
Let us test the app, run it and paste the video URL in the entry, search for the video resolution, and after a successful search, choose the resolution and click the Download Video button, wait for the video to download, and you will get this output:
Check in your project’s folder to confirm if the video has been downloaded successfully, you will get this:
If you enter the video URL, search for the video resolution, and leave the Combobox
empty, you will get this output:
If both the URL entry and the resolution Combobox
are left empty, and the Download Video button is clicked. This is the output:
If you want to search for video resolution without providing the video URL, you will get this:
And finally, if your video download was in progress and you had an unstable or broken internet connection, the output would be as follows:
The app is working perfectly, but something to note here as you are using it, it’s not every video that you might be able to download using the app because some YouTube videos are private, some are blocked in a certain region, some are age-restricted, and some require membership. You can find the list of pytube
exceptions here.
We hope you enjoyed the article on building a YouTube video downloader using Python. This is a great out-of-the-box solution for downloading your favorite YouTube videos offline. We have not covered everything considering what the pytube
library can do, but we believe that the knowledge you have gained can be used to build something meaningful in your future projects.
You can get the complete code here.
Learn also: How to Extract YouTube Data using YouTube API in Python.
Happy coding ♥
Liked what you read? You'll love what you can learn from our AI-powered Code Explainer. Check it out!
View Full Code Switch My Framework
Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!