Hello Gumloop Community, I’m working on a custom node that will initiate a text-to-video process using a series of Pictory APIs as shown below (documentation link =. API Reference). I’ve tried to create one end-to-end node and I’ve tried to seperate out the steps as seperate custom nodes but I just can’t seem to crack it. Anybody got any ideas?
const axios = require(‘axios’);
const { CLIENT_ID, CLIENT_SECRET, X_PICTORY_USER_ID } = process.env;
const API_ENDPOINT = ‘https://api.pictory.ai’;
const headers = (token) => ({
Authorization: token,
‘X-Pictory-User-Id’: X_PICTORY_USER_ID,
});
async function getAccessToken() {
const res = await axios.post(${API_ENDPOINT}/pictoryapis/v1/oauth2/token
, {
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
});
return res.data.access_token;
}
async function createStoryboard(token) {
const res = await axios.post(
${API_ENDPOINT}/pictoryapis/v1/video/storyboard
,
{
videoName: ‘InternationalYogaDay’,
videoWidth: 1920,
videoHeight: 1080,
language: ‘en’,
audio: {
autoBackgroundMusic: true,
backGroundMusicVolume: 0.5,
aiVoiceOvers: [
{
speaker: ‘Aditi’,
},
{
speaker: ‘Aveek’,
},
],
},
scenes: [
{
text: “Today, we come together to celebrate a very special occasion — International Yoga Day, observed every year on June 21st. Yoga is more than just a form of exercise. It is a way of life — a practice that connects the body, mind, and spirit. It brings peace, harmony, and balance to our lives. International Yoga Day was first proposed by India and adopted by the United Nations in 2014. Since then, millions across the globe roll out their mats each year to embrace this ancient practice. So today, let’s take a moment to breathe deeply, stretch our bodies, and calm our minds. Whether you’re a beginner or a regular practitioner, yoga has something to offer everyone. Let’s celebrate this day by promoting health, mindfulness, and well-being — not just for ourselves, but for the world around us.”,
voiceOver: true,
splitTextOnNewLine: false,
splitTextOnPeriod: true,
},
],
},
{ headers: headers(token) }
);
return res.data.data.job_id;
}
async function pollStoryboardJobStatus(jobId, token) {
const url = ${API_ENDPOINT}/pictoryapis/v1/jobs/${jobId}
;
let renderParams = null;
do {
const res = await axios.get(url, { headers: headers(token) });
renderParams = res.data.data.renderParams;
if (renderParams) {
return renderParams;
}
await new Promise((r) => setTimeout(r, 5000));
} while (!renderParams);
}
async function pollRenderVideoJobStatus(jobId, token) {
const url = ${API_ENDPOINT}/pictoryapis/v1/jobs/${jobId}
;
let status = ‘’;
do {
const res = await axios.get(url, { headers: headers(token) });
status = res.data.data.status;
if (status === ‘completed’) {
return res.data.data;
}
await new Promise((r) => setTimeout(r, 5000));
} while (status !== ‘Failed’);
}
async function renderVideo(token, storyboardJobId) {
const res = await axios.put(
${API_ENDPOINT}/pictoryapis/v1/video/render/${storyboardJobId}
,
{
webhook: ‘https://webhook.site/f24bfe6a-7065-4bd8-977c-52d184fc4374’,
},
{ headers: headers(token) }
);
return res.data.data.job_id;
}
(async () => {
//Get access_token from token endpoint
const token = await getAccessToken();
//Create storyboard using text from storyboard endpoint
const storyboardJobId = await createStoryboard(token);
//Wait for the storyboard job to complete
const renderParams = await Promise.any([
pollStoryboardJobStatus(storyboardJobId, token),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(‘TIME_OUT’);
}, 30 * 1000); //30 second timeout
}),
]);
//Render storyboard to video using storyboard job ID from render endpoint
const renderJobId = await renderVideo(token, storyboardJobId);
//Wait for render job to complete
const videoOutput = await Promise.any([
pollRenderVideoJobStatus(renderJobId, token),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(‘TIME_OUT’);
}, 5 * 60 * 1000); //5 minutes timeout
}),
]);
//Rendered output
console.log(videoOutput);
})();