Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Podcast (RSS) to Playlist

0.00/5 (No votes)
1 Oct 2009 1  
Convert podcast feeds into a variety of playlist formats on the fly by applying XSLT on RSS XML

Windows Media Player

Windows Media Player playing the Channel9 video podcasts as a playlist.

Introduction

This article and its code provide a simple way to take a podcast feed and convert it to a playlist file/URL that Windows Media Player, Windows Media Center, Winamp or other players can play by applying XSLT on RSS XML.

Background

I use Windows Media Player (WMP) to watch/listen to several video/audio podcasts. The problem is that it’s not the best player for podcasts and doesn't really have any built-in support for them. I've tried applications such as Juice and a few add-ins for Windows Media Center (such as TV Tonic), but they all seemed to always download the podcast files to a local directory and usually needed to be running in the background to keep the directories in sync with the latest episodes.

I wanted something to where I could just click on a link or plug in a URL in WMP and have it automatically start streaming the latest podcast episodes without waiting for anything to download. I also didn't want to ever have to clean up any directories by removing older files or install any other applications or plug-ins. I wanted to be able to click the next/previous buttons on my keyboard (or remote) and be able to skip to the next or previous episodes in the podcast, just like a playlist works.

The key here is that I wanted a RSS podcast feed, to behave just like a playlist in WMP. The problem is that WMP doesn't understand RSS XML, but it does understand the ASX (Advanced Stream Redirector) playlist XML (among other playlist formats). What I decided to do is create a C# script to take the RSS XML and convert it to ASX XML (or other playlist formats) using an XSLT transformation file.

A Solution

The solution here is just a single .ASPX file (RssToPL.aspx) along with whatever playlist format XSLT files you want to support. To keep this example simple, I just placed all the C# code into the single .ASPX file (no code behind). As long as your XSLT files (RssToAsx.xslt, etc.) are in the same directory as the .ASPX file, you should be set.

If you are utilizing this for your own personal use (like me), this can either be placed on your own personal web server (running on your local machine or elsewhere). If you are a content provider producing podcasts, this is a easy way to provide your users several different ways to access your content.

RssToPL.aspx

<%@ Page Language="C#" %>
<%@ OutputCache Duration="240" VaryByParam="*" %>
<%@ Import Namespace="System.IO"%>
<%@ Import Namespace="System.Linq"%>
<%@ Import Namespace="System.Xml.Linq"%>

<script language="C#" runat="server">

void Page_Load(object sender, EventArgs e)
{
    string sAsxFileName;
    string sRssUrl;
    //set the default xslt filename extension/type
    string sXslFileType = "ASX";
    //prepend the full path of the current directory
    string sXslFile = Path.GetDirectoryName(Request.PhysicalPath) +
					Path.DirectorySeparatorChar;

    //if they provided the OutType QueryString param, lets use this transform file
    if (Request.QueryString["OutType"] != null)
    {
        sXslFileType = Request.QueryString["OutType"];
    }

    //format the xslt filename to use based on the out type
    sXslFile += String.Format("RssTo{0}.xslt", sXslFileType);

    //check to make sure they provided the RssUrl QueryString param
    if (Request.QueryString["RssUrl"] != null)
    {
        sRssUrl = Request.QueryString["RssUrl"];

        //we'll try to use a formatted title of the feed for the asx filename
        sAsxFileName = FormatFileName(GetRssTitle(sRssUrl)) + "." + sXslFileType;
    }
    else
    {
        throw new ApplicationException
	("No RSS Feed URL (RssUrl) QueryString parameter provided.");
    }

    Response.Buffer = true;
    Response.ClearContent();
    Response.ClearHeaders();
    Response.Clear();
    Response.ContentEncoding = Encoding.UTF8;

    //if they provided the "inline" QueryString param,
    //use it to determine how to send the asx file
    //inline sends the asx xml to the browser,
    //attachment prompts the user to download the file
    //attachment is the default
    if ((Request.QueryString["Inline"] != null) && 
	(Request.QueryString["Inline"].ToLower() == "true"))
    {
    	//set Response.ContentType based on output method in xsl 
         //(this lets us switch between xml or text)
    	Response.ContentType = GetXslOutputMethod(sXslFile);
    	Response.AppendHeader("content-disposition", "inline;filename=" + sAsxFileName);
    }
    else
    {
    	//we just use "text/x-unknown-content-type" 
         //because if we set to "text/plain" or "text/xml"
    	//some browsers sometimes change the file 
         //extension to .txt or .xml respectively.
    	Response.ContentType = "text/x-unknown-content-type";
    	Response.AppendHeader("content-disposition", 
			"attachment;filename=" + sAsxFileName);
    }

    //send transformed xml
    Response.Write(TransformXML(sRssUrl, sXslFile));
    Response.End();
}

/// <summary>
/// removed unwanted characters from filename
/// </summary>
/// <param name="fileName">unformatted filename</param>
/// <returns>formatted filename</returns>
private string FormatFileName(string fileName)
{
    string sReturn = fileName;
    sReturn = sReturn.Replace(" ", "");
    sReturn = sReturn.Replace(":", "");
    sReturn = sReturn.Replace("\\", "");
    sReturn = sReturn.Replace("/", "");
    sReturn = sReturn.Replace("@", "");
    sReturn = sReturn.Replace("$", "");
    sReturn = sReturn.Replace("+", "");
    sReturn = sReturn.Replace("%", "");
    sReturn = sReturn.Replace(">", "");
    sReturn = sReturn.Replace("<", "");
    sReturn = sReturn.Replace("?", "");
    sReturn = sReturn.Replace("\"", "");
    sReturn = sReturn.Replace("'", "");
    sReturn = sReturn.Replace(",", "");
    sReturn = sReturn.Replace("`", "");
    return sReturn;
}

/// <summary>
/// Transforms xml from url using an xslt file.
/// In this case, it will take a rss feed url and return the playlist file
/// </summary>
/// <param name="xmlUrl">rss feed url</param>
/// <param name="xslUrl">xslt filename</param>
/// <returns>asx xml</returns>
private string TransformXML(string xmlUrl, string xslUrl)
{
    MemoryStream ms = new MemoryStream();
    string sOutput;
    try
    {
        System.Xml.XPath.XPathDocument oXPath =
		new System.Xml.XPath.XPathDocument(xmlUrl);
        System.Xml.Xsl.XslTransform oXSLT = new System.Xml.Xsl.XslTransform();
        oXSLT.Load(xslUrl);
        oXSLT.Transform(oXPath, null, ms);
        ms.Position = 0;
        StreamReader sr = new StreamReader(ms, Encoding.UTF8);
        sOutput = sr.ReadToEnd().ToString();
    }
    catch (Exception ex)
    {
        //Put in custom error handler here...
        sOutput = ex.ToString();
    }

    return sOutput;
}

/// <summary>
/// Gets the title from an rss feed url
/// </summary>
/// <param name="xmlUrl">rss feed url</param>
/// <returns>return the title from an rss feed url</returns>
private string GetRssTitle(string xmlUrl)
{
    string sReturn;
    try
    {
        XDocument xmlDoc = XDocument.Load(xmlUrl);
        var qry = from r in xmlDoc.Descendants("channel")
        select (string)r.Element("title");
        sReturn = qry.First();
    }
    catch (Exception)
    {
        // there was a problem trying to get the title
        // we'll just use "rss"
        sReturn = "rss";
    }

    return sReturn;
}

/// <summary>
/// Gets the output method (xsl:output method="xml" ...) from the given xsl file
/// </summary>
/// <param name="xslUrl">xsl url/filename</param>
/// <returns>returns "text/xml" or "text/plain"</returns>
private string GetXslOutputMethod(string xslUrl)
{
    string sReturn = "xml";  //default to xml

    try
    {
        XNamespace xsl = "http://www.w3.org/1999/XSL/Transform";
        XDocument xmlDoc = XDocument.Load(xslUrl);

        var qry = from r in xmlDoc.Descendants(xsl + "stylesheet")
                  select r.Element(xsl + "output");

        XElement xslOutput = qry.First();
        if (xslOutput != null) { sReturn = xslOutput.Attribute("method").Value; }
    }
    catch (Exception ex)
    {
        // there was a problem trying to get the output method
        // we'll just use "xml"
    }

    sReturn = sReturn == "text" ? "text/plain" : "text/xml";
    return sReturn;
}

</script>

RssToASX.xslt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="1.0">

    <!-- indicates what our output type is going to be -->
    <xsl:output method="xml" encoding="utf-8"
	indent="yes" omit-xml-declaration="yes" />

    <xsl:template match="rss/channel">
        <asx version="3.0">
            <title>
                <xsl:value-of select="title"/>
            </title>
            <abstract>
                <xsl:value-of select="description" />
            </abstract>
            <xsl:apply-templates select="item" />
        </asx>
    </xsl:template>

    <xsl:template match="item">
        <xsl:variable name="item_enclosure" select="enclosure/@url"/>
            <xsl:if test="string-length(enclosure/@url) > 10">
                <entry>
                    <title>
                        <xsl:value-of select="title" />
                    </title>
                    <author>
                        <xsl:value-of select="author" />
                    </author>
                    <abstract>
                        <xsl:value-of select="description" />
                    </abstract>

                    <ref href="{$item_enclosure}"/>
                </entry>
            </xsl:if>
    </xsl:template>

</xsl:stylesheet>

This XSLT file will only return items with an enclosure, so you don’t have to worry about RSS feeds that mix in blog entries with the podcast.

To use this ASP.NET script, you just need to pass in the URL of the RSS feed and it will return the ASX file (as either inline XML or as a download). For example: http://[yourhost]/RssToPL.aspx?RssUrl=http://channel9.msdn.com/Media/Videos/feed/wmvhigh/ would return a playlist with the MSDN Channel 9 videos (in reverse chronological order).

To take this a step further, I also wanted to be able to have multiple podcasts show up in the same playlist. For example, I wanted both the ".NET Rocks" and "Hansel minutes" podcasts to show up interwoven together in one playlist, sorted by date (not all the ".NET Rocks" first and then all "Hansel minutes" last).

To do this, I decided to use Yahoo Pipes rather than write my own code. Yahoo Pipes is an awesome tool that basically lets you create custom RSS feeds (among other things) based off multiple sources and be able to manipulate them in many different ways. Yahoo Pipes does a lot more than that, but I won't get into it here. Also, if Yahoo Pipes seems a bit complicated for you, you could also use something like RSS Mix.

Using the Code

On the web server, place the RssToPL.aspx and RssToAsx.xslt into the same directory on your ASP.NET web server. The following table lists the QueryString parameters:

Param Value/Description
RssUrl The URL of the podcast RSS feed.
Inline (optional) True or false. If set to true, the XML will show up in the browser window, otherwise the browser will prompt to download the ASX file.
OutType (optional) The output file type of the playlist to return (M3U, WPL, ZPL, WVX, SMIL, PLS, etc.) and define the XSLT file to use. If this parameter is left out, it will default to ASX.

On the client machine (the computer you are using to watch/listen to the podcasts), there are two ways I have effectively used this. One is just by creating a shortcut on your desktop (or some other folder) to the script URL. This way, if you double click the shortcut, it will open Internet Explorer then just prompt you to open the ASX file in Windows Media Player (or your default ASX player), then Internet Explorer automatically closes. I've found this works best with WMP, because it lists all the episodes in the "Now Playing" pane.

The other solution is just to create a hard coded ASX file on your computer that just has a reference to the script URL. For example, place either of the following entries in your ASX file:

<entry><ref href="http://[yourhost]/RssToPL.aspx?RssUrl=[RSSURL]" /></entry>

or:

<entryref href="http://[yourhost]/RssToPL.aspx?RssUrl=[RSSURL]" />

I’ve found this solution works best if you are using Windows Media Center.

Notes

  1. I chose the ASX format as the default because I have worked with it before, but outputting to other formats, such as .M3U, .WPL, .ZPL (Zune), .WVX, .SMIL, .PLS, etc., for other players should be as easy as adding another XSLT template file and changing the OutType QueryString parameter. I've included ASX, M3U, WPL XSLT files in the example. Also, I believe ASX playlists will work with other players such as RealPlayer, Winamp, Media Player Classic, Windows Media Player Mobile, VLC, etc., but it isn't something I've extensively tested.
  2. For complex feed URLs (especially URLs with other QueryString parameters in them, you'll probably need to encode the URL (Server.URLEncode) for this script to work properly.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here