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 demo

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.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.

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.

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.

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.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.

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.

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 };