Build a PodCast on Amazon S3 with a Customtag

written by Geoff Bowers on Wednesday, 22 July, 2009 @ 08:17 PM

Decided to move the webdu conference session podcast to Amazon S3 to improve download performance. Thought it would be cool to put together a custom tag that dynamically builds an iTunes feed from the S3 bucket list XML.

s3itunes.cfm is pretty much complete and should work with any S3 bucket. Take a look at at a sample (webdu2009) of the S3 XML format. Make sure that all the files you want included are secured properly, ie. its read only for world. You may need to make a few changes; for example, the categories are hard coded. I whipped up a bunch of attributes to try and make it as flexible as possible.

Call the custom tag like so:

<cf_s3itunes 
	s3bucket="http://s3.amazonaws.com/webdu2009/"
	title="WebDU 2009"
	subtitle="The antipodean web developer conference."
	author="The Daemonites"
	summary="The premier Antipodean web technology conference..."
	link="http://www.webdu.com.au/"
	copyright="&##x2117; &amp; &##xA9; 2009 Daemon Pty Ltd" 
	itunesname="webDU Conference"
	itunesemail="[email protected]"
	imageurl="http://s3.amazonaws.com/webdu2009/podcast144_400.jpg"
	itunesimage="http://s3.amazonaws.com/webdu2009/podcast600_600.jpg"
	/>

<cfoutput>#feed#</cfoutput>

And this is the underlying custom tag itself (save it as s3itunes.cfm):

<cfsetting enablecfoutputonly="true" /> 
<!--- @@Copyright: Copyright (c) 2009 Daemon Pty Limited. All rights reserved. --->
<!--- @@description: s3itunes; searches for .m4a (audio/mp4) files in a designated S3 bucket and generates an iTunes feed. --->
<!--- @@author: Geoffrey Bowers on 2009-07-14 --->

<!--- exit tag if its been closed, ie don't run twice --->
<cfif thistag.executionmode eq "end">
	<cfexit method="exittag" />
</cfif>

<!--- required attributes --->
<cfparam name="attributes.s3bucket" type="string" />

<!--- optional attributes --->
<cfparam name="attributes.r_feed" default="feed" type="string" />

<cfparam name="attributes.title" default="" type="string" />
<cfparam name="attributes.link" default="" type="string" />
<cfparam name="attributes.subtitle" default="" type="string" />
<cfparam name="attributes.author" default="" type="string" />
<cfparam name="attributes.summary" default="" type="string" />
<cfparam name="attributes.description" default="#attributes.summary#" type="string" />
<cfparam name="attributes.copyright" default="" type="string" />

<cfparam name="attributes.imageurl" default="" type="string" />
<cfparam name="attributes.imagetitle" default="" type="string" />
<cfparam name="attributes.imagelink" default="#attributes.link#" type="string" />

<cfparam name="attributes.itunesname" default="" type="string" />
<cfparam name="attributes.itunesemail" default="[email protected]" type="email" />
<cfparam name="attributes.itunesimage" default="#attributes.imageurl#" type="string" />
<cfparam name="attributes.itunesexplicit" default="false" type="boolean" />

<!--- get the bucket result list --->
<cfhttp url="#attributes.s3bucket#" />

<!--- if somethings wrong, dump response headers --->
<cfif cfhttp.responseheader.status_code neq 200>
	<cfdump var="#cfhttp#" />
	<cfexit method="exittag" />
</cfif>	

<!--- parse s3 bucket xml --->
<cfset xmlS3bucket = xmlParse(cfhttp.filecontent) />
<cfset s3bucketname = xmlS3bucket.listbucketresult.name.xmlText />

<!--- fill in the blanks, as needed --->
<cfif NOT len(attributes.title)>
	<cfset attributes.title = s3BucketName />
</cfif>
<cfif NOT len(attributes.imagetitle)>
	<cfset attributes.imagetitle = attributes.title />
</cfif>

<!--- isolate contents elements (can't use xpath //Contents as elements mixed) --->
<cfset aContents = arrayNew(1) />
<cfloop array="#xmlS3Bucket.ListBucketResult.xmlChildren#" index="element">
	<cfif element.xmlName eq "Contents">
		<cfset arrayAppend(aContents, element) />
	</cfif>
</cfloop>

<!--- build itunes feed output --->
<cfset feedOut = "" />

<!--- build feed head --->
<cfsavecontent variable="feedHead">
<cfoutput><?xml version="1.0" encoding="UTF-8"?>
<!-- http://www.apple.com/itunes/store/podcaststechspecs.html -->
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
<channel>
	<title>#attributes.title#</title>
	<link>#attributes.link#</link>
	<language>en-us</language>
	<copyright>#attributes.copyright#</copyright>
	<itunes:subtitle>#attributes.subtitle#</itunes:subtitle>
	<itunes:author>#attributes.author#</itunes:author>
	<itunes:summary>#attributes.summary#</itunes:summary>
	<description>#attributes.description#</description>

	<itunes:owner>
		<itunes:name>#attributes.itunesname#</itunes:name>
		<itunes:email>#attributes.itunesemail#</itunes:email>
	</itunes:owner>
	
	<cfif len(attributes.imageurl)>
	<image>
		<url>#attributes.imageurl#</url>
		<title>#attributes.imagetitle#</title><!-- should match channel title -->
		<link>#attributes.imagelink#</link>
	</image>
	<itunes:image href="#attributes.itunesimage#" />
	</cfif>

	<itunes:category text="Technology">
		<itunes:category text="Gadgets"/>
		<itunes:category text="Tech News"/>
		<itunes:category text="Software How-To"/>
	</itunes:category>
	
	<itunes:explicit>#attributes.itunesexplicit#</itunes:explicit>
	<!-- //////////////////////////////////////////////////////////////////// -->
</cfoutput>
</cfsavecontent>

<!--- build feed body --->
<cfsavecontent variable="feedBody">
<cfloop array="#aContents#" index="Contents">
	<!--- only match .m4a files --->
	<cfif listlast(Contents.key.xmlText, ".") eq "m4a">
		<!--- clean up title --->
		<cfset itemtitle = replacenocase(listfirst(contents.key.xmltext, "."), "_", " ", "all") />
		<cfoutput>
			<item>
				<title>#xmlFormat(itemtitle)#</title>
				<pubDate>#Contents.lastModified.xmlText#</pubDate>
				<enclosure url='#attributes.s3Bucket##Contents.key.xmlText#' length='#Contents.size.xmltext#' type='audio/mp4' />
				<itunes:duration></itunes:duration>
				<itunes:author></itunes:author>
				<guid>#attributes.s3Bucket##Contents.key.xmlText#</guid>
			</item>
		</cfoutput>
	</cfif>
	</cfloop>
</cfsavecontent>

<!--- build feed foot --->
<cfsavecontent variable="feedFoot">
<cfoutput>
	<!-- /////////////////////////////////////////////// -->
</channel>
</rss>
</cfoutput>
</cfsavecontent>


<!--- composite feedOut --->
<cfset feedOut = feedHead & feedBody & feedFoot />

<!--- set the feed output into the caller scope --->
<cfset setVariable("caller.#attributes.r_feed#", feedOut) />


<!--- debugging 
<cfdump var="#s3bucketname#" label="s3bucketname" expand="false" />
<cfdump var="#aContents#" expand="false" />
<cfdump var="#xmlS3bucket#" expand="false" />
<cfdump var="#cfhttp#" expand="false" />
--->

<cfsetting enablecfoutputonly="false" />

Enjoy!

Comments

Permalink: http://blog.daemon.com.au/blog-post/build-a-podcast-on-amazon-s3-with-a-customtag

Options:

Size

Colors