DEV Community

Cover image for Build a YouTube Video Downloader with Python and yt-dlp
Bilal Aslam
Bilal Aslam

Posted on

Build a YouTube Video Downloader with Python and yt-dlp

Ever wanted to download YouTube videos directly from your Python program?

In this project, I built a simple YouTube video search and downloader using Python. The program lets you enter a search query, choose how many results you want, and download the videos with both video and audio.

This project also helped me understand how yt-dlp, FFmpeg, Python Path, and command-line based video downloading work together.

Note: Make sure you only download content you have permission to download and follow YouTube's Terms of Service and applicable copyright laws.


What I Built

The program can:

  • Search YouTube using a text query
  • Download multiple search results
  • Download the best available video quality
  • Download audio separately when necessary
  • Merge video and audio using FFmpeg
  • Save downloaded videos to a custom folder
  • Automatically create the download directory
  • Let the user decide how many videos to download

For example:

Enter the search query: GODS AMV
Enter the number of videos to download: 2
Enter fullscreen mode Exit fullscreen mode

The program searches for the first two results and downloads them.


Technologies Used

This project uses:

  • Python
  • yt-dlp
  • FFmpeg
  • pathlib

The main library doing the heavy lifting is yt-dlp.


Installing the Required Packages

First, install yt-dlp:

pip install -U yt-dlp
Enter fullscreen mode Exit fullscreen mode

I initially used the youtube_search package as well, but eventually removed it because yt-dlp can perform YouTube searches by itself.

That makes the final code simpler and avoids performing the same search twice.


Installing FFmpeg

FFmpeg is important when YouTube provides video and audio as separate streams.

On Windows, you can install it using:

winget install Gyan.FFmpeg
Enter fullscreen mode Exit fullscreen mode

After installation, restart your terminal and verify it:

ffmpeg -version
Enter fullscreen mode Exit fullscreen mode

If the version information appears, FFmpeg is available from the command line.


Why Do We Need FFmpeg?

One thing I learned while building this project is that a YouTube video isn't always downloaded as one single file.

For higher-quality videos, YouTube can provide:

Video Stream
+
Audio Stream
Enter fullscreen mode Exit fullscreen mode

For example, yt-dlp may report:

Downloading 1 format(s): 399+251
Enter fullscreen mode Exit fullscreen mode

Here, one format is the video stream and the other is the audio stream.

FFmpeg combines them:

Video Stream
      +
Audio Stream
      |
      v
    FFmpeg
      |
      v
Complete MP4
Enter fullscreen mode Exit fullscreen mode

Without FFmpeg, the download may fail when merging multiple formats.


The Final Python Code

Here is the complete version of the project:

from pathlib import Path
import yt_dlp


DOWNLOAD_PATH = Path(r"D:\My Downloads")


def search_and_download(search_query, max_results=1):

    print(f"\nSearching for: {search_query}...")
    print(f"Number of videos requested: {max_results}\n")

    DOWNLOAD_PATH.mkdir(parents=True, exist_ok=True)

    yt_dlp_options = {
        "format": "bestvideo+bestaudio/best",
        "outtmpl": str(
            DOWNLOAD_PATH / "%(title)s.%(ext)s"
        ),
        "noplaylist": True,
        "merge_output_format": "mp4",
    }

    try:

        with yt_dlp.YoutubeDL(yt_dlp_options) as ydl:

            print("Searching for videos...\n")

            info = ydl.extract_info(
                f"ytsearch{max_results}:{search_query}",
                download=True
            )

            entries = info.get("entries", [])

            if not entries:
                print("No videos found.")
                return

            print("\nDownload complete!")

            print(
                f"\nSuccessfully processed "
                f"{len(entries)} video(s)."
            )

            return entries

    except Exception as e:

        print(f"\nError occurred: {e}")


search_query = input(
    "Enter the search query: "
)

max_results = int(
    input(
        "Enter the number of videos to download: "
    )
)

search_and_download(
    search_query,
    max_results
)
Enter fullscreen mode Exit fullscreen mode

How the Code Works

Let's break it down.

1. Importing the Libraries

from pathlib import Path
import yt_dlp
Enter fullscreen mode Exit fullscreen mode

Path comes from Python's built-in pathlib module.

It makes working with file and folder paths much cleaner than manually manipulating strings.

yt_dlp is responsible for searching and downloading the videos.


2. Setting the Download Directory

DOWNLOAD_PATH = Path(r"D:\My Downloads")
Enter fullscreen mode Exit fullscreen mode

This tells the program where downloaded videos should be saved.

I used a raw string:

r"D:\My Downloads"
Enter fullscreen mode Exit fullscreen mode

because Windows paths contain backslashes.


3. Creating the Download Folder

Inside the function:

DOWNLOAD_PATH.mkdir(
    parents=True,
    exist_ok=True
)
Enter fullscreen mode Exit fullscreen mode

This makes sure the folder exists.

exist_ok=True means Python won't throw an error if the directory already exists.

parents=True allows Python to create missing parent directories if necessary.


4. Configuring yt-dlp

The main configuration is:

yt_dlp_options = {
    "format": "bestvideo+bestaudio/best",
    "outtmpl": str(
        DOWNLOAD_PATH / "%(title)s.%(ext)s"
    ),
    "noplaylist": True,
    "merge_output_format": "mp4",
}
Enter fullscreen mode Exit fullscreen mode

Let's look at each option.

format

"format": "bestvideo+bestaudio/best"
Enter fullscreen mode Exit fullscreen mode

This tells yt-dlp to prefer:

Best video
+
Best audio
Enter fullscreen mode Exit fullscreen mode

and fall back to the best combined format when necessary.

This is important because using:

"bestvideo/best"
Enter fullscreen mode Exit fullscreen mode

can result in downloading a video-only stream.


5. Setting the Output Filename

"outtmpl": str(
    DOWNLOAD_PATH / "%(title)s.%(ext)s"
)
Enter fullscreen mode Exit fullscreen mode

%(title)s is replaced by the video's title.

For example:

YouTube Video Title.mp4
Enter fullscreen mode Exit fullscreen mode

instead of something like:

video123.mp4
Enter fullscreen mode Exit fullscreen mode

6. Preventing Playlist Downloads

"noplaylist": True
Enter fullscreen mode Exit fullscreen mode

This tells yt-dlp not to download an entire playlist when the URL or search result happens to belong to one.


7. Merging Into MP4

"merge_output_format": "mp4"
Enter fullscreen mode Exit fullscreen mode

When separate video and audio streams are downloaded, FFmpeg can merge them into an MP4 file.


8. Searching YouTube

The most important part for multiple results is:

info = ydl.extract_info(
    f"ytsearch{max_results}:{search_query}",
    download=True
)
Enter fullscreen mode Exit fullscreen mode

Suppose the user enters:

GODS AMV
Enter fullscreen mode Exit fullscreen mode

and:

2
Enter fullscreen mode Exit fullscreen mode

The resulting search expression becomes:

ytsearch2:GODS AMV
Enter fullscreen mode Exit fullscreen mode

That tells yt-dlp to search for two results.

If the user enters:

5
Enter fullscreen mode Exit fullscreen mode

it becomes:

ytsearch5:GODS AMV
Enter fullscreen mode Exit fullscreen mode

and searches for five results.


The Bug I Initially Had

My first version had a logic problem.

I was using:

YoutubeSearch(
    search_query,
    max_results=max_results
)
Enter fullscreen mode Exit fullscreen mode

to find multiple videos.

But later I used:

ytsearch1:GODS AMV
Enter fullscreen mode Exit fullscreen mode

for the actual download.

So even if I requested:

5 videos
Enter fullscreen mode Exit fullscreen mode

the download logic was effectively asking:

Give me 1 video.
Enter fullscreen mode Exit fullscreen mode

The search results were also not being used for downloading.

The flow was basically:

Search for 5 videos
        |
        v
Get 5 results
        |
        v
Ignore those results
        |
        v
Search again using ytsearch1
        |
        v
Download 1 video
Enter fullscreen mode Exit fullscreen mode

Not exactly the world's greatest software architecture.


The Fix

I removed the unnecessary youtube_search dependency and let yt-dlp handle both searching and downloading.

Now:

f"ytsearch{max_results}:{search_query}"
Enter fullscreen mode Exit fullscreen mode

controls the number of search results.

For example:

ytsearch1:anime AMV
Enter fullscreen mode Exit fullscreen mode

means one result.

ytsearch5:anime AMV
Enter fullscreen mode Exit fullscreen mode

means five results.

ytsearch10:anime AMV
Enter fullscreen mode Exit fullscreen mode

means ten results.


9. Getting the Search Results

After extraction:

entries = info.get("entries", [])
Enter fullscreen mode Exit fullscreen mode

entries contains the videos returned by the search.

We can check whether anything was found:

if not entries:
    print("No videos found.")
    return
Enter fullscreen mode Exit fullscreen mode

If results exist, we display how many were processed:

print(
    f"\nSuccessfully processed "
    f"{len(entries)} video(s)."
)
Enter fullscreen mode Exit fullscreen mode

10. Handling Errors

The download operation is wrapped inside:

try:
    ...
except Exception as e:
    print(f"\nError occurred: {e}")
Enter fullscreen mode Exit fullscreen mode

This prevents the entire Python program from crashing without an explanation.

Instead, the user gets an error message.


Example Run

A typical run looks like:

Enter the search query: GODS AMV
Enter the number of videos to download: 2

Searching for: GODS AMV...
Number of videos requested: 2

Searching for videos...
Enter fullscreen mode Exit fullscreen mode

Then yt-dlp searches for the requested number of videos and downloads them.

The files are saved inside:

D:\My Downloads
Enter fullscreen mode Exit fullscreen mode

An Important Lesson About Video and Audio

One of the most useful things I learned from this project was that "best quality" does not necessarily mean "one file."

A simplified example:

1080p Video
    +
High Quality Audio
    =
Two separate streams
Enter fullscreen mode Exit fullscreen mode

The downloader needs to combine them.

That's why this configuration:

"format": "bestvideo+bestaudio/best"
Enter fullscreen mode Exit fullscreen mode

works together with:

"merge_output_format": "mp4"
Enter fullscreen mode Exit fullscreen mode

and FFmpeg.


Troubleshooting

FFmpeg Not Installed

If you see:

ERROR: You have requested merging of multiple formats
but ffmpeg is not installed.
Enter fullscreen mode Exit fullscreen mode

Install FFmpeg:

winget install Gyan.FFmpeg
Enter fullscreen mode Exit fullscreen mode

Then verify:

ffmpeg -version
Enter fullscreen mode Exit fullscreen mode

Restart your terminal if necessary.


Video Has No Sound

If your downloaded file has no audio, check whether your format is:

"bestvideo"
Enter fullscreen mode Exit fullscreen mode

That can select a video-only stream.

Use:

"bestvideo+bestaudio/best"
Enter fullscreen mode Exit fullscreen mode

instead.

Also make sure FFmpeg is installed and accessible from PATH.


yt-dlp JavaScript Runtime Warning

You may also see a warning similar to:

WARNING: No supported JavaScript runtime could be found.
Enter fullscreen mode Exit fullscreen mode

This is related to YouTube's current extraction requirements and can mean some formats may be unavailable.

If basic downloading works, you may not need to solve this immediately. However, if yt-dlp starts failing to extract certain videos or formats, check the official yt-dlp documentation for the current JavaScript runtime requirements.


What I Learned From This Project

This project looked simple at first:

Search YouTube
      ↓
Download video
Enter fullscreen mode Exit fullscreen mode

But it introduced several useful concepts.

I learned about:

  • Python functions
  • Exception handling
  • pathlib
  • File paths on Windows
  • Third-party Python packages
  • Command-line tools
  • Video and audio streams
  • FFmpeg
  • Search result handling
  • Debugging logic errors
  • Understanding what a library is actually doing behind the scenes

Most importantly, I learned that getting code to run isn't the same as understanding why it works.

For example, my original program technically searched for multiple videos, but my download logic only downloaded one. Looking at the terminal output helped me identify the actual problem.


Possible Improvements

There are several features I could add in the future:

  • Choose video quality
  • Download audio only
  • Download subtitles
  • Show download progress
  • Let the user choose the output folder
  • Add a graphical user interface
  • Add URL-based downloading
  • Validate user input
  • Create a download history
  • Add filename sanitization
  • Allow users to select specific search results before downloading

A GUI version would be an interesting next step.


Project Structure

For a simple version, the project can be:

youtube-downloader/
│
├── youtube_downloader.py
│
└── README.md
Enter fullscreen mode Exit fullscreen mode

The downloaded videos are stored separately:

D:\My Downloads
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

This was a small Python project, but it turned out to be a pretty useful learning exercise.

The biggest lesson for me was debugging.

I initially had two separate problems:

  1. The downloaded videos had no sound.
  2. The number of requested results was not controlling the number of downloads.

The first problem was caused by downloading a video-only stream:

bestvideo
Enter fullscreen mode Exit fullscreen mode

The second was caused by accidentally using:

ytsearch1
Enter fullscreen mode Exit fullscreen mode

instead of dynamically using the user's requested number:

ytsearch{max_results}
Enter fullscreen mode Exit fullscreen mode

After fixing both, the workflow became much cleaner:

User Input
    |
    v
yt-dlp YouTube Search
    |
    v
Requested Number of Results
    |
    v
Video + Audio Streams
    |
    v
FFmpeg
    |
    v
Final MP4
Enter fullscreen mode Exit fullscreen mode

And that's another little Python project added to the learning journey.


Disclaimer

This project is intended for educational purposes. Only download videos when you have the right or permission to do so, and respect copyright, platform rules, and the creator's rights.


If You Found This Useful

I'm currently learning Python and building small projects to understand concepts by actually using them.

If you're also learning Python, don't just copy the code.

Break it.

Change it.

Make it fail.

Then figure out why it failed.

That's where the real learning starts.

Top comments (0)