# Creating a Podcast Feed from a YouTube Playlist


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](https://overcast.fm/)). 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](https://support.google.com/youtube/answer/56101?co=GENIE.Platform%3DDesktop&hl=en),
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](https://www.youtube.com/view_all_playlists)
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](https://github.com/mxpv/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:

```sh
curl -sSL https://get.docker.com | sh
```

2.  Now, create the folder structure for PodSync:

```sh
mkdir -p ~/podsync/config ~/podsync/data
```

3.  Next, you'll need a YouTube API token. Podsync links to
    [this tutorial](https://elfsight.com/blog/2016/12/how-to-get-youtube-api-key-tutorial/)
    that explains how to get your token.

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

```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:

```toml
...
[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](https://github.com/pblgomez/PodSync-Armv6-Docker) Docker image. If you're
using an x86 platform, you can just use the
[official image](https://hub.docker.com/r/mxpv/podsync).

1. Clone and build the ARM Podsync Docker image:

```sh
git clone https://github.com/pblgomez/PodSync-Armv6-Docker podsync-docker
cd podsync-docker
./build.sh
```

2. Now that we've built the ARM Podsync Docker image, we can start a Podsync
   docker container:

```sh
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](https://mehmandarov.com/start-docker-containers-automatically/))

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:

```sh
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:

```sh
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](https://rclone.org), which aptly bills itself as "rsync for cloud
storage".

1. First, [install rclone](https://rclone.org/install/):

```sh
curl https://rclone.org/install.sh | sudo bash
```

2. Next, [setup rclone](https://rclone.org/docs/#configure) with your cloud
   storage provider of choice (Here's the
   [S3 tutorial](https://rclone.org/s3/)).

3. 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"
...
```

4. Restart your Podsync docker container with `docker restart Podsync`, and wait
   for it to finish refreshing the feed.

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

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

5. 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
    ```

6. 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:

```cron
*/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:

```sh
# 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. 😄

