Overview
Bulk generation allows you to create hundreds or thousands of personalized videos efficiently using templates and automation.Use Case: Perfect for personalized marketing campaigns, sales outreach, customer onboarding, and more.
Prerequisites
Before bulk generating videos:You need:
- A template with defined variables
- A list of recipients with their data
- A webhook endpoint to receive completion notifications
- Sufficient API credits/quota
Basic Bulk Generation
Step 1: Prepare Your Data
Copy
// recipients.json
const recipients = [
{
name: "Sarah Johnson",
company: "TechCorp",
role: "CEO",
metric: "30% increase in productivity"
},
{
name: "Mike Chen",
company: "StartupXYZ",
role: "CTO",
metric: "50% faster deployment"
},
{
name: "Emma Davis",
company: "BigCorp Inc",
role: "VP of Sales",
metric: "$2M in additional revenue"
}
// ... more recipients
];
Step 2: Simple Bulk Generator
Copy
async function bulkGenerate(templateId, recipients) {
const results = [];
for (const recipient of recipients) {
try {
const response = await fetch('https://api.slidevid.ai/v1/project/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.SLIDEVID_API_KEY
},
body: JSON.stringify({
type: 'class',
templateId: templateId,
title: `Video for ${recipient.name}`,
scenes: [{
script: template.scenes[0].script,
variables: Object.entries(recipient).map(([key, value]) => ({
key,
value: String(value)
}))
}],
webhook: `https://yoursite.com/webhook?recipient=${recipient.email}`
})
});
const result = await response.json();
if (result.success) {
results.push({
recipient: recipient.name,
projectId: result.data.projectId,
status: 'queued'
});
console.log(`✅ Queued video for ${recipient.name}`);
} else {
throw new Error(result.message);
}
// Rate limiting: wait 1 second between requests
await new Promise(r => setTimeout(r, 1000));
} catch (error) {
console.error(`❌ Failed for ${recipient.name}:`, error.message);
results.push({
recipient: recipient.name,
status: 'failed',
error: error.message
});
}
}
return results;
}
// Execute
const results = await bulkGenerate('template_123', recipients);
console.log(`Generated ${results.filter(r => r.status === 'queued').length}/${recipients.length} videos`);
Advanced Bulk Generation
With Rate Limiting & Retries
Copy
class BulkVideoGenerator {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.slidevid.ai/v1';
this.concurrency = options.concurrency || 5;
this.retryAttempts = options.retryAttempts || 3;
this.retryDelay = options.retryDelay || 5000;
}
async generateOne(templateId, recipient, attempt = 1) {
try {
const response = await fetch(`${this.baseUrl}/project/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': this.apiKey
},
body: JSON.stringify({
type: 'class',
templateId,
title: `Video for ${recipient.name}`,
scenes: [{
variables: Object.entries(recipient).map(([key, value]) => ({
key,
value: String(value)
}))
}],
webhook: recipient.webhook
})
});
const result = await response.json();
if (!response.ok) {
// Retry on rate limit
if (response.status === 429 && attempt < this.retryAttempts) {
const retryAfter = response.headers.get('Retry-After') || this.retryDelay / 1000;
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
return this.generateOne(templateId, recipient, attempt + 1);
}
throw new Error(result.message);
}
return {
success: true,
recipient: recipient.name,
projectId: result.data.projectId
};
} catch (error) {
if (attempt < this.retryAttempts) {
console.log(`Retry ${attempt}/${this.retryAttempts} for ${recipient.name}`);
await new Promise(r => setTimeout(r, this.retryDelay));
return this.generateOne(templateId, recipient, attempt + 1);
}
return {
success: false,
recipient: recipient.name,
error: error.message
};
}
}
async generateBulk(templateId, recipients, onProgress) {
const results = [];
const batches = [];
// Split into batches based on concurrency
for (let i = 0; i < recipients.length; i += this.concurrency) {
batches.push(recipients.slice(i, i + this.concurrency));
}
let completed = 0;
for (const batch of batches) {
const batchResults = await Promise.all(
batch.map(recipient => this.generateOne(templateId, recipient))
);
results.push(...batchResults);
completed += batch.length;
if (onProgress) {
onProgress({
completed,
total: recipients.length,
percentage: Math.round((completed / recipients.length) * 100)
});
}
// Wait between batches to respect rate limits
if (batches.indexOf(batch) < batches.length - 1) {
await new Promise(r => setTimeout(r, 2000));
}
}
return results;
}
getStats(results) {
return {
total: results.length,
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
successRate: Math.round((results.filter(r => r.success).length / results.length) * 100)
};
}
}
// Usage
const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY, {
concurrency: 5,
retryAttempts: 3,
retryDelay: 5000
});
const results = await generator.generateBulk(
'template_123',
recipients,
(progress) => {
console.log(`Progress: ${progress.completed}/${progress.total} (${progress.percentage}%)`);
}
);
const stats = generator.getStats(results);
console.log('Generation complete:', stats);
Processing CSV Files
Read and Process CSV
Copy
import fs from 'fs';
import csv from 'csv-parser';
async function processCSV(filePath, templateId) {
const recipients = [];
return new Promise((resolve, reject) => {
fs.createReadStream(filePath)
.pipe(csv())
.on('data', (row) => {
// Validate required fields
if (row.name && row.email) {
recipients.push({
name: row.name,
email: row.email,
company: row.company || 'Unknown',
role: row.role || 'Professional',
// Add more fields as needed
webhook: `https://yoursite.com/webhook?email=${row.email}`
});
}
})
.on('end', async () => {
console.log(`Loaded ${recipients.length} recipients from CSV`);
const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY);
const results = await generator.generateBulk(templateId, recipients);
resolve(results);
})
.on('error', reject);
});
}
// Usage
const results = await processCSV('./recipients.csv', 'template_123');
Database Integration
Generate from Database Records
Copy
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function generateFromDatabase(templateId, filters = {}) {
// Fetch recipients from database
const contacts = await prisma.contact.findMany({
where: {
status: 'active',
hasConsent: true,
...filters
},
select: {
id: true,
name: true,
email: true,
company: true,
customFields: true
}
});
console.log(`Found ${contacts.length} contacts in database`);
const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY);
const results = await generator.generateBulk(
templateId,
contacts.map(c => ({
...c,
...c.customFields,
webhook: `https://yoursite.com/webhook?contactId=${c.id}`
})),
async (progress) => {
// Update progress in database
await prisma.bulkJob.update({
where: { id: jobId },
data: {
progress: progress.percentage,
completed: progress.completed
}
});
}
);
// Save results to database
for (const result of results) {
if (result.success) {
await prisma.video.create({
data: {
projectId: result.projectId,
contactId: result.recipient.id,
status: 'processing'
}
});
}
}
return results;
}
const results = await generateFromDatabase('template_123', {
segment: 'enterprise'
});
Webhook Management for Bulk Jobs
Track Completions
Copy
// In-memory tracking (use Redis/Database for production)
const bulkJobs = new Map();
// Start bulk job
app.post('/api/bulk/start', async (req, res) => {
const { templateId, recipients } = req.body;
const jobId = `job_${Date.now()}`;
bulkJobs.set(jobId, {
id: jobId,
total: recipients.length,
completed: 0,
videos: [],
startedAt: new Date()
});
// Start generation in background
generateBulkAsync(jobId, templateId, recipients);
res.json({ jobId, status: 'started' });
});
// Webhook handler
app.post('/api/webhook', async (req, res) => {
const { projectId, status, video } = req.body;
const jobId = req.query.jobId;
if (jobId && bulkJobs.has(jobId)) {
const job = bulkJobs.get(jobId);
job.completed++;
if (status === 'completed') {
job.videos.push({
projectId,
url: video.url,
completedAt: new Date()
});
}
// Check if job complete
if (job.completed === job.total) {
job.completedAt = new Date();
job.duration = job.completedAt - job.startedAt;
console.log(`Bulk job ${jobId} completed!`);
console.log(`Generated ${job.videos.length}/${job.total} videos in ${job.duration}ms`);
// Notify user
await notifyJobComplete(jobId, job);
}
bulkJobs.set(jobId, job);
}
res.status(200).send('OK');
});
// Check job status
app.get('/api/bulk/:jobId', (req, res) => {
const job = bulkJobs.get(req.params.jobId);
if (!job) {
return res.status(404).json({ error: 'Job not found' });
}
res.json({
...job,
progress: Math.round((job.completed / job.total) * 100),
status: job.completedAt ? 'completed' : 'processing'
});
});
Performance Optimization
Tips for Large Batches
1
Use Concurrency
Process multiple videos in parallel (5-10 concurrent requests)
Copy
const concurrency = 5;
2
Implement Queuing
Use a job queue (Bull, BullMQ) for reliability
Copy
import Queue from 'bull';
const videoQueue = new Queue('videos', {
redis: { host: 'localhost', port: 6379 }
});
videoQueue.process(async (job) => {
return await generateVideo(job.data);
});
3
Cache Templates
Fetch template details once and reuse
Copy
const template = await getTemplate(templateId);
// Reuse for all recipients
4
Monitor Rate Limits
Track and respect API rate limits
Copy
const rateLimiter = new RateLimiter(100, '1h');
Error Handling Strategies
Partial Failure Recovery
Copy
async function generateWithRecovery(templateId, recipients) {
const results = {
successful: [],
failed: [],
pending: [...recipients]
};
// Save state to disk/database
const saveState = () => {
fs.writeFileSync('bulk-state.json', JSON.stringify(results));
};
for (const recipient of recipients) {
try {
const projectId = await generateOne(templateId, recipient);
results.successful.push({ recipient, projectId });
results.pending = results.pending.filter(r => r !== recipient);
} catch (error) {
results.failed.push({ recipient, error: error.message });
results.pending = results.pending.filter(r => r !== recipient);
}
// Save progress
saveState();
}
return results;
}
// Resume from saved state
function resumeBulkGeneration() {
const state = JSON.parse(fs.readFileSync('bulk-state.json'));
return generateWithRecovery(templateId, state.pending);
}
Best Practices
Start Small
Test with 5-10 videos before scaling to hundreds
Use Webhooks
Never poll for bulk jobs - always use webhooks
Monitor Progress
Track success rates and errors in real-time
Implement Retries
Handle transient failures with exponential backoff
Example: Complete Bulk Campaign
Copy
// complete-campaign.js
import { BulkVideoGenerator } from './bulk-generator';
import { sendEmail } from './email-service';
async function runCampaign(campaignId) {
// 1. Load recipients from database
const recipients = await db.recipients.findMany({
where: { campaignId, status: 'pending' }
});
// 2. Get template
const campaign = await db.campaign.findUnique({
where: { id: campaignId },
include: { template: true }
});
// 3. Generate videos
const generator = new BulkVideoGenerator(process.env.SLIDEVID_API_KEY);
const results = await generator.generateBulk(
campaign.template.id,
recipients.map(r => ({
...r,
webhook: `https://mysite.com/webhook?recipientId=${r.id}&campaignId=${campaignId}`
})),
async (progress) => {
// Update campaign progress
await db.campaign.update({
where: { id: campaignId },
data: { progress: progress.percentage }
});
}
);
// 4. Log results
const stats = generator.getStats(results);
await db.campaign.update({
where: { id: campaignId },
data: {
status: 'completed',
stats: stats,
completedAt: new Date()
}
});
// 5. Notify admin
await sendEmail({
to: campaign.owner.email,
subject: `Campaign ${campaign.name} completed`,
text: `Generated ${stats.successful}/${stats.total} videos (${stats.successRate}% success rate)`
});
return results;
}
// Start campaign
runCampaign('campaign_123');