Create a Podcast Feed with PHP

Share this article

In this article, I’ll demonstrate how to use PHP to generate a podcast feed. We’ll create a simple administrative interface to configure the podcast metadata, add and list episodes, and then go through the generation of the podcast feed itself (which is simply an RSS document). I’ll base the application on this skeleton Slim app
, though many of the principles will remain the same whatever framework you choose. You can download the application itself from GitHub or work through the tutorial and build it as you go along. Ready? Let’s get started!

Key Takeaways

  • Utilize PHP to construct a simple administrative interface for managing podcast metadata and episodes, which culminates in the generation of an RSS feed for the podcast.
  • Incorporate necessary libraries such as Slim, NotORM, Twig, and getID3 in your PHP setup to handle routing, database queries, template rendering, and audio metadata extraction.
  • Create and configure a MySQL database to store episode details, ensuring that the necessary directories for data and uploads are writable by the web server.
  • Develop a configuration page to save podcast details like title, language, and owner information using a serialized file, enabling easy updates through a user-friendly form.
  • Implement podcast episode management features, allowing for the addition of episodes with audio uploads and listing all episodes in a structured table format, while also providing an XML feed output for podcast syndication.

Setting up the Application

The Slim skeleton app contains the basic set up for a web application powered by Slim, and also pulls in NotORM for querying databases and Twig for working with templates. We’ll also use the getID3 library to work with the audio files’ metadata, so open composer.json file and add the following require:
"nass600/get-id3": "dev-master"
Run composer.phar install and the application’s dependencies will be downloaded into a vendor directory. Create the directories data and public/uploads and ensure they are both writeable by the web server. The data directory will store some additional application configuration details, and public/uploads will hold our podcast audio uploads. I’m going to use MySQL for storing the application’s data, but you can choose whichever RDBMS you feel comfortable with. Initially the database only needs to store episode information, so our schema will be rather simple:
CREATE TABLE episodes (
    id INTEGER NOT NULL AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    author VARCHAR(255) NOT NULL,
    summary TEXT NULL,
    description TEXT NULL,
    audio_file VARCHAR(255) NOT NULL,
    created INTEGER NOT NULL,
   
    PRIMARY KEY (id)
);
Copy the file config/config.php.example to config/config.php and update it with your database connection credentials. Also, it’s a good idea to add the data and public/uploads directories as configuration entries so we can reference them elsewhere in our code without hardcoding them.

The Configuration Page

Now we can create a page that will be used to configure the podcast feed itself. A feed incorporates a bunch of metadata such as a title, category information, the owner, and a summary of what it’s about. I’ll keep things as simple as possible and the configuration interface will ultimately be just a form which saves a serialized representation of the configuration to a file. Cut and paste the following to data/configuration.txt and make sure that the file is writeable by the web server.
a:12:{s:5:"title";s:16:"A Sample Podcast";s:8:"language";s:2:"en";s:9:"copyright";s:14:"SitePoint 2013";s:8:"subtitle";s:16:"Podcast subtitle";s:6:"author";s:9:"SitePoint";s:7:"summary";s:31:"Generating a podcast using PHP.";s:11:"description";s:58:"This is a demonstration of generating a Podcast using PHP.";s:10:"owner_name";s:9:"SitePoint";s:11:"owner_email";s:22:"no-reply@sitepoint.com";s:10:"categories";a:4:{i:0;s:30:"Education|Education Technology";i:1;s:18:"Education|Training";i:2;s:21:"Technology|Podcasting";i:3;s:26:"Technology|Software How-To";}s:8:"keywords";s:21:"PHP,podcasts,tutorial";s:8:"explicit";s:2:"no";}
Now we’ll create a very simple class to load and save the configuration. Save the following as lib/SimpleFileConfiguration.php:
<?php
class SimpleFileConfiguration 
{ 
    const DATA_FILE = 'configuration.txt'; 
    public $dataFile; 

    public function __construct(Pimple $c) { 
        $this->dataFile = $c['config']['path.data'] . $this::DATA_FILE; 
    } 
 
    public function load() { 
        $contents = file_get_contents($this->dataFile); 
        return unserialize($contents); 
    } 

    public function save($configuration) { 
        $contents = serialize($configuration); 
        file_put_contents($this->dataFile, $contents); 
    } 
}
Add the instantiation of SimpleFileConfiguration to include/services.php: so it’ll be easily accessible throughout the application:
<?php
$c['PodcastConfig'] = $c->share(function ($c) { 
    return new SimpleFileConfiguration($c); 
}
Create the file routes/configure.php with the /configure routes:
<?php
$app->get('/configure', function () use ($app, $c) { 
    $config = $c['PodcastConfig']->load(); 
    $app->view()->setData(array( 
        'configuration' => $config 
    )); 
    $app->render('configure.html'); 
}); 

$app->post('/configure', function () use ($app, $c) { 
    $data = $app->request()->post(); 
    $c['PodcastConfig']->save($data); 
    $app->flash('success', 'Configuration Saved'); 
    $app->redirect('/configure'); 
});
And finally, create the template file templates/configure.html that will present the form to update the configuration values.
<form method="post" enctype="multipart/form-data" action="/configure">
 <fieldset>
  <legend>Details</legend>
  
  <label>Title</label>
  <input name="title" type="text" placeholder="Please enter a title..." value="{{ configuration.title }}" class="input-xlarge">

  <label>Language</label>
  <select name="language">
   <option value="en">English</select>
   <!-- You can put more languages here... -->
  </select>

  <label>Copyright</label>
  <input name="copyright" type="text" placeholder="Please enter copyright information..." value="{{ configuration.copyright }}" class="input-xlarge">

  <label>Subtitle</label>
  <input name="subtitle" type="text" placeholder="Optionally enter a subtitle..." value="{{ configuration.subtitle }}" class="input-xlarge">

  <label>Author</label>
  <input name="author" type="text" placeholder="Please enter the Podcast's author..." value="{{ configuration.author }}" class="input-xlarge">

  <label>Summary</label>
  <textarea name="summary" cols="50" rows="2" placeholder="Please enter a summary..." class="input-xlarge">{{ configuration.summary }}</textarea>    

  <label>Description</label>
  <textarea name="description" cols="50" rows="5" placeholder="Please enter a description..." class="input-xlarge">{{ configuration.description }}</textarea>    
    
 </fieldset>
  
 <fieldset>
  <legend>Owner Details</legend>
    
  <label>Name</label>
  <input name="owner_name" type="text" placeholder="Please enter a the podcast owner's name..." value="{{ configuration.owner_name }}" class="input-xlarge">

  <label>E-mail</label>
  <input name="owner_email" type="text" placeholder="Please enter a the podcast owner's e-mail..." value="{{ configuration.owner_email }}" class="input-xlarge">
 </fieldset>

 <fieldset>
  <legend>Categorization</legend>

  <label>Categories</label>
  <select name="categories[]" multiple="true" class="input-xlarge">
   <optgroup label="Arts">
    <option value="Arts|Design">Design</option> 
    <option value="Arts|Fashion & Beauty">Fashion & Beauty</option> 
    <option value="Arts|Food">Food</option> 
    <option value="Arts|Literature">Literature</option> 
...
   </optgroup>
  </select>

  <label>Keywords</label>
  <textarea name="keywords" cols="50" rows="2" placeholder="Optionally enter some keywords (comma-separated)..." class="input-xlarge">{{ configuration.keywords }}</textarea>    

  <label>Explicit content?</label>
  <select name="explicit">
   <option value="no" {% if configuration.explicit == 'no' %}selected="selected"{% endif %}>No</select>
   <option value="yes" {% if configuration.explicit == 'yes' %}selected="selected"{% endif %}>Yes</select>
  </select>
 </fieldset>

 <div class="form-actions">
  <button type="submit" class="btn btn-primary">Save Configuration</button>  	
 </div>
</form>
I’ve copied the list of available podcast categories from the list defined by Apple for submission to iTunes. Some of them, such as Comedy, are self-contained categories and others have child categories. For those with children, I’ve used the pipe character as a separator in the option values.

Adding an Episode

Next up we’ll create the page where we can create a new podcast episode. Let’s define the routes in routes/podcast.php:
<?php
$app->get('/episode', function () use ($app) { 
    $app->render('episode-add.html'); 
}); 

$app->post('/episode', function () use ($app, $c) { 
    $db = $c['db']; 
    $data = $app->request()->post(); 
    $dir = $c['config']['path.uploads']; 

    $filepath = $dir . basename($_FILES['file']['name']); 
    move_uploaded_file($_FILES['file']['tmp_name'], $filepath); 

    $id = $db->episodes->insert(array( 
        'title'       =>  $data['title'], 
        'author'      =>  $data['author'], 
        'summary'     =>  $data['summary'], 
        'description' =>  $data['description'], 
        'audio_file'  =>  $filepath, 
        'created'     =>  time() 
    )); 

    $app->flash('success', 'Episode Created'); 
    $app->redirect('/podcast'); 
});
I’m keeping things simple here; there’s no validation and uploading the audio file is very basic, but you get the idea. I’m also not going to go over implementing edit or delete functionality here; it’s pretty straightforward stuff that you can implement yourself later. Now create the template file templates/episode-add.html with the form to add a new podcast:
<form method="post" enctype="multipart/form-data" action="/episode">
 <fieldset>
  <legend>Details</legend>

  <label>Title</label>
  <input name="title" type="text" placeholder="Please enter a title...">

  <label>Author</label>
  <input name="author" type="text" placeholder="Please enter the author..." value="">        

  <label>Summary</label>
  <textarea name="summary" cols="50" rows="2" placeholder="Please enter a summary..."></textarea>    

  <label>Description</label>
  <textarea name="description" cols="50" rows="5" placeholder="Please enter a description..."></textarea>    

  <label>Audio File</label>
  <input name="file" type="file" />
    
  <div class="form-actions">
   <button type="submit" class="btn btn-primary">Add Episode</button>  	
  </div>
 </fieldset>
</form>

Listing Podcast Episodes

To create an overview page which lists all of the episodes in the podcast, we can grab the list of episodes from the database using NotORM and pass the result directly to the view. Add the following to routes/podcast.php:
$app->get('/podcast', function () use ($app, $c) { 
    $db = $c['db']; 
    $app->view()->setData(array( 
        'podcast' => $db->episodes()->order('created DESC') 
    )); 
    $app->render('podcast.html'); 
});
And then create templates/podcast.html:
<table class="table table-bordered table-striped"> 
 <thead> 
  <tr> 
   <td>Title</td> 
   <td>Summary</td>   
  </tr>  
 </thead> 
 <tbody> 
{% for episode in podcast %} 
  <tr> 
   <td>{{ episode.title }}</td> 
   <td>{{ episode.summary }}</td>   
  </tr> 
{% endfor %} 
 </tbody> 
</table> 
{% endblock %}
We need to publish a feed to make the podcast available, which means getting our hands dirty with some XML. For that I’ll define the route /podcast.xml and use DOMDocument.
<?php
$app->get('/podcast.xml', function () use ($app, $c) { 
    $db = $c['db']; 
    $conf = $c['PodcastConfig']->load(); 

    $xml = new DOMDocument(); 
    $root = $xml->appendChild($xml->createElement('rss')); 
    $root->setAttribute('xmlns:itunes', 'http://www.itunes.com/dtds/podcast-1.0.dtd'); 
    $root->setAttribute('xmlns:media', 'http://search.yahoo.com/mrss/'); 
    $root->setAttribute('xmlns:feedburner', 'http://rssnamespace.org/feedburner/ext/1.0'); 
    $root->setAttribute('version', '2.0'); 

    $link = sprintf( 
        '%s://%s/podcast', 
        $app->request()->getScheme(), 
        $app->request()->getHost() 
    ); 

    $chan = $root->appendChild($xml->createElement('channel')); 
    $chan->appendChild($xml->createElement('title', $conf['title'])); 
    $chan->appendChild($xml->createElement('link', $link)); 
    $chan->appendChild($xml->createElement('generator', 'SitePoint Podcast Tutorial')); 
    $chan->appendChild($xml->createElement('language', $conf['language'])); 
...

    foreach ($db->episodes()->order('created ASC') as $episode) { 
        $audioURL = sprintf( 
            '%s://%s/uploads/%s', 
            $app->request()->getScheme(), 
            $app->request()->getHost(), 
            basename($episode['audio_file']) 
        ); 

        $item = $chan->appendChild($xml->createElement('item')); 
        $item->appendChild($xml->createElement('title', $episode['title'])); 
        $item->appendChild($xml->createElement('link', $audioURL)); 
        $item->appendChild($xml->createElement('itunes:author', $episode['title'])); 
        $item->appendChild($xml->createElement('itunes:summary', $episode['summary'])); 
        $item->appendChild($xml->createElement('guid', $audioURL)); 

        $finfo = finfo_open(FILEINFO_MIME_TYPE); 
        $enclosure = $item->appendChild($xml->createElement('enclosure')); 
        $enclosure->setAttribute('url', $episode['audio_file']); 
        $enclosure->setAttribute('length', filesize($episode['audio_file'])); 
        $enclosure->setAttribute('type', finfo_file($finfo, $episode['audio_file'])); 

        $item->appendChild($xml->createElement('pubDate', date('D, d M Y H:i:s O', $episode['created']))); 
 
        $getID3 = new getID3(); 
        $fileinfo = $getID3->analyze($episode['audio_file']); 
        $item->appendChild($xml->createElement('itunes:duration', $fileinfo['playtime_string'])); 
    } 

    $xml->formatOutput = true; 

    $res= $app->response(); 
    $res['Content-Type'] = 'application/json'; 
   print $xml->saveXML(); 
});
The highlights from the feed generation are:
  • Following the requirements on Apple’s website, we create the XML document with the root element rss and provide the necessary channel information. I hardcoded the generator tag here; really you can set it to whatever you like.
  • We iterate through the episodes and create an item element for each one. If we had a unique page for each episode – something pretty straightforward to set up – we could use it for the Globally Unique Identifier (GUID), but for now we’re just using the URL of the audio file itself since obviously that will be unique.
  • To create the enclosure element which contains the URL, file size, and MIME type of the actual audio file, we use filesize() and the Fileinfo extension to get the MIME type.
  • To include the duration of the audio track, we use the getID3 library.
  • We finish everything off by setting the correct headers and outputting the XML.
Navigate to /podcast.xml and you should see the XML for the podcast feed. Run it through a few feed validators (tools.forret.com/podcast/validator.php, castfeedvalidator.com and feedvalidator.org) for good measure, and then you’re ready to submit it to iTunes!

Summary

In this article, I’ve shown how you can build a simple application to create and publish your own podcasts. There are a number of things missing from this implementation, such as editing and deleting episodes, proper validation and security, etc. They fall outside the scope of this article but are simple to add. Feel free to download the source from GitHub and code away! Image via Fotolia

Frequently Asked Questions (FAQs) about Creating a Podcast Feed with PHP

How can I create a podcast feed with PHP from scratch?

Creating a podcast feed with PHP from scratch involves several steps. First, you need to set up a PHP server environment. You can use software like XAMPP or MAMP for this. Once your server is set up, you can start writing your PHP script. This script will be responsible for generating your podcast feed in the correct format. You’ll need to include information like the title of your podcast, a description, and links to your audio files. Once your script is complete, you can run it on your server to generate your podcast feed.

What are the essential elements of a podcast feed?

A podcast feed must contain several essential elements. These include the channel title, link, and description, which provide information about the podcast as a whole. Each individual podcast episode also requires a title, link, and description. Additionally, you’ll need to include the URL of the audio file for each episode. Other optional elements include the podcast’s language, copyright information, and a link to an image to represent the podcast.

How can I validate my podcast feed?

After creating your podcast feed, it’s important to validate it to ensure it’s in the correct format. There are several online tools available for this, such as Cast Feed Validator and Podbase. Simply enter the URL of your podcast feed into one of these tools, and it will check for any errors or issues.

How can I submit my podcast feed to podcast directories?

Once your podcast feed is set up and validated, you can submit it to podcast directories. Each directory has its own submission process, but generally, you’ll need to provide the URL of your podcast feed, along with some information about your podcast. Some popular podcast directories include Apple Podcasts, Spotify, and Google Podcasts.

Can I use PHP to create a podcast feed with multiple episodes?

Yes, you can use PHP to create a podcast feed with multiple episodes. Each episode will be represented as an item in your feed. You’ll need to include information for each episode, such as the title, description, and link to the audio file.

How can I update my podcast feed to add new episodes?

To add new episodes to your podcast feed, you’ll need to update your PHP script to include the new episode information. Once you’ve updated your script, you can run it again to generate a new version of your podcast feed.

Can I include images in my podcast feed?

Yes, you can include images in your podcast feed. This is typically done by including a link to an image file in your feed. The image could represent the podcast as a whole, or it could be specific to an individual episode.

How can I troubleshoot issues with my podcast feed?

If you’re having issues with your podcast feed, there are several steps you can take to troubleshoot. First, validate your feed to check for any errors. If the validator finds any issues, it will provide information on how to fix them. If your feed is valid but you’re still having issues, it may be a problem with the podcast directory you’re using. Check their documentation for troubleshooting tips.

Can I use PHP to create a video podcast feed?

Yes, you can use PHP to create a video podcast feed. The process is similar to creating an audio podcast feed, but instead of linking to an audio file for each episode, you’ll link to a video file.

How can I make my podcast feed compatible with all podcast directories?

To ensure your podcast feed is compatible with all podcast directories, it’s important to follow the RSS 2.0 specification, which is the standard format for podcast feeds. Additionally, each podcast directory may have its own specific requirements, so it’s a good idea to check the documentation for each directory you plan to submit to.

Lukas WhiteLukas White
View Author

Lukas is a freelance web and mobile developer based in Manchester in the North of England. He's been developing in PHP since moving away from those early days in web development of using all manner of tools such as Java Server Pages, classic ASP and XML data islands, along with JavaScript - back when it really was JavaScript and Netscape ruled the roost. When he's not developing websites and mobile applications and complaining that this was all fields, Lukas likes to cook all manner of World foods.

Intermediate
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week