Shoutout
I was inspired by the incomparable Topfunky's podcast application:
http://svn.topfunky.com/podcast/
I've added code to manage the iTunes metadata that extends the original RSS standard.
Ingredients
You need to use the RSS library that comes with that standard Ruby library, but if you want to add iTunes metadata, you'll need to update the standard library like so:
cd /usr/local/src/Then you need to require the right modules in a Rails initializer file. Mine is config/initializers/require.rb:
wget http://www.cozmixng.org/~kou/download/rss-0.1.9.tar.gz
tar zxvf rss-0.1.9.tar.gz
cd rss-0.1.9
ruby setup.rb
sudo ruby setup.rb
require 'rss/2.0'Model
require 'rss/itunes'
Like Topfunky, I found it easier to include the bulk of the function in the model. You could also build this up using a Builder template, which might be easier to test, since you can use assert_xml_select to test it. But my philosophy was generate it in the model, test it as best I could with a unit test, then test the output completely in my controller test.
def self.rss
author = "Overall Podcast Author/Artist"
rss = RSS::Rss.new("2.0")
channel = RSS::Rss::Channel.new
category = RSS::ITunesChannelModel::ITunesCategory.new("Arts")
category.itunes_categories << \
RSS::ITunesChannelModel::ITunesCategory.new("Literature")
channel.itunes_categories << category
channel.title = "Podcast Title"
channel.description = "Podcast description, can be a paragraph"
channel.link = "http://www.example.com/"
channel.language = "en-us"
channel.copyright = "Copyright #{Date.today.year} I Own This"
channel.lastBuildDate = Audio.last_modified.updated_at
# the above uses a method I built on the Audio model that finds
# the last modified file and makes that the build date for the
# whole podcast channel
# below is your "album art"
channel.image = RSS::Rss::Channel::Image.new
channel.image.url = "http://www.example.com/images/app_rss_logo.jpg"
channel.image.title = "Same as podcast title"
channel.image.link = "Should be same as link for whole channel"
channel.itunes_author = author
channel.itunes_owner = RSS::ITunesChannelModel::ITunesOwner.new
channel.itunes_owner.itunes_name=author
channel.itunes_owner.itunes_email='info@example.com'
channel.itunes_keywords = %w(Common Misspellings of Key Words)
channel.itunes_subtitle = "This appears in the description column of iTunes"
channel.itunes_summary = "This appears when you click the 'circle I' button in iTunes"
# below is what iTunes uses for your "album art", different from RSS standard
channel.itunes_image = RSS::ITunesChannelModel::ITunesImage.new("/path/to/logo.png")
channel.itunes_explicit = "No"
# above could also be "Yes" or "Clean"
Audio.find(:all).each do |audio|
item = RSS::Rss::Channel::Item.new
item.title = audio.title
link = "http://www.example.com/#{audio.public_filename}"
item.link = link
item.itunes_keywords = %w(Keywords For This Particular Audio Clip)
item.guid = RSS::Rss::Channel::Item::Guid.new
item.guid.content = link
item.guid.isPermaLink = true
item.pubDate = audio.updated_at
description = "Long description of this particular audio file, appears in circle I section of
iTunes"
item.description = description
item.itunes_summary = description
item.itunes_subtitle = audio.nice_title
item.itunes_explicit = "No"
item.itunes_author = author
# TODO can add duration once we can compute that somehow
item.enclosure = \
RSS::Rss::Channel::Item::Enclosure.new(item.link, audio.size, 'audio/mpeg')
channel.items << item
end
rss.channel = channel
return rss.to_s
end
Easy, right? What you're doing is building up an RSS feed that complies with the RSS 2.0 standard and with Apple's iTunes extension to the base standard. The category code is a little tricky and there may be a better way to do it. I found the RSS library docs a bit hard to understand.
Controller
All of the heavy lifting is done for us, so the controller is easy.
class AudioController < ApplicationController
def index
respond_to do |format|
format.html { @audio = Audio.find(:all) }
format.xml { render :xml => Audio.rss }
end
end
end
Route
Because of my inflection rules (where audio is singular and plural) I can't use the baked-in REST route to handle this situation, so for this to work I needed to manually wire the following route:
map.formatted_audio 'audio.:format', :controller => 'audio', :action => 'index'
Unit Test
I haven't written enough tests for this, but here's a start so I at least know the right number of audio files are in there. In audio_test.rb:
def test_should_generate_audio_rss
assert_equal Audio.count, Audio.rss.scan(/- /).size
end
Functional Test
As mentioned earlier, testing the method here gives you the added benefit of assert_select, but you need to apply Jamis Buck's wizardry to get it to work with XML files by creating an assert_xml_select method.
I'm testing almost the exact same thing as the unit test, but at least I know the file gets served up the way I expect it, and now I'm also testing whether the items have been nested properly within the channel.
def test_should_get_audio_rss
get :index, :format => 'xml'
assert_response :success
assert_xml_select 'channel item', :count => Audio.count
end
I want to improve this by incorporating XML validation into my tests but haven't figured that out yet. For now, I'm validating the feed itself.
Feed Validation
The last step before submitting to iTunes is to make sure this feed works and is valid XML. I used FeedValidator.
iTunes Submission
Pretty straightforward. Just follow Apple's directions. Getting the category right was a little tough, and there's also an issue with iTunes not recognizing the feed image properly. Doesn't look like they've fixed it yet.
Submitting Elsewhere
I added the following to the head section of my app's layout to help other aggregators find the feed:
%link{:rel=>"alternate", :type=>"application/rss+xml", :title=>"Stoop Storytelling Podcast", :href => formatted_audio_path(:format => 'xml', :only_path=>false)}
This reference was helpful in constructing the above tag.
Advertising the Link
Finally, I posted links to the iTunes store on the site itself. If people have iTunes or another podcast client installed, clicking on the store link will cause them to subscribe and help boost the rating of the podcast (thus, you don't want to give people the link to the audio.xml file itself so direct subscriptions won't get counted by Apple).
Final Product
You can listen to the podcasts here:
http://www.itunes.com/podcast?id=262444919
References
Beyond the references already cited, I relied heavily on the RSS library tutorial and reference.
Future
Is there interest in a plugin to help streamline this process further?
7 comments:
Dude, you're a life saver with the download link!
thanks! That was probably the hardest part, realizing that there was a newer version of the standard libraries and then finding it.
You can use RSS Maker:
require 'rss'
author = "Overall Podcast Author/Artist"
rss = RSS::Maker.make("2.0") do |maker|
channel = maker.channel
channel.title = "Podcast Title"
channel.description = "Podcast description, can be a paragraph"
channel.link = "http://www.example.com/"
channel.language = "en-us"
channel.copyright = "Copyright #{Date.today.year} I Own This"
channel.lastBuildDate = Audio.last_modified.updated_at
image = maker.image
image.url = "http://www.example.com/images/app_rss_logo.jpg"
image.title = "Same as podcast title"
channel.itunes_author = author
channel.itunes_owner.itunes_name = author
channel.itunes_owner.itunes_email='info@example.com'
channel.itunes_keywords = %w(Common Misspellings of Key Words)
channel.itunes_subtitle = "This appears in the description column of iTunes"
channel.itunes_summary = "This appears when you click the 'circle I' button in iTunes"
# below is what iTunes uses for your "album art", different from RSS standard
channel.itunes_image = "/path/to/logo.png"
channel.itunes_explicit = "No"
category = channel.itunes_categories.new_itunes_category
category.text = "Arts"
category.new_itunes_category.text = "Literature"
Audio.find(:all).each do |audio|
maker.items.new_item do |item|
item.title = audio.title
link = "http://www.example.com/#{audio.public_filename}"
item.link = link
item.itunes_keywords = %w(Keywords For This Particular Audio Clip)
item.guid.content = link
item.guid.isPermaLink = true
item.pubDate = audio.updated_at
description = "Long description of this particular audio file, appears in circle I section of iTunes"
item.description = description
item.itunes_summary = description
item.itunes_subtitle = audio.nice_title
item.itunes_explicit = "No"
item.itunes_author = author
# TODO can add duration once we can compute that somehow
item.enclosure.url = item.link
item.enclosure.length = audio.size
item.enclosure.type = 'audio/mpeg'
end
end
end
Sorry. new_itunes_category should be new_category.
Why do we need this when we have feedburner? It provides tools to create iTunes RSS feeds.
Nice work with this. To answer your question about the plugin, yes, that would be nice.
I would love a plugin for this.
Post a Comment