Script that transparently wraps
www/yt-dlp.
Features:
- Console/terminal compatible.
- Downloads subtitles in all available languages.
- Downloads and embeds thumbnail image.
- Creates an "mkv" file, and separate subtitle file(s) if subtitles are available.
- Prioritises best video quality.
- Can use a configuration file to set defaults, some of which can be overridden from the command line.
- Can specify proxy string for authenticating proxy traversal.
- Can specify individual item URL or playlist (useful for seasons) URL.
- Can specify a file with a list of URLs to download (which can be all playlists or all individual items).
Sample configuration file (
~/.config/yt-dlp_wrapper/yt-dlp_wrapper.conf):
Code:
format='bv*+ba/b'
proxyString='http://user:password@proxy:3128'
urlList='false'
playList='--no-playlist'
The above configuration file would set format to prioritise highest video quality, set an authenticating proxy and credentials, the source would not be a file of URLs, and the URL would be a single item and not a playlist.
The script itself (for above configuration file example:
yt-dlp_wrapper.sh):
Code:
#!/bin/sh
#-------------------------------------------------------------------------#
# References
#-------------------------------------------------------------------------#
# man sh
# man sysexits
# man test
# read --help
#
# https://stackoverflow.com/questions/592620/how-can-i-check-if-a-program-exists-from-a-bash-script
# - Test if command/programme exists/is installed.
#
# https://www.grymoire.com/Unix/Sh.html#uh-78
# - Iterate lines in file.
# - Which leads to warning: https://www.shellcheck.net/wiki/SC2002 .
#
# https://www.shellcheck.net/wiki/SC2002
# - Finally get "correct" syntax for reading file line by line.
#
# https://unix.stackexchange.com/questions/1763/how-do-i-split-the-0-variable-to-find-directory-and-relative-paths-in-bash
# - Get full path in script invocation ($0).
#
# https://www.grymoire.com/Unix/Sh.html#uh-96
# - Checking for optional arguments.
#
# https://www.grymoire.com/Unix/Sh.html#uh-71
# - $@ versus ${1+$@} .
# **!! First incorrect option terminates processing of command line,
# what is left becomes parameters.
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Contributors
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Dependencies
#-------------------------------------------------------------------------#
# Bourne/POSIX shell, or Bourne/POSIX compatible shell.
# 'yt-dlp' port/package.
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Known issues
#-------------------------------------------------------------------------#
# 'yt-dlp' always generates "ERROR: [generic] '' is not a valid URL", but
# continues the download anyway.
#
# Mixing playlist and non playlist URLs in a URL list file may yield
# unpredictable results.
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Version history
#-------------------------------------------------------------------------#
version="1.0.0"
# Version 1.0.0:
# - Initial release.
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Signal Traps
#-------------------------------------------------------------------------#
cleanup ( )
{
# cleanup code goes here
echo "INT or QUIT trap received..."
}
# [Ctrl][c]=SIGINT [Ctrl][\]=SIGQUIT
trap "cleanup" INT QUIT
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Functions
#-------------------------------------------------------------------------#
version ( )
{
printf "%s\nVersion: %s\n" "${scriptNameExt}" "${version}"
exit 0
}
show_usage ( )
{
printf "%s\n" "Usage:"
printf "%s\n" "${scriptNameExt} -v | --version"
printf "%s\n" "${scriptNameExt} -h | --help"
printf "%s\n" "${scriptNameExt} [-d|--debug] [--proxy proxy_string | --proxy=proxy_string] [--url_playlist] [--url_list_file] source"
printf "%s\n" ''
printf "%s\n" "-v|--version - shows the version and exits"
printf "%s\n" "-h|--help - shows usage and exits"
printf "%s\n" "-d|--debug - shows verbose yt-dlp output"
printf "%s\n" "--url_playlist - indicates source URL (or source URL list file) is a playlist (or file containing playlist URLs)"
printf "%s\n" "--url_list_file - indicates source URL is a file containing a list of URLs to download, which are playlists if '--url_playlist' is defined"
printf "\n"
printf "%s\n" "Where:"
printf "%s\n" "- proxy_string is the value to set http_proxy to."
printf "%s\n" " E.g. http://user:passwd@proxy:3128"
printf "%s\n" "- source is either a single item URL, a playlist URL, a file of URLs, or a file of playlist URLs depending on whether --url_list_file and/or --url_playlist are specified."
printf "\n"
printf "%s\n" "Note:"
printf "%s\n" "- mixing URLs that are playlists and not playlists in the same URL list file is not defined, behaviour is unpredictable."
exit 0
}
command_check ( )
{
# $1 = command to be checked for existence
# $2 = Action: [ exit (default) | continue ]
# $3 = Message
# $4 = Exit code
commandCheckAction="${2:-exit}"
commandCheckMessage="${3:-${1} not available.}"
commandCheckExitCode="${4:-69}" # man sysexits: 69 = service unavailable: seems closest
if [ "$( command -v "${1}" > /dev/null 2>&1; echo "$?" )" != '0' ]
then
if ! [ "${commandCheckAction}" = 'continue' ]
then
printf "%s\n" "Error: ${commandCheckMessage}" >&2
exit "${commandCheckExitCode}"
else
printf "%s\n" "Warning: ${commandCheckMessage}"
fi
fi
}
get_script_configuration ( )
{
if [ -f "${scriptName}/${scriptName}.conf" ] # Settings in script/product subdirectory - presupposes run from bin directory
then
⚠ . "${scriptName}/${scriptName}.conf"
fi
if [ -f "/etc/${scriptName}/${scriptName}.conf" ] # Linux system wide settings
then
⚠ . "/etc/${scriptName}/${scriptName}.conf"
fi
if [ -f "/usr/local/etc/${scriptName}/${scriptName}.conf" ] # FreeBSD system wide settings
then
⚠ . "/usr/local/etc/${scriptName}/${scriptName}.conf"
fi
if [ -f "$HOME/${scriptName}/${scriptName}.conf" ] # User settings
then
⚠ . "$HOME/${scriptName}/${scriptName}.conf"
fi
if [ -f "$HOME/.config/${scriptName}/${scriptName}.conf" ] # User settings
then
⚠ . "$HOME/.config/${scriptName}/${scriptName}.conf"
fi
}
set_defaults ()
{
format="${format:-bv*+ba/b}"
proxyString=''
urlList="${urlList:-false}"
playList="${playList:---no-playlist}"
}
process_cmdline ( )
{
while :
do
case "$1" in
-d|--debug)
debugLevel="--verbose"
;;
-h|--help)
show_usage
;;
-v|--version)
version
;;
--proxy=*)
proxyString="${1##--proxy=}";
;;
--proxy)
processedTokenCount=$((processedTokenCount + 1));
shift;
proxyString="${1}";
;;
--url_list_file)
urlList="true";
;;
--url_playlist)
playList='';
;;
*)
# removing '*)' case will require '--' for "pass through" options/parameters to work
# BUT: not providing the '--' will never exit option parsing - so DO NOT disable '*)' case!
break;
;;
esac
processedTokenCount=$((processedTokenCount + 1));
shift
done
}
url_download ()
{
# $1 = URL to download
printf "%s\n" "/------------------------------"
printf "Downloading media link: %s\n" "${1}"
${runEnv} yt-dlp "${debugLevel}" --no-overwrites "${playList}" --format "${format}" --remux mkv --write-subs --sub-langs all --embed-thumbnail --embed-metadata --embed-chapters "${1}"
printf "%s\n" "------------------------------/"
}
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# Initialisation
#-------------------------------------------------------------------------#
requiredDependencies='yt-dlp' # list of required programmes (exit script if missing)
optionalDependencies='' # list of optional programmes (continue script if missing)
debugLevel=''
runEnv=''
#-------------------------------------------------------------------------#
#-------------------------------------------------------------------------#
# main
#-------------------------------------------------------------------------#
# script name:
scriptNameExt="${0##*/}"
# script name minus extension:
scriptName="${scriptNameExt%.sh}"
# get script/product default options (stored as global variables)
get_script_configuration
set_defaults
# override script/product default configuration with options if specified
processedTokenCount=0
process_cmdline "$@"
shift $((processedTokenCount)) # must remove processed tokens from "$@" outside function
# store download source (file or item)
download_source="${1}"
# Check for required commands/programmes
if [ -n "${optionalDependencies}" ]
then
for dependency in ${optionalDependencies}
do
command_check "${dependency}" 'continue'
done
fi
if [ -n "${requiredDependencies}" ]
then
for dependency in ${requiredDependencies}
do
command_check "${dependency}" 'exit'
done
fi
# set proxy URL if specified:
if [ -n "${proxyString}" ]
then
runEnv="env http_proxy=${proxyString}"
fi
if [ "${urlList}" != "true" ]
then
# URL is single download (which may be a playlist)
url_download "${download_source}"
else
# URL is filename of file containing multiple download URLs (all of which may be a playlist)
while read -r line;
do
url_download "${line}"
done < "${download_source}"
fi
exit 0
#-------------------------------------------------------------------------