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
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
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
After installation, restart your terminal and verify it:
ffmpeg -version
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
For example, yt-dlp may report:
Downloading 1 format(s): 399+251
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
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
)
How the Code Works
Let's break it down.
1. Importing the Libraries
from pathlib import Path
import yt_dlp
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")
This tells the program where downloaded videos should be saved.
I used a raw string:
r"D:\My Downloads"
because Windows paths contain backslashes.
3. Creating the Download Folder
Inside the function:
DOWNLOAD_PATH.mkdir(
parents=True,
exist_ok=True
)
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",
}
Let's look at each option.
format
"format": "bestvideo+bestaudio/best"
This tells yt-dlp to prefer:
Best video
+
Best audio
and fall back to the best combined format when necessary.
This is important because using:
"bestvideo/best"
can result in downloading a video-only stream.
5. Setting the Output Filename
"outtmpl": str(
DOWNLOAD_PATH / "%(title)s.%(ext)s"
)
%(title)s is replaced by the video's title.
For example:
YouTube Video Title.mp4
instead of something like:
video123.mp4
6. Preventing Playlist Downloads
"noplaylist": True
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"
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
)
Suppose the user enters:
GODS AMV
and:
2
The resulting search expression becomes:
ytsearch2:GODS AMV
That tells yt-dlp to search for two results.
If the user enters:
5
it becomes:
ytsearch5:GODS AMV
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
)
to find multiple videos.
But later I used:
ytsearch1:GODS AMV
for the actual download.
So even if I requested:
5 videos
the download logic was effectively asking:
Give me 1 video.
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
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}"
controls the number of search results.
For example:
ytsearch1:anime AMV
means one result.
ytsearch5:anime AMV
means five results.
ytsearch10:anime AMV
means ten results.
9. Getting the Search Results
After extraction:
entries = info.get("entries", [])
entries contains the videos returned by the search.
We can check whether anything was found:
if not entries:
print("No videos found.")
return
If results exist, we display how many were processed:
print(
f"\nSuccessfully processed "
f"{len(entries)} video(s)."
)
10. Handling Errors
The download operation is wrapped inside:
try:
...
except Exception as e:
print(f"\nError occurred: {e}")
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...
Then yt-dlp searches for the requested number of videos and downloads them.
The files are saved inside:
D:\My Downloads
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
The downloader needs to combine them.
That's why this configuration:
"format": "bestvideo+bestaudio/best"
works together with:
"merge_output_format": "mp4"
and FFmpeg.
Troubleshooting
FFmpeg Not Installed
If you see:
ERROR: You have requested merging of multiple formats
but ffmpeg is not installed.
Install FFmpeg:
winget install Gyan.FFmpeg
Then verify:
ffmpeg -version
Restart your terminal if necessary.
Video Has No Sound
If your downloaded file has no audio, check whether your format is:
"bestvideo"
That can select a video-only stream.
Use:
"bestvideo+bestaudio/best"
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.
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
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
The downloaded videos are stored separately:
D:\My Downloads
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:
- The downloaded videos had no sound.
- The number of requested results was not controlling the number of downloads.
The first problem was caused by downloading a video-only stream:
bestvideo
The second was caused by accidentally using:
ytsearch1
instead of dynamically using the user's requested number:
ytsearch{max_results}
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
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)