How to Make a YouTube Video Downloader in Python

Learn how you can build a CLI and GUI YouTube video downloader using Pytube in Python.
  · · 23 min read · Updated nov 2022 · Web Scraping · GUI Programming

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.

Building the Command Line Version

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!

Building the GUI Version

In this section, we will build the GUI version of the YouTube video downloader app. This is what we are going to be building:

Designing the User Interface

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:

Making the Resolution Combobox

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:

Creating the Progress Bar & Download Button

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!

Implementing the Search Video Resolution Functionality

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.

Implementing the Video Download and Threading Functionality

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.

Conclusion

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
Sharing is caring!



Read Also



Comment panel

    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!