#!/bin/zsh # Download YouTube-feeds as if they are podcasts. # Error codes: # 1 = Could not read file or directory. # 2 = Configuration option not set. # 3 = youtube-dl error. # License: AGPL-3.0-only, see LICENSE. function die() { [[ -n "${2}" ]] && echo "Error: ${2}" >&2 exit ${1} } # Return path of configuration directory. function get_config_path() { local path if [[ -n "${XDG_CONFIG_HOME}" ]]; then path="${XDG_CONFIG_HOME}/download-yt-feeds" else path="${HOME}/.config/download-yt-feeds" fi [[ -d "${path}" ]] || mkdir -p "${path}" [[ -r "${path}" ]] || die 1 "${path} is not readable." echo "${path}" } # Return URLs of YouTube-feeds in feedlist. # All lines that don't start with "http" are ignored. function read_feedlist() { local feed_path="$(get_config_path)/feedlist" [[ -r "${feed_path}" ]] || die 1 "${feed_path} is not readable." local -a entries while read line; do if [[ "${line}" =~ "^http" ]]; then local -a entry=("${(@s/ /)line}") entries+=(${entry[1]}) fi done <"${feed_path}" print -r -- ${(qq)entries} } function main() { local config_path="$(get_config_path)/config" [[ -r "${config_path}" ]] || die 1 "${config_path} is not readable." source "${config_path}" [[ -z "${download_dir}" ]] && die 2 "download_dir not specified." [[ -z "${keep_for_days}" ]] && die 2 "keep_for_days not specified." [[ -z "${ytdl_format}" ]] && local ytdl_format="best" if [[ -z "${ytdl_binary}" ]]; then if command -v yt-dlp > /dev/null; then local ytdl_binary="yt-dlp" else local ytdl_binary="youtube-dl" fi fi find "${download_dir}" -type f -mtime +"${keep_for_days}" -delete find "${download_dir}" -maxdepth 1 -type d -exec \ rmdir --ignore-fail-on-non-empty {} \; mkdir -p "${download_dir}" local files_before="$(find "${download_dir}" -type f)" local -a feedlist=("${(@Q)${(z)$(read_feedlist)}}") for feed in ${feedlist}; do ${ytdl_binary} \ --download-archive "$(get_config_path)/downloaded" \ --dateafter "now-${keep_for_days}days" \ --output "${download_dir}/%(uploader)s/%(title)s.%(ext)s" \ --format "${ytdl_format}" \ ${=ytdl_extra_args} \ "${feed}" # [[ ${?} -eq 0 ]] || die 3 "${ytdl_binary} returned an error." done local files_after="$(find "${download_dir}" -type f)" local time="$(date +'%F_%R')" while read file; do [[ "${file}" =~ "\.vtt$" ]] && continue # Skip subtitle files. # https://stackoverflow.com/a/8811800/5965450 if [[ "${files_before#*${file}}" == "${files_before}" ]]; then if [[ "${file}" =~ "#" ]]; then local file_new="${file:gs/#/_}" mv "${file}" "${file_new}" file="${file_new}" fi echo "${file/${download_dir}\//}" \ >> "${download_dir}/New_${time}.m3u" fi done <<<"${files_after}" } main