Podcast RSS Generator

2024-Sep-02

I wanted to play some audiobooks on my light phone, so I made a bourne shell script to generate an RSS feed. I was surprised that I couldn’t find any prior work to do this.

Put all your .mp3 files in a directory, then add build.sh and config.sh with your settings:

title="My Audiobook"
description="An audiobook I like"
url="https://example.com/ebooks/mine"

The script will create rss.xml in the same directory.

It uses ffprobe from ffmpeg to figure out each track’s title. All my tracks have ID3 tags, so it may fail if yours don’t.

It can deal with spaces in filenames, but not double-quotes. & and < might cause problems too. Anyway, it’s good enough for me.


Here’s the script inline:

#! /bin/sh

set -e

cd $(dirname $0)
. config.sh

out="rss.xml"
exec 1>$out
echo "Writing to $out" 1>&2

cat <<EOF
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
  <channel>
    <title>$title</title>
    <link>$url</link>
    <language>en</language>
    <description>$description</description>
    <atom:link href="$url/$out" rel="self" type="application/rss+xml" />
EOF

if [ -f cover.jpg ]; then
  cat <<EOF
    <image>
      <url>$url/cover.jpg</url>
      <title>$title</title>
      <link>$url</link>
    </image>
EOF
fi

for fn in *.mp3; do
  echo "- $fn" 1>&2

  ffprobe -loglevel quiet -show_entries format -output_format json "$fn" > format.json
  title=$(cat format.json | jq -r '.format.tags.title')
  duration=$(cat format.json | jq -r '.format.duration | tonumber | ceil')
  #bps=$(cat format.json | jq -r '.format.bit_rate')
  #kbps=$(($bps / 1000))
  rm format.json
  guid=$(sha1sum "$fn" | awk '{print $1}')
  size=$(stat -c %s "$fn")
  mtime=$(date -R -d @$(stat -c %Y "$fn"))
  ufn=$(echo $fn | tr ' ' '+')

  cat <<EOF
    <item>
      <title>$title</title>
      <pubDate>$mtime</pubDate>
      <itunes:duration>$duration</itunes:duration>
      <enclosure url="$url/$ufn" type="audio/mpeg" length="$size" />
      <guid isPermaLink="false">$guid</guid>
    </item>
EOF
done

cat <<EOF
  </channel>
</rss>
EOF