В этой статье я покажу, как использовать PHP для создания подкаста. Мы создадим простой административный интерфейс для настройки метаданных подкаста, добавления и перечисления эпизодов, а затем проведем сам процесс создания подкаста (который является просто документом RSS).
Я буду основывать приложение на этом скелетном приложении Slim , хотя многие принципы останутся неизменными независимо от того, какую среду вы выберете. Вы можете загрузить само приложение с GitHub или поработать над учебником и создавать его по мере продвижения.
Готов? Давайте начнем!
Настройка приложения
Скелетное приложение Slim содержит базовую настройку для веб-приложения на базе Slim, а также включает в себя NotORM для запросов к базам данных и Twig для работы с шаблонами. Мы также будем использовать библиотеку getID3 для работы с метаданными аудиофайлов, поэтому откройте файл composer.json
и добавьте следующее требование:
"nass600/get-id3": "dev-master"
Запустите composer.phar install
и зависимости приложения будут загружены в каталог vendor
.
Создайте data
каталогов и public/uploads
и убедитесь, что они доступны для записи на веб-сервере. В каталоге data
будут храниться некоторые дополнительные сведения о конфигурации приложения, а public/uploads
будет содержать наши аудиозаписи подкастов.
Я собираюсь использовать MySQL для хранения данных приложения, но вы можете выбрать любую СУБД, которая вам удобнее. Первоначально база данных должна хранить только информацию об эпизодах, поэтому наша схема будет довольно простой:
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) );
Скопируйте файл config/config.php.example
в config/config.php
и обновите его, config/config.php
учетные данные для подключения к базе данных. Также рекомендуется добавить data
и каталоги public/uploads
качестве записей конфигурации, чтобы мы могли ссылаться на них в другом месте нашего кода без их жесткого кодирования.
Страница конфигурации
Теперь мы можем создать страницу, которая будет использоваться для настройки самого подкаста. Фид включает в себя набор метаданных, таких как заголовок, информация о категории, владелец и краткое описание того, о чем он. Я сделаю все как можно проще, и интерфейс конфигурации в конечном итоге будет просто формой, которая сохраняет сериализованное представление конфигурации в файл.
Вырежьте и вставьте следующее в data/configuration.txt
и убедитесь, что файл доступен для записи на веб-сервере.
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:"[email protected]";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";}
Теперь мы создадим очень простой класс для загрузки и сохранения конфигурации. Сохраните следующее как 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); } }
Добавьте экземпляр SimpleFileConfiguration
в SimpleFileConfiguration
include/services.php
:, чтобы он был легко доступен во всем приложении:
<?php $c['PodcastConfig'] = $c->share(function ($c) { return new SimpleFileConfiguration($c); }
Создайте файл routes/configure.php
с помощью /configure
маршрутов:
<?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'); });
И, наконец, создайте файл templates/configure.html
котором будет представлена форма для обновления значений конфигурации.
<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>
Я скопировал список доступных категорий подкастов из списка, определенного Apple, для отправки в iTunes. Некоторые из них, такие как комедия, являются самостоятельными категориями, а другие имеют дочерние категории. Для тех, у кого есть дети, я использовал символ канала в качестве разделителя в значениях параметров.
Добавление эпизода
Затем мы создадим страницу, где мы сможем создать новый эпизод подкаста. Давайте определим маршруты в 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'); });
Я держу вещи простыми здесь; нет никакой проверки, и загрузка аудио файла очень проста, но вы поняли идею. Я также не собираюсь останавливаться на реализации функции редактирования или удаления здесь; это довольно простой материал, который вы можете реализовать позже.
Теперь создайте файл templates/episode-add.html
с формой для добавления нового подкаста:
<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>
Перечисление Эпизодов Подкаста
Чтобы создать обзорную страницу со списком всех эпизодов в подкасте, мы можем получить список эпизодов из базы данных с помощью NotORM и передать результат непосредственно в представление.
Добавьте следующее в 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'); });
А затем создайте 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 %}
Нам нужно опубликовать ленту новостей, чтобы сделать подкаст доступным, а это значит, что мы можем запачкать руки XML. Для этого я определю маршрут /podcast.xml
и DOMDocument
использовать 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 MYH: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(); });
Основные моменты от поколения подачи:
- Следуя требованиям на веб-сайте Apple , мы создаем XML-документ с корневым элементом
rss
и предоставляем необходимую информацию о канале. Я жестко запрограммировал тегgenerator
здесь; на самом деле вы можете установить его на то, что вам нравится. - Мы перебираем эпизоды и создаем элемент
item
для каждого из них. Если бы у нас была уникальная страница для каждого эпизода — что-то довольно простое для настройки — мы могли бы использовать ее для Глобального уникального идентификатора (GUID), но сейчас мы просто используем URL-адрес самого аудиофайла, поскольку, очевидно, это будет уникальный. - Чтобы создать элемент
enclosure
который содержит URL, размер файла и тип MIME фактического аудиофайла, мы используемfilesize()
и расширение Fileinfo, чтобы получить тип MIME. - Чтобы включить продолжительность звуковой дорожки, мы используем библиотеку getID3.
- Мы заканчиваем все, устанавливая правильные заголовки и выводя XML.
Перейдите в /podcast.xml
и вы должны увидеть XML для канала подкаста. Запустите его через несколько валидаторов каналов ( tools.forret.com/podcast/validator.php , castfeedvalidator.com и feedvalidator.org ), и тогда вы будете готовы отправить его в iTunes !
Резюме
В этой статье я показал, как вы можете создать простое приложение для создания и публикации собственных подкастов. В этой реализации отсутствует ряд вещей, таких как редактирование и удаление эпизодов, надлежащая проверка и безопасность и т. Д. Они выходят за рамки этой статьи, но их просто добавить. Не стесняйтесь от GitHub и код!
Изображение через Fotolia