RSS feed video generator
Discover how we used the Moovly API in conjunction with a given RSS feed to generate a video on the go
Watch demo1. Intro
For this demo we will create a PHP scrip that generates a video containing the three latest items on a given RSS-feed with some extra checks on the validity of the RSS feeds. This demo covers RSS 2.0 specification and will be using the "breaking news" NASA RSS feed. Yet every RSS feed complying to RSS 2.0 spec can be used as a drop-in replacement.
2. Create video template
2.1. Create project
First things first. As always, we start by creating a new project (from scratch in this case) in the Moovly dashboard. For this video we will pick a 16:9 ratio and use a coloured background.
2.2. Create video
This video contains some simple objects: an image, some text fields, and the Moovly logo in an outro. Some of those objects will be marked as template fields so they can be changed with our Automator API. But before all that, we make sure that the video looks good with all our placeholder information.
As labels in the timeline objects we use names that are meaningful. Though this has no impact on the video itself, we recommend to use meaningful naming since the API returns those labels as well to make things easier on you.
2.3. Setup template variables
Once we're satisfied with the looks of our video, we can start creating template fields from our placeholder objects.
Title & Description
We make the title and description field fixed
. Fixed means that we can specify a maximum width and height for a field. Which makes sure that once we replace our placeholder text, the new text won't exceed the dimensions of our current placeholder text.
Image
The image in our video is used as a background so it makes sense to mark the object fit as cover
. That way we'll have the optimal placement for that image: it will always fill its space without being stretched.
2.4. Create template from project
At this point our video (with placeholder data) is finished and we will create a template from our project in the Moovly dashboard. Since you can always update a template later (by updating the original project), there's no need to worry about having a 100% perfect video at this moment.
Now that we created the template, it can be used in a multitude of ways. Like our Quick Edit menu in the dashboard, a Wordpress plugin, or by using our Automator API.
We will continue this tutorial by using the API.
3. API Authentication
3.1. Generate Moovly API token
To make use of the Moovly API, you need to generate a personal access token. This can be done in our Developer Portal. The API tokens you generate will be valid for one year. So at least once a year, you'll need to generate a new token.
4. Fill template with RSS feed items
Now that we've created a template and have obtained API keys for Moovly, we can start to create a PHP script that fetches items from the given RSS feed and uses that data to replace our placeholder template fields in our Moovly template.
4.1 Get template variables
First and foremost we'll prepare an array with all template variables mapped on a more readable key. We can retrieve all template variables by getting the details of the template we want to use:
## Get template
curl "https://api.moovly.com/generator/v1/templates/40f2b98a-1d2d-4ade-a227-d9d6c2e7f54d" \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer $MOOVLY_API_TOKEN'
This cURL request returns a JSON with a variables
field containing all variables
"variables": [
{
"id": "04dd6c70-0183-45d3-a6d2-59e931fe9453",
"name": "$IMAGE_1",
"weight": 1,
"type": "image",
"requirements": {
"width": 719,
"height": 480
},
"default": "https:\/\/assets.moovly.com\/converted\/images\/image-35715fa6b0e9e35006066f8beb3bb940.jpg",
"clip_id": "8eb6e27944f201c0b217"
},
{
"id": "3b577eb8-bfea-4f77-ad0c-994b0457645a",
"name": "$TITLE_3",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 300,
"minimum_length": 1
},
"default": "\u003Cp\u003ETITLE NEWS ITEM NUMBER THREE\u003C\/p\u003E",
"clip_id": "746e62966d316be96b07"
},
{
"id": "3bf1ad52-a565-4b43-b306-f26222b93628",
"name": "$IMAGE_3",
"weight": 1,
"type": "image",
"requirements": {
"width": 723,
"height": 480
},
"default": "https:\/\/assets.moovly.com\/converted\/images\/image-7818625e207844e6778e38b23c911ad4.jpg",
"clip_id": "746e62966d316be96b07"
},
{
"id": "650d872c-d0c3-43c7-a5a5-cc26dafa373f",
"name": "$DATE",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 100,
"minimum_length": 1
},
"default": "\u003Cp\u003EDAY\/MONTH\/YEAR\u003C\/p\u003E",
"clip_id": "a93468f19ae7fddbc316"
},
{
"id": "6d43dc25-fc07-44b0-ac5e-0cf113a0d281",
"name": "$TITLE_2",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 200,
"minimum_length": 1
},
"default": "\u003Cp\u003ETITLE NEWS ITEM NUMBER TWO\u003C\/p\u003E",
"clip_id": "fbe61fef5de26bdfb975"
},
{
"id": "951fb6dd-3d90-493d-96e7-42681527afc6",
"name": "$TITLE_1",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 300,
"minimum_length": 1
},
"default": "\u003Cp\u003ETITLE NEWS ITEM NUMBER ONE\u003C\/p\u003E",
"clip_id": "8eb6e27944f201c0b217"
},
{
"id": "9853eeed-3968-4bea-a759-f71b69ee6d97",
"name": "$RSS_FEED_NAME",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 200,
"minimum_length": 1
},
"default": "\u003Cp\u003ECOMPANY NAME\u003C\/p\u003E",
"clip_id": "a93468f19ae7fddbc316"
},
{
"id": "a3ea2228-2af9-47a6-875a-ac737fa77e6d",
"name": "$DESCRIPTION_2",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 1000,
"minimum_length": 1
},
"default": "\u003Cp\u003EAt vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt moll",
"clip_id": "fbe61fef5de26bdfb975"
},
{
"id": "b17cb347-ccc9-42f3-a944-6392ce0aebfc",
"name": "$IMAGE_2",
"weight": 1,
"type": "image",
"requirements": {
"width": 720,
"height": 480
},
"default": "https:\/\/assets.moovly.com\/converted\/images\/image-d2940ddada890cd36c391456c63b0822.jpg",
"clip_id": "fbe61fef5de26bdfb975"
},
{
"id": "dc442952-082d-4616-827b-1356ff310ca4",
"name": "$DESCRIPTION_3",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 1000,
"minimum_length": 1
},
"default": "\u003Cp\u003EAt vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt moll",
"clip_id": "746e62966d316be96b07"
},
{
"id": "f6f9c789-2506-434b-aa5a-04670c0bcf58",
"name": "$DESCRIPTION_1",
"weight": 1,
"type": "text",
"requirements": {
"multiline": false,
"maximum_length": 1000,
"minimum_length": 1
},
"default": "\u003Cp\u003EAt vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt moll",
"clip_id": "8eb6e27944f201c0b217"
}
],
Using the variables
data, we'll create a more comprehensive mapping for the template variable ids
static $templateId = '40f2b98a-1d2d-4ade-a227-d9d6c2e7f54d';
static $templateVariableIds = [
'date' => '650d872c-d0c3-43c7-a5a5-cc26dafa373f',
'title' => '9853eeed-3968-4bea-a759-f71b69ee6d97',
'items' => [
[
'title' => '951fb6dd-3d90-493d-96e7-42681527afc6',
'body' => 'f6f9c789-2506-434b-aa5a-04670c0bcf58',
'image' => '04dd6c70-0183-45d3-a6d2-59e931fe9453'
],
[
'title' => '6d43dc25-fc07-44b0-ac5e-0cf113a0d281',
'body' => 'a3ea2228-2af9-47a6-875a-ac737fa77e6d',
'image' => 'b17cb347-ccc9-42f3-a944-6392ce0aebfc'
],
[
'title' => '3b577eb8-bfea-4f77-ad0c-994b0457645a',
'body' => 'dc442952-082d-4616-827b-1356ff310ca4',
'image' => '3bf1ad52-a565-4b43-b306-f26222b93628'
]
]
];
4.2 Check RSS feed validity
First we'll check if the given feed is an RSS feed, not an atom feed. Atom and RSS have different fields. Atom falls outside the scope of this example. Then we'll check if the RSS feed has equal or more items then the $templateVariableIds
$rssFeedData = new SimpleXMLElement(file_get_contents($rssFeed));
if ($rssFeedData->getName() !== 'rss') {
throw new \Exception('Given URL is no RSS feed');
}
if ($rssFeedData->channel->item->count() < count(RssFeedService::$templateVariableIds['items'])) {
throw new \Exception(
sprintf(
'Not enough items on feed to generate a video: %d needed, %d found',
count(RssFeedService::$templateVariableIds['items']),
$rssFeedData->channel->item->count()
)
);
}
4.3 Map RSS feed data to template variable ids
Now we'll map the item data from the $rssFeedData
. title
and description
are required fields in the RSS spec. Yet the template allows for a custom image and images are not required in RSS. When one is set it will be referred to in the enclosure
of an item. If none is provided or it's not an image (audio and video files are supported inside the RSS spec) we'll pass a null
value for that template variable. In this case, the automater will use the placeholder image provided when creating the video.
$templateVariables = [
RssFeedService::$templateVariableIds['date'] => date('d-m-Y'),
RssFeedService::$templateVariableIds['title'] => (string) $rssFeedData->channel->title
];
foreach (RssFeedService::$templateVariableIds['items'] as $i => $item) {
$templateVariables[$item['title']] = (string) $rssFeedData->channel->item[$i]->title ?: null;
$templateVariables[$item['body']] = strip_tags((string) $rssFeedData->channel->item[$i]->description) ?: null;
$imageUrl = null;
if (property_exists($rssFeedData->channel->item[$i], 'enclosure') && preg_match('/^image\/.+/', $rssFeedData->channel->item[$i]->enclosure->attributes()->type)
) {
$imageUrl = (string) $rssFeedData->channel->item[$i]->enclosure->attributes()->url;
}
$templateVariables[$item['image']] = $imageUrl;
}
4.4 Post job to Moovly
Now all variables are set we can create a generator job using the Moovly API. Note the values
field inside the body contains an array of arrays. For each array given here a new video will be generated, knowing this we could create multiple video's in 1 API call. We also would like to get a notification email to $email
when the video has been created. We support other notifications than email as well, a list can be found in the docs.
$this->moovlyAPIClient->post(
'/generator/v1/jobs',
[
'body' => json_encode([
'template_id' => RssFeedService::$templateId,
'options' => [
"quality" => "1080p",
"create_render" => true,
"create_project" => false
],
'values' => [
[
'external_id' => 'nasa-news-' . date('Y-m-d'),
'template_variables' => $templateVariables,
]
],
'notifications' => [
[
'type' => 'email',
'payload' => [
'email' => $email
]
]
]
])
]
);
4.5 Full example code
<?php
use GuzzleHttp\Client;
use SimpleXMLElement;
class RssFeedService
{
protected $moovlyAPIClient;
static $templateId = '40f2b98a-1d2d-4ade-a227-d9d6c2e7f54d';
static $templateVariableIds = [
'date' => '650d872c-d0c3-43c7-a5a5-cc26dafa373f',
'title' => '9853eeed-3968-4bea-a759-f71b69ee6d97',
'items' => [
[
'title' => '951fb6dd-3d90-493d-96e7-42681527afc6',
'body' => 'f6f9c789-2506-434b-aa5a-04670c0bcf58',
'image' => '04dd6c70-0183-45d3-a6d2-59e931fe9453'
],
[
'title' => '6d43dc25-fc07-44b0-ac5e-0cf113a0d281',
'body' => 'a3ea2228-2af9-47a6-875a-ac737fa77e6d',
'image' => 'b17cb347-ccc9-42f3-a944-6392ce0aebfc'
],
[
'title' => '3b577eb8-bfea-4f77-ad0c-994b0457645a',
'body' => 'dc442952-082d-4616-827b-1356ff310ca4',
'image' => '3bf1ad52-a565-4b43-b306-f26222b93628'
]
]
];
public function __construct()
{
$this->moovlyAPIClient = new Client(
[
'base_uri' => 'https://api.moovly.com',
'headers' => [
'Authorization' => getenv('API_TOKEN'),
'Content-Type' => 'application/json'
]
]
);
}
public function create($rssFeed, $email)
{
$rssFeedData = new SimpleXMLElement(file_get_contents($rssFeed));
if ($rssFeedData->getName() !== 'rss') {
throw new \Exception('Given URL is no RSS feed');
}
if ($rssFeedData->channel->item->count() < count(RssFeedService::$templateVariableIds['items'])) {
throw new \Exception(
sprintf(
'Not enough items on feed to generate a video: %d needed, %d found',
count(RssFeedService::$templateVariableIds['items']),
$rssFeedData->channel->item->count()
)
);
}
$templateVariables = [
RssFeedService::$templateVariableIds['date'] => date('d-m-Y'),
RssFeedService::$templateVariableIds['title'] => (string) $rssFeedData->channel->title
];
foreach (RssFeedService::$templateVariableIds['items'] as $i => $item) {
$templateVariables[$item['title']] = (string) $rssFeedData->channel->item[$i]->title ?: null;
$templateVariables[$item['body']] = strip_tags((string) $rssFeedData->channel->item[$i]->description) ?: null;
$imageUrl = null;
if (property_exists($rssFeedData->channel->item[$i], 'enclosure')
&& preg_match('/^image\/.+/', $rssFeedData->channel->item[$i]->enclosure->attributes()->type)
) {
$imageUrl = (string) $rssFeedData->channel->item[$i]->enclosure->attributes()->url;
}
$templateVariables[$item['image']] = $imageUrl;
}
$this->moovlyAPIClient->post(
'/generator/v1/jobs',
[
'body' => json_encode([
'template_id' => RssFeedService::$templateId,
'options' => [
"quality" => "1080p",
"create_render" => true,
"create_project" => false
],
'values' => [
[
'external_id' => 'nasa-news-' . date('Y-m-d'),
'template_variables' => $templateVariables,
]
],
'notifications' => [
[
'type' => 'email',
'payload' => [
'email' => $email
]
]
]
])
]
);
}
}