Page Body

Download Media With yt-dlp

Often, it is convenient to have a local copy of media that you would like to access. You may be in an area that does not have a strong Internet connection, or any connection at all, so having local copies can be a boon when streaming is not viable. Also, you may want to keep a content copy for archival purposes.

Using a content provider's download options can be inefficient. A Command Line Interface (CLI) tool called yt-dlp can help you streamline the download process.

Note: If you are not familiar with the GNU/Linux command line interface, review the Conventions page before proceeding.

Installation

You can install yt-dlp from PyPI, the Python Package Index. On a GNU/Linux distribution like Fedora, your system will be set up to use pip (Pip Installs Packages), the package installer for Python. On a Debian system, you may need to install it:

# apt install python3-pip && python3 -m pip install --user --upgrade pip

The above command installs the Python 3 version of pip and upgrades the pip package manager.

You can install yt-dlp with the following command:

python3 -m pip install --user yt-dlp

Configuration

The prior pip installation commands install software to ${HOME}/.local/bin. Before proceeding, ensure that this location is in your shell environment's ${PATH} variable, so that the shell can find the software when you issue germane commands. Details on how to configure this variable are on the Conventions page.

GNU/Linux distributions like Debian and Fedora often incorporate ${HOME}/.local/bin into the ${PATH} variable, but if this location did not exist when you began your shell session, you may need to restart your system to ensure that it is a searchable location for your shell.

By default, the yt-dlp command will save its output to the directory that you issue the command from. You can change this by creating a yt-dlp configuration file and specifying a new download directory/output file format:

$ mkdir -p "${HOME}/.config/yt-dlp" &&
    {
        echo '# Save media to Downloads directory in your home directory'
        echo '-o "${HOME}/Downloads/%(title)s.%(ext)s"'
    } > "${HOME}/.config/yt-dlp/config"

The above command will create the yt-dlp directory in ${HOME}/.config (if this directory does not exist on your system, the -p option will ensure that it is created), create a config file in that directory, and add lines to the config file to have the yt-dlp command download media to your home directory's Downloads directory. Files will be saved with the media content's title and extension as the filename.

Upgrade

It is important to keep your software up-to-date. If you followed the prior installation instructions, you can upgrade yt-dlp like so:

python3 -m pip install --user --upgrade yt-dlp

Usage

yt-dlp should work with a vast amount of sites. Also, the command is highly configurable through myriad options. You can customize your commands to alter output filenames with filename templates and specify which video/audio format to obtain.

To see if multiple formats are available for a given piece of media, you can use yt-dlp's -F (--list-formats) option. For example:

$ yt-dlp -F \
    'https://betamax.video/videos/watch/0a6feebf-a2d6-4965-9942-34bf63d92054'
[PeerTube] 0a6feebf-a2d6-4965-9942-34bf63d92054: Downloading JSON metadata
[info] Available formats for 0a6feebf-a2d6-4965-9942-34bf63d92054:
format code  extension  resolution note
480p         mp4        480p       80.12MiB
1080p        mp4        1080p      190.49MiB (best)

However, it is simplest to let the yt-dlp command select the best format of a given piece of content for you. You can use the following special names to select the case formats:

  • best Selects the best quality format represented by a single file with both video and audio
  • bestvideo Selects the best quality video-only format (may not be available)
  • bestaudio Selects the best quality audio-only format (may not be available)

Then, if you just want the best quality format file available that has both video and audio in a single file, you can use a command like this (the -f option is short for --format):

yt-dlp -f 'best' 'https://betamax.video/videos/watch/0a6feebf-a2d6-4965-9942-34bf63d92054'

Some media providers keep the best quality video and audio in separate video-only and audio-only files, respectively. There is a way to utilize yt-dlp to create a combined best quality file from these disparate files.

First, make sure that you have ffmpeg installed. This program is likely available in the repository of your GNU/Linux distribution (if not, you may need to add/enable an additional repository to obtain it).

For example, here is how you can install ffmpeg on Debian:

# apt install ffmpeg

Then, you can obtain the best video-only file, the best audio-only file, and merge them into a single file with both video and audio like so:

yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' ex_url...

You can find more examples regarding comparable commands in yt-dlp's documentation.

If you encounter a media provider that only has video file formats available, and you only want the video's audio, you can use yt-dlp to download the video, extract the audio, and provide an audio-only file. This will require ffmpeg, as well.

To obtain just the audio from a file with both video and audio, you can use yt-dlp's -x (--extract-audio) option:

yt-dlp -f 'best' -x ex_url...

Functions

Instead of continually entering these commands, it might be helpful to create Bash functions that you can easily call up on the command line and pass content URLs as arguments. For example, the following Bash functions, by default, download either the best or bestaudio formats for media content:

# Download video content
yv() {
    if [[ "${1}" == 'c' ]] && command -v ffmpeg > '/dev/null'; then 
        # Download best video file
        yt-dlp \
            -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' "${2}"
    else
        yt-dlp -f 'best' "${1}"
    fi
}


# Download audio content
ya() {
    if [[ "${1}" == 'c' ]] && command -v ffmpeg > '/dev/null'; then 
        # Download best audio file
        yt-dlp -f 'bestaudio[ext=m4a]' "${2}"
    else
        yt-dlp -f 'bestaudio' "${1}"
    fi
}

However, if you pass 'c' (comprehensive) as the first argument to the functions, they try to download the best quality files available and merge them into a single file. If that is not an option, they fall back to the default behavior:

yv 'c' \
    'https://betamax.video/videos/watch/0a6feebf-a2d6-4965-9942-34bf63d92054'

You will need to add these functions to your shell's configuration file to make them persistently available. For example, you can add them to your user's .bashrc file like so:

$ echo -e '\n# Download video content
yv() {
    if [[ "${1}" == '\''c'\'' ]] && command -v ffmpeg > '\''/dev/null'\''; then 
        # Download best video file
        yt-dlp \
            -f '\''bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'\'' "${2}"
    else
        yt-dlp -f '\''best'\'' "${1}"
    fi
}


# Download audio content
ya() {
    if [[ "${1}" == '\''c'\'' ]] && command -v ffmpeg > '\''/dev/null'\''; then 
        # Download best audio file
        yt-dlp -f '\''bestaudio[ext=m4a]'\'' "${2}"
    else
        yt-dlp -f '\''bestaudio'\'' "${1}"
    fi
}' >> "${HOME}/.bashrc"

Either stop and restart your terminal, or run source "${HOME}/.bashrc" to see the changes reflected in your shell.

Now, you can use the functions to download content on the command line, e.g., yv 'https://diode.zone/videos/watch/cfab9b7c-89d7-463f-8256-72629ed96791'.

Multiple Downloads

Running yt-dlp commands or the functions above are fine for single downloads, but if you ever need to download many files in a short period of time, it can become tedious. The following is a Bash script that you can use to improve this process.

#!/usr/bin/env bash
# Author: siliconsoul.org
# Purpose: Download audio/video content.

# Program

# Regex to match URLs
url_regex='https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}'
url_regex+='\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)'

# Obtain script name and save to variable
script_name="${0##/*/}"
# Remove suffix from script name and save to variable
sn_no_suffix="${script_name%.*}"

# Create temporary directory for script error log
# and save to variable
error_dir="$(mktemp -d "/tmp/${sn_no_suffix}.XXXXXXXX")"

# Create file in temporary directory and
# save error log location to variable
error_log="$(touch "${error_dir}/${sn_no_suffix}.log" &&
    echo "${error_dir}/${sn_no_suffix}.log")"

# Prompt for audio or video download, save to variable
echo ''
read -p 'Audio (a) or Video (v)? ' -r choice

# Check for invalid input, reprompt if necessary
while [[ "${choice}" != 'a' && "${choice}" != 'v' ]]; do
    echo -e '\nInvalid input. Try again or enter q to quit.\n'
    read -p 'Audio (a) or Video (v)? ' -r choice
    # If user enters q, exit the script
    if [[ "${choice}" == 'q' ]]; then
        exit 1
    fi
done

# Set value of output mode variable based on user input
if [[ "${choice}" == 'a' ]]; then
    output_mode='Audio'
elif [[ "${choice}" == 'v' ]]; then
    output_mode='Video'
fi

# Give option to enter URL, change the mode, or quit
while read -p \
        'Enter URL (m to change mode, d to debug, or q to quit): ' \
        -r user_input; do
    # Break loop if user selects q
    if [[ "${user_input}" == 'q' ]]; then
        break
    # Else, if user chooses to view the debug log
    elif [[ "${user_input}" == 'd' ]]; then
        xdg-open "${error_log}" # Open the debug log
        echo -e "\\nCurrent mode: ${output_mode}" # Output current mode
    # Else, if user chooses to change the mode
    elif [[ "${user_input}" == 'm' ]]; then
        # Prompt for audio or video download, save to variable
        echo ''
        read -p 'Audio (a) or Video (v)? ' -r choice
        # Check for invalid input, reprompt if necessary
        while [[ "${choice}" != 'a' && "${choice}" != 'v' ]]; do
            echo -e '\nInvalid input. Try again or enter q to quit.\n'
            read -p 'Audio (a) or Video (v)? ' -r choice
            # If user enters q, exit the script
            if [[ "${choice}" == 'q' ]]; then
                exit 1
            fi
        done
        # Set value of output mode variable, based on user input
        if [[ "${choice}" == 'a' ]]; then
            output_mode='Audio'
        elif [[ "${choice}" == 'v' ]]; then
            output_mode='Video'
        fi
        echo -e "\\nCurrent mode: ${output_mode}" # Output current mode
    else
        # Check for invalid URL input, reprompt if necessary;
        # output current mode
        while ! [[ "${user_input}" =~ ${url_regex} ]]; do
            echo -e '\nInvalid input. Try again or enter q to quit.\n'
            read -p 'Please enter a valid URL: ' -r user_input
            # If user enters q, exit the script
            if [[ "${user_input}" == 'q' ]]; then
                exit 1
            fi
        done
        echo -e "\\nCurrent mode: ${output_mode}" # Output current mode
        # If choice is audio
        if [[ "${choice}" == 'a' ]]; then
            # Obtain best audio m4a file
            if [[ "${1}" == 'c' ]] && command -v ffmpeg > '/dev/null'; then
                yt-dlp \
                    -f 'bestaudio[ext=m4a]' \
                    -q "${user_input}" 2> "${error_log}" &
            else
                # Obtain best audio only-format
                yt-dlp \
                    -f 'bestaudio' \
                    -q "${user_input}" 2> "${error_log}" &
            fi
        # If choice is video
        elif [[ "${choice}" == 'v' ]]; then
            # Obtain best video/audio files and merge them
            if [[ "${1}" == 'c' ]] && command -v ffmpeg > '/dev/null'; then
                yt-dlp \
                    -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' \
                    -q "${user_input}" 2> "${error_log}" &
            # Obtain best quality format with both video/audio
            else
                yt-dlp -f 'best' -q "${user_input}" 2> "${error_log}" &
            fi
        fi
    fi
done

echo -e '\nEnjoy the content!'

exit 0  # Exit script with successful exit status

In short, the script gives you the opportunity to continually add URLs for content that you would like to download.

As with the prior yv and ya functions, yt-dlp downloads the best files by default, but will attempt for higher quality outputs if you pass the 'c' argument to the script when you call it (e.g., if you name the script yt_dlp.bash, you could call it as yt_dlp.bash 'c' to have yt-dlp attempt to produce the highest quality output file).

After the script starts, you choose which mode that you want to run it in (i.e., Audio or Video mode). On each iteration of the program's main loop, you are asked to enter a new URL. From here, you also have the option to either change the script's mode (m), debug the script (d), or quit (q).

yt-dlp's output has been suppressed to prevent the script from being disrupted. So, if you ever try to download content and you do not get the expected result, check your system's /tmp/ directory for a subdirectory that begins with the name of the script. This subdirectory will contain a file that also begins with the name of the script and ends with .log. yt-dlp's error output is redirected to this file.

Documentation

For more information on yt-dlp, check out its man page by running man 1 yt-dlp or visit its project repository.

Enjoyed this post?

Subscribe to the feed for the latest updates.