Video Merge

Combine multiple videos and images into a single video with optional audio overlay. Perfect for creating slideshows, video compilations, or adding background music.

Create a Merge Job

Create a merge job to combine multiple assets into a single video:

merge.ts
import { Stack0 } from '@stack0/sdk'
const stack0 = new Stack0({
apiKey: process.env.STACK0_API_KEY!
})
// Create a merge job
const job = await stack0.cdn.createMergeJob({
projectSlug: 'my-project',
inputs: [
{ assetId: 'intro-video-id' },
{ assetId: 'image-id', duration: 5 }, // Show image for 5 seconds
{ assetId: 'main-video-id', startTime: 10, endTime: 60 }, // Trim to 50 seconds
],
audioTrack: {
assetId: 'background-music-id',
loop: true,
fadeIn: 2,
fadeOut: 3,
},
output: {
format: 'mp4',
quality: '1080p',
filename: 'final-video.mp4',
},
webhookUrl: 'https://your-app.com/webhook',
})
console.log(`Merge job started: ${job.id}`)

Input Types

You can merge videos and images in any order. Each input can be configured with the following options:

OptionTypeDescription
assetIdstringUUID of the video or image asset (required)
durationnumberDisplay duration in seconds (required for images, max 3600)
startTimenumberTrim start point in seconds (videos only)
endTimenumberTrim end point in seconds (videos only)

Audio Track Overlay

Add background music or voiceover to your merged video:

audio-overlay.ts
// Add background music with fade effects
const job = await stack0.cdn.createMergeJob({
projectSlug: 'my-project',
inputs: [
{ assetId: 'video-1' },
{ assetId: 'video-2' },
{ assetId: 'video-3' },
],
audioTrack: {
assetId: 'music-track-id',
loop: true, // Loop if audio is shorter than video
fadeIn: 2, // 2 second fade in
fadeOut: 3, // 3 second fade out
},
})
OptionTypeDescription
assetIdstringUUID of the audio asset (required)
loopbooleanLoop audio if shorter than video (default: false)
fadeInnumberFade in duration in seconds (0-10)
fadeOutnumberFade out duration in seconds (0-10)

Output Configuration

Configure the output format and quality:

OptionTypeDescription
formatstringOutput format: mp4, webm (default: mp4)
qualitystringQuality preset: 360p, 480p, 720p, 1080p, 1440p, 2160p (default: 720p)
filenamestringCustom filename for the output (max 255 chars)

Check Job Status

job-status.ts
// Get merge job with output asset details
const job = await stack0.cdn.getMergeJob('job-id')
console.log(`Status: ${job.status}`)
console.log(`Progress: ${job.progress}%`)
if (job.status === 'completed' && job.outputAsset) {
console.log(`Output video: ${job.outputAsset.cdnUrl}`)
console.log(`Duration: ${job.outputAsset.duration}s`)
console.log(`Size: ${job.outputAsset.size} bytes`)
}
// Status values:
// 'pending' - Job created, waiting to be processed
// 'queued' - Job queued for processing
// 'processing' - Currently merging
// 'completed' - Finished successfully
// 'failed' - Merge failed
// 'cancelled' - Job was cancelled

List Merge Jobs

list-jobs.ts
// List all merge jobs for a project
const { jobs, total, hasMore } = await stack0.cdn.listMergeJobs({
projectSlug: 'my-project',
status: 'completed', // Optional filter
limit: 20,
offset: 0,
})
for (const job of jobs) {
console.log(`${job.id}: ${job.status} (${job.progress}%)`)
}

Cancel a Merge Job

cancel-job.ts
// Cancel a pending or processing merge job
await stack0.cdn.cancelMergeJob('job-id')
console.log('Merge job cancelled')

Create a Photo Slideshow

Example: Create a slideshow from images with background music:

slideshow.ts
// Create a photo slideshow with music
const job = await stack0.cdn.createMergeJob({
projectSlug: 'my-project',
inputs: [
{ assetId: 'photo-1', duration: 3 },
{ assetId: 'photo-2', duration: 3 },
{ assetId: 'photo-3', duration: 3 },
{ assetId: 'photo-4', duration: 3 },
{ assetId: 'photo-5', duration: 3 },
],
audioTrack: {
assetId: 'background-music',
fadeIn: 1,
fadeOut: 2,
},
output: {
format: 'mp4',
quality: '1080p',
filename: 'vacation-slideshow.mp4',
},
})
// Poll for completion
let status = job.status
while (status === 'pending' || status === 'queued' || status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 5000))
const updated = await stack0.cdn.getMergeJob(job.id)
status = updated.status
console.log(`Progress: ${updated.progress}%`)
}
const final = await stack0.cdn.getMergeJob(job.id)
if (final.status === 'completed' && final.outputAsset) {
console.log(`Slideshow ready: ${final.outputAsset.cdnUrl}`)
}

Combine Video Clips

Example: Combine multiple video clips with trimming:

combine-clips.ts
// Combine video clips with trimming
const job = await stack0.cdn.createMergeJob({
projectSlug: 'my-project',
inputs: [
// Full intro video
{ assetId: 'intro-video' },
// Trim the highlight from 30s to 90s
{ assetId: 'main-footage', startTime: 30, endTime: 90 },
// Show a title card for 5 seconds
{ assetId: 'title-card-image', duration: 5 },
// Trim outro to just the first 10 seconds
{ assetId: 'outro-video', endTime: 10 },
],
output: {
format: 'mp4',
quality: '1080p',
filename: 'highlight-reel.mp4',
},
webhookUrl: 'https://your-app.com/api/webhook/merge-complete',
})

Webhook Notifications

Receive notifications when a merge job completes:

webhook.ts
// Webhook payload example
{
"event": "merge.completed",
"jobId": "job-uuid-here",
"status": "completed",
"outputAssetId": "asset-uuid-here",
"outputUrl": "https://cdn.stack0.dev/...",
"duration": 125.5,
"createdAt": "2024-01-15T10:00:00Z",
"completedAt": "2024-01-15T10:02:30Z"
}
// Handle webhook in your app
app.post('/api/webhook/merge-complete', (req, res) => {
const { event, jobId, status, outputUrl } = req.body
if (event === 'merge.completed' && status === 'completed') {
console.log(`Merge job ${jobId} completed: ${outputUrl}`)
// Update your database, notify users, etc.
}
res.status(200).send('OK')
})

Limits

Max Inputs

100

videos/images per job

Image Duration

1 hour

max duration per image

Fade Duration

10 sec

max fade in/out