I’m sure I’ll be modifying this more over time. But, it reads from a text file (podcasts.txt) in the given DOWNLOAD_PATH–this file contains the RSS URL(s) for the podcast(s). It organizes the podcasts into directories, if those directories don’t already exist. It uses eyeD3 to add metadata (including cover art). Works fairly well, though I’m still stomping bugs.

Maybe next I’ll put together a nice little frontend for it–maybe convert the whole thing to a Blazor app. But, for now, it does the thing and it’s fun being a script kiddie 🙂

#!/bin/bash

create_path() {
    TITLE_PATH="${DOWNLOAD_PATH}/${1}"

    if [ ! -d "${TITLE_PATH}" ]; then
       mkdir "${TITLE_PATH}"
    fi
}

sanitize_filename() {
  local filename="$1"
  # Replace unfriendly characters with underscore
  sanitized=$(echo "$filename" | sed 's/[\/:*?"<>|]/_/g')
  echo "$sanitized"
}

dl_podcast() {
    local RSS_FEED_URL=$1

    curl -s "$RSS_FEED_URL" > /tmp/rss_feed.xml

    title=$(get_podcast_title "${RSS_FEED_URL}")

    create_path "${title}"

    # Initialize arrays to store episode titles and enclosure URLs
    titles=()
    urls=()

    # Extract episode titles and enclosure URLs
    mapfile -t titles < <(xmllint --xpath '//item/title/text()' /tmp/rss_feed.xml)
    mapfile -t urls < <(xmllint --xpath '//item/enclosure/@url' /tmp/rss_feed.xml)

    # Print the episode titles and enclosure URLs
    j=${#titles[@]}
    padtowidth=${#j}
    for ((i=0; i<${#titles[@]}; i++)); do
	ep_title=$(echo "${titles[$i]}" | sed -e 's/<!\[CDATA\[//' -e 's/\]\]>//')
        url=`echo ${urls[$i]} | sed "s/url=//g"`
        url=`echo ${url} | sed "s/\"//g"`
        filenum=`printf "%0*d\n" $padtowidth $j`
        file="${filenum} $ep_title.mp3"
        file=$(sanitize_filename "${file}")

        if [ ! -f "${TITLE_PATH}/${file}" ]; then
            echo "Downloading: ${file} (${url})"
            wget -qO "${TITLE_PATH}/${file}" "${url}"
	    /usr/bin/eyeD3 --track "${filenum}" --disc-num "1" "${TITLE_PATH}/${file}"
	    /usr/bin/eyeD3 --add-image="/tmp/cover.jpg":FRONT_COVER "${TITLE_PATH}/${file}"
        fi
        j=`expr ${j} - 1`
    done

    unset ${TITLE_PATH}
}

get_podcast_title() {
    local url="${1}"
    local title="$(curl -s "$url" | grep -oP '<title>\K[^<]+' | sed -n '2p')"
    echo "${title}"
}

get_podcast_cover() {
    local url=$1
    local cover_url=$(curl -s "$url" | grep -oP '<itunes:image\s+href="\K[^"]+' | head -n 1)

    curl -s -o /tmp/cover.jpg ${cover_url}
}

cleanup() {
    rm /tmp/cover.jpg
    rm /tmp/rss_feed.xml
}

read_lines_into_array() {
    local file=$1

    # Check if file exists
    if [ ! -f "$file" ]; then
        echo "Error: File $file not found."
        exit 1
    fi

    # Read the file line by line and append each line to the array
    mapfile -t lines_array < "${file}"

    # Return the array
    echo "${lines_array[@]}"
}

process_podcasts() {
    local -n podcasts=$1

    for link in "${podcasts[@]}"; do
        get_podcast_cover "$link"

        title=$(get_podcast_title "$link")

        dl_podcast $link
    done
}

main()
{
    DOWNLOAD_PATH="<PODCAST DIRECTORY GOES HERE>"
    TITLE_PATH=${DOWNLOAD_PATH}

    if [ $# -ne 1 ]; then
        echo "Usage: $0 <rss url>"
        exit 1
    fi

    file=$1

    # Call the function and store the result in an array
    declare -A links
    links=$(read_lines_into_array "$file")

    # Print the contents of the resulting array
    process_podcasts links
    cleanup
}

main "$@"