Help needed to create a Pictory API custom node

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

Hey @Chris1! If you’re reporting an issue with a flow or an error in a run, please include the run link and make sure it’s shareable so we can take a look.

  1. Find your run link on the history page. Format: https://www.gumloop.com/pipeline?run_id={{your_run_id}}&workbook_id={{workbook_id}}

  2. Make it shareable by clicking “Share” → ‘Anyone with the link can view’ in the top-left corner of the flow screen.
    GIF guide

  3. Provide details about the issue—more context helps us troubleshoot faster.

You can find your run history here: https://www.gumloop.com/history

Hey @Chris1 – Can you share the exact error you’re seeing please? Also, just to clarify custom node or run code nodes can’t actually output the file object but you can upload it to the working directory and then reference it via file name.
Upload file - Gumloop. Another option is to upload the file to an external service like S3 and then output the file link.

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.