Home » Tutorials » How to Make a YouTube Video Downloader with Flask in Python

How to Make a YouTube Video Downloader with Flask in Python

Hello everyone! Today, we’re diving into an exciting web development project: building your own YouTube video downloader. This isn’t just about coding; it’s a chance to turn your ideas into tools that you can use daily. We’re going to explore the exciting possibilities within Python, making this journey both educational and a whole lot of fun.

In this article, you’ll learn how to create a YouTube video downloader using Flask and PyTube in Python. This tutorial is designed to guide you through every step of the process, from setting up your Flask environment to writing the script that pulls videos directly from YouTube.

Let’s get started!

Table of Contents

Before We Start

First, we need to create a directory named “templates” in our Python project folder to hold our HTML templates.

Learn also: How to Build a YouTube Video Downloader using Tkinter in Python

Necessary Libraries

For the code to function properly you should install these libraries via the terminal or your command prompt by running these commands:

$ pip install Flask 
$ pip install pytube

Imports

We start by importing the main Flask class for later customization. Next, we import the render_template function, which allows us to render HTML templates for the web pages. We also import request to handle HTTP requests and send_file for downloading files to the client.

Additionally, we import redirect and url_for for URL redirection. Lastly, we import os to interact with the operating system and re for support with regular expressions.

from flask import Flask, render_template, request, send_file, redirect, url_for
from pytube import YouTube
import os
import re

Creating Flask Application Instance

Next, we create an instance of the main flask class so we can customize it however we like, and we will name it app.

app = Flask(__name__)
  • Check this tutorial if you want to master FLASK.

Setting Download Directory

For this step, we begin by creating a variable named DOWNLOAD_DIRECTORY, which stores the name of the directory that will hold our downloaded videos. In my case, I have set this to “downloads“. This part of the code checks if the directory exists using os.path.exists(). If it doesn’t, it will be created using os.makedirs().

DOWNLOAD_DIRECTORY = "downloads"


if not os.path.exists(DOWNLOAD_DIRECTORY):
   os.makedirs(DOWNLOAD_DIRECTORY

Defining Functions

sanitize_filename Function

The purpose of this function is to replace invalid characters ‘[<>:”/\|?*]’ with underscores ‘_’ using regular expressions. This ensures that the filenames are compatible with the file system.

def sanitize_filename(filename):
   # Replace invalid characters with underscores
   return re.sub(r'[<>:"/\\|?*]', '_', filename)

Defining Routes

Route for Index Page

We start by applying the @app.route('/') decorator, which specifies the URL route for the index page. Next, we define the index() function that uses the render_template function to render the index.html template, displaying the homepage to the user.

@app.route('/')
def index():
   return render_template('index.html')

Route for Handling Video Download

We start by adding the @app.route decorator that specifies the URL route to handle HTTP POST requests for video downloads. Next, we define the download() function, which begins by extracting the input URL to form a YouTube object. It then retrieves the highest-resolution video stream using streams.get_highest_resolution(). The video title is sanitized using the sanitize_filename() function, and a “.mp4” extension is appended to create the filename. This filename is then combined with the DOWNLOAD_DIRECTORY using os.path.join() to set the download location. The download is initiated with stream.download().

Once the download completes, the video is sent to the browser as an attachment using send_file(), ensuring it isn’t displayed directly in the browser. If any errors occur, the render_template() function renders the error.html template to display the error.

@app.route('/download', methods=['POST'])
def download():
   url = request.form['url']
   print("Received URL:", url)
   try:
       yt = YouTube(url)
       print("Video Title:", yt.title)
       stream = yt.streams.get_highest_resolution()
       title = sanitize_filename(yt.title)
       filename = f"{title}.mp4"
       filepath = os.path.join(DOWNLOAD_DIRECTORY, filename)
       print("Downloading...")
       stream.download(output_path=DOWNLOAD_DIRECTORY, filename=filename)
       print("Download Complete.")
       return send_file(filepath, as_attachment=True)
   except Exception as e:
       print("Error:", str(e))
       return render_template('error.html', error=str(e))

Main Block

This part ensures that the code is only executed when the script is run directly, not when it is imported as a module.

if __name__ == '__main__':
   app.run(debug=True)

YouTube Video Downloader Full Code

from flask import Flask, render_template, request, send_file, redirect, url_for
from pytube import YouTube
import os
import re


app = Flask(__name__)


DOWNLOAD_DIRECTORY = "downloads"


if not os.path.exists(DOWNLOAD_DIRECTORY):
   os.makedirs(DOWNLOAD_DIRECTORY)


def sanitize_filename(filename):
   # Replace invalid characters with underscores
   return re.sub(r'[<>:"/\\|?*]', '_', filename)


@app.route('/')
def index():
   return render_template('index.html')


@app.route('/download', methods=['POST'])
def download():
   url = request.form['url']
   print("Received URL:", url)
   try:
       yt = YouTube(url)
       print("Video Title:", yt.title)
       stream = yt.streams.get_highest_resolution()
       title = sanitize_filename(yt.title)
       filename = f"{title}.mp4"
       filepath = os.path.join(DOWNLOAD_DIRECTORY, filename)
       print("Downloading...")
       stream.download(output_path=DOWNLOAD_DIRECTORY, filename=filename)
       print("Download Complete.")
       return send_file(filepath, as_attachment=True)
   except Exception as e:
       print("Error:", str(e))
       return render_template('error.html', error=str(e))


if __name__ == '__main__':
   app.run(debug=True)

Index HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>YouTube Downloader - The Pycodes</title>
  <!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet">
  <!-- Custom CSS -->
  <style>
      body {
          background-color: #f8f9fa;
          font-family: Arial, sans-serif;
      }
      .container {
          max-width: 600px;
          margin: 50px auto;
      }
      .jumbotron {
          background-color: #343a40;
          color: #fff;
          padding: 30px;
          border-radius: 10px;
      }
      h1 {
          font-size: 2.5rem;
          margin-bottom: 30px;
          text-align: center;
      }
      label {
          font-size: 1.2rem;
      }
      input[type="text"] {
          width: 100%;
          padding: 10px;
          margin-bottom: 20px;
          border-radius: 5px;
          border: 1px solid #ced4da;
      }
      button[type="submit"] {
          background-color: #007bff;
          color: #fff;
          border: none;
          padding: 10px 20px;
          border-radius: 5px;
          cursor: pointer;
      }
      button[type="submit"]:hover {
          background-color: #0056b3;
      }
  </style>
</head>
<body>
  <div class="container">
      <div class="jumbotron">
          <h1>The Pycodes</h1>
          <h1>YouTube Downloader</h1>
          <form action="/download" method="post">
              <div class="mb-3">
                  <label for="url" class="form-label">Enter YouTube Video URL:</label>
                  <input type="text" id="url" name="url" class="form-control" required>
              </div>
              <button type="submit" class="btn btn-primary">Download</button>
          </form>
      </div>
  </div>
</body>
</html>

Document Declaration and Language Setting:

This part declares that the document is of type HTML and sets the language of the document to English.

<!DOCTYPE html>
<html lang="en">

Head Section:

This section is the head of the HTML document. It sets the document’s character encoding to UTF-8 and configures the viewport to match the device’s width and initial scale for optimal display on all devices. It also includes the web page’s title.

Additionally, this section links to the Bootstrap CSS for styling and introduces custom styles that define the appearance of various elements like containers, headings, and buttons. These styles can adjust attributes such as background color, text color, padding, and width.

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>YouTube Downloader - The Pycodes</title>
  <!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet">
  <!-- Custom CSS -->
  <style>
      body {
          background-color: #f8f9fa;
          font-family: Arial, sans-serif;
      }
      .container {
          max-width: 600px;
          margin: 50px auto;
      }
      .jumbotron {
          background-color: #343a40;
          color: #fff;
          padding: 30px;
          border-radius: 10px;
      }
      h1 {
          font-size: 2.5rem;
          margin-bottom: 30px;
          text-align: center;
      }
      label {
          font-size: 1.2rem;
      }
      input[type="text"] {
          width: 100%;
          padding: 10px;
          margin-bottom: 20px;
          border-radius: 5px;
          border: 1px solid #ced4da;
      }
      button[type="submit"] {
          background-color: #007bff;
          color: #fff;
          border: none;
          padding: 10px 20px;
          border-radius: 5px;
          cursor: pointer;
      }
      button[type="submit"]:hover {
          background-color: #0056b3;
      }
  </style>
</head>

Body Section:

In the body of the document, we first establish a container for layout purposes, which centers the content on the page. Inside the container, we use a jumbotron class to style a section with a distinct visual emphasis. Within this section, two headings (h1) display the titles “The Pycodes” and “YouTube Downloader“, setting the context for the functionality of the webpage.

Below these headings, a form is defined to post data to the /download URL, intended for handling YouTube video downloads. The form contains a single input field labeled “Enter YouTube Video URL:“, which is marked as required to ensure that the user must provide a value before submission. The form is completed with a button styled with Bootstrap’s btn and btn-primary classes, which submits the form data.

<body>
  <div class="container">
      <div class="jumbotron">
          <h1>The Pycodes</h1>
          <h1>YouTube Downloader</h1>
          <form action="/download" method="post">
              <div class="mb-3">
                  <label for="url" class="form-label">Enter YouTube Video URL:</label>
                  <input type="text" id="url" name="url" class="form-control" required>
              </div>
              <button type="submit" class="btn btn-primary">Download</button>
          </form>
      </div>
  </div>
</body>
</html>

Error HTML

This template is similar to the previous HTML with only one difference being the addition of an error alert component to handle and display errors, which was not present in the previous template.

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Error - The Pycodes</title>
   <!-- Bootstrap CSS -->
   <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet">
   <!-- Custom CSS -->
   <style>
       body {
           background-color: #f8f9fa;
           font-family: Arial, sans-serif;
       }
       .container {
           max-width: 500px;
           margin: 50px auto;
       }
       .alert {
           padding: 20px;
           margin-bottom: 20px;
           border: 1px solid transparent;
           border-radius: .25rem;
       }
       .alert-danger {
           color: #721c24;
           background-color: #f8d7da;
           border-color: #f5c6cb;
       }
       .alert-heading {
           color: inherit;
       }
       p {
           margin-bottom: 0;
       }
   </style>
</head>
<body>
   <div class="container">
       <div class="alert alert-danger" role="alert">
           <h4 class="alert-heading">Error!</h4>
           <p>{{ error }}</p>
           <hr>
           <p class="mb-0">Please try again with a valid YouTube video URL.</p>
       </div>
       <a href="{{ url_for('index') }}" class="btn btn-primary">Back to Home</a>
   </div>
</body>
</html>

Body Section:

As usual, the body section of the HTML contains the web page’s elements. We begin by creating a container using the container class. Since this is an error page designed to provide user feedback, we employ the alert alert-danger classes to create a red-background alert box.

Inside the box, a heading indicates an error, accompanied by a paragraph detailing the error’s content and another paragraph offering advice. A “Back to Home” button is linked to the index page, allowing users to easily return to it. The document concludes with the necessary closing tags.

<body>
   <div class="container">
       <div class="alert alert-danger" role="alert">
           <h4 class="alert-heading">Error!</h4>
           <p>{{ error }}</p>
           <hr>
           <p class="mb-0">Please try again with a valid YouTube video URL.</p>
       </div>
       <a href="{{ url_for('index') }}" class="btn btn-primary">Back to Home</a>
   </div>
</body>
</html>

Example

Here we have put the wrong URL:

Happy Coding!

2 thoughts on “How to Make a YouTube Video Downloader with Flask in Python”

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top