VideoCascadeLogo
VideoCascade

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:

  1. Submitting a video for processing (via URL or file upload)
  2. Polling for completion status
  3. Downloading the processed result
  4. 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');
}

Next Steps