Showing posts with label podcasting. Show all posts
Showing posts with label podcasting. Show all posts

Friday, January 20, 2006

Changing FeedCreator.class.php for Podcasting

As I mentioned in my Podcasting HowTo, I used Kai Blankenhorn's FeedCreator.class.php as the basis of my podcasting feed. In this posting I will detail my additions to his code to support podcasting and iTunes.

You may download the final file here

First, at line 31, I updated the change log:


v1.7.2-iTunes
added support for iTunes tags
v1.7.2-podcast
added support for podcast (added enclosure)
made the Item category be an Array as you are allowed to specify
multiple category elements in the RSS spec

Then, at what becomes line 173 (after the changelog changes), I added
these classes:

/**
* Version string.
**/
define("FEEDCREATOR_VERSION", "FeedCreator 1.7.2-iTunes");
/**
* An Enclosure is a part of an Item
*
* @author Steven Pothoven <steven@pothoven.net>
* @since 1.7.2-podcast
*/
class Enclosure {
/**
* Attributes of an enclosure
*/
var $url, $length, $type = "audio/mpeg";
}
/**
* iTunes extensions to RSS 2.0
*
* @author Steven Pothoven <steven@pothoven.net>
* @since 1.7.2-iTunes
*/
class iTunes {
/**
* This tag can only be populated using iTunes specific categories.
*/
var $category, $subcategory;
/**
* This tag should be used to note whether or not your Podcast contains explicit material.
* There are 2 possible values for this tag: Yes or No
*/
var $explicit;
/*
* At the Channel level, this tag is a short description that provides general information about the Podcast. It will appear next to your Podcast as users browse through listings of Podcasts
* At the Item level, this tag is a short description that provides specific information for each episode.
* Limited to 255 characters or less, plain text, no HTML
*/
var $subtitle;
/*
* At the Channel level, this tag is a long description that will appear next to your Podcast cover art when a user selects your Podcast.
* At the Item level, this tag is a long description that will be displayed in an expanded window when users click on an episode.
* Limited to 4000 characters or less, plain text, no HTML
*/
var $summary;
/*
* At the Channel level this tag contains the name of the person or company that is most widely attributed to publishing the Podcast and will be displayed immediately underneath the title of the Podcast.
* If applicable, at the item level, this tag can contain information about the person(s) featured on a specific episode.
*/
var $author;
/*
* This tag is for informational purposes only and will allow users to know the duration prior to download
* The tag is formatted: HH:MM:SS
*/
var $duration;
/*
* This tag allows users to search on text keywords
* Limited to 255 characters or less, plain text, no HTML, words must be separated by spaces
*/
var $keywords;
/*
* This tag contains the e-mail address that will be used to contact the owner of the Podcast for communication specifically about their Podcast on iTunes.
* Required element specifying the email address of the owner.
*/
var $owner_email;
/*
* Optional element specifying the name of the owner.
*/
var $owner_name;
/*
* This tag specifies the artwork for the Channel and Item(s). This artwork can be larger than the maximum allowed by RSS.
* Preferred size: 300 x 300 at 72 dpi
* Minimum size: 170 pixels x 170 pixels square at 72 dpi
* Format: JPG, PNG, uncompressed
*/
var $image;
/*
* This tag is used to block a podcast or an episode within a podcast from being posted to iTunes. Only use this tag when you want a podcast or an episode to appear within the iTunes podcast directory.
*/
var $block;
}


In the FeedItem class I added these lines at the new line 283:

/**
* Support for attachments
*/
var $enclosure;
/**
* Support for iTunes
*/
var $itunes;


In the _setFormat function, I added a new case at the new line 491:

case "PODCAST":
$this->_feed = new PodcastCreator();
break;


In the createFeed function, I added the iTunes extentions at line
1102:

/* iTunes add iTunes specific tags */
if ($this->itunes!="") {
if ($this->itunes->category!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->itunes->category)."\">\n";
if ($this->itunes->subcategory!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->itunes->subcategory)."\"/>\n";
}
$feed.= " </itunes:category>\n";
}
if ($this->itunes->explicit!="") {
$feed.= " <itunes:explicit>".$this->itunes->explicit."</itunes:explicit>\n";
}
if ($this->itunes->subtitle!="") {
$feed.= " <itunes:subtitle>".htmlspecialchars($this->itunes->subtitle)."</itunes:subtitle>\n";
}
if ($this->itunes->summary!="") {
$feed.= " <itunes:summary>".htmlspecialchars($this->itunes->summary)."</itunes:summary>\n";
}
if ($this->itunes->author!="") {
$feed.= " <itunes:author>".htmlspecialchars($this->itunes->author)."</itunes:author>\n";
}
if ($this->itunes->keywords!="") {
$feed.= " <itunes:keywords>".htmlspecialchars($this->itunes->keywords)."</itunes:keywords>\n";
}
if ($this->itunes->owner_email!="") {
$feed.= " <itunes:owner>\n";
$feed.= " <itunes:email>".$this->itunes->owner_email."</itunes:email>\n";
if ($this->itunes->owner_name!="") {
$feed.= " <itunes:name>".$this->itunes->owner_name."</itunes:name>\n";
}
$feed.= " </itunes:owner>\n";
}
if ($this->itunes->image!="") {
$feed.= " <itunes:link rel=\"image\" type=\"image/jpeg\" href=\"".$this->itunes->image."\">[image]</itunes:link>\n";
}


and additional iTunes and podcast extensions at line 1154:

/* podcasts add the enclosure element */
if ($this->items[$i]->enclosure!="") {
$feed.= " <enclosure url=\"".str_replace(" ", "%20", htmlspecialchars($this->items[$i]->enclosure->url)). "\" length=\"".htmlspecialchars($this->items[$i]->enclosure->length). "\" type=\"".htmlspecialchars($this->items[$i]->enclosure->type). "\"/>\n";
}
/* iTunes add iTunes specific tags */
if ($this->items[$i]->itunes!="") {
if ($this->items[$i]->itunes->category!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->items[$i]->itunes->category)."\">\n";
if ($this->items[$i]->itunes->subcategory!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->items[$i]->itunes->subcategory)."\"/>\n";
}
$feed.= " </itunes:category>\n";
}
if ($this->items[$i]->itunes->explicit!="") {
$feed.= " <itunes:explicit>".$this->items[$i]->itunes->explicit."</itunes:explicit>\n";
}
if ($this->items[$i]->itunes->subtitle!="") {
$feed.= " <itunes:subtitle>".htmlspecialchars($this->items[$i]->itunes->subtitle)."</itunes:subtitle>\n";
}
if ($this->items[$i]->itunes->summary!="") {
$feed.= " <itunes:summary>".htmlspecialchars($this->items[$i]->itunes->summary)."</itunes:summary>\n";
}
if ($this->items[$i]->itunes->author!="") {
$feed.= " <itunes:author>".htmlspecialchars($this->items[$i]->itunes->author)."</itunes:author>\n";
}
if ($this->items[$i]->itunes->keywords!="") {
$feed.= " <itunes:keywords>".htmlspecialchars($this->items[$i]->itunes->keywords)."</itunes:keywords>\n";
}
if ($this->items[$i]->itunes->duration!="") {
$feed.= " <itunes:duration>".$this->items[$i]->itunes->duration."</itunes:duration>\n";
}
if ($this->items[$i]->itunes->image!="") {
$feed.= " <itunes:link rel=\"image\" type=\"image/jpeg\" href=\"".$this->items[$i]->itunes->image."\">[image]</itunes:link>\n";
}


Finally, I added the PodcastCreator class at line 1235:

/**
* PodcastCreator is a FeedCreator that implements Podcast
*
* @see http://backend.userland.com/rss
* @since 1.7.2-podcast
* @author Steven Pothoven <steven@pothoven.net>
*/
class PodcastCreator extends RSSCreator20 {
function PodcastCreator() {
parent::_setRSSVersion("2.0");
parent::_setXMLNS("itunes=\"http://www.itunes.com/DTDs/Podcast-1.0.dtd\"");
}
}

Podcasting HowTo

As I mentioned in my last post, I added podcasting to our church web site. Today I thought I'd share how I did that for those interested in doing something similar.

To start with, I place all the sermon MP3 files into a separate directory. The MP3 files themselves adhere to the naming standard of:

SermonDate_Speaker_Passage.mp3

Where the date is in YYMMDD format. So, this past weeks sermons file is named:

060115_John Keen_Mark 1;1-8.mp3

This makes it very easy add sermons to the list by just uploading the file. Note: the passage uses a ';' to delineate chapter and verse instead of a ':', this is because a ':' is not allowed in a filename on some systems (Windows). The code will correct that when displayed.

I started with a simple table to be shown on the web page. Shortly after that I thought an RSS feed of the contents would be handy so people could be motified of new sermons without visiting the web page. The RSS feed lead to the podcast feed, as they are nearly the same thing.

They all essentially do the same thing, so I'll skip right to the podcast version. However, I'll share one little tidbit from the web page version. From the table on the web page, you can either stream the MP3, or download the file. Streaming an MP3 file from your server doesn't take any special software. I wrote a simple PHP script which I name m3u.php which allows me to stream any MP3 file. The contents of the m3u.php looks like:


<?php
header("Content-Type: audio/x-mpegurl");
print 'http://www.hostname.com/path/to/mp3/' . $_GET['filename'];
?>

So, in my table code, invoke it as (in PHP):

<a href="sermons/m3u.php?filename=' . $filename . '">

To make the podcast feed, I started with Kai Blankenhorn's FeedCreator.class.php. It worked very well for the RSS feed, but it doesn't support the podcast extensions. So, I updated it to add the podcast and iTunes extensions to RSS. If you're interested, I'll be happy to provide my updated version. I'll summarize the changes in a separate post so I don't make this post too long.

Now, I'll walk through my podcast generating PHP class. First, I need a function to list all the files in my mp3 directory:

// Function list_dir will list all the files in a given directory
//
function list_dir($dirname)
{
static $result_array = array();
$handle=opendir($dirname);
while ($file=readdir($handle)) {
// ignore the current and parent directory references ('.' and '..')
if (0 == strpos($file, '.')) {
continue;
}
$result_array[]=$file;
}
closedir ($handle);
return $result_array;
}

I then set up the common header portion of the podcast feed:

$rss = new UniversalFeedCreator();
// if cache is less than 12 hours (43200 seconds) old, use it
// this is because the feed only changes once a week to begin with, and this
// script does a lot of ID3 tag manipulation which can take a while
$rss->useCached("PODCAST", "sermons/podcast.xml", 43200);
$rss->title = "Seminole PCA Sermons";
$rss->description = "Recent sermons from the pulpit of Seminole Presbyterian Church";
$rss->language = "en-us";
$rss->link = "http://www.seminolepca.org";
$rss->webmaster = "webadmin@seminolepca.org";
$rss->docs = "http://blogs.law.harvard.edu/tech/rss";
$rss->syndicationURL = "http://www.seminolepca.org/".$PHP_SELF;
$rss->category = "Religion";

$image = new FeedImage();
$image->title = "Seminole PCA, Tampa, FL";
$image->url = "http://www.seminolepca.org/images/rss-SPC.jpg";
$image->link = "http://www.seminolepca.org";
$image->description = "Feed provided by seminolepca.org. Click to visit.";
$image->width = 144;
$image->height = 204;
$rss->image = $image;

$itunes = new iTunes();
$itunes->category = "Religion & Spirituality";
$itunes->subcategory = "Christianity";
$itunes->subtitle = $rss->description;
$itunes->owner_email = "church@seminolepca.org";
$itunes->owner_name = "Seminole Presbyterian Church";
$itunes->author = "Seminole Presbyterian Church";
$itunes->image = "http://www.seminolepca.org/images/podcast-SPC.jpg";
$rss->itunes = $itunes;

Now, I loop through my list of mp3 files to build the contents of the podcast feed:

// PHP code to dynamically generate RSS feed
//
// Start of code -------
$mp3dir="sermons/mp3";
// get a list of the "mp3" directory
$array=list_dir($mp3dir);
// reverse sort the array (puts newest sermon first)
rsort ($array);
reset ($array);

// for each file in the filelist, generate the appropriate HTML table code
foreach ($array as $filename) {
// break apart the filename into the 3 data fields; date, speaker,
// and subject using file naming convention:
// YYMMDD_Speaker_Passage.mp3
//
$tok = strtok($filename, "_");
$unixdate = mktime(0, 0, 0, substr($tok, 2, 2), substr($tok, 4, 2), substr($tok, 0, 2));
$sermondate=date("F d, Y", $unixdate);

$tok=strtok("_");
$speaker=$tok;
$tok=strtok(".");
$passage=$tok;
$chapter = strtok($passage, ";");
$verse = strtok(".");
if ($verse !== false) {
$passage = $chapter . ':' . $verse;
} else {
$passage = $chapter;
}

at this point in my PHP code, I also use the getid3 PHP library to ensure that all the ID3 tags are set correctly in the mp3 file. This is not important for actually generating the podcast feed, so I'll omit it here.

$item = new FeedItem();
$item->title = $speaker . ' on ' . $passage;
$item->link = "http://www.seminolepca.org/" . $mp3dir . "/" . $filename;
$item->description = $sermondate . ' - ' . $speaker . ' expounds on ' . $passage;
$item->date = $unixdate;
$item->source = "http://www.seminolepca.org";
$item->author = $speaker;
$item->enclosure = new Enclosure();
$item->enclosure->url = "http://www.seminolepca.org/" . $mp3dir . "/" . $filename;
$item->enclosure->length= $id3['filesize'];
$item->category[] = $itunes->category;

$item->itunes = new iTunes();
$item->itunes->category = $itunes->category;
$item->itunes->subcategory = $itunes->subcategory;
$item->itunes->keywords = "Seminole Presbyterian Church PCA Reformed Christian Sermon Tampa Florida FL";
$item->itunes->summary = $item->description;
$item->itunes->author = $item->author;
$item->itunes->duration = $id3['playtime_string'];
$item->itunes->image = "http://www.seminolepca.org/images/podcast-SPC.jpg";
$rss->addItem( $item);
}

Finally, I save feed for caching purposes and return it:

$rss->saveFeed("PODCAST", "sermons/podcast.xml");

The resulting podcast XML can be seen here.

Once that's working, go register you feed at the various podcast directories such as:
iPassage (if you podcasting sermons)
iPodder
Podcast.net
Podcast Alley
iTunes (HowTo)

I published my feed in iPodder, Podcast.net, and Podcast Alley and was automatically propagated to other directories including the iTunes Store.

For reference, here is the official Apple iTunes/Podcasting specification.