#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${YOUTUBE_BACKUP_ENV_FILE:-${SCRIPT_DIR}/.env}" if [[ -f "${ENV_FILE}" ]]; then set -a # shellcheck disable=SC1090 . "${ENV_FILE}" set +a fi BACKUP_ROOT="${YOUTUBE_BACKUP_ROOT:-/data/youtube-backup}" PLAYLISTS_FILE="${YOUTUBE_BACKUP_PLAYLISTS_FILE:-${BACKUP_ROOT}/playlists.txt}" ARCHIVE_FILE="${YOUTUBE_BACKUP_ARCHIVE_FILE:-${BACKUP_ROOT}/archive.txt}" VIDEOS_DIR="${YOUTUBE_BACKUP_VIDEOS_DIR:-${BACKUP_ROOT}/videos}" LOG_DIR="${YOUTUBE_BACKUP_LOG_DIR:-${BACKUP_ROOT}/logs}" COOKIES_FILE="${YOUTUBE_BACKUP_COOKIES_FILE:-}" COOKIES_FROM_BROWSER="${YOUTUBE_BACKUP_COOKIES_FROM_BROWSER:-}" FORMAT="${YOUTUBE_BACKUP_FORMAT:-bv*+ba/best}" OUTPUT_TEMPLATE="${YOUTUBE_BACKUP_OUTPUT_TEMPLATE:-%(playlist_title)s/%(upload_date>%Y-%m-%d,release_date>%Y-%m-%d,epoch>%Y-%m-%d)s - %(title)s [%(id)s].%(ext)s}" MERGE_FORMAT="${YOUTUBE_BACKUP_MERGE_FORMAT:-mp4}" JS_RUNTIMES="${YOUTUBE_BACKUP_JS_RUNTIMES:-node}" WRITE_THUMBNAIL="${YOUTUBE_BACKUP_WRITE_THUMBNAIL:-false}" RATE_LIMIT="${YOUTUBE_BACKUP_RATE_LIMIT:-}" SLEEP_REQUESTS="${YOUTUBE_BACKUP_SLEEP_REQUESTS:-1}" EXTRA_ARGS="${YOUTUBE_BACKUP_EXTRA_ARGS:-}" if ! command -v yt-dlp >/dev/null 2>&1; then echo "yt-dlp is required. Install it with: pipx install yt-dlp" >&2 exit 1 fi if [[ ! -f "${PLAYLISTS_FILE}" ]]; then mkdir -p "$(dirname "${PLAYLISTS_FILE}")" cat >"${PLAYLISTS_FILE}" <<'EOF' # Add one YouTube playlist/channel/video URL per line. # Examples: # https://www.youtube.com/playlist?list=YOUR_PLAYLIST_ID # https://www.youtube.com/@YOUR_CHANNEL/videos EOF echo "Created ${PLAYLISTS_FILE}. Add your playlist URLs and rerun." >&2 exit 1 fi if ! grep -Ev '^\s*($|#)' "${PLAYLISTS_FILE}" >/dev/null; then echo "${PLAYLISTS_FILE} does not contain any playlist URLs." >&2 exit 1 fi mkdir -p "${VIDEOS_DIR}" "${LOG_DIR}" "$(dirname "${ARCHIVE_FILE}")" touch "${ARCHIVE_FILE}" timestamp="$(date -u +%Y%m%dT%H%M%SZ)" log_file="${LOG_DIR}/youtube-backup-${timestamp}.log" args=( --batch-file "${PLAYLISTS_FILE}" --download-archive "${ARCHIVE_FILE}" --paths "home:${VIDEOS_DIR}" --output "${OUTPUT_TEMPLATE}" --format "${FORMAT}" --merge-output-format "${MERGE_FORMAT}" --ignore-errors --continue --no-overwrites --restrict-filenames --write-info-json --write-description --embed-metadata --embed-thumbnail --js-runtimes "${JS_RUNTIMES}" --sleep-requests "${SLEEP_REQUESTS}" ) if [[ "${WRITE_THUMBNAIL}" == "true" ]]; then args+=(--write-thumbnail --convert-thumbnails jpg) fi if [[ -n "${COOKIES_FILE}" ]]; then args+=(--cookies "${COOKIES_FILE}") fi if [[ -n "${COOKIES_FROM_BROWSER}" ]]; then args+=(--cookies-from-browser "${COOKIES_FROM_BROWSER}") fi if [[ -n "${RATE_LIMIT}" ]]; then args+=(--limit-rate "${RATE_LIMIT}") fi if [[ -n "${EXTRA_ARGS}" ]]; then # Intentionally split for operator-provided yt-dlp flags from a local .env. # shellcheck disable=SC2206 extra_args_array=(${EXTRA_ARGS}) args+=("${extra_args_array[@]}") fi echo "Backing up YouTube playlists from ${PLAYLISTS_FILE}" echo "Videos: ${VIDEOS_DIR}" echo "Archive: ${ARCHIVE_FILE}" echo "Log: ${log_file}" yt-dlp "${args[@]}" 2>&1 | tee "${log_file}"