Basic Video Processing
Complete examples for basic video processing with file uploads, polling, and error handling
Learn the fundamentals of video processing with VideoCascade API through practical, production-ready examples. This guide covers everything from simple URL processing to file uploads, status polling, and robust error handling.
Overview
Basic video processing involves:
- Submitting a video for processing (via URL or file upload)
- Polling for completion status
- Downloading the processed result
- Error handling for robust applications
Simple Video Processing
The most basic example: process a video from a URL with silence removal and audio normalization.
async function processVideo(videoUrl) {
try {
// Submit video for processing
const response = await fetch('https://api.videocascade.com/v1/videos', {
method: 'POST',
headers: {
'Authorization': 'Bearer vca_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileUrl: videoUrl,
removeSilence: true,
normalizeAudio: true,
outputFormat: 'mp4',
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Video submitted:', data.videoId);
console.log('Job ID:', data.jobId);
return data.videoId;
} catch (error) {
console.error('Error processing video:', error);
throw error;
}
}
// Usage
const videoId = await processVideo('https://example.com/video.mp4');import requests
def process_video(video_url):
"""Submit video for processing"""
try:
response = requests.post(
'https://api.videocascade.com/v1/videos',
headers={
'Authorization': 'Bearer vca_your_api_key',
'Content-Type': 'application/json',
},
json={
'fileUrl': video_url,
'removeSilence': True,
'normalizeAudio': True,
'outputFormat': 'mp4',
}
)
response.raise_for_status()
data = response.json()
print(f"Video submitted: {data['videoId']}")
print(f"Job ID: {data['jobId']}")
return data['videoId']
except requests.exceptions.RequestException as error:
print(f"Error processing video: {error}")
raise
# Usage
video_id = process_video('https://example.com/video.mp4')interface VideoSubmitResponse {
videoId: string;
jobId: string;
status: string;
message: string;
}
interface VideoProcessOptions {
fileUrl: string;
removeSilence?: boolean;
normalizeAudio?: boolean;
outputFormat?: 'mp4' | 'mov' | 'avi' | 'webm';
}
async function processVideo(
videoUrl: string
): Promise<string> {
try {
const options: VideoProcessOptions = {
fileUrl: videoUrl,
removeSilence: true,
normalizeAudio: true,
outputFormat: 'mp4',
};
const response = await fetch('https://api.videocascade.com/v1/videos', {
method: 'POST',
headers: {
'Authorization': 'Bearer vca_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify(options),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: VideoSubmitResponse = await response.json();
console.log('Video submitted:', data.videoId);
console.log('Job ID:', data.jobId);
return data.videoId;
} catch (error) {
console.error('Error processing video:', error);
throw error;
}
}
// Usage
const videoId = await processVideo('https://example.com/video.mp4');curl -X POST https://api.videocascade.com/v1/videos \
-H "Authorization: Bearer vca_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"fileUrl": "https://example.com/video.mp4",
"removeSilence": true,
"normalizeAudio": true,
"outputFormat": "mp4"
}'Expected Response:
{
"videoId": "v_abc12345def67890",
"jobId": "job_xyz98765abc43210",
"status": "queued",
"message": "Video processing job created successfully"
}Polling for Completion
After submitting a video, poll the status endpoint until processing completes.
async function waitForCompletion(videoId, maxAttempts = 60) {
const pollInterval = 5000; // 5 seconds
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const response = await fetch(
`https://api.videocascade.com/v1/videos/${videoId}`,
{
headers: {
'Authorization': 'Bearer vca_your_api_key',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const video = await response.json();
console.log(`Status: ${video.status} (${video.progressPercent}%)`);
if (video.status === 'succeeded') {
console.log('Processing completed!');
console.log('Download URL:', video.finalVideoUrl);
return {
success: true,
videoUrl: video.finalVideoUrl,
metadata: video.videoMetadata,
};
}
if (video.status === 'failed') {
console.error('Processing failed:', video.errorMessage);
return {
success: false,
error: video.errorMessage,
};
}
// Wait before next poll
await new Promise(resolve => setTimeout(resolve, pollInterval));
} catch (error) {
console.error('Error checking status:', error);
throw error;
}
}
throw new Error('Timeout: Video processing took too long');
}
// Usage
const result = await waitForCompletion('v_abc12345def67890');
if (result.success) {
console.log('Video ready:', result.videoUrl);
}import time
import requests
def wait_for_completion(video_id, max_attempts=60):
"""Poll until video processing completes"""
poll_interval = 5 # seconds
for attempt in range(max_attempts):
try:
response = requests.get(
f'https://api.videocascade.com/v1/videos/{video_id}',
headers={'Authorization': 'Bearer vca_your_api_key'}
)
response.raise_for_status()
video = response.json()
print(f"Status: {video['status']} ({video.get('progressPercent', 0)}%)")
if video['status'] == 'succeeded':
print('Processing completed!')
print(f"Download URL: {video['finalVideoUrl']}")
return {
'success': True,
'videoUrl': video['finalVideoUrl'],
'metadata': video.get('videoMetadata'),
}
if video['status'] == 'failed':
print(f"Processing failed: {video.get('errorMessage')}")
return {
'success': False,
'error': video.get('errorMessage'),
}
# Wait before next poll
time.sleep(poll_interval)
except requests.exceptions.RequestException as error:
print(f"Error checking status: {error}")
raise
raise TimeoutError('Video processing took too long')
# Usage
result = wait_for_completion('v_abc12345def67890')
if result['success']:
print(f"Video ready: {result['videoUrl']}")interface VideoStatus {
videoId: string;
status: 'queued' | 'running' | 'succeeded' | 'failed';
progressPercent?: number;
finalVideoUrl?: string;
videoMetadata?: VideoMetadata;
errorMessage?: string;
}
interface VideoMetadata {
width: number;
height: number;
duration: number;
fps: number;
}
interface CompletionResult {
success: boolean;
videoUrl?: string;
metadata?: VideoMetadata;
error?: string;
}
async function waitForCompletion(
videoId: string,
maxAttempts: number = 60
): Promise<CompletionResult> {
const pollInterval = 5000; // 5 seconds
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const response = await fetch(
`https://api.videocascade.com/v1/videos/${videoId}`,
{
headers: {
'Authorization': 'Bearer vca_your_api_key',
},
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const video: VideoStatus = await response.json();
console.log(`Status: ${video.status} (${video.progressPercent || 0}%)`);
if (video.status === 'succeeded') {
console.log('Processing completed!');
console.log('Download URL:', video.finalVideoUrl);
return {
success: true,
videoUrl: video.finalVideoUrl,
metadata: video.videoMetadata,
};
}
if (video.status === 'failed') {
console.error('Processing failed:', video.errorMessage);
return {
success: false,
error: video.errorMessage,
};
}
// Wait before next poll
await new Promise(resolve => setTimeout(resolve, pollInterval));
} catch (error) {
console.error('Error checking status:', error);
throw error;
}
}
throw new Error('Timeout: Video processing took too long');
}
// Usage
const result = await waitForCompletion('v_abc12345def67890');
if (result.success && result.videoUrl) {
console.log('Video ready:', result.videoUrl);
}File Upload Example
Upload a video file directly instead of using a URL.
async function uploadAndProcessVideo(file) {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('removeSilence', 'true');
formData.append('normalizeAudio', 'true');
formData.append('outputFormat', 'mp4');
const response = await fetch(
'https://api.videocascade.com/v1/videos/upload',
{
method: 'POST',
headers: {
'Authorization': 'Bearer vca_your_api_key',
},
body: formData,
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Video uploaded:', data.videoId);
return data.videoId;
} catch (error) {
console.error('Error uploading video:', error);
throw error;
}
}
// Browser usage with file input
document.getElementById('videoInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
const videoId = await uploadAndProcessVideo(file);
const result = await waitForCompletion(videoId);
console.log('Processed video:', result.videoUrl);
}
});
// Node.js usage with file system
const fs = require('fs');
const FormData = require('form-data');
async function uploadVideoFromDisk(filePath) {
const formData = new FormData();
formData.append('file', fs.createReadStream(filePath));
formData.append('removeSilence', 'true');
formData.append('normalizeAudio', 'true');
const response = await fetch(
'https://api.videocascade.com/v1/videos/upload',
{
method: 'POST',
headers: {
'Authorization': 'Bearer vca_your_api_key',
...formData.getHeaders(),
},
body: formData,
}
);
return response.json();
}import requests
def upload_and_process_video(file_path):
"""Upload and process a video file"""
try:
with open(file_path, 'rb') as video_file:
files = {'file': video_file}
data = {
'removeSilence': 'true',
'normalizeAudio': 'true',
'outputFormat': 'mp4',
}
response = requests.post(
'https://api.videocascade.com/v1/videos/upload',
headers={'Authorization': 'Bearer vca_your_api_key'},
files=files,
data=data
)
response.raise_for_status()
result = response.json()
print(f"Video uploaded: {result['videoId']}")
return result['videoId']
except Exception as error:
print(f"Error uploading video: {error}")
raise
# Usage
video_id = upload_and_process_video('/path/to/video.mp4')
result = wait_for_completion(video_id)
print(f"Processed video: {result['videoUrl']}")async function uploadAndProcessVideo(file: File): Promise<string> {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('removeSilence', 'true');
formData.append('normalizeAudio', 'true');
formData.append('outputFormat', 'mp4');
const response = await fetch(
'https://api.videocascade.com/v1/videos/upload',
{
method: 'POST',
headers: {
'Authorization': 'Bearer vca_your_api_key',
},
body: formData,
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: VideoSubmitResponse = await response.json();
console.log('Video uploaded:', data.videoId);
return data.videoId;
} catch (error) {
console.error('Error uploading video:', error);
throw error;
}
}
// Browser usage
const handleFileUpload = async (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (file) {
const videoId = await uploadAndProcessVideo(file);
const result = await waitForCompletion(videoId);
console.log('Processed video:', result.videoUrl);
}
};
document.getElementById('videoInput')?.addEventListener('change', handleFileUpload);Complete End-to-End Example
A complete, production-ready example with error handling, retries, and webhook integration.
class VideoCascadeClient {
constructor(apiKey, baseUrl = 'https://api.videocascade.com/v1') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
}
async processVideo(options) {
try {
const response = await fetch(`${this.baseUrl}/videos`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(options),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error submitting video:', error);
throw error;
}
}
async getStatus(videoId) {
try {
const response = await fetch(`${this.baseUrl}/videos/${videoId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching status:', error);
throw error;
}
}
async waitForCompletion(videoId, options = {}) {
const {
maxAttempts = 120,
pollInterval = 5000,
onProgress = () => {},
} = options;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const video = await this.getStatus(videoId);
onProgress({
status: video.status,
progress: video.progressPercent || 0,
attempt: attempt + 1,
maxAttempts,
});
if (video.status === 'succeeded') {
return {
success: true,
video,
};
}
if (video.status === 'failed') {
return {
success: false,
error: video.errorMessage,
video,
};
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
throw new Error('Processing timeout');
}
async downloadVideo(url, outputPath) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
// Node.js
if (typeof window === 'undefined') {
const fs = require('fs').promises;
await fs.writeFile(outputPath, Buffer.from(buffer));
} else {
// Browser
const blob = new Blob([buffer]);
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = outputPath;
link.click();
}
}
}
// Usage Example
async function main() {
const client = new VideoCascadeClient('vca_your_api_key');
try {
// Submit video
console.log('Submitting video...');
const submission = await client.processVideo({
fileUrl: 'https://example.com/video.mp4',
removeSilence: true,
normalizeAudio: true,
aspectRatio: '16:9',
compressionQuality: 95,
webhookUrl: 'https://yourapp.com/webhooks/video-complete',
});
console.log(`Video ID: ${submission.videoId}`);
// Wait for completion with progress
console.log('Waiting for processing...');
const result = await client.waitForCompletion(submission.videoId, {
onProgress: ({ status, progress, attempt, maxAttempts }) => {
console.log(`[${attempt}/${maxAttempts}] ${status}: ${progress}%`);
},
});
if (result.success) {
console.log('Processing completed!');
console.log('Video URL:', result.video.finalVideoUrl);
console.log('Duration:', result.video.videoMetadata.duration, 'seconds');
// Download the video
await client.downloadVideo(
result.video.finalVideoUrl,
'processed-video.mp4'
);
console.log('Video downloaded!');
} else {
console.error('Processing failed:', result.error);
}
} catch (error) {
console.error('Fatal error:', error);
process.exit(1);
}
}
main();import time
import requests
from typing import Optional, Callable, Dict, Any
class VideoCascadeClient:
def __init__(self, api_key: str, base_url: str = 'https://api.videocascade.com/v1'):
self.api_key = api_key
self.base_url = base_url
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
}
def process_video(self, options: Dict[str, Any]) -> Dict[str, Any]:
"""Submit video for processing"""
try:
response = requests.post(
f'{self.base_url}/videos',
headers=self.headers,
json=options
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as error:
print(f"Error submitting video: {error}")
raise
def get_status(self, video_id: str) -> Dict[str, Any]:
"""Get video processing status"""
try:
response = requests.get(
f'{self.base_url}/videos/{video_id}',
headers={'Authorization': f'Bearer {self.api_key}'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as error:
print(f"Error fetching status: {error}")
raise
def wait_for_completion(
self,
video_id: str,
max_attempts: int = 120,
poll_interval: int = 5,
on_progress: Optional[Callable] = None
) -> Dict[str, Any]:
"""Wait for video processing to complete"""
for attempt in range(max_attempts):
video = self.get_status(video_id)
if on_progress:
on_progress({
'status': video['status'],
'progress': video.get('progressPercent', 0),
'attempt': attempt + 1,
'maxAttempts': max_attempts,
})
if video['status'] == 'succeeded':
return {
'success': True,
'video': video,
}
if video['status'] == 'failed':
return {
'success': False,
'error': video.get('errorMessage'),
'video': video,
}
time.sleep(poll_interval)
raise TimeoutError('Processing timeout')
def download_video(self, url: str, output_path: str) -> None:
"""Download processed video"""
response = requests.get(url, stream=True)
response.raise_for_status()
with open(output_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
# Usage Example
def main():
client = VideoCascadeClient('vca_your_api_key')
try:
# Submit video
print('Submitting video...')
submission = client.process_video({
'fileUrl': 'https://example.com/video.mp4',
'removeSilence': True,
'normalizeAudio': True,
'aspectRatio': '16:9',
'compressionQuality': 95,
'webhookUrl': 'https://yourapp.com/webhooks/video-complete',
})
print(f"Video ID: {submission['videoId']}")
# Wait for completion with progress
print('Waiting for processing...')
def on_progress(info):
print(f"[{info['attempt']}/{info['maxAttempts']}] {info['status']}: {info['progress']}%")
result = client.wait_for_completion(
submission['videoId'],
on_progress=on_progress
)
if result['success']:
print('Processing completed!')
video = result['video']
print(f"Video URL: {video['finalVideoUrl']}")
print(f"Duration: {video['videoMetadata']['duration']} seconds")
# Download the video
client.download_video(video['finalVideoUrl'], 'processed-video.mp4')
print('Video downloaded!')
else:
print(f"Processing failed: {result['error']}")
except Exception as error:
print(f"Fatal error: {error}")
exit(1)
if __name__ == '__main__':
main()interface ProcessOptions {
fileUrl: string;
removeSilence?: boolean;
normalizeAudio?: boolean;
aspectRatio?: string;
compressionQuality?: number;
webhookUrl?: string;
}
interface ProgressInfo {
status: string;
progress: number;
attempt: number;
maxAttempts: number;
}
interface ProcessingResult {
success: boolean;
video?: VideoStatus;
error?: string;
}
class VideoCascadeClient {
private apiKey: string;
private baseUrl: string;
constructor(apiKey: string, baseUrl: string = 'https://api.videocascade.com/v1') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
}
async processVideo(options: ProcessOptions): Promise<VideoSubmitResponse> {
try {
const response = await fetch(`${this.baseUrl}/videos`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(options),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error submitting video:', error);
throw error;
}
}
async getStatus(videoId: string): Promise<VideoStatus> {
try {
const response = await fetch(`${this.baseUrl}/videos/${videoId}`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching status:', error);
throw error;
}
}
async waitForCompletion(
videoId: string,
options: {
maxAttempts?: number;
pollInterval?: number;
onProgress?: (info: ProgressInfo) => void;
} = {}
): Promise<ProcessingResult> {
const {
maxAttempts = 120,
pollInterval = 5000,
onProgress = () => {},
} = options;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const video = await this.getStatus(videoId);
onProgress({
status: video.status,
progress: video.progressPercent || 0,
attempt: attempt + 1,
maxAttempts,
});
if (video.status === 'succeeded') {
return {
success: true,
video,
};
}
if (video.status === 'failed') {
return {
success: false,
error: video.errorMessage,
video,
};
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
throw new Error('Processing timeout');
}
async downloadVideo(url: string, outputPath: string): Promise<void> {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
// Browser environment
if (typeof window !== 'undefined') {
const blob = new Blob([buffer]);
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = outputPath;
link.click();
} else {
// Node.js environment
const fs = await import('fs/promises');
await fs.writeFile(outputPath, Buffer.from(buffer));
}
}
}
// Usage Example
async function main() {
const client = new VideoCascadeClient('vca_your_api_key');
try {
// Submit video
console.log('Submitting video...');
const submission = await client.processVideo({
fileUrl: 'https://example.com/video.mp4',
removeSilence: true,
normalizeAudio: true,
aspectRatio: '16:9',
compressionQuality: 95,
webhookUrl: 'https://yourapp.com/webhooks/video-complete',
});
console.log(`Video ID: ${submission.videoId}`);
// Wait for completion with progress
console.log('Waiting for processing...');
const result = await client.waitForCompletion(submission.videoId, {
onProgress: ({ status, progress, attempt, maxAttempts }) => {
console.log(`[${attempt}/${maxAttempts}] ${status}: ${progress}%`);
},
});
if (result.success && result.video) {
console.log('Processing completed!');
console.log('Video URL:', result.video.finalVideoUrl);
console.log('Duration:', result.video.videoMetadata?.duration, 'seconds');
// Download the video
if (result.video.finalVideoUrl) {
await client.downloadVideo(
result.video.finalVideoUrl,
'processed-video.mp4'
);
console.log('Video downloaded!');
}
} else {
console.error('Processing failed:', result.error);
}
} catch (error) {
console.error('Fatal error:', error);
process.exit(1);
}
}
main();Real-World Use Case: User Upload Platform
A complete example for handling user video uploads in a web application.
// pages/api/videos/upload.ts
import { NextApiRequest, NextApiResponse } from 'next';
import formidable from 'formidable';
import fs from 'fs/promises';
import FormData from 'form-data';
export const config = {
api: {
bodyParser: false,
},
};
interface VideoRecord {
id: string;
userId: string;
videoId: string;
status: string;
videoUrl?: string;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
// Parse multipart form data
const form = formidable();
const [fields, files] = await form.parse(req);
const userId = fields.userId?.[0];
const videoFile = files.video?.[0];
if (!userId || !videoFile) {
return res.status(400).json({ error: 'Missing required fields' });
}
// Upload to VideoCascade
const formData = new FormData();
formData.append('file', await fs.readFile(videoFile.filepath), {
filename: videoFile.originalFilename || 'video.mp4',
});
formData.append('removeSilence', 'true');
formData.append('normalizeAudio', 'true');
formData.append('aspectRatio', '16:9');
formData.append('webhookUrl', `${process.env.APP_URL}/api/webhooks/video-complete`);
const response = await fetch(
'https://api.videocascade.com/v1/videos/upload',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VIDEO_CASCADE_API_KEY}`,
...formData.getHeaders(),
},
body: formData,
}
);
if (!response.ok) {
throw new Error(`VideoCascade API error: ${response.status}`);
}
const data = await response.json();
// Save to database
const videoRecord: VideoRecord = {
id: generateId(),
userId,
videoId: data.videoId,
status: 'processing',
};
await db.videos.create(videoRecord);
// Clean up temp file
await fs.unlink(videoFile.filepath);
return res.status(200).json({
success: true,
videoId: data.videoId,
recordId: videoRecord.id,
});
} catch (error) {
console.error('Upload error:', error);
return res.status(500).json({
error: 'Failed to process video upload',
});
}
}
// pages/api/webhooks/video-complete.ts
import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const payload = req.body;
// Verify webhook signature
const webhookSecret = process.env.WEBHOOK_SECRET;
if (!verifySignature(JSON.stringify(payload), payload.signature, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Update database
if (payload.event === 'video.completed') {
await db.videos.update({
where: { videoId: payload.videoId },
data: {
status: 'completed',
videoUrl: payload.finalVideoUrl,
completedAt: new Date(),
},
});
// Notify user
const video = await db.videos.findUnique({
where: { videoId: payload.videoId },
include: { user: true },
});
if (video?.user?.email) {
await sendEmail({
to: video.user.email,
subject: 'Your video is ready!',
body: `Your video has been processed. View it here: ${video.videoUrl}`,
});
}
} else if (payload.event === 'video.failed') {
await db.videos.update({
where: { videoId: payload.videoId },
data: {
status: 'failed',
errorMessage: payload.errorMessage,
},
});
}
return res.status(200).json({ success: true });
} catch (error) {
console.error('Webhook error:', error);
return res.status(500).json({ error: 'Webhook processing failed' });
}
}
function verifySignature(payload: string, signature: string, secret: string): boolean {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf8');
const expectedSignature = `sha256=${hmac.digest('base64')}`;
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch {
return false;
}
}const express = require('express');
const multer = require('multer');
const FormData = require('form-data');
const fs = require('fs').promises;
const crypto = require('crypto');
const app = express();
const upload = multer({ dest: 'uploads/' });
// PostgreSQL client
const { Pool } = require('pg');
const db = new Pool({
connectionString: process.env.DATABASE_URL,
});
// Upload endpoint
app.post('/api/videos/upload', upload.single('video'), async (req, res) => {
try {
const { userId } = req.body;
const videoFile = req.file;
if (!userId || !videoFile) {
return res.status(400).json({ error: 'Missing required fields' });
}
// Upload to VideoCascade
const formData = new FormData();
formData.append('file', await fs.readFile(videoFile.path), {
filename: videoFile.originalname,
});
formData.append('removeSilence', 'true');
formData.append('normalizeAudio', 'true');
formData.append('aspectRatio', '16:9');
formData.append('webhookUrl', `${process.env.APP_URL}/api/webhooks/video-complete`);
const response = await fetch(
'https://api.videocascade.com/v1/videos/upload',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VIDEO_CASCADE_API_KEY}`,
...formData.getHeaders(),
},
body: formData,
}
);
if (!response.ok) {
throw new Error(`VideoCascade API error: ${response.status}`);
}
const data = await response.json();
// Save to database
const result = await db.query(
`INSERT INTO videos (user_id, video_id, status, created_at)
VALUES ($1, $2, $3, NOW())
RETURNING id`,
[userId, data.videoId, 'processing']
);
// Clean up temp file
await fs.unlink(videoFile.path);
return res.status(200).json({
success: true,
videoId: data.videoId,
recordId: result.rows[0].id,
});
} catch (error) {
console.error('Upload error:', error);
return res.status(500).json({
error: 'Failed to process video upload',
});
}
});
// Webhook endpoint
app.use('/api/webhooks/video-complete', express.raw({ type: 'application/json' }));
app.post('/api/webhooks/video-complete', async (req, res) => {
try {
const rawBody = req.body.toString('utf8');
const payload = JSON.parse(rawBody);
// Verify webhook signature
if (!verifySignature(rawBody, payload.signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Update database
if (payload.event === 'video.completed') {
await db.query(
`UPDATE videos
SET status = $1, video_url = $2, completed_at = NOW()
WHERE video_id = $3`,
['completed', payload.finalVideoUrl, payload.videoId]
);
// Get user info and notify
const result = await db.query(
`SELECT v.*, u.email FROM videos v
JOIN users u ON v.user_id = u.id
WHERE v.video_id = $1`,
[payload.videoId]
);
if (result.rows[0]?.email) {
await sendEmail({
to: result.rows[0].email,
subject: 'Your video is ready!',
body: `Your video has been processed. View it here: ${payload.finalVideoUrl}`,
});
}
} else if (payload.event === 'video.failed') {
await db.query(
`UPDATE videos
SET status = $1, error_message = $2
WHERE video_id = $3`,
['failed', payload.errorMessage, payload.videoId]
);
}
return res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
return res.status(500).send('Webhook processing failed');
}
});
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf8');
const expectedSignature = `sha256=${hmac.digest('base64')}`;
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch {
return false;
}
}
app.listen(3000, () => {
console.log('Server running on port 3000');
});Error Handling Best Practices
Robust error handling for production applications.
class VideoProcessingError extends Error {
constructor(message, code, details) {
super(message);
this.name = 'VideoProcessingError';
this.code = code;
this.details = details;
}
}
async function processVideoWithRetry(videoUrl, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempt ${attempt}/${maxRetries}`);
// Submit video
const response = await fetch('https://api.videocascade.com/v1/videos', {
method: 'POST',
headers: {
'Authorization': 'Bearer vca_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileUrl: videoUrl,
removeSilence: true,
normalizeAudio: true,
}),
});
// Handle HTTP errors
if (!response.ok) {
const error = await response.json();
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new VideoProcessingError(
error.message || 'Client error',
'CLIENT_ERROR',
{ status: response.status, ...error }
);
}
// Retry server errors (5xx)
throw new VideoProcessingError(
'Server error',
'SERVER_ERROR',
{ status: response.status }
);
}
const data = await response.json();
// Wait for completion
const result = await waitForCompletionWithTimeout(data.videoId, 300000); // 5 min timeout
if (result.success) {
return result;
}
// Video processing failed
throw new VideoProcessingError(
result.error || 'Processing failed',
'PROCESSING_FAILED',
{ videoId: data.videoId }
);
} catch (error) {
lastError = error;
// Don't retry certain errors
if (
error.code === 'CLIENT_ERROR' ||
error.code === 'PROCESSING_FAILED'
) {
throw error;
}
// Wait before retry (exponential backoff)
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new VideoProcessingError(
'Max retries exceeded',
'MAX_RETRIES',
{ lastError }
);
}
async function waitForCompletionWithTimeout(videoId, timeout) {
const startTime = Date.now();
const pollInterval = 5000;
while (Date.now() - startTime < timeout) {
try {
const response = await fetch(
`https://api.videocascade.com/v1/videos/${videoId}`,
{
headers: {
'Authorization': 'Bearer vca_your_api_key',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const video = await response.json();
if (video.status === 'succeeded') {
return {
success: true,
video,
};
}
if (video.status === 'failed') {
return {
success: false,
error: video.errorMessage,
};
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
} catch (error) {
console.error('Polling error:', error);
// Continue polling despite errors
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
throw new VideoProcessingError(
'Processing timeout',
'TIMEOUT',
{ videoId, timeout }
);
}
// Usage with comprehensive error handling
async function main() {
try {
const result = await processVideoWithRetry('https://example.com/video.mp4');
console.log('Success!', result.video.finalVideoUrl);
} catch (error) {
if (error instanceof VideoProcessingError) {
switch (error.code) {
case 'CLIENT_ERROR':
console.error('Invalid request:', error.message);
// Log to error tracking, notify admins
break;
case 'PROCESSING_FAILED':
console.error('Video processing failed:', error.message);
// Try alternative processing or notify user
break;
case 'TIMEOUT':
console.error('Processing took too long');
// Queue for later retry or manual review
break;
case 'MAX_RETRIES':
console.error('All retry attempts failed');
// Escalate to support team
break;
default:
console.error('Unknown error:', error);
}
} else {
console.error('Unexpected error:', error);
}
}
}import time
import requests
from typing import Dict, Any, Optional
class VideoProcessingError(Exception):
def **init**(self, message: str, code: str, details: Optional[Dict] = None):
super().**init**(message)
self.code = code
self.details = details or {}
def process_video_with_retry(video_url: str, max_retries: int = 3) -> Dict[str, Any]:
"""Process video with automatic retries"""
last_error = None
for attempt in range(1, max_retries + 1):
try:
print(f"Attempt {attempt}/{max_retries}")
# Submit video
response = requests.post(
'https://api.videocascade.com/v1/videos',
headers={
'Authorization': 'Bearer vca_your_api_key',
'Content-Type': 'application/json',
},
json={
'fileUrl': video_url,
'removeSilence': True,
'normalizeAudio': True,
}
)
# Handle HTTP errors
if not response.ok:
error_data = response.json() if response.content else {}
# Don't retry client errors (4xx)
if 400 <= response.status_code < 500:
raise VideoProcessingError(
error_data.get('message', 'Client error'),
'CLIENT_ERROR',
{'status': response.status_code, **error_data}
)
# Retry server errors (5xx)
raise VideoProcessingError(
'Server error',
'SERVER_ERROR',
{'status': response.status_code}
)
data = response.json()
# Wait for completion
result = wait_for_completion_with_timeout(data['videoId'], timeout=300)
if result['success']:
return result
# Video processing failed
raise VideoProcessingError(
result.get('error', 'Processing failed'),
'PROCESSING_FAILED',
{'videoId': data['videoId']}
)
except VideoProcessingError as error:
last_error = error
# Don't retry certain errors
if error.code in ['CLIENT_ERROR', 'PROCESSING_FAILED']:
raise
# Wait before retry (exponential backoff)
if attempt < max_retries:
delay = min(1 * (2 ** attempt), 10)
print(f"Retrying in {delay}s...")
time.sleep(delay)
except Exception as error:
last_error = error
if attempt < max_retries:
time.sleep(2 ** attempt)
raise VideoProcessingError(
'Max retries exceeded',
'MAX_RETRIES',
{'lastError': str(last_error)}
)
def wait_for_completion_with_timeout(
video_id: str,
timeout: int = 300
) -> Dict[str, Any]:
"""Wait for completion with timeout"""
start_time = time.time()
poll_interval = 5
while time.time() - start_time < timeout:
try:
response = requests.get(
f'https://api.videocascade.com/v1/videos/{video_id}',
headers={'Authorization': 'Bearer vca_your_api_key'}
)
if not response.ok:
raise Exception(f"HTTP {response.status_code}")
video = response.json()
if video['status'] == 'succeeded':
return {
'success': True,
'video': video,
}
if video['status'] == 'failed':
return {
'success': False,
'error': video.get('errorMessage'),
}
time.sleep(poll_interval)
except Exception as error:
print(f"Polling error: {error}")
time.sleep(poll_interval)
raise VideoProcessingError(
'Processing timeout',
'TIMEOUT',
{'videoId': video_id, 'timeout': timeout}
)
# Usage with comprehensive error handling
def main():
try:
result = process_video_with_retry('https://example.com/video.mp4')
print(f"Success! {result['video']['finalVideoUrl']}")
except VideoProcessingError as error:
if error.code == 'CLIENT_ERROR':
print(f"Invalid request: {error}")
# Log to error tracking, notify admins
elif error.code == 'PROCESSING_FAILED':
print(f"Video processing failed: {error}")
# Try alternative processing or notify user
elif error.code == 'TIMEOUT':
print("Processing took too long")
# Queue for later retry or manual review
elif error.code == 'MAX_RETRIES':
print("All retry attempts failed")
# Escalate to support team
else:
print(f"Unknown error: {error}")
except Exception as error:
print(f"Unexpected error: {error}")
if **name** == '**main**':
main()Best Practices
1. Use Webhooks Over Polling
Webhooks are more efficient than polling for production applications:
// Good: Use webhooks
{
fileUrl: 'https://example.com/video.mp4',
webhookUrl: 'https://yourapp.com/webhooks/video-complete',
// No need to poll!
}
// Less efficient: Constant polling
while (status !== 'completed') {
await sleep(5000);
status = await checkStatus(videoId);
}2. Implement Exponential Backoff
Use exponential backoff for retries:
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.min(1000 * Math.pow(2, i), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}3. Store Video IDs
Always store video IDs for tracking and debugging:
await db.videos.create({
id: generateId(),
userId: req.user.id,
videoId: data.videoId,
jobId: data.jobId,
status: 'processing',
submittedAt: new Date(),
});4. Validate Before Processing
Validate videos before submitting:
function validateVideo(file) {
const maxSize = 500 * 1024 * 1024; // 500 MB
const allowedTypes = ['video/mp4', 'video/mov', 'video/avi'];
if (file.size > maxSize) {
throw new Error('Video file too large (max 500MB)');
}
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid video format');
}
}5. Handle Edge Cases
Account for edge cases in production:
// Handle videos that are already processed
if (video.status === 'succeeded' && video.finalVideoUrl) {
return video.finalVideoUrl; // Return cached result
}
// Handle stuck jobs
const ageMinutes = (Date.now() - video.createdAt) / 1000 / 60;
if (ageMinutes > 60 && video.status === 'running') {
console.warn('Video processing stuck, may need intervention');
}
// Handle missing videos
if (!video) {
throw new Error('Video not found - may have been deleted');
}