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
- Necessary Libraries
- Imports
- Creating Flask Application Instance
- Setting Download Directory
- Defining Functions
- Defining Routes
- Main Block
- YouTube Video Downloader Full Code
- Example
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
- Check this tutorial to master HTML in FLASK.
<!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!
Very helpful, Thank you.
Welcome.