The iTunes module makes this a lot easier by creating a default view and an iTunes-specific Views style plugin http://drupal.org/project/itunes

This is a wrap-up on how to create a proper podcast feed on Drupal 5.7 (will probably work on 6.2 as well), based on a particular content type, valid for the iTunes Music Store and redirected via Feedburner.

After wondering around for a bit on Drupal.org in search of a proper solution for this I found myself browsing and browsing but not finding a proper way to create an iTunes feed that is *valid* considering Apples documentation but preserves the option to update this when Apple updates it format standards.

I am using CCK to manage a content type called Series, our editors are creating posts with a normal title, body and author, but also adding a field called mp4_value, which contains a link to the podcast file created elsewhere.

So, after playing around with Views I created a template which does the trick:

Create the View

Create a view called 'Podcast', provide Page View and use a nice url for it (mine is called tvfeed, but I have Feedburner redirecting it at /podcast, more on this later)

The view type should be Full Nodes, I am using 0 nodes per page, since I want the feed to show all the content containing a podcast download link.

At Filters, make sure the Node: Type is one of Series (in my case), and that the actual Node is published (Node: published equals Yes). Further on Sort Criteria I added the line Node: Created Time - Descending to make sure the most recent video is on top.

Create the template

We need to add to extra files to our theme directory:

page-tvfeed.tpl.php
node-tvfeed.tpl.php

Later we can get to this template by using the url /tvfeed

Plus, to have more control over the way PhpTemplate is rendering my content I added the following functions:

In template.php:

<?php
function _phptemplate_variables($hook, $vars = array()) {
	if ($hook == 'node') {
	// Additional node templates based on original path, path alias and type.
	// More specific paths have more weight. Path aliases have more weight
	// than original paths. Suggestions with node type have more weight than without. 
		$alias = $_GET['q'];
		$suggestions = array();
		$name_prefix = 'node';
		$node_type = !empty($vars['node']->type) ? '-' . $vars['node']->type : '';
		$add_path = '';
		// generating additional node template names, based on original path
		foreach (explode('/', $alias) as $path_part) {
			$add_path .= !empty($path_part) ? '-' . $path_part : '';
			$suggestions[] = $name_prefix . $add_path;
			$suggestions[] = $name_prefix . $node_type . $add_path;
		}
	// adding suggestions
		$vars['template_files'] = $suggestions;
	// using aliases?
		if (module_exists('path')) {
			$alias = drupal_get_path_alias($_GET['q']);
			if ($alias != $_GET['q']) {
				$suggestions = array();
				$add_path = '';
				// generating additional node  template names, based on alias path
				foreach (explode('/', $alias) as $path_part) {
					$add_path .= !empty($path_part) ? '-' . $path_part : '';
					$suggestions[] = $name_prefix . $add_path;
					$suggestions[] = $name_prefix . $node_type . $add_path;
				}
			}
			// adding suggestions
			$vars['template_files'] = array_merge($vars['template_files'], $suggestions);
		};
	}
	return $vars;
}
?>

As found at http://drupal.org/node/117491, thanks schnizZzla!

Here comes the tricky part. When creating a template for a particular view, the module will always wrap your templates in HTML containing the title of the view in div classes. This is usefull when styling it with custom css, but now we want nice and clean XML, so I modified a function found in views.module to go in template.php:

<?php
function theme_views_view_podcast($view, $type, $nodes) {
	// This is a template.php overwrite of the theme_views_view function in views.module, 
	// I omitted moste of the code, since I didn't have so many dependencies anyhow. 
	$num_nodes = count($nodes);
	if ($type == 'page') {
		drupal_set_title(filter_xss_admin(views_get_title($view, 'page')));
	}
	$plugins = _views_get_style_plugins();
	$view_type = ($type == 'block') ? $view->block_type : $view->page_type;
	if ($num_nodes || $plugins[$view_type]['even_empty']) {
			// Removed the standard HTML here, we want some nice XML themed by node-podcast.tpl.php
			$output .= views_theme($plugins[$view_type]['theme'], $view, $nodes, $type);
		}
	return $output;
}
?>

Note that the function title contains podcast, this corresponds with the name of the View we're using.

Basically, this is a rewrite of the function parsing your view template, except it doesn't wrap the HTML around it. Nice.

Code for page-tvfeed.tpl.php

This fellow is wrapping our nodes in proper iTunes XML, so we need to add the following lines to start with:

<?php
drupal_set_header('Content-Type: text/xml; charset=utf-8');

print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 
?>

Now we can enter the iTunes heading exactly how iTunes Music Store likes it:

<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:media="http://search.yahoo.com/mrss/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">


<channel>
	<title>Bright.tv podcast</title>
	<link>http://www.bright.tv/podcast</link>
	<generator>Bright.tv</generator>
  	<language>nl-nl</language>
  	<copyright>Techmedia BV <?php print date(Y); ?></copyright>
	<itunes:subtitle>innovative lifestyle</itunes:subtitle>
	<itunes:author>Bright.tv</itunes:author>

	<itunes:summary>Videopodcast van www.bright.tv. Bekijk de episodes fullscreen op Bright.tv!</itunes:summary>
	<description>Videopodcast van www.bright.tv. Bekijk de episodes fullscreen op Bright.tv!</description>

	<itunes:owner>
		<itunes:name>Techmedia</itunes:name>
		<itunes:email>tv@bright.tv</itunes:email>
	</itunes:owner>

	<itunes:image href="http://www.bright.tv/misc/podcast_tv.jpg" />
	<itunes:keywords>innovative,lifestyle,mobile,tech,video,tv,webvideo,bright,magazine,internet,design,architecture,living,uitpakparty,designdag,cockpit,crashcourse,report</itunes:keywords>
	
	<itunes:category text="Technology">
		<itunes:category text="Gadgets"/>
		<itunes:category text="Tech News"/>
	</itunes:category>

	<itunes:explicit>no</itunes:explicit>
	<media:rating scheme="urn:simple">nonadult</media:rating>

<?php
	print $content;
?>

</channel>
</rss>

Note the print $content line, that's where our $nodes get parsed, it's using node-tvfeed.tpl.php for every $node.

Code for node-tvfeed.tpl.php

Here's the theme for the nodes in particular. No need to set the header to XML again, this is already done by the page.tpl.php above.

<?php
	$tax = $node->taxonomy;
	$keys = array_keys($tax);
	$tid = $keys[0];
	$cat = $node->taxonomy[$tid]->name;
?>

<item>
	<title><?php print $cat .': '. $node->title; ?></title>
	<itunes:author><?php print $node->name; ?></itunes:author>
	<itunes:subtitle><?php print strip_tags($node->content['body']['#value']) ?></itunes:subtitle>
	<itunes:summary><?php print strip_tags($node->content['body']['#value']) ?></itunes:summary>
	<enclosure url="<?php print $node->field_mp4[0]['value'] ?>" length="8727310" type="video/mp4" />
	<guid><?php print $node->field_mp4[0]['value'] ?></guid>
	<pubDate><? print date('r',$node->created); ?></pubDate>
	<itunes:duration>7:04</itunes:duration>
</item>

Note the $cat variable, this is printing the category name straight from the $node array, more information on this at http://drupal.org/node/214207

You can also see that I'm retrieving the CCK value of the node at $node->field_mp4[0]['value'].

So, this should render a nice XML feed of a particular content type at /tvfeed.

Allright, let's get this through Feedburner, to gather some statistics on your users. Create a Feedburner account and add the url, you can tick the box 'Item Enclosure Downloads' at the page Feedburner PRO stats to track your podcast downloads as well. At Feedburner I added the url /tvfeed , but my visitors (and iTunes Music Store as well) are subscribing via /podcast.

To fix this, we need to add the following code to .htaccess:

# Podcast redirect
RewriteRule ^/podcast(.*)$ http://feeds.feedburner.com/BrighttvPodcast [R=301,L]
Redirect /podcast/index.php http://feeds.feedburner.com/BrighttvPodcast

(I added the rule for index.php as well, since we used to update our feed at that location as well, and I don't feel like telling our users to update their podcast subscription)

There you go!

You can see my result at http://www.bright.tv/podcast
Or at the iTunes Music Store:
http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewPodcast?id=283307696

(You can add your own podcast to the iTunes Music Store from within iTunes, when signed in with your iTunes account.)