Skip to main content

Podcaster

Hosting a podcast and creating its website with Eleventy.

Podcaster Blog

Storing episode metadata in filenames

Each podcast episode in Podcaster is represented by a template with the tag podcastEpisode. On my podcast websites, I put all the podcast episode templates in a /src/posts folder, and I give each template the podcastEpisode tag by using a directory data file.

So my /src/posts folder looks like this:

# /src/posts

.
├── 2021-11-04-ep1-yesterdays-enterprise.md
├── 2021-11-12-ep2-the-house-of-quark.md
├── 2021-11-19-ep3-lineage.md
├── 2021-11-26-ep4-forget-me-not.md
├── 2021-12-03-ep5-the-corbomite-maneuver.md
└── posts.11tydata.js

And the directory data file looks like this:

// posts.11tydata.js

export default {
  'tags': ['podcastEpisode']
}

The value exported from the directory data file gets added to the data cascade for all the templates in that directory.

But are there any other properties that we can calculate in the directory data file?

Eleventy will already calculate a post’s date from the dates at the start of the filename. I include that date so that posts are listed in chronological order, but it also means that I don’t have to specify the post date in the template’s front matter.

I include the episode number in the filename as well. Can I use the directory data file to calculate the episode number from the filename too?

Turns out, I can. Here’s an updated directory data file.

// posts.11tydata.js

export default {
  tags: ['podcastEpisode'],
  eleventyComputed: {
    'episode.episodeNumber' (data) {
      if (data.episode?.episodeNumber) {
        return data.episode?.episodeNumber
      }
      const matchResult = data.page.fileSlug.match(/^ep(\d+)/)
      if (matchResult) {
        return parseInt(matchResult[1])
      }
    }
  }
}

eleventyComputed lets you calculate data values based on values that have been already calculated. Here we’re getting the page’s fileSlug, which is just the filename with the date at the beginning removed. We search for the letters ep at the start of the fileSlug, we get the number that appears after that, and then we return it as episode.episodeNumber, which is where Podcaster wants to find it.

What about podcasts with seasons? Here’s a file listing from the 500 Year Diary podcast website.

# /src/posts

.
├── 2024-04-14-s1e1-the-power-of-the-daleks.md
├── 2024-04-21-s1e2-spearhead-from-space.md
├── 2024-04-28-s1e3-terror-of-the-autons.md
├── 2024-05-05-s1e4-the-christmas-invasion.md
├── 2024-05-12-s1e5-torchwood-everything-changes.md
└── 2024-05-19-s1e6-sja-invasion-of-the-bane.md

And here’s the directory data file that calculates both episode.episodeNumber and episode.seasonNumber from the post’s filename.

export default {
  tags: ['podcastEpisode'],
  eleventyComputed: {
    'episode.episodeNumber' (data) {
      if (data.episode?.episodeNumber) {
        return data.episode?.episodeNumber
      }
      const matchResult = data.page.fileSlug.match(/^s(?:\d+)e(\d+)/)
      if (matchResult) {
        return parseInt(matchResult[1])
      }
    },
    'episode.seasonNumber' (data) {
      if (data.episode?.seasonNumber) {
        return data.episode?.seasonNumber
      }
      const matchResult = data.page.fileSlug.match(/^s(\d+)/)
      if (matchResult) {
        return parseInt(matchResult[1])
      }
    }
  }
}

The episode data documentation makes it seem like you need to provide a lot of information in the front matter of your podcast episode templates. But using the technique I just described, as well as Podcaster’s automatic calculation of episode size and duration and the defaults Podcaster provides, you can get the front matter data down to nearly nothing. Here’s an example from 500 Year Diary, with no Podcaster-specific front matter at all.

---
title: Where Kelsey Went
recordingDate: 2024-04-19
topic: "The Sarah Jane Adventures: Invasion of the Bane"
diaryDate: 2007-01-01
---

Presenting an episode on your site

In my first post a few weeks ago, I showed you a simple way to create a home page for your podcast — by creating a template which loops through the podcastEpisode collection and renders some HTML containing the information for each episode.

Today, I’m going to show you an include file which presents more extensive detail about an episode. This include file can be used by itself on the episode’s dedicated page, but it can also be used as part of a list of episodes — like an index page or a tag page, for example.

An include file like this is sometimes called a partial.


Here’s the code for the partial. It’s a Nunjucks template.

{% if not post %}{% set post = collections.podcastEpisode | getCollectionItem %}{% endif %}
<article>
  {%- if linkInTitle -%}
  <h1><a href="{{ post.url }}">{{ post.data.title | safe }}</a></h1>
  {%- else -%}
  <h1>{{ post.data.title | safe }}</h1>
  {%- endif -%}
  <p class="post-metadata">Episode {{ post.data.episode.episodeNumber }} &middot; {{ post.data.topic | safe }} &middot; {{ post.date | readableDate }}</p>
  {% if excerptOnly %}
    {{ post.data.excerpt | safe }}
  {% else %}
    {{ post.content | safe }}
  {% endif %}
  <figure class="audio">
    <audio controls preload="metadata" src="{{ post.data.episode.url }}"></audio>
    <figcaption>
      Episode {{ post.data.episode.episodeNumber }}: {{ post.data.title }}
      &middot; Recorded on {{ post.data.episode.recordingDate | readableDate }}
      &middot; <a target="_blank" href="{{ post.data.episode.url }}">Download</a> ({{ post.data.episode.size | readableSize(1) }})
    </figcaption>
  </figure>
</article>

Let’s start with that cryptic first line.

{% if not post %}{% set post = collections.podcastEpisode | getCollectionItem %}{% endif %}

This line allows us to use this partial in two different contexts — as part of a list of episodes, or by itself on the episode’s page.

Let’s say we’re looping through some episodes to display a list.

{% for post in collections.podcastEpisode | reverse %}
{% include 'episode.njk' %}
{% endfor %}

When we’re looping through a collection of posts like that, each post is represented by a collection item. If our loop variable is post, we will refer to the post’s date or content as {{ post.date }} or {{ post.content }}, and to its front matter data as {{ post.data.title }} and {{ post.data.topic }}.

When we’re not looping through a collection, we can refer to all that data more simply, as {{ date }}, {{ content }}, {{ title }} and {{ topic }}.

To make this partial usable in both contexts, the partial assumes that it’s dealing with a collection item called post. The first line checks if there’s a post variable defined already. If there is, the partial assumes that we’re in a loop and that that’s the current collection item. If not, it fetches the collection item for the current page from the podcastEpisode collection and assigns that to post.

Note

This means that if you use this partial in a loop, the loop variable must be called post.


Let’s continue.

We put all of the episode’s information inside an <article> element — a single HTML element which we can give its own margins or backgrounds or borders.

Within that element, the first piece of information presented is the title.

{%- if linkInTitle -%}
<h1><a href="{{ post.url }}">{{ post.data.title | safe }}</a></h1>
{%- else -%}
<h1>{{ post.data.title | safe }}</h1>
{%- endif -%}

When this partial is used in a list, we probably want the title to link out to the episode’s dedicated page. To make this happen, we set the linkInTitle variable to true, like this:

----
linkInTitle: true
---
{% for post in collections.podcastEpisode | reverse %}
{% include 'episode.njk' %}
{% endfor %}

After the title, there’s a line of episode metadata: the episode number, the topic and the release date.

And then after that, there’s either the post content, which will contain the episode’s full show notes, or the post excerpt, which is shorter version of the show notes which might be used in a list. The variable excerptOnly determines which.

----
excerptOnly: true
---
{% for post in collections.podcastEpisode | reverse %}
{% include 'episode.njk' %}
{% endfor %}

Excerpts are an optional feature of Podcaster. They’re quite flexible: they have a sensible default, while allowing you to set them how you would like. Here’s how excerpts are explained in the documentation.

After that, there’s an audio player, accompanied by a caption containing the episode number and title, the recording date, and a link for downloading the episode (accompanied by the size of the episode, for the sake of politeness).


And that’s that. The partial presented here is a simplified version of the one I use on the Flight Through Entirety website, so go and take a look at the site’s home page if you want to see it in action.

Describing a podcast episode

Podcasts are an audio medium, of course, but your listeners’ podcast players describe each of your episodes using text — a title, a short description and a long description.

And so Podcaster lets you provide all three of those.

I’ve posted about titles already — so let’s talk about the short and long descriptions.

Continue reading…

Creating a home page

You can put whatever you want on your podcast site’s home page, but the most obvious and straightforward approach might be to present a list of episodes in reverse chronological order.

Here’s how to do that.

Continue reading…