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 jobconst 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:
| Option | Type | Description |
|---|---|---|
| assetId | string | UUID of the video or image asset (required) |
| duration | number | Display duration in seconds (required for images, max 3600) |
| startTime | number | Trim start point in seconds (videos only) |
| endTime | number | Trim 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 effectsconst 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 videofadeIn: 2, // 2 second fade infadeOut: 3, // 3 second fade out},})
| Option | Type | Description |
|---|---|---|
| assetId | string | UUID of the audio asset (required) |
| loop | boolean | Loop audio if shorter than video (default: false) |
| fadeIn | number | Fade in duration in seconds (0-10) |
| fadeOut | number | Fade out duration in seconds (0-10) |
Output Configuration
Configure the output format and quality:
| Option | Type | Description |
|---|---|---|
| format | string | Output format: mp4, webm (default: mp4) |
| quality | string | Quality preset: 360p, 480p, 720p, 1080p, 1440p, 2160p (default: 720p) |
| filename | string | Custom filename for the output (max 255 chars) |
Check Job Status
job-status.ts
// Get merge job with output asset detailsconst 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 projectconst { jobs, total, hasMore } = await stack0.cdn.listMergeJobs({projectSlug: 'my-project',status: 'completed', // Optional filterlimit: 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 jobawait 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 musicconst 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 completionlet status = job.statuswhile (status === 'pending' || status === 'queued' || status === 'processing') {await new Promise(resolve => setTimeout(resolve, 5000))const updated = await stack0.cdn.getMergeJob(job.id)status = updated.statusconsole.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 trimmingconst 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 appapp.post('/api/webhook/merge-complete', (req, res) => {const { event, jobId, status, outputUrl } = req.bodyif (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