Skip to content

Instantly share code, notes, and snippets.

@thetemplateblog
Last active September 2, 2025 11:11
Show Gist options
  • Save thetemplateblog/4b6e423f263c0c0a9e4e42a488675b61 to your computer and use it in GitHub Desktop.
Save thetemplateblog/4b6e423f263c0c0a9e4e42a488675b61 to your computer and use it in GitHub Desktop.
Remotion API Server Complete Setup Script

Remotion API Server Setup

A complete automation script to set up a Remotion-based video generation API server on Ubuntu. Generate professional videos programmatically with hardware acceleration support.

πŸš€ Quick Start

# Download and run the setup script
curl -o setup-remotion.sh https://gist.githubusercontent.com/your-script-url

# Or create the file locally
nano setup-remotion.sh
# (paste the script content)

# Make it executable and run
chmod +x setup-remotion.sh
./setup-remotion.sh

πŸ“‹ What This Script Does

System Setup

  • βœ… Updates Ubuntu packages
  • βœ… Installs Node.js 18+
  • βœ… Installs FFmpeg for video processing
  • βœ… Installs build tools and dependencies
  • βœ… Configures firewall for port 3001

Remotion Project

  • βœ… Creates complete Remotion project with animated particle effects
  • βœ… Installs all required dependencies (@remotion/cli, @remotion/renderer, etc.)
  • βœ… Sets up TypeScript configuration
  • βœ… Creates animated components with 150+ particles and visual effects

API Server

  • βœ… Express.js HTTP API wrapper around Remotion
  • βœ… RESTful endpoints for video generation
  • βœ… Job management and file download system
  • βœ… Systemd service for production deployment

Testing & Examples

  • βœ… Test render script to verify setup
  • βœ… cURL examples for API usage
  • βœ… Monitoring and debugging tools

πŸ–₯️ System Requirements

Minimum Requirements

  • OS: Ubuntu 18.04+ (or compatible Linux distribution)
  • CPU: 4+ cores recommended
  • RAM: 8GB minimum, 16GB recommended
  • Storage: 10GB free space
  • Network: Internet connection for dependencies

Recommended for Best Performance

  • CPU: 8+ cores (AMD Ryzen 7/Intel i7 or better)
  • RAM: 32GB for complex videos
  • Hardware: Modern hardware with hardware video encoding support
  • Storage: SSD for faster I/O operations

πŸ“ Project Structure

After installation, you'll have:

~/remotion-api-server/
β”œβ”€β”€ src/                          # Remotion source files
β”‚   β”œβ”€β”€ HelloWorld/              # Video components
β”‚   β”‚   β”œβ”€β”€ Logo.tsx
β”‚   β”‚   β”œβ”€β”€ Title.tsx
β”‚   β”‚   └── Subtitle.tsx
β”‚   β”œβ”€β”€ HelloWorld.tsx           # Main video component with particle effects
β”‚   β”œβ”€β”€ Root.tsx                 # Composition configuration
β”‚   └── index.ts                 # Entry point
β”œβ”€β”€ out/                         # Rendered video output
β”œβ”€β”€ api-server.js                # HTTP API server
β”œβ”€β”€ test-render.js               # Test script
β”œβ”€β”€ curl-examples.sh             # API usage examples
β”œβ”€β”€ package.json                 # Node.js dependencies
└── tsconfig.json               # TypeScript configuration

🌐 API Endpoints

Health Check

GET /health

Returns server status and bundle information.

List Compositions

GET /compositions

Returns available video templates.

Render Video

POST /render

Starts a new video render job.

Request Body:

{
  "compositionId": "HelloWorld",
  "props": {
    "titleText": "Your Custom Title",
    "titleColor": "#FF6B6B",
    "logoColor1": "#4ECDC4",
    "logoColor2": "#45B7D1"
  },
  "codec": "h264",
  "crf": 23,
  "concurrency": 8
}

Download Video

GET /download/:filename

Downloads the rendered video file.

πŸ’‘ Usage Examples

Step-by-Step Video Generation (Recommended)

# Step 1: Check server health
curl -s http://your-server:3001/health

# Step 2: Start a render and capture response
RESPONSE=$(curl -s -X POST http://your-server:3001/render \
  -H "Content-Type: application/json" \
  -d '{
    "compositionId": "HelloWorld",
    "props": {
      "titleText": "My Epic Video",
      "titleColor": "#00FFFF",
      "logoColor1": "#FF6B6B",
      "logoColor2": "#4ECDC4"
    },
    "codec": "h264",
    "crf": 23,
    "concurrency": 4
  }')

# Step 3: Extract filename from response
echo "$RESPONSE"
FILENAME=$(echo "$RESPONSE" | grep -o '"filename":"[^"]*"' | cut -d'"' -f4)
echo "Render started: $FILENAME"

# Step 4: Wait for render to complete (2-5 minutes)
echo "Waiting for render to complete..."
sleep 180

# Step 5: Download the video
curl -O http://your-server:3001/download/$FILENAME
ls -la *.mp4

Quick Test Render

# Simple test with minimal props
curl -X POST http://your-server:3001/render \
  -H "Content-Type: application/json" \
  -d '{"compositionId":"HelloWorld","props":{"titleText":"Test Video"}}' \
  -s | jq '.'

High-Quality Cinematic Video

# Epic cinematic render with orchestral music
curl -X POST http://your-server:3001/render \
  -H "Content-Type: application/json" \
  -d '{
    "compositionId": "HelloWorld",
    "props": {
      "titleText": "CONSCIOUSNESS AWAKENS",
      "titleColor": "#FFD700",
      "logoColor1": "#FF6B35",
      "logoColor2": "#F7931E"
    },
    "codec": "h264",
    "crf": 12,
    "concurrency": 8
  }' | jq '.'

# Wait 3-5 minutes, then download using filename from response

Debugging Common Issues

# Check if server is running and bundled
curl -s http://your-server:3001/health | jq '.'

# List available compositions
curl -s http://your-server:3001/compositions | jq '.'

# Test render with verbose output to see errors
curl -X POST http://your-server:3001/render \
  -H "Content-Type: application/json" \
  -d '{"compositionId":"HelloWorld"}' \
  -v

# Check if file exists before downloading
curl -I http://your-server:3001/download/render_TIMESTAMP.mp4

# Server logs (if running as service)
sudo journalctl -u remotion-api -f

Complete Workflow Script

#!/bin/bash
SERVER="http://your-server:3001"

echo "πŸ” Checking server..."
curl -s "$SERVER/health" | jq '.'

echo "🎬 Starting render..."
RESPONSE=$(curl -s -X POST "$SERVER/render" \
  -H "Content-Type: application/json" \
  -d '{
    "compositionId": "HelloWorld",
    "props": {
      "titleText": "Amazing Video",
      "titleColor": "#FF4081"
    },
    "crf": 18
  }')

echo "$RESPONSE" | jq '.'

# Extract filename
FILENAME=$(echo "$RESPONSE" | jq -r '.filename')

if [ "$FILENAME" != "null" ]; then
  echo "⏳ Waiting for render: $FILENAME"
  
  # Wait and check until file is ready
  while true; do
    HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$SERVER/download/$FILENAME")
    if [ "$HTTP_CODE" = "200" ]; then
      echo "βœ… Render complete! Downloading..."
      curl -O "$SERVER/download/$FILENAME"
      break
    elif [ "$HTTP_CODE" = "404" ]; then
      echo "⏳ Still rendering..."
      sleep 30
    else
      echo "❌ Error: HTTP $HTTP_CODE"
      break
    fi
  done
else
  echo "❌ Render failed"
fi

πŸ› οΈ Management Commands

Development

cd ~/remotion-api-server

# Start API server
npm run api

# Start Remotion Studio (preview)
npm run dev

# Test render locally
npm run test-render

Production

# Enable systemd service
sudo systemctl enable remotion-api
sudo systemctl start remotion-api

# Check service status
sudo systemctl status remotion-api

# View logs
sudo journalctl -u remotion-api -f

πŸ”§ Configuration

Video Quality Settings

  • CRF 10-15: Highest quality, larger files
  • CRF 18-23: Good quality, balanced size (recommended)
  • CRF 28-35: Lower quality, smaller files

Performance Tuning

  • Concurrency: Number of parallel render processes (usually CPU cores)
  • Resolution: 1920x1080 (default), 3840x2160 (4K), etc.
  • FPS: 30fps (default), 60fps for smooth motion

Hardware Acceleration

While Remotion is primarily CPU-bound, you can enable hardware video encoding:

// In api-server.js, add to renderMedia options:
ffmpegOverride: ({ args }) => [
  ...args.filter(arg => !arg.includes('libx264')),
  '-c:v', 'h264_nvenc',
  '-preset', 'fast'
]

🚨 Troubleshooting

Common Issues

Port 3001 already in use:

sudo lsof -ti:3001 | xargs kill -9

Node.js version too old:

# Update to Node.js 18+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

FFmpeg not found:

sudo apt update
sudo apt install ffmpeg -y

Permission denied errors:

# Fix ownership
sudo chown -R $USER:$USER ~/remotion-api-server

Performance Issues

Slow renders:

  • Increase concurrency in render requests
  • Use SSD storage for better I/O
  • Add more RAM for complex compositions
  • Monitor CPU usage during renders

High memory usage:

  • Reduce concurrency if running out of RAM
  • Close unnecessary applications
  • Consider upgrading to 32GB+ RAM for heavy workloads

πŸ“Š Monitoring

Hardware Usage (if available)

# Monitor hardware during renders
watch -n 1 nvidia-smi   # For NVIDIA hardware
# or
watch -n 1 intel_gpu_top   # For Intel hardware

System Resources

# Monitor CPU and memory
htop

# Monitor disk usage
df -h

API Performance

# Check render times
curl -s http://your-server:3001/health

# Monitor server logs
tail -f /var/log/syslog | grep remotion

πŸ”’ Security Considerations

Production Deployment

  • Enable firewall and restrict access to port 3001
  • Use reverse proxy (nginx) with SSL
  • Implement API authentication
  • Rate limit requests to prevent abuse
  • Monitor disk usage (rendered videos can accumulate)

Network Security

# Restrict access to specific IPs
sudo ufw allow from YOUR_IP to any port 3001

# Or use nginx reverse proxy with authentication

πŸ“ˆ Scaling

Multiple Servers

  • Deploy on multiple machines
  • Use load balancer (nginx, HAProxy)
  • Shared storage for video output (NFS, S3)

Queue System

  • Implement Redis/Bull queue for job management
  • Handle multiple concurrent render requests
  • Add retry logic for failed renders

🀝 Integration Examples

MyPosts Platform Integration

// Generate video in your application
const response = await fetch('http://render-server:3001/render', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    compositionId: 'HelloWorld',
    props: {
      titleText: claudeGeneratedText,
      titleColor: userBrandColor
    }
  })
});

const { filename } = await response.json();
// Handle download and upload to social platforms

Webhook Integration

// Add webhook notifications to api-server.js
const webhookUrl = process.env.WEBHOOK_URL;
if (webhookUrl) {
  await fetch(webhookUrl, {
    method: 'POST',
    body: JSON.stringify({ 
      event: 'render_complete', 
      filename, 
      downloadUrl 
    })
  });
}

πŸ“ License

This setup script and configuration is provided as-is for educational and development purposes. Please ensure you comply with all relevant licenses for the included software:

  • Remotion: Check their licensing terms
  • FFmpeg: LGPL license
  • Node.js: MIT license

πŸ†˜ Support

For issues with this setup:

  1. Check the logs: sudo journalctl -u remotion-api -f
  2. Verify dependencies: Run npm run test-render
  3. System resources: Monitor CPU/RAM usage during renders
  4. Network connectivity: Ensure port 3001 is accessible

🎬 What's Next?

  • Add custom compositions: Create your own video templates
  • Integrate TTS: Add text-to-speech for voiceovers
  • Audio support: Add background music and sound effects
  • Advanced effects: Implement WebGL shaders for hardware acceleration
  • Cloud deployment: Deploy to AWS/GCP with auto-scaling

Happy video rendering! πŸš€

#!/bin/bash
# Remotion API Server Complete Setup Script
# Run this on your Ubuntu server
set -e # Exit on any error
echo "πŸš€ Starting Remotion API Server Setup..."
echo "========================================"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_step() {
echo -e "${BLUE}πŸ”„ $1${NC}"
}
print_success() {
echo -e "${GREEN}βœ… $1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}❌ $1${NC}"
}
# Check if running as root
if [[ $EUID -eq 0 ]]; then
print_error "This script should not be run as root"
exit 1
fi
# Update system
print_step "Updating system packages..."
sudo apt update && sudo apt upgrade -y
print_success "System updated"
# Install Node.js 18+
print_step "Installing Node.js 18..."
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
print_success "Node.js installed: $(node --version)"
# Install FFmpeg
print_step "Installing FFmpeg..."
sudo apt install ffmpeg -y
print_success "FFmpeg installed: $(ffmpeg -version | head -n1)"
# Install build essentials
print_step "Installing build tools..."
sudo apt install -y build-essential python3-dev
print_success "Build tools installed"
# Create project directory
PROJECT_DIR="$HOME/remotion-api-server"
print_step "Creating project directory: $PROJECT_DIR"
rm -rf "$PROJECT_DIR" # Remove if exists
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
print_success "Project directory created"
# Initialize package.json
print_step "Initializing Node.js project..."
cat > package.json << 'EOF'
{
"name": "remotion-api-server",
"version": "1.0.0",
"description": "Remotion API Server for video generation",
"main": "api-server.js",
"scripts": {
"dev": "remotion studio",
"api": "node api-server.js",
"build": "remotion bundle",
"test-render": "node test-render.js"
},
"dependencies": {
"@remotion/bundler": "4.0.334",
"@remotion/cli": "4.0.334",
"@remotion/renderer": "4.0.334",
"@remotion/zod-types": "4.0.334",
"express": "^4.18.2",
"react": "19.0.0",
"react-dom": "19.0.0",
"remotion": "4.0.334",
"zod": "^3.22.3"
},
"devDependencies": {
"@types/react": "19.0.0",
"@types/web": "0.0.166",
"typescript": "5.8.2"
}
}
EOF
print_success "Package.json created"
# Install dependencies
print_step "Installing Node.js dependencies..."
npm install
print_success "Dependencies installed"
# Create src directory structure
print_step "Creating source directory structure..."
mkdir -p src/HelloWorld out
print_success "Directory structure created"
# Create TypeScript config
print_step "Creating TypeScript configuration..."
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "DOM.Iterable", "ES6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
EOF
print_success "TypeScript config created"
# Create index.ts
print_step "Creating main entry point..."
cat > src/index.ts << 'EOF'
import {registerRoot} from 'remotion';
import {RemotionRoot} from './Root';
registerRoot(RemotionRoot);
EOF
# Create Root.tsx
cat > src/Root.tsx << 'EOF'
import {Composition} from 'remotion';
import {HelloWorld, myCompSchema} from './HelloWorld';
export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
id="HelloWorld"
component={HelloWorld}
durationInFrames={1800} // 60 seconds at 30fps
fps={30}
width={1920}
height={1080}
schema={myCompSchema}
defaultProps={{
titleText: "REMOTION API SERVER",
titleColor: "#FFFFFF",
logoColor1: "#91EAE4",
logoColor2: "#86A8E7",
}}
/>
</>
);
};
EOF
# Create HelloWorld component with GPU stress test
cat > src/HelloWorld.tsx << 'EOF'
import { spring } from "remotion";
import {
AbsoluteFill,
interpolate,
Sequence,
useCurrentFrame,
useVideoConfig,
} from "remotion";
import { Logo } from "./HelloWorld/Logo";
import { Subtitle } from "./HelloWorld/Subtitle";
import { Title } from "./HelloWorld/Title";
import { z } from "zod";
import { zColor } from "@remotion/zod-types";
export const myCompSchema = z.object({
titleText: z.string(),
titleColor: zColor(),
logoColor1: zColor(),
logoColor2: zColor(),
});
// Heavy particle component for GPU stress
const HeavyParticle: React.FC<{
index: number;
frame: number;
fps: number;
totalParticles: number;
}> = ({ index, frame, fps, totalParticles }) => {
const baseAngle = (index / totalParticles) * Math.PI * 2;
const spiralFactor = frame * 0.02;
const chaos = Math.sin(frame * 0.05 + index) * Math.cos(frame * 0.03 + index * 2);
const radius1 = 100 + Math.sin(frame * 0.01 + index) * 80;
const radius2 = 200 + Math.cos(frame * 0.015 + index * 1.5) * 120;
const x1 = Math.cos(baseAngle + spiralFactor) * radius1;
const y1 = Math.sin(baseAngle + spiralFactor) * radius1;
const x2 = Math.cos(baseAngle * 2 + spiralFactor * 1.5) * radius2;
const y2 = Math.sin(baseAngle * 2 + spiralFactor * 1.5) * radius2;
const finalX = (x1 + x2) * 0.5 + chaos * 30;
const finalY = (y1 + y2) * 0.5 + chaos * 30;
const hue = (index * 137.5 + frame * 2) % 360;
const saturation = 70 + Math.sin(frame * 0.1 + index) * 30;
const lightness = 50 + Math.cos(frame * 0.08 + index) * 20;
const opacity = Math.sin(frame * 0.05 + index) * 0.3 + Math.cos(frame * 0.07 + index * 2) * 0.2 + 0.5;
const size = 2 + Math.sin(frame * 0.08 + index) * 1.5;
return (
<div
style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: `translate(${finalX}px, ${finalY}px) rotate(${frame + index * 10}deg)`,
width: size,
height: size,
backgroundColor: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
borderRadius: '50%',
opacity: Math.max(0, Math.min(1, opacity)),
boxShadow: `0 0 ${size * 2}px hsl(${hue}, ${saturation}%, ${lightness}%)`,
filter: `blur(${Math.sin(frame * 0.02 + index) * 0.3 + 0.3}px)`,
}}
/>
);
};
export const HelloWorld: React.FC<z.infer<typeof myCompSchema>> = ({
titleText: propOne,
titleColor: propTwo,
logoColor1,
logoColor2,
}) => {
const frame = useCurrentFrame();
const { durationInFrames, fps } = useVideoConfig();
const logoTranslationProgress = spring({
frame: frame - 25,
fps,
config: { damping: 100 },
});
const logoTranslation = interpolate(logoTranslationProgress, [0, 1], [0, -150]);
const opacity = interpolate(
frame,
[durationInFrames - 25, durationInFrames - 15],
[1, 0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
);
const heavyParticles = Array.from({ length: 150 }, (_, i) => i);
const bgHue1 = (frame * 1.5) % 360;
const bgHue2 = (frame * 2 + 120) % 360;
return (
<AbsoluteFill>
<div
style={{
width: '100%',
height: '100%',
background: `
radial-gradient(circle at 30% 30%, hsl(${bgHue1}, 60%, 20%) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, hsl(${bgHue2}, 60%, 20%) 0%, transparent 50%),
linear-gradient(45deg, hsl(${bgHue1}, 40%, 5%) 0%, hsl(${bgHue2}, 40%, 8%) 100%)
`,
filter: `blur(${Math.sin(frame * 0.02) * 1 + 1}px)`,
}}
/>
{heavyParticles.map((index) => (
<HeavyParticle
key={`particle-${index}`}
index={index}
frame={frame}
fps={fps}
totalParticles={heavyParticles.length}
/>
))}
<AbsoluteFill style={{ opacity, mixBlendMode: 'overlay' }}>
<AbsoluteFill style={{ transform: `translateY(${logoTranslation}px)` }}>
<Logo logoColor1={logoColor1} logoColor2={logoColor2} />
</AbsoluteFill>
<Sequence from={35}>
<Title titleText={propOne} titleColor={propTwo} />
</Sequence>
<Sequence from={75}>
<Subtitle />
</Sequence>
</AbsoluteFill>
</AbsoluteFill>
);
};
EOF
# Create HelloWorld components directory
mkdir -p src/HelloWorld
# Create Logo component
cat > src/HelloWorld/Logo.tsx << 'EOF'
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Logo: React.FC<{
logoColor1: string;
logoColor2: string;
}> = ({logoColor1, logoColor2}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const scale = spring({
frame,
fps,
config: {
damping: 100,
stiffness: 200,
},
});
return (
<div
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: `translate(-50%, -50%) scale(${scale})`,
fontSize: 100,
fontWeight: 'bold',
background: `linear-gradient(45deg, ${logoColor1}, ${logoColor2})`,
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
color: 'transparent',
}}
>
REMOTION
</div>
);
};
EOF
# Create Title component
cat > src/HelloWorld/Title.tsx << 'EOF'
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Title: React.FC<{
titleText: string;
titleColor: string;
}> = ({titleText, titleColor}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const scale = spring({
frame,
fps,
config: {
damping: 100,
},
});
return (
<div
style={{
position: 'absolute',
top: '60%',
left: '50%',
transform: `translate(-50%, -50%) scale(${scale})`,
fontSize: 60,
fontWeight: 'bold',
color: titleColor,
textAlign: 'center',
textShadow: '0 0 20px rgba(255, 255, 255, 0.5)',
}}
>
{titleText}
</div>
);
};
EOF
# Create Subtitle component
cat > src/HelloWorld/Subtitle.tsx << 'EOF'
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Subtitle: React.FC = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const opacity = spring({
frame,
fps,
config: {
damping: 100,
},
});
return (
<div
style={{
position: 'absolute',
top: '70%',
left: '50%',
transform: 'translate(-50%, -50%)',
fontSize: 30,
color: 'white',
opacity,
textAlign: 'center',
}}
>
GPU Accelerated Video Generation
</div>
);
};
EOF
print_success "Remotion components created"
# Create API server
print_step "Creating API server..."
cat > api-server.js << 'EOF'
const express = require('express');
const { bundle } = require('@remotion/bundler');
const { renderMedia, selectComposition, getCompositions } = require('@remotion/renderer');
const path = require('path');
const fs = require('fs').promises;
const app = express();
app.use(express.json());
let bundleLocation = null;
async function initializeBundle() {
console.log('πŸ“¦ Pre-bundling Remotion project...');
bundleLocation = await bundle({
entryPoint: path.resolve('./src/index.ts'),
});
console.log('βœ… Bundle ready');
}
app.get('/health', (req, res) => {
res.json({
status: 'ok',
bundled: !!bundleLocation,
timestamp: new Date().toISOString()
});
});
app.get('/compositions', async (req, res) => {
try {
if (!bundleLocation) await initializeBundle();
const compositions = await getCompositions(bundleLocation);
res.json({ compositions });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.post('/render', async (req, res) => {
try {
const {
compositionId = 'HelloWorld',
props = {},
codec = 'h264',
crf = 23,
concurrency = 8,
outputFormat = 'mp4'
} = req.body;
if (!bundleLocation) await initializeBundle();
const composition = await selectComposition({
serveUrl: bundleLocation,
id: compositionId,
});
const timestamp = Date.now();
const filename = `render_${timestamp}.${outputFormat}`;
const outputLocation = path.resolve('./out', filename);
await fs.mkdir('./out', { recursive: true });
console.log(`🎬 Starting render: ${compositionId}`);
const startTime = Date.now();
await renderMedia({
composition,
serveUrl: bundleLocation,
codec,
outputLocation,
inputProps: props,
crf,
concurrency,
pixelFormat: 'yuv420p',
onProgress: ({ progress }) => {
console.log(`πŸ“Š ${compositionId}: ${(progress * 100).toFixed(1)}%`);
},
chromiumOptions: {
gl: 'angle',
},
});
const renderTime = Date.now() - startTime;
console.log(`βœ… Render completed in ${renderTime}ms`);
res.json({
success: true,
filename,
downloadUrl: `/download/${filename}`,
renderTime,
composition: {
id: composition.id,
width: composition.width,
height: composition.height,
fps: composition.fps,
durationInFrames: composition.durationInFrames,
},
props,
});
} catch (error) {
console.error('❌ Render failed:', error);
res.status(500).json({ error: error.message });
}
});
app.get('/download/:filename', async (req, res) => {
try {
const { filename } = req.params;
const filePath = path.resolve('./out', filename);
await fs.access(filePath);
res.download(filePath);
} catch (error) {
res.status(404).json({ error: 'File not found' });
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, '0.0.0.0', async () => {
console.log(`πŸš€ Remotion API Server running on http://0.0.0.0:${PORT}`);
try {
await initializeBundle();
} catch (error) {
console.error('❌ Failed to initialize bundle:', error);
}
});
EOF
print_success "API server created"
# Create test script
print_step "Creating test script..."
cat > test-render.js << 'EOF'
const { bundle } = require('@remotion/bundler');
const { renderMedia, selectComposition } = require('@remotion/renderer');
const path = require('path');
async function testRender() {
try {
console.log('πŸ§ͺ Testing Remotion render...');
const bundleLocation = await bundle({
entryPoint: path.resolve('./src/index.ts'),
});
const composition = await selectComposition({
serveUrl: bundleLocation,
id: 'HelloWorld',
});
console.log('πŸ“Š Composition:', composition);
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
outputLocation: './out/test-render.mp4',
inputProps: {
titleText: 'Test Render Success!',
titleColor: '#00FF00',
logoColor1: '#FF6B6B',
logoColor2: '#4ECDC4',
},
concurrency: 4,
onProgress: ({ progress }) => {
console.log(`πŸ“Š Progress: ${(progress * 100).toFixed(1)}%`);
},
});
console.log('βœ… Test render completed!');
} catch (error) {
console.error('❌ Test render failed:', error);
}
}
testRender();
EOF
# Create example cURL commands file
cat > curl-examples.sh << 'EOF'
#!/bin/bash
# Example cURL commands for Remotion API
SERVER="http://localhost:3001"
echo "πŸ” Health check:"
curl -s "$SERVER/health" | jq '.'
echo -e "\nπŸ“‹ List compositions:"
curl -s "$SERVER/compositions" | jq '.'
echo -e "\n🎬 Start render:"
curl -X POST "$SERVER/render" \
-H "Content-Type: application/json" \
-d '{
"compositionId": "HelloWorld",
"props": {
"titleText": "API Generated Video!",
"titleColor": "#FF6B6B",
"logoColor1": "#4ECDC4",
"logoColor2": "#45B7D1"
},
"codec": "h264",
"crf": 18,
"concurrency": 8
}' | jq '.'
echo -e "\nπŸ’‘ To download a video:"
echo "curl -O $SERVER/download/render_TIMESTAMP.mp4"
EOF
chmod +x curl-examples.sh
print_success "Example scripts created"
# Create systemd service file
print_step "Creating systemd service..."
sudo tee /etc/systemd/system/remotion-api.service > /dev/null << EOF
[Unit]
Description=Remotion API Server
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$PROJECT_DIR
ExecStart=/usr/bin/node api-server.js
Restart=always
RestartSec=3
Environment=NODE_ENV=production
Environment=PORT=3001
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
print_success "Systemd service created"
# Set up firewall rule
print_step "Setting up firewall rule for port 3001..."
if command -v ufw &> /dev/null; then
sudo ufw allow 3001/tcp
print_success "Firewall rule added for port 3001"
else
print_warning "UFW not installed, please manually configure firewall for port 3001"
fi
# Final setup summary
print_step "Running final checks..."
# Check Node.js
if node --version > /dev/null 2>&1; then
print_success "Node.js: $(node --version)"
else
print_error "Node.js installation failed"
exit 1
fi
# Check FFmpeg
if ffmpeg -version > /dev/null 2>&1; then
print_success "FFmpeg: $(ffmpeg -version | head -n1 | cut -d' ' -f1-3)"
else
print_error "FFmpeg installation failed"
exit 1
fi
# Check hardware acceleration support
if nvidia-smi > /dev/null 2>&1; then
print_success "NVIDIA hardware detected: $(nvidia-smi --query-gpu=name --format=csv,noheader,nounits)"
elif lspci | grep -i vga | grep -i intel > /dev/null 2>&1; then
print_success "Intel graphics detected"
elif lspci | grep -i vga | grep -i amd > /dev/null 2>&1; then
print_success "AMD graphics detected"
else
print_warning "No dedicated graphics hardware detected"
fi
echo ""
echo "πŸŽ‰ INSTALLATION COMPLETE!"
echo "========================"
echo ""
echo "πŸ“ Project location: $PROJECT_DIR"
echo "🌐 API will run on: http://$(hostname -I | awk '{print $1}'):3001"
echo ""
echo "πŸš€ To start the API server:"
echo " cd $PROJECT_DIR"
echo " npm run api"
echo ""
echo "πŸ§ͺ To test the setup:"
echo " npm run test-render"
echo ""
echo "πŸ”§ To start as a service:"
echo " sudo systemctl enable remotion-api"
echo " sudo systemctl start remotion-api"
echo ""
echo "πŸ“– Example API calls:"
echo " ./curl-examples.sh"
echo ""
echo "Happy video rendering! 🎬"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment