In this tutorial, you'll learn how to use Node.js to programmatically create story videos for posting on social media, such as Instagram Stories, YouTube Shorts, or TikTok videos.
Particularly in the past few years, the use of vertical short video has really taken off. You can find it pretty much everywhere now, including Instagram (Stories), YouTube (Shorts), and TikTok. This raises the question of whether these videos can be created using code?
The answer is yes, definitely! Especially storytelling videos, which are short videos with a few lines of text, are easily generated using JavaScript. A basic understanding of Node.js will be sufficient. Perhaps you're a software developer and want to give your users the ability to create their own videos for sharing on social media. Or maybe you want to automate your own social media outreach through the use of software. This guide will show you how.
Though this tutorial will specifically focus on video rendering with Node.js, it provides a great starting point for adding AI such as Stable Diffusion or ChatGPT down the road, to make fully AI-driven videos. Here's an example video of what we'll be rendering with JavaScript:
We'll need the following prerequisites:
Also check out the JavaScript Preview SDK if you want to render these videos in the browser!
This project's source code is on GitHub, but I recommend following along to get a better grasp of the basics, so you can create your own video-powered applications in no time.
Start by creating a new directory for our Node.js application:
$ mkdir create-story-video
Navigate to the directory:
$ cd create-story-video
We create the project using the npm init command. This creates a package.json file:
$ npm init -y
Let's install the creatomate NPM package into our project:
$ npm i creatomate
The project is now set up, so we can start adding source files. Besides the index.js file, we'll create a file for each scene we want in our video: createScene1.js, createScene2.js, createScene3.js, and createScene4.js.
First, let's create the index.js, which is the entry point for our project. You can paste the code below into your favorite editor:
1const Creatomate = require('creatomate');
2
3const client = new Creatomate.Client('YOUR_API_KEY');
4
5// TODO: The source of our video
6const source = new Creatomate.Source({
7 outputFormat: 'mp4',
8});
9
10console.log('Please wait while your video is being rendered...');
11
12client.render({ source })
13 .then((renders) => {
14 console.log('Completed:', renders);
15 })
16 .catch((error) => console.error(error));
17
As you can see, we are starting with importing the creatomate package, which enables us to render video from our Node.js application. As we will be using Creatomate's render farm to do the rendering, we do not need to have high-end hardware on the system where our script is running. In order to authenticate with the API, we must specify our API key, which can be found under the project settings. Be sure to replace YOUR_API_KEY with your own API key.
The Creatomate.Client class has an async method called render() that takes the source of the video that we want to generate. Basically, it works like this. Just as we use JavaScript code to build our web applications, we can use JSON to create a video. In other words, this source serves as a blueprint for the video – it describes exactly how the video should look, from the very beginning to the very end. The API can then use that source to render an MP4 video file.
It is truly that simple! Running the code above already gets us a video:
$ node index.js
As our source is as good as empty, we only get a blank screen. But the foundation is already there, we just have to provide a source. That's what we're going to talk about next.
Let's take another look at the video that we intend to make, as shown in the intro. There are four scenes in the video, each with a background clip and caption, plus background music. To make the example code easier to understand, we're going to organize the code for each scene into separate files; createScene1.js, createScene2.js, etc. Obviously, you could also create an array of scenes using a for loop, but in this tutorial, the goal is to keep things simple.
Now let's insert the video source:
1const Creatomate = require('creatomate');
2const { createScene1 } = require('./createScene1');
3const { createScene2 } = require('./createScene2');
4const { createScene3 } = require('./createScene3');
5const { createScene4 } = require('./createScene4');
6
7const client = new Creatomate.Client('YOUR_API_KEY');
8
9const source = new Creatomate.Source({
10
11 // Create a video (mp4). This can also be set to 'gif', 'jpg', or 'png'.
12 outputFormat: 'mp4',
13
14 // Dimensions of the output video
15 width: 1080,
16 height: 1920,
17
18 // Frame rate in frames per second
19 frameRate: 60,
20
21 // Extract a still image from the video to be used as thumbnail or poster
22 snapshotTime: 3.5,
23
24 // Content of the video
25 elements: [
26
27 // Background music
28 new Creatomate.Audio({
29 source: 'https://creatomate-static.s3.amazonaws.com' +
30 '/demo/pixabay-best-summer-128473.mp3',
31 duration: null,
32 audioFadeOut: 2,
33 }),
34
35 createScene1(),
36 createScene2(),
37 createScene3(),
38 createScene4(),
39 ],
40});
41
42console.log('Please wait while your video is being rendered...');
43
44client.render({ source })
45 .then((renders) => {
46 console.log('Completed:', renders);
47 })
48 .catch((error) => console.error(error));
49
Let's take a closer look at the code above before moving forward. First of all, we set outputFormat to mp4 in order to generate an MP4 file. Following this, we define the dimensions of the video by providing its width and height. The frame rate is set to 60 fps.
We then add elements to our video. An element is a component of our video, such as text, images, or video clips. Each element has its own settings, such as source, duration, and audioFadeOut. Here we're adding a Creatomate.Audio element for background music, as well as elements for each scene.
As a matter of brevity, I will not go into detail on each property, but refer to the JSON-to-video documentation for a more detailed explanation. For now, we simply need to know that the duration and audioFadeOut properties will make the music play as long as the video and fade out two seconds before it ends.
Next, we will implement the scenes.
To implement the first scene of the video, let's create a new file, createScene1.js. Here's the code:
1const Creatomate = require('creatomate');
2
3function createScene1() {
4
5 return new Creatomate.Composition({
6
7 track: 1,
8 duration: 5,
9
10 elements: [
11
12 // Background image
13 new Creatomate.Image({
14
15 source: 'https://creatomate-static.s3.amazonaws.com' +
16 '/demo/unsplash-jeremy-bishop-dvACrXUExLs.jpg',
17
18 // Slowly zoom out the background image (Ken Burns effect)
19 animations: [
20 new Creatomate.Scale({
21 easing: 'linear',
22 startScale: '150%',
23 endScale: '100%',
24 fade: false,
25 }),
26 ],
27 }),
28
29 ],
30 });
31}
32
33module.exports = { createScene1 };
34
This function returns a Creatomate.Composition element. The composition element lets us group together multiple elements. By doing this, the group behaves like a single element, so it can be animated and styled like one. It's great for implementing scenes.
By setting track to 1 and duration to 5, the composition will be placed on the 1st track with a duration of 5 seconds. The reason why this is important is that we want the scenes to play sequentially, and for that to happen, they all need to be on the same track. If we don't specify the track property, each scene would be automatically placed on separate tracks, resulting in them all playing at the same time – clearly not our intention.
Additionally, we define an array of elements that make up the composition. As a background image for this scene, we will add a Creatomate.Image element. Next, we'll add a Scale animation to make it look like it's zooming out.
Now that we've got the background all set up, let's add some text. This is accomplished by adding another element to the composition, Creatomate.Text.
As you can see from the following code, the text is positioned using the x, y, width, and height properties. Additionally, we specify a custom style that mimics the default text style of a TikTok video. For a complete listing of all the options available, refer to the text element documentation. Additionally, you can experiment with different styles using the template editor, then open the source editor to see how they relate to each property.
As a final touch, we add a text animation using Creatomate.TextSlideUpLineByLine. Every animation can be fully customized, so take a look at the GitHub repository of the Node.js SDK as a reference, or use the template editor to create your own custom animation.
1const Creatomate = require('creatomate');
2
3function createScene1() {
4
5 return new Creatomate.Composition({
6
7 track: 1,
8 duration: 5,
9
10 elements: [
11
12 // Background image
13 new Creatomate.Image({
14
15 source: 'https://creatomate-static.s3.amazonaws.com' +
16 '/demo/unsplash-jeremy-bishop-dvACrXUExLs.jpg',
17
18 // Slowly zoom out the background image (Ken Burns effect)
19 animations: [
20 new Creatomate.Scale({
21 easing: 'linear',
22 startScale: '150%',
23 endScale: '100%',
24 fade: false,
25 }),
26 ],
27 }),
28
29 new Creatomate.Text({
30
31 // Put the text at the top
32 x: '50%',
33 y: '33%',
34 width: '88%',
35 height: '40%',
36
37 // Relative font size
38 fontFamily: 'Montserrat',
39 fontWeight: '600',
40 fontSize: '6.2 vmin',
41 lineHeight: '100%',
42
43 // Center text alignment
44 xAlignment: '50%',
45 yAlignment: '50%',
46
47 // TikTok default text style
48 fillColor: '#ffffff',
49 strokeColor: '#000000',
50 strokeWidth: '1.05 vmin',
51
52 text: 'Did you know you can create TikTok, Instagram, ' +
53 'and YouTube videos using Node.js? 🔥',
54
55 // Animate the text (slide up line-by-line)
56 animations: [
57 new Creatomate.TextSlideUpLineByLine({
58 time: 0,
59 duration: 1.5,
60 easing: 'quadratic-out',
61 scope: 'split-clip',
62 distance: '100%',
63 }),
64 ],
65
66 }),
67
68 ],
69 });
70}
71
72module.exports = { createScene1 };
73
That's it for this scene. Let's move on to the next one!
The second scene isn't that different from the first, but it has a few new things we'll talk about next. Check out the code below. Just like the previous scene, we're placing the second scene on track 1. As mentioned, this results in this scene automatically being placed after the previous one.
In this scene, we also added a transition property. This is because we want a transition animation between scenes 1 and 2. For that reason, we add a transition to the element that's being transitioned to; the second scene. For this animation, we used Creatomate.WipeDown. As a reminder, you can always view all available animations using the template editor.
Another thing we did differently compared to the first scene is that we used different animations for the background image and text caption. This illustration displays the flexibility of the animations – by changing the startScale and endScale of the Creatomate.Scale animation, we can produce a completely different result. As an example, we could also change the focus point of the zoom using the xAnchor and yAnchor settings. Since these values are omitted in this example, zooming out of the center is the default behavior.
1const Creatomate = require('creatomate');
2
3function createScene2() {
4
5 return new Creatomate.Composition({
6
7 track: 1,
8 duration: 6,
9
10 // Animate between the previous and current scene (wipe down)
11 transition: new Creatomate.WipeDown({
12 duration: 1,
13 fade: false,
14 }),
15
16 elements: [
17
18 // Background image
19 new Creatomate.Image({
20
21 source: 'https://creatomate-static.s3.amazonaws.com' +
22 '/demo/unsplash-matteo-catanese-4KrQq8Z6Y5c.jpg',
23
24 // Slowly zoom in the background image (Ken Burns effect)
25 animations: [
26 new Creatomate.Scale({
27 easing: 'linear',
28 startScale: '100%',
29 endScale: '150%',
30 fade: false,
31 }),
32 ],
33 }),
34
35 new Creatomate.Text({
36
37 // Put the text at the center
38 x: '50%',
39 y: '50%',
40 width: '88%',
41 height: '40%',
42
43 // Relative font size
44 fontFamily: 'Montserrat',
45 fontWeight: '600',
46 fontSize: '6.2 vmin',
47 lineHeight: '100%',
48
49 // Center text alignment
50 xAlignment: '50%',
51 yAlignment: '50%',
52
53 // TikTok default text style
54 fillColor: '#ffffff',
55 strokeColor: '#000000',
56 strokeWidth: '1.05 vmin',
57
58 text: 'You can programmatically add your own text here. ' +
59 'All you need is Node.js! 😊',
60
61 // Animate the text (typewriter effect)
62 animations: [
63 new Creatomate.TextTypewriter({
64 time: 1,
65 duration: 5,
66 easing: 'quadratic-out',
67 typingStart: 0,
68 typingDuration: 1,
69 }),
70 ],
71
72 }),
73
74 ],
75 });
76}
77
78module.exports = { createScene2 };
79
Let's move on to the third scene. We're going to do a few things differently in this composition. To start, we're going to use yet another transition animation; the Creatomate.CircularWipe effect. Also, the background of this scene is a video clip, not an image.
As an example of what can be done with styling options, we used a popular Instagram text style. It is distinguished by its rounded text background. This effect can be achieved using Creatomate.TextBackground. This style enables you to generate Instagram-like videos by code that look identical to those that can be created through the Instagram app.
Like every scene, this text caption is animated differently. As you can see, the animation does not only animate text, but also the text background.
1const Creatomate = require('creatomate');
2
3function createScene3() {
4
5 return new Creatomate.Composition({
6
7 track: 1,
8 duration: 6,
9
10 // Animate between the previous and current scene (circular wipe)
11 transition: new Creatomate.CircularWipe({
12 duration: 1,
13 fade: false,
14 }),
15
16 elements: [
17
18 // Background video
19 new Creatomate.Video({
20 source: 'https://creatomate-static.s3.amazonaws.com' +
21 '/demo/pexels-aerial-footage-of-the-mountains-peak-3121327.mp4',
22 }),
23
24 new Creatomate.Text({
25
26 // Put the text at the bottom
27 x: '50%',
28 y: '66%',
29 width: '88%',
30 height: '40%',
31
32 // Relative font size
33 fontFamily: 'Montserrat',
34 fontWeight: '600',
35 fontSize: '6.2 vmin',
36 lineHeight: '100%',
37
38 // Center text alignment
39 xAlignment: '50%',
40 yAlignment: '50%',
41
42 // Instagram text style
43 fillColor: '#000000',
44 background: new Creatomate.TextBackground('#ffffff', '68%', '32%', '24%', '5%'),
45
46 text: 'Use any type of style, image, video clip, emoji, or background music. 🖌️🎨',
47
48 // Animate the text (slide right line-by-line)
49 animations: [
50 new Creatomate.TextSlideRightLineByLine({
51 time: 1,
52 duration: 1.5,
53 easing: 'quadratic-out',
54 fade: false,
55 backgroundEffect: 'sliding',
56 }),
57 ],
58
59 }),
60
61 ],
62 });
63}
64
65module.exports = { createScene3 };
66
Now we will implement the fourth and final scene of our story video. The only significant difference between this scene and the previous one is the addition of a repeating animation to our text caption.
We're going to make the text wiggle for however long it stays on screen. The animations property below shows that we're combining two animations: Creatomate.TextFlyInLineByLine and Creatomate.Wiggle. As opposed to the fly-in animation, the wiggle animation is a repeating effect. In this case, we provide it with a zRotation of 4° and a frequency of 0.5 Hz, resulting in the text wiggling as it does.
Repeating animations are yet another way to create appealing social media videos. As mentioned, each of these animations can be customized using the template editor, so make sure to explore all the options.
1const Creatomate = require('creatomate');
2
3function createScene4() {
4
5 return new Creatomate.Composition({
6
7 track: 1,
8 duration: 6,
9
10 // Animate between the previous and current scene (fade effect)
11 transition: new Creatomate.Fade({
12 duration: 1,
13 }),
14
15 elements: [
16
17 // Background video
18 new Creatomate.Video({
19 source: 'https://creatomate-static.s3.amazonaws.com' +
20 '/demo/pexels-person-on-a-cliff-overlooking-the-mountains-2040075.mp4',
21 }),
22
23 new Creatomate.Text({
24
25 // Put the text at the top
26 x: '50%',
27 y: '33%',
28 width: '88%',
29 height: '40%',
30
31 // Relative font size
32 fontFamily: 'Montserrat',
33 fontWeight: '600',
34 fontSize: '6.2 vmin',
35 lineHeight: '100%',
36
37 // Center text alignment
38 xAlignment: '50%',
39 yAlignment: '50%',
40
41 // TikTok default text style
42 fillColor: '#ffffff',
43 strokeColor: '#000000',
44 strokeWidth: '1.05 vmin',
45
46 text: 'Automate the creation of any kind of storytelling video. 🚀',
47
48 // Animate the text
49 animations: [
50
51 // Fly-in text animation
52 new Creatomate.TextFlyInLineByLine({
53 time: 1,
54 duration: 1.5,
55 easing: 'quadratic-out',
56 }),
57
58 // Make the text wiggle as long as it is on screen
59 new Creatomate.Wiggle({
60 easing: 'linear',
61 zRotation: '4°',
62 rampDuration: '0%',
63 frequency: '0.5 Hz',
64 }),
65 ],
66
67 }),
68
69 ],
70 });
71}
72
73module.exports = { createScene4 };
74
You can get the full source code of this example from this GitHub repository.
We've now gone through every Node.js file. The only thing left to do is run the script to create the final video. Make sure all source files are saved, then run this command:
$ node index.js
It may take a few moments for the video to render. As soon as the video is complete, you will be provided with an array of renders. Since we are only rendering one video at a time, the output will look like this:
1[
2 {
3 "id": "e528f1f0-5c89-411c-91c6-d8b2da8c4740",
4 "status": "succeeded",
5 "url": "https://cdn.creatomate.com/renders/e528f1f0-5c89-411c-91c6-d8b2da8c4740.mp4",
6 "snapshotUrl": "https://cdn.creatomate.com/snapshots/e528f1f0-5c89-411c-91c6-d8b2da8c4740.jpg",
7 "outputFormat": "mp4",
8 "renderScale": 1,
9 "width": 1080,
10 "height": 1920,
11 "frameRate": 60,
12 "duration": 20,
13 "fileSize": 13633617
14 }
15]
16
You can see that the video has been generated and hosted at the URL that has been returned. We are also provided with a still image of the video at a specific time, as we configured using the snapshotTime option in index.js. This still image can be used as the video's thumbnail or poster.
We have only scratched the surface of what is possible with Node.js in terms of creating video. By utilizing the Node.js video library and its JSON-to-video syntax, we are able to create highly dynamic videos entirely using JavaScript. You can get an idea of how flexible the video SDK is by viewing the example videos, which are all structured and built using the JSON structure described in this tutorial.
If you want to take it a step further by creating more advanced video designs, check out the Node.js quick start guide as well. That article will demonstrate how to create MP4 files using a video editor while still using Node.js.
Happy video rendering!