NASA video of the day
Discover how we used the Moovly API in conjunction with the NASA API to generate a new video each day
Watch demo1. Intro
For this demo we will create an AWS Lambda function that generates a video on a daily basis by using a "picture of the day" that we fetch from the NASA API. The video includes that image itself and the title of the image. We then added an additional service that saves the videos in a database to display all of them on one page. This demo however will focus on the creation of the videos with a Moovly template and the Moovly API.
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 1:1 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.
Date
For the date field, we will use the same fixed
property as the title and description property. That way can't exceed the size we pick.
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.
3.2. Generate NASA API token
As a daily source of pictures, we use the NASA API. They too have a process of generating an API token. This can be generated at https://api.nasa.gov.
4. Create Lambda
Now that we've created a template and have obtained API keys for both Moovly and NASA, we can start to implement an AWS Lambda function that fetches data from NASA and uses that data to replace our placeholder template fields in our Moovly template.
4.1. Intro
As the implementation language for the Lambda function we've chose Node.js. Of course other options work just as fine, so pick the language of your choice.
We setup a default structure for the Lambda function. In our case of Node.js, this is an index.js file with an exported handler function.
//index.js
const handler = async () => {
// HERE COMES THE CODE
};
module.exports = { handler };
4.2. Retrieve NASA image
We create a function that will fetch NASA's picture of the day.
const NASA_PICTURE_OF_THE_DAY_URL = "https://api.nasa.gov/planetary/apod";
// environment variable NASA_API_TOKEN=$APITOKEN
const getNasaPictureOfTheDay = () => {
return fetch(
`${NASA_PICTURE_OF_THE_DAY_URL}?api_key=${process.env.NASA_API_TOKEN}`
).then((res) => res.json());
};
// Example response
{
"date": "2022-04-27",
"explanation": "The explanation",
"hdurl": "https://apod.nasa.gov/apod/image/2204/JupiterDarkSpot_JunoTT_3298.jpg",
"media_type": "image",
"service_version": "v1",
"title": "Moon Shadow on Jupiter",
"url": "https://apod.nasa.gov/apod/image/2204/JupiterDarkSpot_JunoTT_1080.jpg"
}
4.3. Insert data in template variables
const getWrittenDate = (date) =>
new Date(date).toLocaleString("en-us", {
month: "long",
year: "numeric",
day: "numeric",
});
const jobOptions = {
quality: "1080p",
create_render: true,
create_project: false,
};
// The explanation is too long for the template field
const firstSentenceOfExplanation = `${
pictureOfTheDay.explanation.split(".")[0]
}.`;
// JobValues is an array here; in theory you could create multiple videos at once
const jobValues = [
{
title: `Nasa picture of the day ${pictureOfTheDay.date}`,
external_id: reference_id,
template_variables: {
"d0116ef7-3271-450d-b596-d7a12c408824": pictureOfTheDay.hdurl,
"7be822d6-baf3-11ec-afbd-0afd511c093b": firstSentenceOfExplanation,
"7be82d6f-baf3-11ec-afbd-0afd511c093b": getWrittenDate(
pictureOfTheDay.date
),
"aef58ec9-bb05-11ec-afbd-0afd511c093b":
pictureOfTheDay.copyright || "Unknown Creator",
"aef577ea-bb05-11ec-afbd-0afd511c093b": pictureOfTheDay.title,
},
},
];
const jobResponse = {
template_id: process.env.TEMPLATE_ID,
options: jobOptions,
values: jobValues,
notifications: [
{
type: "callback",
payload: {
url: `${process.env.NASA_PICTURE_OF_THE_DAY_ENDPOINT}/api/v1/demos/nasa-picture-of-day/video-ready`,
},
},
],
};
4.4. Post job to Moovly
const postJobToMoovly = (body) => {
return fetch(MOOVLY_JOB_CREATE_URL, {
method: "POST",
headers: {
Authorization: process.env.API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}).then((res) => res.json());
};
const result = await postJobToMoovly(jobResponse);
4.5. Run Lambda (daily)
Set an event or cron to let the Lambda run at an interval that suits you. In our demo case of a daily video, we run the Lambda once a day.
4.6. Full example code
const fetch = require("node-fetch");
const NASA_PICTURE_OF_THE_DAY_URL = "https://api.nasa.gov/planetary/apod";
const MOOVLY_JOB_CREATE_URL = "https://api.moovly.com/generator/v1/jobs";
const getWrittenDate = (date) =>
new Date(date).toLocaleString("en-us", {
month: "long",
year: "numeric",
day: "numeric",
});
const getNasaPictureOfTheDay = () => {
return fetch(
`${NASA_PICTURE_OF_THE_DAY_URL}?api_key=${process.env.NASA_API_TOKEN}`
).then((res) => res.json());
};
const postJobToMoovly = (body) => {
return fetch(MOOVLY_JOB_CREATE_URL, {
method: "POST",
headers: {
Authorization: process.env.API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}).then((res) => res.json());
};
const handler = async () => {
const pictureOfTheDay = await getNasaPictureOfTheDay();
// We only support images in our demo template
if (pictureOfTheDay.media_type === "image") {
try {
const jobOptions = {
quality: "1080p",
create_render: true,
create_project: false,
};
// The explanation is too long for the template field
const firstSentenceOfExplanation = `${
pictureOfTheDay.explanation.split(".")[0]
}.`;
const jobValues = [
{
title: `Nasa picture of the day ${pictureOfTheDay.date}`,
external_id: reference_id,
template_variables: {
"d0116ef7-3271-450d-b596-d7a12c408824": pictureOfTheDay.hdurl,
"7be822d6-baf3-11ec-afbd-0afd511c093b": firstSentenceOfExplanation,
"7be82d6f-baf3-11ec-afbd-0afd511c093b": getWrittenDate(
pictureOfTheDay.date
),
"aef58ec9-bb05-11ec-afbd-0afd511c093b":
pictureOfTheDay.copyright || "Unknown Creator",
"aef577ea-bb05-11ec-afbd-0afd511c093b": pictureOfTheDay.title,
},
},
];
const jobResponse = {
template_id: process.env.TEMPLATE_ID,
options: jobOptions,
values: jobValues,
notifications: [
{
type: "callback",
payload: {
url: `${process.env.NASA_PICTURE_OF_THE_DAY_ENDPOINT}/api/v1/demos/nasa-picture-of-day/video-ready`,
},
},
],
};
const result = await postJobToMoovly(jobResponse);
} catch (e) {
console.log(e);
}
}
};
module.exports = { handler };