#!/usr/bin/env bash # Exit 1 if any command errors set -e # $1 here is the first argument to this script BATCH_FILE="$1" DOWNLOAD_DIR="/export/jellyfin" YTDLP_LOG_FILE="/export/ytdlp.log" # 'cat << 'EOF"' is used here to allow for newlines in the command YTDLP_COMMAND=$(cat << EOF /srv/.local/bin/yt-dlp -P $DOWNLOAD_DIR --sub-format srt --write-subs --write-thumbnail EOF ) printUsage() { cat << EOF Usage: $(basename "$0") EOF } # log() takes a log level and log line and uses the LOG_LEVEL env var to # control how/when it is logged # To enable debug logging set LOG_LEVEL=debug as an env var log() { # '$1' is the first argument passed to this function passed_level="$1" # '$2' is the first argument passed to this function log_line="$2" # This is passed from the env as 'LOG_LEVEL=' env_level="$LOG_LEVEL" # Convert env_level to int case $env_level in "DEBUG") min_level_int=0;; "INFO") min_level_int=1;; "ERROR") min_level_int=2;; *) min_level_int=1;; esac # Convert passed_level to int case $passed_level in "DEBUG") log_level_int=0;; "INFO") log_level_int=1;; "ERROR") log_level_int=1;; *) log_level_int=99;; esac if [ $log_level_int -ge $min_level_int ]; then echo "[$passed_level] $log_line" fi } validate_args() { # If no argument specified, exit 1 if [ -z "$BATCH_FILE" ]; then printUsage exit 1 fi # Exit 1 if passed batch file is not a file (or doesn't exist) if [ ! -f "$BATCH_FILE" ]; then echo "Error: Batch file '$BATCH_FILE' not found" printUsage exit 1 fi # Exit 0 if passed file contains no urls if ! grep -q "^.*http.*" "$BATCH_FILE"; then echo "File '$BATCH_FILE' contains no URLs" exit 1 fi if [[ ! -O "$BATCH_FILE" ]]; then echo "File '$BATCH_FILE' is not owned by user ID '${UID}'" echo "Run 'chown ${UID} $BATCH_FILE' before mounting" exit 1 fi if [[ ! -O "$DOWNLOAD_DIR" ]]; then echo "Directory '$DOWNLOAD_DIR' is not owned by user ID '${UID}'" echo "Run 'chown -R ${UID} $DOWNLOAD_DIR' before mounting" exit 1 fi } process_batchfile() { # Using `read` parse each line of the file as a '$line' while setting IFS='' # to prevent lines containing things such as `\n` from being parsed as a # literal newline while IFS='' read -r line; do # Ignore any commented lines RegEx explanation: https://regexr.com/7hs52 regex_match_leading_comment='^\s*[#].*$' if [[ "$line" =~ $regex_match_leading_comment ]]; then # RegEx explanation: https://regexr.com/7i073 line_without_comment=$(echo "$line" | sed 's/^\s*[#]\s*\(.*\)$/\1/') log "INFO" "Skipping: $line_without_comment" continue fi log "INFO" "Downloading: $line" # Temporarily allow commands to fail without exiting set +e # Run the command with $line as an argument # Quotes are used here as a measure to protect against "\n" and the like # '>' is used to overwrite the log file each call so only the last commands # output is shown $YTDLP_COMMAND "$line" > $YTDLP_LOG_FILE 2>&1 # Save the exit code of the last command exit_code="$?" # Re-enable exiting 1 on command failure set -e if [ "$exit_code" -ne "0" ]; then log "ERROR" "Command failed: '$YTDLP_COMMAND'" cat $YTDLP_LOG_FILE continue fi log "INFO" "Finished downloading '$line'" # By here we can assume the file was downloaded successfully, thus we can # comment out the line in the file log "DEBUG" "Commenting out '$line' in '$BATCH_FILE'" # Prepend a '#" on the current line in the file # '|' is used because URLs contain '/' which is the usual sed delimiter # 'cp' to a buffer file is used because sed cannot modify the mounted # file's inode when using Docker ('-c' is not available in Ubuntu's sed) new_file="/export/$(basename "$BATCH_FILE").new" cp "$BATCH_FILE" "$new_file" sed -i "s|\($line\)|# \1|" "$new_file" cp "$new_file" "$BATCH_FILE" log "DEBUG" "Finished commenting '$line' from '$BATCH_FILE'" done <<< "$(cat "$BATCH_FILE")" } main() { validate_args log "INFO" "Processing $BATCH_FILE" process_batchfile log "INFO" "Finished processing $BATCH_FILE" } # Run main() main