It's the age-old problem: there's a lot of interesting stuff to watch on YouTube, but too few hours in the day to watch it all. Fortunately, a lot of YouTube content still has value if you just listen to the audio; many channels upload content where the video aspect is incidental, like video essays or tech talks.

What I wanted is a way to listen to these videos in an audio-only format, preferably with offline capability. Thankfully, I already use an app designed to download audio files and play them offline: my podcast player (I use Overcast). There are ways to download videos into Overcast on a one-off basis using Siri Shortcuts, but I wanted something that was frictionless and could be done from any YouTube interface.

YouTube has the concept of a “Watch Later” playlist, which you can use to bookmark videos for later viewing. What I envisioned was a “Listen Later” playlist, which would create a MP3 podcast feed of any videos I add to it.

It took a bit of fiddling, but I've had a working version of this for a month and it's great. The setup is a bit involved, but once you get it working, it's entirely hands-off.

1. Creating a “Listen Later” Playlist

The first thing to do is create a playlist that will become your podcast feed. For this to work, make sure that the playlist is either unlisted (i.e. public, but not search indexed) or public. As far as I know, private feeds will not work.

  1. Go to your playlists dashboard
  2. Create a new unlisted playlist.
  3. Save the playlist URL of the your playlist.
    • The URL of your playlist will look like: https://www.youtube.com/playlist?list=PLAYLIST_ID

2. Setting up Podsync

PodSync is an open source project that downloads a playlist as MP3s, and generates a podcast RSS feed for what its downloaded. It used to be its own hosted service, until the owner found the upkeep costs too expensive; fortunately, they open-sourced the project.

I'll be setting up PodSync on a Raspberry Pi 2. Anything of similar or better performance should be sufficient. You could, of course, also setup PodSync on a VM running in the cloud – but a spare Raspberry Pi is “free”.

  1. First, install Docker on the Raspberry Pi:
curl -sSL https://get.docker.com | sh
  1. Now, create the folder structure for PodSync:
mkdir -p ~/podsync/config ~/podsync/data
  1. Next, you'll need a YouTube API token. Podsync links to this tutorial that explains how to get your token.

  2. Now, populate a basic config.toml at ~/podsync/config/config.toml:

[server]
port = 8080
data_dir = "/app/data"

[tokens]
# The API token you created
youtube = "API_TOKEN"

[feeds]
  [feeds.listen_later]
  # The playlist you created earlier
  url = "https://www.youtube.com/playlist?list=PLAYLIST_ID"
  # The number of episodes to query each update
  page_size = 25
  # How often query for updates, examples: "60m", "4h", "2h45m"
  update_period = "10m"
  quality = "low"
  format = "audio"

You can create multiple feeds in the config.toml if you wish. The example above creates a feed called listen_later, the example below creates 2 feeds:

...
[feeds]
  [feeds.bon_appetit]
  url = "https://www.youtube.com/channel/UCbpMy0Fg74eXXkvxJrtEn3w"
  ...

  [feeds.gophercon_uk]
  url = "https://www.youtube.com/channel/UC9ZNrGdT2aAdrNbX78lbNlQ"
  ...

3. Running Podsync on Docker

For simplicity/reproducibility, we'll be running Podsync in a Docker container.

As of writing, the official PodSync Docker image doesn't support ARM. To run Podsync on a Raspberry Pi, we'll be using this Docker image. If you're using an x86 platform, you can just use the official image.

  1. Clone and build the ARM Podsync Docker image:
git clone https://github.com/pblgomez/PodSync-Armv6-Docker podsync-docker
cd podsync-docker
./build.sh
  1. Now that we've built the ARM Podsync Docker image, we can start a Podsync docker container:
docker run -d \
    --name='Podsync' \
    --net='bridge' \
    -e 'PODSYNC_CONFIG_PATH'='/app/config/config.toml' \
    -v ~/podsync/data/:'/app/data':'rw' \
    -v ~/podsync/config/:'/app/config':'rw' \
   'local/podsync'

(Bonus) Run Podsync on startup with systemd

(Thanks to this tutorial)

Registering our Podsync container as a service with systemd ensures that the container is started on system startup, and is restarted if it crashes.

We'll be creating a basic service entry for our Podsync container. Populate /etc/systemd/system/docker-podsync.service with the following config:

[Unit]
Description=Podsync Container
Requires=docker.service
After=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker start -a Podsync
ExecStop=/usr/bin/docker stop -t 2 Podsync

[Install]
WantedBy=local.target

Now, we'll reload systemd and register our new service:

sudo systemctl daemon-reload
sudo systemctl start docker-podsync.service
sudo systemctl enable docker-podsync.service
sudo reboot

At this point, you can run the following command to see your Podsync instance in action:

docker logs -f Podsync

If you look in ~/podsync/data, you should see a listen_later.xml (the RSS feed of your “podcast”) and a listen_later/ folder containing a list of MP3 files.

3. Hosting the Feed on S3

You could expose your Podsync instance to the internet, but I'm weary of exposing anything on my home network to the open internet. Instead, I chose to sync my podsync feed/files to a cloud storage provider (S3). That way, my podcast client can always pull down new feed items, even if I'm not at home.

I found that the easiest way to do this was by using rclone, which aptly bills itself as “rsync for cloud storage”.

  1. First, install rclone:
curl https://rclone.org/install.sh | sudo bash
  1. Next, setup rclone with your cloud storage provider of choice (Here's the S3 tutorial).

  2. Update your ~/podsync/config/config.toml to prepend the correct host name. This updates the podcast RSS feed so that it correctly points to files in your S3 bucket, instead of localhost.

[server]
port = 8080
data_dir = "/app/data"
hostname = "https://$BUCKET_NAME.s3.amazonaws.com"
...
  1. Restart your Podsync docker container with docker restart Podsync, and wait for it to finish refreshing the feed.

  2. Now, you can run rclone to upload the MP3s and RSS feed from Podsync to your storage bucket.

rclone sync /path/to/podsync/data/ s3:$BUCKET_NAME --include "*.{xml,mp3}"
  1. If everything uploaded correctly, you should be able to load the following URL into your podcast player, and see all the episodes that Podsync downloaded:

    https://$BUCKET_NAME.s3.amazonaws.com/listen_later.xml
    
  2. With manual testing done, you can setup a cron job for rclone so that the feed is periodically updated. I have mine to refresh every 30 minutes.

    Run crontab -e and add the following entry:

*/30 * * * * /usr/bin/rclone sync /path/to/podsync/data/ s3:$BUCKET_NAME --include "*.{xml,mp3}"

(Bonus) Cleaning up old episodes

If you stop at this point, you have a fully working “Listen Later” podcast feed. However, I'm running my Podsync instance off of a Raspberry Pi, and don't want to fill up its storage with an endlessly growing playlist.

This shell script deletes all but the last 20 most recent “episodes” in the feed:

# Keep the last 20 newest files in the listen_later directory
find /path/to/podsync/data/listen_later/ -type f -printf "%A+\t%p\n" | sort -r | cut -f2 | tail -n +20 | xargs --no-run-if-empty rm --

I put this script in my ~/podsync directory, and setup a cron job to clean up old episodes every day.


And that's it! I love this type of frictionless automation; I can add a video to my “Listen Later” playlist from the YouTube app on my phone, and within half an hour it shows up as a podcast episode in Overcast. 😄