From 42656b7b4ce2c85e5cec1676ab22876e38440019 Mon Sep 17 00:00:00 2001 From: Derk Zomer Date: Sun, 10 Jul 2022 21:29:52 +1000 Subject: [PATCH] Revert "1000 videos" --- .../1000-videos-in-60-seconds/README.md | 298 ------------------ .../edit-api/1000-videos-in-60-seconds/app.js | 70 ---- .../1000-videos-in-60-seconds/template.json | 128 -------- 3 files changed, 496 deletions(-) delete mode 100644 examples/edit-api/1000-videos-in-60-seconds/README.md delete mode 100644 examples/edit-api/1000-videos-in-60-seconds/app.js delete mode 100644 examples/edit-api/1000-videos-in-60-seconds/template.json diff --git a/examples/edit-api/1000-videos-in-60-seconds/README.md b/examples/edit-api/1000-videos-in-60-seconds/README.md deleted file mode 100644 index fbc5d36..0000000 --- a/examples/edit-api/1000-videos-in-60-seconds/README.md +++ /dev/null @@ -1,298 +0,0 @@ -# How I built 1,000 personalised videos in 60 seconds -In this article, we will show you how to build 1,000 personalized birthday videos for 1,000 different people. This is -close to impossible to do using traditional video editors such as Adobe [Premiere](https://www.adobe.com/au/products/premiere.html) -and After [Effects](https://www.adobe.com/au/products/aftereffects.html), with specialized personalization agencies -charging tens of thousands of dollars for this type of service. - -The Shotstack API allows you to render tens of thousands of videos in the cloud, and personalise each individual video -through small changes to a JSON file. - -With our API capable of rendering hundreds videos concurrently in the cloud, **the ability to work at enormous scale -allows you to render all 1,000 of our videos in less than 30 seconds.** - -# Getting started - -## Shotstack API key - -The speed behind this example was made possible through a Shotstack Enterprise account, but you can sign up for a -[free account](https://dashboard.shotstack.io/register) that provides you with all the same functionalities -and allows you to test the service. - -The example in this article has some throttling built in, but you'll have to be aware of the usage limits of 1 request -per second, and 2,000 requests per month for free accounts if you plan to try this out yourself. - -## Personalised videos using Node.js - -I'll use vanilla javascript through [Node.js](https://nodejs.org/en/) to build the application, but feel free to use what -works for you. We have SDKs available for [PHP](https://github.com/shotstack/shotstack-sdk-php), [Node](https://github.com/shotstack/shotstack-sdk-node), and [Ruby](https://github.com/shotstack/shotstack-sdk-ruby). - -### Personalised videos using Integromat - -You can also use our [Integromat app](https://www.integromat.com/en/integrations/shotstack?pc=shotstack) to achieve -the same result without using any code at all. - -# Choosing our footage - -I built a promotional video which we'll use to offer our fictitious customers a discount on their birthday. This video has -been completely built within Shotstack, but you can achieve the same result using a pre-rendered video built in a video editing -solution such as After Effects. - -https://youtu.be/01I6xFbhXFE - -We'll remove some of the video's content, and use those empty spaces for our personalised data. This data will be placed into -a JSON file, and will place back all of those missing items with new values and animations. - -For this particular template we'll personalise the name, age, their younger "fake" age, the discount code, and the discount -amount. - -https://youtu.be/2rijxKxUdXY - -Shotstack uses a JSON object that acts like an editable timeline. This works in a similar fashion to how any desktop video -editing solution would work, with a timeline representing the video duration, and individual clips allowing you to manipulate its content. - -For this article I won't go into how this video was built, but if you're interested in understanding how this particular video -was edited you can [take a closer look at](https://gist.github.com/derkzomer/78cc7527d293fd1b8b26fc29c063c931) the JSON template. -And if you're really keen [this tutorial](https://shotstack.io/learn/hello-world) goes a little deeper into how you can design these videos yourself. - -For this example we'll use a simplified template where the animations have been pre-rendered, making it easy for us to add in -specific HTML assets where our personalised content should go: - -```json -{ - "timeline": { - "fonts": [ - { - "src": "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/fonts/FiraCode-Regular.ttf" - }, - { - "src": "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/fonts/Nexa-Bold.otf" - } - ], - "background": "#000000", - "tracks": [ - { - "clips": [ - { - "asset": { - "type": "html", - "html": "

firstName

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 128px; color: #ffffff; text-align: left; }" - }, - "start": 2.35, - "length": 1.45, - "offset": { - "y": -0.23, - "x": 0.05 - }, - "transition": { - "out": "carouselDown" - } - }, - { - "asset": { - "type": "html", - "html": "

age

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 149px; color: white; text-align: left; }" - }, - "position": "center", - "start": 5.2, - "length": 1.3, - "offset": { - "y": -0.23, - "x": 0.05 - }, - "transition": { - "in": "zoom", - "out": "zoom" - } - }, - { - "asset": { - "type": "html", - "html": "

fakeAge

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 384px; color: #ffffff; text-align: left;}" - }, - "position": "center", - "start": 11.15, - "length": 2.5, - "offset": { - "y": -0.05, - "x": 0.05 - }, - "transition": { - "out": "carouselLeft" - } - }, - { - "asset": { - "type": "html", - "html": "

discountCode

", - "css": "p { font-family: \"Fira Coda\"; font-size: 42px; color: #3498db; text-align: center; background-color: #ffffff; padding: 32px; line-height: 106px; }", - "width": 320, - "height": 107 - }, - "start": 17.2, - "length": 4.8, - "offset": { - "y": -0.05, - "x": 0 - } - }, - { - "asset": { - "type": "html", - "html": "

For discount Off

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 58px; color: #3498db; text-align: left;} span { color: #e74c3c; }" - }, - "start": 19.2, - "length": 1.2, - "offset": { - "y": -0.302, - "x": 0.04 - } - }, - { - "asset": { - "type": "html", - "html": "

For discount Off

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 58px; color: #3498db; text-align: left;} span { color: #e74c3c; }" - }, - "start": 20.3, - "length": 1.7, - "offset": { - "y": -0.302, - "x": 0.04 - } - } - ] - }, - { - "clips": [ - { - "asset": { - "type": "video", - "src": "https://shotstack-content.s3-ap-southeast-2.amazonaws.com/birthday/birthday-template.mp4", - "volume": 1 - }, - "start": 0, - "length": 22 - } - ] - } - ] - }, - "output": { - "format": "mp4", - "resolution": "sd" - } -} -``` - -# Personalise videos using a spreadsheet - -For our customers we'll use a dataset with information on about 1,000 concocted users. This will include their name, age, fake age, -their discount code, and the discount amount. We will then use the data in this CSV to fill in the JSON template and send it to the -API. You can find the complete [spreadsheet](https://gist.github.com/derkzomer/154768b3c6f4a84e6fd71a31b53e92ce) on GitHub. - -# Create videos from CSV data - -The only thing you need to do now is iterate over the CSV file, add those personalised datapoints to the JSON template, and send -each video edit to the API for rendering. - -The below script works using an .env environment file with the Shotstack API key details in it. You can take a look at my -[.env file](https://gist.github.com/derkzomer/683737119911be3a2a73fe5520bb9b7e) to see how we can use environment -variables in our script. - -```javascript -require("dotenv").config(); - -const fs = require("fs"); -const axios = require("axios").default; -const csv = require("fast-csv"); -const argv = require("yargs").argv; -const numWords = require("num-words"); -const getAge = require("get-age"); -const capitalize = require("capitalize"); -const Throttle = require("throttle-stream"); - -const USER_LIST = "./user-list-test.csv"; -const VIDEO_LIST = "./video-list.csv"; -const API_KEY = process.env.SHOTSTACK_KEY; -const ENDPOINT = process.env.SHOTSTACK_ENDPOINT; -const CUSTOMER_ID = process.env.SHOTSTACK_CUSTOMER_ID; -const PREVIEW_URL = process.env.SHOTSTACK_PREVIEW_URL; -const TEMPLATE = fs.readFileSync("./template.json", "utf8"); -const fileStream = fs.createWriteStream(VIDEO_LIST, { flags: "a" }); - -let count = 0; - -fs.createReadStream(USER_LIST) - .pipe(new Throttle({ bytes: 200, interval: 1000 })) - .pipe(csv.parse()) - .on("data", (row) => { - let age = getAge(row[1]); - let ageInWords = capitalize.words(numWords(age)); - - var mapObj = { - firstName: row[0], - age: ageInWords, - fakeAge: row[2], - discountCode: row[3], - discount: row[4], - }; - - let template = JSON.parse( - JSON.stringify(TEMPLATE).replace( - /firstName|age|fakeAge|discountCode|discount/gi, - function (matched) { - return mapObj[matched]; - } - ) - ); - - axios({ - method: "post", - url: ENDPOINT, - headers: { - "x-api-key": API_KEY, - "content-type": "application/json", - }, - data: template, - }).then( - (response) => { - if (response.status !== 201) { - console.log(row[0], response.data.response.id); - return; - } - - let video = response.data.response.id + ".mp4"; - fileStream.write(`${row[0]},${PREVIEW_URL}${CUSTOMER_ID}/${video}\n`); - console.log("Video queued for: " + row[0]); - }, - (error) => { - throw error; - } - ); - }); -``` - -# The result - 1000 personalised videos - -Once you run the script you'll see all thousand personalised templates be sent through to the API for rendering, and all output -data will end up in a CSV file called video-list.csv, which will include the urls for each individual video. -Image for post - -![A small sample of the 10,000 personalised videos created](/assets/img/learn/articles/birthday-mosaic.jpg) - -The below list contains the first 20 videos, but if you don't believe me you can find the whole list [here](https://gist.github.com/derkzomer/33c0301bbd5024d8a8a7fe6560afe487). - - - -# So what next? - -Personalised marketing [has shown](https://www.forbes.com/sites/jiawertz/2018/08/31/personalized-video-content-can-be-the-marketing-breakthrough-brands-need/?sh=3974fbf32e6f) -to lead to higher email open rates, higher click-through rates, better engagement, and provides you with a new way to build relationships with your audience. - -The above only shows you a small example of what can be done by personalising videos at scale. You can use Shotstack to build -personalised media experiences for your customers and automate your customer engagement by linking Shotstack to Sendgrid via -Integromat, or many other hyper-personalised automations that takes your customer engagement strategy to the next level. diff --git a/examples/edit-api/1000-videos-in-60-seconds/app.js b/examples/edit-api/1000-videos-in-60-seconds/app.js deleted file mode 100644 index 363bd84..0000000 --- a/examples/edit-api/1000-videos-in-60-seconds/app.js +++ /dev/null @@ -1,70 +0,0 @@ -require("dotenv").config(); - -const fs = require("fs"); -const axios = require("axios").default; -const csv = require("fast-csv"); -const argv = require("yargs").argv; -const numWords = require("num-words"); -const getAge = require("get-age"); -const capitalize = require("capitalize"); -const Throttle = require("throttle-stream"); - -const USER_LIST = "./user-list-test.csv"; -const VIDEO_LIST = "./video-list.csv"; -const API_KEY = process.env.SHOTSTACK_KEY; -const ENDPOINT = process.env.SHOTSTACK_ENDPOINT; -const CUSTOMER_ID = process.env.SHOTSTACK_CUSTOMER_ID; -const PREVIEW_URL = process.env.SHOTSTACK_PREVIEW_URL; -const TEMPLATE = fs.readFileSync("./template.json", "utf8"); -const fileStream = fs.createWriteStream(VIDEO_LIST, { flags: "a" }); - -let count = 0; - -fs.createReadStream(USER_LIST) - .pipe(new Throttle({ bytes: 200, interval: 1000 })) - .pipe(csv.parse()) - .on("data", (row) => { - let age = getAge(row[1]); - let ageInWords = capitalize.words(numWords(age)); - - var mapObj = { - firstName: row[0], - age: ageInWords, - fakeAge: row[2], - discountCode: row[3], - discount: row[4], - }; - - let template = JSON.parse( - JSON.stringify(TEMPLATE).replace( - /firstName|age|fakeAge|discountCode|discount/gi, - function (matched) { - return mapObj[matched]; - } - ) - ); - - axios({ - method: "post", - url: ENDPOINT, - headers: { - "x-api-key": API_KEY, - "content-type": "application/json", - }, - data: template, - }).then( - (response) => { - if (response.status !== 201) { - console.log(row[0], response.data.response.id); - return; - } - - let video = response.data.response.id + ".mp4"; - fileStream.write(`${row[0]},${PREVIEW_URL}${CUSTOMER_ID}/${video}\n`); - console.log("Video queued for: " + row[0]); - }, - (error) => { - throw error; - } - ); - }); \ No newline at end of file diff --git a/examples/edit-api/1000-videos-in-60-seconds/template.json b/examples/edit-api/1000-videos-in-60-seconds/template.json deleted file mode 100644 index e20ba7c..0000000 --- a/examples/edit-api/1000-videos-in-60-seconds/template.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "timeline": { - "fonts": [ - { - "src": "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/fonts/FiraCode-Regular.ttf" - }, - { - "src": "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/fonts/Nexa-Bold.otf" - } - ], - "background": "#000000", - "tracks": [ - { - "clips": [ - { - "asset": { - "type": "html", - "html": "

firstName

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 128px; color: #ffffff; text-align: left; }" - }, - "start": 2.35, - "length": 1.45, - "offset": { - "y": -0.23, - "x": 0.05 - }, - "transition": { - "out": "carouselDown" - } - }, - { - "asset": { - "type": "html", - "html": "

age

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 149px; color: white; text-align: left; }" - }, - "position": "center", - "start": 5.2, - "length": 1.3, - "offset": { - "y": -0.23, - "x": 0.05 - }, - "transition": { - "in": "zoom", - "out": "zoom" - } - }, - { - "asset": { - "type": "html", - "html": "

fakeAge

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 384px; color: #ffffff; text-align: left;}" - }, - "position": "center", - "start": 11.15, - "length": 2.5, - "offset": { - "y": -0.05, - "x": 0.05 - }, - "transition": { - "out": "carouselLeft" - } - }, - { - "asset": { - "type": "html", - "html": "

discountCode

", - "css": "p { font-family: \"Fira Coda\"; font-size: 42px; color: #3498db; text-align: center; background-color: #ffffff; padding: 32px; line-height: 106px; }", - "width": 320, - "height": 107 - }, - "start": 17.2, - "length": 4.8, - "offset": { - "y": -0.05, - "x": 0 - } - }, - { - "asset": { - "type": "html", - "html": "

For discount Off

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 58px; color: #3498db; text-align: left;} span { color: #e74c3c; }" - }, - "start": 19.2, - "length": 1.2, - "offset": { - "y": -0.302, - "x": 0.04 - } - }, - { - "asset": { - "type": "html", - "html": "

For discount Off

", - "css": "p { font-family: \"Nexa Bold\"; font-size: 58px; color: #3498db; text-align: left;} span { color: #e74c3c; }" - }, - "start": 20.3, - "length": 1.7, - "offset": { - "y": -0.302, - "x": 0.04 - } - } - ] - }, - { - "clips": [ - { - "asset": { - "type": "video", - "src": "https://shotstack-content.s3-ap-southeast-2.amazonaws.com/birthday/birthday-template.mp4", - "volume": 1 - }, - "start": 0, - "length": 22 - } - ] - } - ] - }, - "output": { - "format": "mp4", - "resolution": "sd" - } -} \ No newline at end of file