Make a video with images using the Edit API

Are you looking to a make video from images? Do you want to create a video from images using an API? And, to bring your video to life do you also want to add music, motion effects and transitions?

In this post, I'll show you how you can effortlessly transform your images into stunning videos, using motion effects like the Ken Burns effect, smooth fade transitions and add a soundtrack.

While you can create videos from images using traditional desktop video editing software, this article will focus on converting images to video using an API. Using a video editing API lets you automate the video editing process and create videos at scale.

Some common use cases include:

  • Adding a video slideshow to your real estate listings website
  • Creating product videos from images for your e-commerce website
  • Honouring a loved one with a touching memorial video
  • Transforming your holiday photos into a heart-warming montage

About the Shotstack video editing API

Shotstack is an API first, video automation platform that enables you to create, edit, build, and share dynamic videos at scale. For this post, we will be leveraging Shotstack's Edit API to seamlessly turn images into immersive videos with music and motion effects.

Prerequisites

Before you begin, sign up to the Shotstack website to get a free to use API key. You'll need this key to make authorized requests to the Edit API. You should also be familiar with running commands from the command line of your operating system as we will be running commands using cURL as well as a Node.js script.

Create a video from images using JSON

The simplest way to create a video from images using the Shotstack Edit API us to use cURL and a JSON file.

First, create a new file named edit.json, copy and paste the following JSON and save the file:

{
"timeline": {
"soundtrack": {
"src": "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/music/freepd/advertising.mp3",
"effect": "fadeInFadeOut"
},
"tracks": [
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/189349/pexels-photo-189349.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 0,
"length": 5,
"effect": "zoomIn",
"transition": {
"in": "fade",
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/1680140/pexels-photo-1680140.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 4,
"length": 5,
"effect": "slideUp",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/847393/pexels-photo-847393.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 8,
"length": 5,
"effect": "slideLeft",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/1295138/pexels-photo-1295138.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 12,
"length": 5,
"effect": "zoomOut",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/1452701/pexels-photo-1452701.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 16,
"length": 5,
"effect": "slideDown",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/756856/pexels-photo-756856.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 20,
"length": 5,
"effect": "slideRight",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "https://images.pexels.com/photos/1533720/pexels-photo-1533720.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
"start": 24,
"length": 5,
"effect": "zoomIn",
"transition": {
"out": "fade"
}
}
]
}
]
},
"output": {
"format": "mp4",
"resolution": "sd"
}
}

The JSON schema in this file adds the following properties to the video:

  • The mp3 file to use for the soundtrack.
  • A video track that contains a sequence of clips with an image asset, a URL to an image on the Pexels stock library web site.
  • The settings for each clip including the start time, duration, and visual effects.
  • The output settings for the video to be an MP4 file with SD resolution (1024px x 576px).

Next, from your shell or command line, run the following command, make sure you use your own stage/sandbox API key as the value for the x-api-key header parameter, instead of YOUR_API_KEY:

curl -X POST \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d @edit.json \
https://api.shotstack.io/edit/stage/render

This command sends a POST request to the Shotstack Edit API with the JSON payload in the edit.json file. You should receive a response like this:

{
"success": true,
"message": "Created",
"response": {
"message": "Render Successfully Queued",
"id": "8c9686be-62c1-486b-bd70-13f1f20d936f"
}
}

Copy the id from the response, we'll be using this to check the render status in the next step.

Check the status of the video render

The video will take a few seconds to render so wait for a few moments and then run the following command, make sure to replace the id in the URL with the one received in the previous API call response:

curl -X GET \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
https://api.shotstack.io/edit/stage/render/8c9686be-62c1-486b-bd70-13f1f20d936f

Check the response for a status parameter to see if the video has finished rendering; if it has, it should say done. If it's still in progress, you might see statuses like queued, rendering, saving, or fetching. In that case, simply wait for a few seconds and then send the same request again.

Once you receive the done status in the response, visit the link at the url parameter in your web browser. It should look like this:

Create a video from images using a template

Another way to create a video is using the the Studio Editor within the Shotstack dashboard. The Studio Editor is an online video editor that lets you design a video using a visual timeline based video editor with a preview and drag and drop interface.

Within the template you can add merge fields and placeholders that can be replaced with your own images when you render the video using the API.

You can design the template from scratch but it is also possible to import JSON in to the editor to instantly create a template.

Follow the steps below to create the template:

  1. Open the Shotstack dashboard and open the Shotstack Studio Editor.
  2. Make sure you are using the Stage environment by clicking the environment button in the top right corner if it says Prod and change the stage on the next page.
  3. Click Create Blank Template to create a new empty template.
  4. The New Template modal should appear. Enter a name for the template, select Video as the output, and click Done.
  5. Click on the JSON VIEW toggle located in the bottom left corner of the editing window to open the JSON editor.
  6. In the JSON editor view, copy and paste the following JSON:
{
"timeline": {
"soundtrack": {
"src": "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/music/freepd/advertising.mp3",
"effect": "fadeInFadeOut"
},
"tracks": [
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_1 }}"
},
"start": 0,
"length": 5,
"effect": "zoomIn",
"transition": {
"in": "fade",
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_2 }}"
},
"start": 4,
"length": 5,
"effect": "slideUp",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_3 }}"
},
"start": 8,
"length": 5,
"effect": "slideLeft",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_4 }}"
},
"start": 12,
"length": 5,
"effect": "zoomOut",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_5 }}"
},
"start": 16,
"length": 5,
"effect": "slideDown",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_6 }}"
},
"start": 20,
"length": 5,
"effect": "slideRight",
"transition": {
"out": "fade"
}
}
]
},
{
"clips": [
{
"asset": {
"type": "image",
"src": "{{ IMAGE_7 }}"
},
"start": 24,
"length": 5,
"effect": "zoomIn",
"transition": {
"out": "fade"
},
"scale": 1,
"offset": {
"x": 0,
"y": 0
},
"position": "center"
}
]
}
]
},
"output": {
"format": "mp4",
"size": {
"width": 1024,
"height": 576
}
},
"merge": [
{
"find": "IMAGE_1",
"replace": "https://images.pexels.com/photos/189349/pexels-photo-189349.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_2",
"replace": "https://images.pexels.com/photos/1680140/pexels-photo-1680140.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_3",
"replace": "https://images.pexels.com/photos/847393/pexels-photo-847393.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_4",
"replace": "https://images.pexels.com/photos/1295138/pexels-photo-1295138.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_5",
"replace": "https://images.pexels.com/photos/1452701/pexels-photo-1452701.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_6",
"replace": "https://images.pexels.com/photos/756856/pexels-photo-756856.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_7",
"replace": "https://images.pexels.com/photos/1533720/pexels-photo-1533720.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
}
]
}
  1. Toggle off the JSON VIEW. You should now be able to see a preview of the video in the editor and the timeline.
  2. Click the Save button in the top left corner next to the template name.
  3. Click the Use Template in the top right corner and then choose API from the modal.
  4. A Code Snippet panel will appear on the right hand side of your screen.
  5. Choose cURL from the LANGUAGE/LIBRARY dropdown and Template ID from the DATA FORMAT dropdown.
  6. Click Copy to copy the code ready to run from your command line.

Render the video using the template

The command you copied from the previous step will look similar to below, paste the command to your terminal and run it to send the request to the Edit API templates/render endpoint:

curl --request POST 'https://api.shotstack.io/edit/stage/templates/render' \
--header 'content-type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data-raw '{
"id": "3788ba7b-bea2-451b-aa70-b751b13882bf",
"merge": [
{
"find": "IMAGE_1",
"replace": "https://images.pexels.com/photos/189349/pexels-photo-189349.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_2",
"replace": "https://images.pexels.com/photos/1680140/pexels-photo-1680140.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_3",
"replace": "https://images.pexels.com/photos/847393/pexels-photo-847393.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_4",
"replace": "https://images.pexels.com/photos/1295138/pexels-photo-1295138.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_5",
"replace": "https://images.pexels.com/photos/1452701/pexels-photo-1452701.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_6",
"replace": "https://images.pexels.com/photos/756856/pexels-photo-756856.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_7",
"replace": "https://images.pexels.com/photos/1533720/pexels-photo-1533720.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
}
]
}'

The JSON we pasted in to the video editor is very similar to the original JSON, except instead of adding the URL's to the clips, we use placeholders that look like this: {{ IMAGE_1 }}. The merge fields array contains a list of objects that contain the placeholder to find and the URL to the image that will replace it.

The cURL command uses the template ID created when the template was first saved, and the merge fields array to find the placeholder values and replace them with the images specified.

When you run the command you will receive a response just like in the previous example:

{
"success": true,
"message": "Created",
"response": {
"message": "Render Successfully Queued",
"id": "a8bf787a-f6f0-4d6c-9b79-88b931942876"
}
}

As before, copy the ID and check the status of the video render using the following command, make sure to replace the id in the URL with the correct render id:

curl -X GET \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
https://api.shotstack.io/edit/stage/render/a8bf787a-f6f0-4d6c-9b79-88b931942876

When the status is done you can visit the URL in the url parameter to view the video. It will of course look the same as before, but this time we used a template to create the video.

Modify the video template using different images

You can modify the template to use different images by changing the URL's in the merge fields array. To demonstrate, make the same API call but use different image URLs in the merge fields array:

curl --request POST 'https://api.shotstack.io/edit/stage/templates/render' \
--header 'content-type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data-raw '{
"id": "3788ba7b-bea2-451b-aa70-b751b13882bf",
"merge": [
{
"find": "IMAGE_1",
"replace": "https://images.pexels.com/photos/618833/pexels-photo-618833.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_2",
"replace": "https://images.pexels.com/photos/808465/pexels-photo-808465.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_3",
"replace": "https://images.pexels.com/photos/789380/pexels-photo-789380.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_4",
"replace": "https://images.pexels.com/photos/691034/pexels-photo-691034.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_5",
"replace": "https://images.pexels.com/photos/2335126/pexels-photo-2335126.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_6",
"replace": "https://images.pexels.com/photos/1062057/pexels-photo-1062057.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
},
{
"find": "IMAGE_7",
"replace": "https://images.pexels.com/photos/1004682/pexels-photo-1004682.jpeg?auto=compress&cs=tinysrgb&fit=crop&h=720&w=1280"
}
]
}'

We have now rendered a completely different video using the same template, just by changing the images:

Create video from images using an SDK

If you want to have more programmatic control over video creation, you can use one of our SDKs to interact with the Edit API. We have SDKs for Node.js, PHP, Python, and Ruby. For the purpose of this guide, we'll use the Node.js SDK.

First create a folder where you will add the script and install the Shotstack Node SDK. Then, inside the folder run the following command to install the SDK:

npm install shotstack-sdk

Next, create a new file named edit.js and open it in your favourite code editor and copy the following code to it:

const Shotstack = require('shotstack-sdk');

const defaultClient = Shotstack.ApiClient.instance;
const DeveloperKey = defaultClient.authentications['DeveloperKey'];
const api = new Shotstack.EditApi();

defaultClient.basePath = 'https://api.shotstack.io/edit/stage';
DeveloperKey.apiKey = 'YOUR_API_KEY';

const images = [
'https://images.pexels.com/photos/326259/pexels-photo-326259.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/248747/pexels-photo-248747.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/3311574/pexels-photo-3311574.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/593172/pexels-photo-593172.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/315938/pexels-photo-315938.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/1592384/pexels-photo-1592384.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/3399938/pexels-photo-3399938.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/5214413/pexels-photo-5214413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
];

const clips = [];
const length = 1.5;
let start = 0;

const soundtrack = new Shotstack.Soundtrack;
soundtrack
.setSrc('https://templates.shotstack.io/basic/asset/audio/music/unminus/runs.mp3')
.setEffect('fadeOut');

images.forEach((image) => {
const imageAsset = new Shotstack.ImageAsset;
imageAsset
.setSrc(image);

const clip = new Shotstack.Clip;
clip
.setAsset(imageAsset)
.setStart(start)
.setLength(length)
.setEffect('zoomIn');

start = start + length;
clips.push(clip);
});

const track = new Shotstack.Track;
track
.setClips(clips);

const timeline = new Shotstack.Timeline;
timeline
.setSoundtrack(soundtrack)
.setTracks([track]);

const output = new Shotstack.Output;
output
.setFormat('mp4')
.setResolution('sd');

const edit = new Shotstack.Edit;
edit
.setTimeline(timeline)
.setOutput(output);

api.postRender(edit).then((data) => {
const { id, message } = data.response;

console.log(message);
console.log('>> The render id: ' + id);
}, (error) => {
console.error('Request failed: ', error);
});

The code above uses the Shotstack SDK to create a video montage from an array of images. The array of images is prepared at the top of the script and we then loop through it and use the SDK to create the clips, start time and length, and add a motion effect. The rest of the script uses the SDK to prepare the edit and send it to the Shotstack API for rendering.

Make sure you use your own API key for the sandbox/staging environment as the value for the DeveloperKey.apiKey variable instead of YOUR_API_KEY.

Finally, run the script using the following command:

node edit.js

Expect an output like this:

Render Successfully Queued
>> The render id: 67609896-e1a2-4999-894d-af238f167c11

Copy the id, as we'll be using it to fetch the video in the next step.

Check the status of the video render

Just like we checked the status of the video render using cURL, we can also do this using the SDK.

In the same folder create a file called status.js and copy the following code to it:

const Shotstack = require('shotstack-sdk');

const defaultClient = Shotstack.ApiClient.instance;
const DeveloperKey = defaultClient.authentications['DeveloperKey'];
const api = new Shotstack.EditApi();

defaultClient.basePath = 'https://api.shotstack.io/edit/stage';
DeveloperKey.apiKey = 'YOUR_API_KEY';

const id = process.argv[2];

api.getRender(id).then((data) => {
const { status, url } = data.response;

console.log('Status: ' + status);

if (status == 'done') {
console.log('>> Asset URL: ' + url);
}
}, (error) => {
console.error('Request failed or not found: ', error);
});

Remember to replace the YOUR_API_KEY placeholder with your own API key once again.

Next run the script using the following command, make sure to replace the id in the command with the one you copied from the previous step:

node status.js 67609896-e1a2-4999-894d-af238f167c11

Once the video has finished rendering and the status is done you should see an output like below:

Status: done
>> Asset URL: https://shotstack-api-stage-output.s3-ap-southeast-2.amazonaws.com/hkwxdnk0f1/67609896-e1a2-4999-894d-af238f167c11.mp4

Open the Asset URL in your browser to play the video. Here is how the video looks, this time we used a different soundtrack and automotive themed photos:

Because the video is generated programmatically from an array of images you can now start having fun creating new videos. You could populate the array from a database or 3rd party API, like the Pexels API using their search endpoint.

Here is an example using some very simple array functions to randomise and splice the array of images, just add these lines after the image array is declared:

// Shuffle the order of the array
images.sort(() => Math.random() - 0.5);

// Remove up to 4 images from the array
images.splice(0, Math.floor(Math.random() * 4) + 1);

Now the video will be different every time you run the script. Give it a try and see what other ways you can come up with the generate slideshow style videos from images.

Maab Saleem

BY MAAB SALEEM
17th January, 2024

Become an Automated Video Editing Pro

Every month we share articles like this one to keep you up to speed with automated video editing.