Need to create a podcast-friendly RSS feed


#1

I’m looking to migrate my existing WordPress (yuck) installation for our podcast onto Hugo. Podcasts require some special management for RSS feeds, etc. Fortunately, the only content on our site are podcast episodes (we don’t really blog there, so I don’t need a feed that includes type “post”, only type “episode”).

I need to be able to add the iTunes specific podcast enclosure type things. Ideally I would have a “general” RSS feed, and also one just for the podcast subscribers, but it seems that Hugo only allows for one RSS feed right now.

It seems that the most ideal would be for me to use the /layouts/rss.xml template and add the necessary pieces?


#2

The taxonomies and sections also have RSS feeds, if I’m not totally mistaken, so if you tag your podcasts, that should do it?


#3

That would definitely work - I already built a custom rss template that adds all the enclosure/podcasty things we need, but doing it with the other taxonomies (aka tagging) would give me that flexibility for the future which isn’t a bad idea.

The RSS template doesn’t support frontmatter, right? So i can’t use a redirect in there? Just thinking about migrating the existing feed URL we already have with iTunes.


#4

Correct.


#5

Hugo uses HTML redirects which RSS wouldn’t support anyway. It’s best to handle this server side with a 301.


#6

@mattstratton I’m super excited to see it when it’s done. Please add the site to the showcase when you launch.


#7

I will definitely! I’m making a lot of progress (I got a little tired of the front-end work for a while and now I’m suffering through manually migrating 42 podcast episodes from Wordpress into Hugo docs)…

I could write some automation to do it, but it’s also giving me a chance to do some content housekeeping at the same time :slight_smile:


#8

I have a working podcast site built in Hugo. ohpodcast.com I’ve offloaded the podcast-specific parts of the RSS feed to feedburner, but I did have a section RSS feed that incorporated front matter into the XML output. I never checked any of that code into GitHub, so it might be gone. But I can at least tell you what I do have working.

As I mentioned, feedburner is doing the heavy lifting. They take any post containing an MP3 and create a “media enclosure” that is compliant with iTunes and other podcast apps. I provided feedburner with a few bits of information about the podcast (description, categories, etc) and it does the rest.

Front Matter

I’ve added a few custom fields in the front matter of each post, which find their way to various shortcodes.

---
Date: 2015-09-24
Title: How to load test your website before a big event like Black Friday
author: Alan Bush
mp3: http://drops.albush.com/How-to-load-test-your-website-before-a-big-event-like-Black-Friday.mp3
duration: "54:26"
length: 39188375
ogg: http://drops.albush.com/How-to-load-test-your-website-before-a-big-event-like-Black-Friday.ogg
ogg_length: 68465422
number: "116"
summary: So you've got a big event like Black Friday coming up. Will your website be able to handle the extra volume of traffic? If you don't know (hint, you should know) then it's time to do some load testing. We'll look at a few techniques to load test your site before your big event.
tags:
- Black Friday
- Load Testing
youtube_id: ivrMJ6IETuA
---

mp3 and ogg are the actual audio files I generate for each post, they end up in the audio.html shortcode as described below. I store the actual audio files in a Rackspace Cloud Files container (disclosure, I’m a Rackspace employee) so that the actual audio files will still be available to podcast listeners even if the rest of the site goes offline. length and ogg_length are filesize in bytes. The shortcode does some math to convert to MB. (These are relics of the data that was passed from front matter to section RSS, and aren’t necessary anymore. I need to clean that up and just use a string to display download size.) youtube_id is passed to the youtube.html shortecode.

Short Codes

Each post is an episode recap, and includes an embed of the audio and original video, and options to download/subscribe. The audio/subscribe features are part of a shortcode:

audio.html

<div class="container">
<audio controls>
        <source src="{{ $.Page.Params.mp3 }}" type="audio/mp4">
        {{ if $.Page.Params.ogg }}<source src="{{ $.Page.Params.ogg }}" type="audio/ogg">{{end}}
        Sorry, your browser does not support the <code>audio</code> element.
</audio><br>
Download: <a href="{{ $.Page.Params.mp3 }}">MP3</a> ({{ div $.Page.Params.length 1048576}} MB) {{ if $.Page.Params.ogg }}| <a href="{{ $.Page.Params.ogg }}">OGG</a> {{if $.Page.Params.ogg_length }} ({{ div $.Page.Params.ogg_length 1048576 }} MB) {{end}}{{end}}<br>
Subscribe: <a href="https://itunes.apple.com/us/podcast/rackspace-office-hours-netcast/id1035176963?mt=2">iTunes</a> | <a href="http://subscribeonandroid.com/feeds.feedburner.com/OfficeHoursPodcast">Android</a> | <a href="http://feeds.feedburner.com/OfficeHoursPodcast">RSS</a>
</div>

That’s what I have working now. I’m going to see if I can find the work I did on creating a custom feed, so I can share that as well.


#9

I found this RSS for the section:

<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
     xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}</title>
    <atom:link href="{{ .Site.BaseURL }}/index.xml" rel="self" type="application/rss+xml" />
    <link>{{ .Site.BaseURL }}</link>
    <description>Recent content {{ with .Title }}in {{.}} {{ end }}on {{ .Site.Title }}</description>
    <lastBuildDate>{{ .Date }}</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <language>en</language>
    <copyright>{{ with .Site.Copyright }}{{.}}{{ end }}</copyright>
    <itunes:new-feed-url>{{ .Site.BaseURL }}/podcasts/index.xml</itunes:new-feed-url>
    <itunes:subtitle>"This is a subtitle"</itunes:subtitle>
    <itunes:summary>"This is the podcast description"</itunes:summary>
    <itunes:category text="Technology">
        <itunes:category text="Tech News" />
    </itunes:category>
    <itunes:author>"Office Hours Hangout"</itunes:author>
    <itunes:owner>
        <itunes:name>"Alan Bush"</itunes:name>
        <itunes:email>"email@email.com"</itunes:email>
    </itunes:owner>
    <itunes:block>no</itunes:block>
    <itunes:explicit>no</itunes:explicit>
    <itunes:image href="http://drops.albush.com/RAX%20CLOUD%20OFFICE%20HANGOUT%20FINAL.png" />
    {{ range first 15 .Data.Pages }}
    <item>
        <link>{{ .Permalink }}</link>
        <title>{{ .Title }}</title>
        <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
        <description>{{ .Params.summary }}</description>

        <enclosure url="{{ .Params.mp3 }}" length="{{ .Params.length }}" type="audio/mpeg" />
        <guid>{{ .Params.mp3 }}</guid>

        <itunes:author>{{ .Params.author }}</itunes:author>
        <itunes:summary>{{ .Content | html }}</itunes:summary>
        <itunes:image href="http://drops.albush.com/RAX%20CLOUD%20OFFICE%20HANGOUT%20FINAL.png" />
        <itunes:duration>{{ .Params.duration }}</itunes:duration>
        <itunes:keywords>{{ .Params.tags }}</itunes:keywords>
    </item>
    {{ end }}
  </channel>
</rss>

It’s not perfect, it’s a mix of hard coded and templated content, but it worked. I still have the RSS from that section cached: http://albush.com/podcast/index.xml, and it checks out as valid RSS: http://castfeedvalidator.com/?url=http://albush.com/podcast/index.xml.

Hope that helps, and I definitely want to check out your podcast, especially if it’s up and running on Hugo.


#10

This is fantastically helpful. I might end up offloading our feed to Blubrry anyway, but I still want to have it generated this way for portability. The one I have working now is totally working with the episode frontmatter, although the bytes and time fields aren’t generated, so your shortcodes will help me with that!

Edit: Oh, I see now your code doesn’t calculate those values, which was the tricky part (mostly for the legacy porting; adding those values for a new episode is no big deal, but going back through the old one is a bit of a pain, but some curl/sed/awk-fu will probably help me out there


#11

Happy to be helpful. awk/sed was very helpful for me, as well.
Let us know how it turns out.


#12

I think I got the feed nailed. There’s still a bit that is a tiny bit more complicated to do in the frontmatter (but it’s because of me insisting all episode numbers be xxx.mp3, instead of x.mp3, so it’s 001.mp3 and not 1.mp3, so at the moment I have the URL to the mp3 fully pathed in the frontmatter, whereas it could be dynamically generated based on episode number (which is a frontmatter param I use for something else) but it’s not the end of the world.

Here’s the code for the RSS feed:

<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
  <channel>
    <title>Arrested DevOps</title>
    <link>{{ .Permalink }}</link>
    <language>en-us</language>
    <copyright>Copyright 2015 Arrested DevOps</copyright>
    <itunes:subtitle>There's always DevOps in the Banana Stand</itunes:subtitle>
    <itunes:author>Matt Stratton, Trevor Hess, and Bridget Kromhout</itunes:author>
    <itunes:summary>Arrested DevOps is a high-level, bi-weekly panel discussion of DevOps concepts. We give our listeners a tantalizing taste of the basic technologies and ideas of DevOps to entice them to try more.</itunes:summary>
    <description>Arrested DevOps is a high-level, bi-weekly panel discussion of DevOps concepts. We give our listeners a tantalizing taste of the basic technologies and ideas of DevOps to entice them to try more.</description>
    <itunes:owner>
    <itunes:name>Matt Stratton</itunes:name>
    <itunes:email>matt.stratton@gmail.com</itunes:email>
    </itunes:owner>
    <itunes:image href="http://arresteddevops.com/app/uploads/powerpress/ado-podcast-logo.png" />
    <itunes:category text="Technology">
      <itunes:category text="Software How-To" />
      <itunes:category text="Tech News" />
    </itunes:category>
    {{ range first 50 .Data.Pages }}
    {{ if eq .Type "episode"}}
    <item>
      <title>{{ title .Title }} - ADO{{ .Params.episode }}</title>
      <itunes:author>Matt Stratton, Trevor Hess, and Bridget Kromhout</itunes:author>
      <itunes:summary><![CDATA[{{ .Description }}]></itunes:summary>
      <enclosure url="{{ .Params.podcast }}" length="{{ .Params.podcast_bytes}}" type="audio/x-m4a" />
      <guid>{{ .Params.podcast }}</guid>
      <link>{{ .Permalink }}</link>
      <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} </pubDate>
      <itunes:duration>{{ .Params.podcast_duration}}</itunes:duration>
    </item>
    {{ end }}
    {{ end }}
  </channel>
</rss>

And an example episode frontmatter:

+++
Description = "What does \"infrastructure as code\" actually mean? How is it different from configuration management? Special guests Joshua Timberman (Chef), Eric Sorenson (Puppet Labs), and Robyn Bergeron (Ansible) talk with Matt and Trevor about this very topic."
aliases = []
author = "Matt"
categories = []
date = "2015-10-01T08:31:42-05:00"
episode = "44"
friendly = "infrastructure-as-code"
guests = ["jtimberman", "esorenson", "rbergeron"]
images = ["http://arresteddevops.github.io/img/social/fb/infrastructure-as-code.png", ""]
news_keywords = []
podcast = "https://media.blubrry.com/arresteddevops/content.blubrry.com/arresteddevops/arrested-devops-podcast-episode044.mp3"
podcast_bytes = "52282002"
podcast_duration = "1:02:14"
sponsors = ["victorops", "datadog"]
tags = []
title = "infrastructure as code with Joshua Timberman, Eric Sorenson, and Robyn Bergeron"
youtube = "7voRnzzUZb4"

+++

(Sidenote - I can see that I need to fix the image referenced in the rss feed, as that is on the current (non-hugo) site, and the link will break, and also that in my example frontmatter I forgot to add the episode number alias. So good thing I posted this!)


#13

OK, so we just went officially “live” with our Hugo-powered site. It’s online at http://www.arresteddevops.com.

This is definitely MVP and we have a lot more work to do. And of course some folks may still get shunted to the WordPress version due to DNS caching (although I had the TTL dialed down to 2 minutes for a while before the cutover, so I think we are good).

The feed actually comes from Podtrack, but it ingests it from the RSS I built on the site, so it should work as expected!


#14

Ho do you get your CDATA tags parsed properly? In my template file I have:

<itunes:summary><![CDATA[{{ .Description }}]]></itunes:summary>

However, hugo parses that into:

<itunes:summary>&lt;![CDATA[Blah Blah description]]></itunes:summary>

Since the initial < is translated into an html entity, my podcast reader chokes on it.


#15

Have you sovled this @jmcclelland?
There’s a solution posted which says chomp gets this to work


but it doesn’t work for me using Hugo 0.40.2. I still get the < escaped to <


#16

I haven’t read the whole thread, but reading about apparent parsing issues with CDATA, I use those in my ATOM feed template without any issue… it looks like this:

{{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
...
{{ printf `<content type="html"><![CDATA[%s]]></content>` .Content | safeHTML }}

ref


#17

that has solved it. Thanks, @kaushalmodi!


#18

For me too:

{{ `<content:encoded><![CDATA[` | safeHTML }}{{ .Content | safeHTML }} ]]></content:encoded>
      {{ `<itunes:summary><![CDATA[` | safeHTML}}{{ htmlUnescape .Content | safeHTML }}]]></itunes:summary>