Composition Class
TheComposition class is the core of the media layer - it handles local FFmpeg operations for professional video editing. No API calls or credits required.
Media Layer: This class processes videos locally using FFmpeg. No internet connection or credits required. For background removal (API layer), see VideoBGRemoverClient.
Creating Compositions
- Node.js
- Python
Copy
import { Composition, Background } from '@videobgremover/sdk'
// With background
const bg = Background.fromColor('#FF0000', 1920, 1080, 30)
const comp = new Composition(bg)
// With explicit canvas size
const comp = Composition.canvas(1920, 1080, 30)
// Empty composition (add background later)
const comp = new Composition()
comp.background(bg)
Copy
from videobgremover import Composition, Background
# With background
bg = Background.from_color('#FF0000', 1920, 1080, 30)
comp = Composition(bg)
# With explicit canvas size
comp = Composition.canvas(1920, 1080, 30)
# Empty composition (add background later)
comp = Composition()
comp.background(bg)
Adding Layers
Add transparent videos as layers in your composition:- Node.js
- Python
Copy
// Add layer and get handle for configuration
const layer = comp.add(transparent, 'main_video')
// Configure the layer using fluent API
layer.at(Anchor.CENTER)
.size(SizeMode.CONTAIN)
.opacity(0.9)
// Or chain everything
comp.add(transparent, 'pip')
.at(Anchor.TOP_RIGHT, -50, 50)
.size(SizeMode.CANVAS_PERCENT, { percent: 25 })
.start(5.0)
.duration(10.0)
.opacity(0.8)
Copy
# Add layer and get handle for configuration
layer = comp.add(transparent, 'main_video')
# Configure the layer using fluent API
layer.at(Anchor.CENTER) \
.size(SizeMode.CONTAIN) \
.opacity(0.9)
# Or chain everything
comp.add(transparent, 'pip') \
.at(Anchor.TOP_RIGHT, dx=-50, dy=50) \
.size(SizeMode.CANVAS_PERCENT, percent=25) \
.start(5.0) \
.duration(10.0) \
.opacity(0.8)
Canvas Configuration
- Node.js
- Python
Copy
// Set explicit canvas dimensions
const comp = new Composition(background)
const resizedComp = comp.setCanvas(3840, 2160, 60) // 4K 60fps
// Set explicit duration
const timedComp = comp.setDuration(30.0) // Force 30-second duration
Copy
# Set explicit canvas dimensions
comp = Composition(background)
comp.set_canvas(3840, 2160, 60) # 4K 60fps
# Set explicit duration
comp.set_duration(30.0) # Force 30-second duration
Export Methods
- Node.js
- Python
Copy
// Export to file
await comp.toFile('output.mp4', EncoderProfile.h264())
// With progress tracking
const progressCallback = (status: string) => {
console.log(`Export: ${status}`)
}
await comp.toFile('output.mp4', EncoderProfile.h264(), progressCallback)
// Verbose output for debugging
await comp.toFile('output.mp4', EncoderProfile.h264(), undefined, true)
// See FFmpeg command without executing
const command = comp.dryRun()
console.log('FFmpeg command:', command)
Copy
# Export to file
comp.to_file('output.mp4', EncoderProfile.h264())
# With progress tracking
def progress_callback(status):
print(f'Export: {status}')
comp.to_file('output.mp4', EncoderProfile.h264(), on_progress=progress_callback)
# Verbose output for debugging
comp.to_file('output.mp4', EncoderProfile.h264(), verbose=True)
# See FFmpeg command without executing
command = comp.dry_run()
print(f'FFmpeg command: {command}')
LayerHandle Class
TheLayerHandle provides a fluent API for configuring individual layers. You get a handle when calling comp.add().
Positioning Methods
- Node.js
- Python
Copy
const layer = comp.add(transparent, 'my_layer')
// Basic anchor positioning
layer.at(Anchor.CENTER)
layer.at(Anchor.TOP_LEFT)
layer.at(Anchor.BOTTOM_RIGHT)
// With pixel offsets
layer.at(Anchor.CENTER, 100, 50) // 100px right, 50px down
layer.at(Anchor.TOP_RIGHT, -30, 30) // 30px from right edge, 30px from top
// Custom expressions for dynamic positioning
layer.xy('W/2 + 100*cos(2*PI*t/5)', 'H/2 + 100*sin(2*PI*t/5)') // Circular motion
Copy
layer = comp.add(transparent, 'my_layer')
# Basic anchor positioning
layer.at(Anchor.CENTER)
layer.at(Anchor.TOP_LEFT)
layer.at(Anchor.BOTTOM_RIGHT)
# With pixel offsets
layer.at(Anchor.CENTER, dx=100, dy=50) # 100px right, 50px down
layer.at(Anchor.TOP_RIGHT, dx=-30, dy=30) # 30px from right edge, 30px from top
# Custom expressions for dynamic positioning
layer.xy('W/2 + 100*cos(2*PI*t/5)', 'H/2 + 100*sin(2*PI*t/5)') # Circular motion
Sizing Methods
- Node.js
- Python
Copy
// Fit within canvas (letterbox if needed)
layer.size(SizeMode.CONTAIN)
// Fill canvas (crop if needed)
layer.size(SizeMode.COVER)
// Exact pixel dimensions
layer.size(SizeMode.PX, { width: 800, height: 600 })
// Percentage of canvas
layer.size(SizeMode.CANVAS_PERCENT, { percent: 50 }) // 50% square
layer.size(SizeMode.CANVAS_PERCENT, { width: 75, height: 25 }) // 75% wide, 25% tall
layer.size(SizeMode.CANVAS_PERCENT, { width: 60 }) // 60% wide, maintain aspect
// Scale relative to original video
layer.size(SizeMode.SCALE, { scale: 1.5 }) // 150% of original
layer.size(SizeMode.SCALE, { width: 2.0, height: 0.8 }) // 200% wide, 80% tall
// Fit to canvas dimensions
layer.size(SizeMode.FIT_WIDTH) // Scale to match canvas width
layer.size(SizeMode.FIT_HEIGHT) // Scale to match canvas height
Copy
# Fit within canvas (letterbox if needed)
layer.size(SizeMode.CONTAIN)
# Fill canvas (crop if needed)
layer.size(SizeMode.COVER)
# Exact pixel dimensions
layer.size(SizeMode.PX, width=800, height=600)
# Percentage of canvas
layer.size(SizeMode.CANVAS_PERCENT, percent=50) # 50% square
layer.size(SizeMode.CANVAS_PERCENT, width=75, height=25) # 75% wide, 25% tall
layer.size(SizeMode.CANVAS_PERCENT, width=60) # 60% wide, maintain aspect
# Scale relative to original video
layer.size(SizeMode.SCALE, scale=1.5) # 150% of original
layer.size(SizeMode.SCALE, width=2.0, height=0.8) # 200% wide, 80% tall
# Fit to canvas dimensions
layer.size(SizeMode.FIT_WIDTH) # Scale to match canvas width
layer.size(SizeMode.FIT_HEIGHT) # Scale to match canvas height
Visual Effects
- Node.js
- Python
Copy
// Opacity (0.0 = invisible, 1.0 = opaque)
layer.opacity(0.8)
// Rotation in degrees
layer.rotate(15.0) // 15° clockwise
layer.rotate(-45.0) // 45° counter-clockwise
// Cropping (x, y, width, height)
layer.crop(10, 20, 800, 600)
// Z-order (higher = in front)
layer.z(10)
// Alpha channel control
layer.alpha(true) // Use transparency (default)
layer.alpha(false) // Ignore transparency (opaque)
Copy
# Opacity (0.0 = invisible, 1.0 = opaque)
layer.opacity(0.8)
# Rotation in degrees
layer.rotate(15.0) # 15° clockwise
layer.rotate(-45.0) # 45° counter-clockwise
# Cropping (x, y, width, height)
layer.crop(10, 20, 800, 600)
# Z-order (higher = in front)
layer.z(10)
# Alpha channel control
layer.alpha(enabled=True) # Use transparency (default)
layer.alpha(enabled=False) # Ignore transparency (opaque)
Timing Methods
- Node.js
- Python
Copy
// Composition timeline control
layer.start(2.0) // Start at 2 seconds
layer.end(10.0) // End at 10 seconds
layer.duration(5.0) // Show for 5 seconds (from start time)
// Source trimming (which part of source video to use)
layer.subclip(5, 15) // Use seconds 5-15 of source video
layer.subclip(3) // Use from 3 seconds to end of source
Copy
# Composition timeline control
layer.start(2.0) # Start at 2 seconds
layer.end(10.0) # End at 10 seconds
layer.duration(5.0) # Show for 5 seconds (from start time)
# Source trimming (which part of source video to use)
layer.subclip(5, 15) # Use seconds 5-15 of source video
layer.subclip(3) # Use from 3 seconds to end of source
Audio Control
- Node.js
- Python
Copy
// Audio from this layer
layer.audio(true, 1.0) // Enable audio at full volume
layer.audio(true, 0.5) // Enable audio at 50% volume
layer.audio(false) // Disable audio
Copy
# Audio from this layer
layer.audio(enabled=True, volume=1.0) # Enable audio at full volume
layer.audio(enabled=True, volume=0.5) # Enable audio at 50% volume
layer.audio(enabled=False) # Disable audio
Method Chaining
All LayerHandle methods return the handle itself, allowing for fluent chaining:- Node.js
- Python
Copy
// Chain multiple operations
comp.add(transparent, 'complex_layer')
.at(Anchor.TOP_RIGHT, -50, 50) // Position
.size(SizeMode.CANVAS_PERCENT, { percent: 30 }) // Size
.opacity(0.8) // Visual effect
.rotate(10.0) // Visual effect
.start(5.0) // Timing
.duration(10.0) // Timing
.audio(true, 0.7) // Audio
.z(20) // Z-order
Copy
# Chain multiple operations
comp.add(transparent, 'complex_layer') \
.at(Anchor.TOP_RIGHT, dx=-50, dy=50) \ # Position
.size(SizeMode.CANVAS_PERCENT, percent=30) \ # Size
.opacity(0.8) \ # Visual effect
.rotate(10.0) \ # Visual effect
.start(5.0) \ # Timing
.duration(10.0) \ # Timing
.audio(enabled=True, volume=0.7) \ # Audio
.z(20) # Z-order
Duration Control
Compositions follow a simple 3-rule system for determining duration:Rule 1: Video Background Controls Duration
- Node.js
- Python
Copy
// 30-second background video = 30-second composition
const bg = Background.fromVideo('30sec_background.mp4')
const comp = new Composition(bg)
comp.add(shortForeground) // Even if foreground is 5 seconds
// Final video: 30 seconds
Copy
# 30-second background video = 30-second composition
bg = Background.from_video('30sec_background.mp4')
comp = Composition(bg)
comp.add(short_foreground) # Even if foreground is 5 seconds
# Final video: 30 seconds
Rule 2: Color/Image Backgrounds Use Longest Foreground
- Node.js
- Python
Copy
// Color background adapts to content
const bg = Background.fromColor('#FF0000', 1920, 1080, 30)
const comp = new Composition(bg)
comp.add(tenSecondVideo) // 10-second video
comp.add(fiveSecondVideo) // 5-second video
// Final video: 10 seconds (longest foreground)
Copy
# Color background adapts to content
bg = Background.from_color('#FF0000', 1920, 1080, 30)
comp = Composition(bg)
comp.add(ten_second_video) # 10-second video
comp.add(five_second_video) # 5-second video
# Final video: 10 seconds (longest foreground)
Rule 3: Explicit Duration Override
- Node.js
- Python
Copy
// Force specific duration
const comp = new Composition(anyBackground)
comp.setDuration(45.0) // Force 45-second duration
comp.add(video)
// Final video: exactly 45 seconds
Copy
# Force specific duration
comp = Composition(any_background)
comp.set_duration(45.0) # Force 45-second duration
comp.add(video)
# Final video: exactly 45 seconds
Audio System
The composition system automatically handles audio from multiple sources:Single Audio Source
- Node.js
- Python
Copy
// Only one layer has audio - used directly
comp.add(videoWithAudio, 'main').audio(true, 1.0)
comp.add(videoNoAudio, 'overlay').audio(false)
Copy
# Only one layer has audio - used directly
comp.add(video_with_audio, 'main').audio(enabled=True, volume=1.0)
comp.add(video_no_audio, 'overlay').audio(enabled=False)
Multiple Audio Sources
- Node.js
- Python
Copy
// Multiple layers with audio - automatically mixed
comp.add(narrator, 'voice').audio(true, 0.8) // Main audio at 80%
comp.add(background, 'music').audio(true, 0.3) // Background music at 30%
comp.add(effects, 'sfx').audio(true, 0.6) // Sound effects at 60%
// System automatically creates audio mix
Copy
# Multiple layers with audio - automatically mixed
comp.add(narrator, 'voice').audio(enabled=True, volume=0.8) # Main audio at 80%
comp.add(background, 'music').audio(enabled=True, volume=0.3) # Background music at 30%
comp.add(effects, 'sfx').audio(enabled=True, volume=0.6) # Sound effects at 60%
# System automatically creates audio mix
Advanced Examples
Multi-Layer News Layout
- Node.js
- Python
Copy
// News broadcast layout
const comp = new Composition(Background.fromColor('#1E293B', 1920, 1080, 30))
// Main presenter (center-right)
comp.add(presenter, 'presenter')
.at(Anchor.CENTER_RIGHT, -100)
.size(SizeMode.CANVAS_PERCENT, { percent: 55 })
// Breaking news ticker (bottom)
comp.add(tickerVideo, 'ticker')
.at(Anchor.BOTTOM_CENTER, 0, -20)
.size(SizeMode.CANVAS_PERCENT, { width: 90, height: 8 })
// Logo (top-left)
comp.add(logoVideo, 'logo')
.at(Anchor.TOP_LEFT, 30, 30)
.size(SizeMode.CANVAS_PERCENT, { percent: 12 })
.opacity(0.9)
await comp.toFile('news_broadcast.mp4', EncoderProfile.h264())
Copy
# News broadcast layout
comp = Composition(Background.from_color('#1E293B', 1920, 1080, 30))
# Main presenter (center-right)
comp.add(presenter, 'presenter') \
.at(Anchor.CENTER_RIGHT, dx=-100) \
.size(SizeMode.CANVAS_PERCENT, percent=55)
# Breaking news ticker (bottom)
comp.add(ticker_video, 'ticker') \
.at(Anchor.BOTTOM_CENTER, dy=-20) \
.size(SizeMode.CANVAS_PERCENT, width=90, height=8)
# Logo (top-left)
comp.add(logo_video, 'logo') \
.at(Anchor.TOP_LEFT, dx=30, dy=30) \
.size(SizeMode.CANVAS_PERCENT, percent=12) \
.opacity(0.9)
comp.to_file('news_broadcast.mp4', EncoderProfile.h264())
Timed Presentation
- Node.js
- Python
Copy
// Presentation with timed elements
const comp = new Composition(Background.fromImage('slide_bg.jpg', 30))
// Speaker appears throughout
comp.add(speaker, 'speaker')
.at(Anchor.CENTER_LEFT, 100)
.size(SizeMode.CANVAS_PERCENT, { percent: 40 })
// Slide 1: 0-10 seconds
comp.add(slide1, 'slide1')
.start(0).duration(10)
.at(Anchor.CENTER_RIGHT, -100)
.size(SizeMode.CANVAS_PERCENT, { percent: 45 })
// Slide 2: 10-20 seconds
comp.add(slide2, 'slide2')
.start(10).duration(10)
.at(Anchor.CENTER_RIGHT, -100)
.size(SizeMode.CANVAS_PERCENT, { percent: 45 })
// Conclusion: 20-25 seconds
comp.add(conclusion, 'conclusion')
.start(20).duration(5)
.at(Anchor.CENTER)
.size(SizeMode.CONTAIN)
Copy
# Presentation with timed elements
comp = Composition(Background.from_image('slide_bg.jpg', fps=30))
# Speaker appears throughout
comp.add(speaker, 'speaker') \
.at(Anchor.CENTER_LEFT, dx=100) \
.size(SizeMode.CANVAS_PERCENT, percent=40)
# Slide 1: 0-10 seconds
comp.add(slide1, 'slide1') \
.start(0).duration(10) \
.at(Anchor.CENTER_RIGHT, dx=-100) \
.size(SizeMode.CANVAS_PERCENT, percent=45)
# Slide 2: 10-20 seconds
comp.add(slide2, 'slide2') \
.start(10).duration(10) \
.at(Anchor.CENTER_RIGHT, dx=-100) \
.size(SizeMode.CANVAS_PERCENT, percent=45)
# Conclusion: 20-25 seconds
comp.add(conclusion, 'conclusion') \
.start(20).duration(5) \
.at(Anchor.CENTER) \
.size(SizeMode.CONTAIN)
Debugging
Dry Run
See the FFmpeg command without executing:- Node.js
- Python
Copy
const command = comp.dryRun()
console.log('FFmpeg command:')
console.log(command)
// Example output:
// ffmpeg -y -f lavfi -i color=c=#FF0000:size=1920x1080:rate=30
// -i transparent.webm -filter_complex "[1:v]scale=1920:1080:force_original_aspect_ratio=decrease[layer_0_scale];[0:v][layer_0_scale]overlay=x='(W-w)/2':y='(H-h)/2'[out]"
// -map [out] -map 1:a? -c:v libx264 -crf 18 -preset medium output.mp4
Copy
command = comp.dry_run()
print('FFmpeg command:')
print(command)
# Example output:
# ffmpeg -y -f lavfi -i color=c=#FF0000:size=1920x1080:rate=30
# -i transparent.webm -filter_complex "[1:v]scale=1920:1080:force_original_aspect_ratio=decrease[layer_0_scale];[0:v][layer_0_scale]overlay=x='(W-w)/2':y='(H-h)/2'[out]"
# -map [out] -map 1:a? -c:v libx264 -crf 18 -preset medium output.mp4
Verbose Export
See FFmpeg output in real-time:- Node.js
- Python
Copy
// Show FFmpeg output for debugging
await comp.toFile('debug.mp4', EncoderProfile.h264(), undefined, true)
Copy
# Show FFmpeg output for debugging
comp.to_file('debug.mp4', EncoderProfile.h264(), verbose=True)
Related Classes
- Background: Background creation guide
- EncoderProfile: Export format configuration
- Video: Video loading and background removal
- Positioning Guide: Complete positioning and sizing guide
- Timing Guide: Timeline and duration control
