Examples

Real-world examples and patterns for building with ClipFM.

Social Media Clip Bot

Build a bot that automatically creates viral clips from trending videos and posts them to social media.

Twitter Integration
Trend Monitoring
Auto-posting
Social Media Bot Implementation
// Social Media Clip Bot
// Automatically create clips from trending videos

import { ClipFM } from '@clipfm/sdk';
import { TwitterApi } from 'twitter-api-v2';

const clipfm = new ClipFM({ apiKey: process.env.CLIPFM_API_KEY });
const twitter = new TwitterApi(process.env.TWITTER_BEARER_TOKEN);

async function createViralClip(videoUrl: string, trend: string) {
  // Create a clip optimized for social media
  const clip = await clipfm.clips.create({
    video_url: videoUrl,
    duration: 30, // Short clips perform better
    style: 'viral',
    title: `#${trend} - Must Watch!`,
    metadata: {
      platform: 'twitter',
      trend: trend,
      created_at: new Date().toISOString()
    }
  });

  // Wait for processing
  const readyClip = await clipfm.clips.waitForReady(clip.id);
  
  // Post to Twitter
  const tweet = await twitter.v2.tweet({
    text: `šŸ”„ ${readyClip.title}\n\nWatch now: ${readyClip.url}\n\n#clips #viral`,
  });

  return { clip: readyClip, tweet };
}

// Monitor trending topics and create clips
async function monitorTrends() {
  const trends = await twitter.v2.trendsByPlace(1); // US trends
  
  for (const trend of trends.trends.slice(0, 5)) {
    // Your logic to find relevant videos
    const videos = await findVideosForTrend(trend.name);
    
    for (const video of videos) {
      await createViralClip(video.url, trend.name);
    }
  }
}

Batch Video Processing

Process multiple videos efficiently with progress tracking, error handling, and rate limiting.

Concurrent Processing
Progress Tracking
Error Recovery
Batch Processor Implementation
// Batch Video Processing
// Process multiple videos efficiently with progress tracking

import { ClipFM } from '@clipfm/sdk';
import { EventEmitter } from 'events';

interface BatchJob {
  id: string;
  videos: string[];
  status: 'pending' | 'processing' | 'completed' | 'failed';
  progress: number;
  results: any[];
}

class BatchProcessor extends EventEmitter {
  private clipfm: ClipFM;
  private concurrency: number;

  constructor(apiKey: string, concurrency = 5) {
    super();
    this.clipfm = new ClipFM({ apiKey });
    this.concurrency = concurrency;
  }

  async processBatch(videos: string[], options: any): Promise<BatchJob> {
    const job: BatchJob = {
      id: `batch_${Date.now()}`,
      videos,
      status: 'processing',
      progress: 0,
      results: []
    };

    this.emit('job:start', job);

    // Process videos in chunks for rate limiting
    const chunks = this.chunkArray(videos, this.concurrency);
    
    for (const chunk of chunks) {
      const promises = chunk.map(video => 
        this.processVideo(video, options)
          .then(result => {
            job.progress = (job.results.length / videos.length) * 100;
            job.results.push(result);
            this.emit('job:progress', job);
            return result;
          })
          .catch(error => {
            this.emit('job:error', { job, error, video });
            return { video, error: error.message };
          })
      );

      await Promise.all(promises);
    }

    job.status = 'completed';
    this.emit('job:complete', job);
    return job;
  }

  private async processVideo(videoUrl: string, options: any) {
    // Create clip
    const clip = await this.clipfm.clips.create({
      video_url: videoUrl,
      ...options
    });

    // Wait for completion with timeout
    const timeout = 300000; // 5 minutes
    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      const status = await this.clipfm.clips.get(clip.id);
      
      if (status.status === 'ready') {
        return status;
      }
      
      if (status.status === 'failed') {
        throw new Error(`Clip processing failed: ${status.error}`);
      }

      await new Promise(resolve => setTimeout(resolve, 5000));
    }

    throw new Error('Processing timeout');
  }

  private chunkArray<T>(array: T[], size: number): T[][] {
    const chunks: T[][] = [];
    for (let i = 0; i < array.length; i += size) {
      chunks.push(array.slice(i, i + size));
    }
    return chunks;
  }
}

// Usage example
const processor = new BatchProcessor(process.env.CLIPFM_API_KEY!);

processor.on('job:progress', (job) => {
  console.log(`Progress: ${job.progress.toFixed(1)}%`);
});

processor.on('job:error', ({ video, error }) => {
  console.error(`Failed to process ${video}: ${error}`);
});

const videos = [
  'https://example.com/video1.mp4',
  'https://example.com/video2.mp4',
  // ... more videos
];

const job = await processor.processBatch(videos, {
  duration: 60,
  style: 'professional'
});

Webhook Integration with Queue

Handle webhooks reliably using a job queue with retry logic and signature verification.

Job Queue
Signature Verification
Retry Logic
Webhook Handler Implementation
// Webhook Integration with Queue
// Handle webhooks reliably with retry logic

import express from 'express';
import { ClipFM } from '@clipfm/sdk';
import Bull from 'bull';
import { createHmac } from 'crypto';

const app = express();
const clipfm = new ClipFM({ apiKey: process.env.CLIPFM_API_KEY });

// Create a job queue for processing webhooks
const webhookQueue = new Bull('webhook-processing', {
  redis: {
    host: 'localhost',
    port: 6379
  }
});

// Verify webhook signature
function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return signature === expectedSignature;
}

// Webhook endpoint
app.post('/webhooks/clipfm', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-clipfm-signature'] as string;
  
  // Verify the webhook is from ClipFM
  if (!verifyWebhookSignature(
    req.body.toString(),
    signature,
    process.env.CLIPFM_WEBHOOK_SECRET!
  )) {
    return res.status(401).send('Invalid signature');
  }
  
  const payload = JSON.parse(req.body.toString());
  
  // Add to queue for reliable processing
  await webhookQueue.add('process-webhook', payload, {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 2000
    }
  });
  
  // Respond quickly to acknowledge receipt
  res.status(200).send('OK');
});

// Process webhooks from queue
webhookQueue.process('process-webhook', async (job) => {
  const { event, data } = job.data;
  
  switch (event) {
    case 'clip.ready':
      await handleClipReady(data);
      break;
    
    case 'clip.failed':
      await handleClipFailed(data);
      break;
    
    case 'clip.deleted':
      await handleClipDeleted(data);
      break;
    
    default:
      console.log(`Unknown event: ${event}`);
  }
});

async function handleClipReady(data: any) {
  const { clip } = data;
  
  // Update your database
  await db.clips.update({
    where: { id: clip.id },
    data: {
      status: 'ready',
      url: clip.url,
      thumbnail_url: clip.thumbnail_url,
      processed_at: new Date()
    }
  });
  
  // Notify the user
  await sendNotification(clip.user_id, {
    type: 'clip_ready',
    title: 'Your clip is ready!',
    message: `Your clip "${clip.title}" has been processed successfully.`,
    action_url: clip.url
  });
  
  // Post to social media if configured
  if (clip.metadata?.auto_post) {
    await postToSocialMedia(clip);
  }
}

async function handleClipFailed(data: any) {
  const { clip, error } = data;
  
  // Update database
  await db.clips.update({
    where: { id: clip.id },
    data: {
      status: 'failed',
      error: error.message,
      failed_at: new Date()
    }
  });
  
  // Notify user of failure
  await sendNotification(clip.user_id, {
    type: 'clip_failed',
    title: 'Clip processing failed',
    message: `Failed to process "${clip.title}": ${error.message}`
  });
  
  // Retry with different settings if applicable
  if (error.code === 'UNSUPPORTED_FORMAT') {
    await retryWithDifferentFormat(clip);
  }
}

// Set up webhook in ClipFM dashboard
async function setupWebhook() {
  const webhook = await clipfm.webhooks.create({
    url: 'https://your-app.com/webhooks/clipfm',
    events: ['clip.ready', 'clip.failed', 'clip.deleted'],
    secret: process.env.CLIPFM_WEBHOOK_SECRET
  });
  
  console.log('Webhook created:', webhook.id);
}

Analytics Dashboard

Aggregate and visualize clip performance data to build comprehensive analytics dashboards.

Data Aggregation
Performance Metrics
Export Reports
Analytics Service Implementation
// Analytics Dashboard
// Aggregate and visualize clip performance data

import { ClipFM } from '@clipfm/sdk';
import { startOfDay, endOfDay, subDays } from 'date-fns';

interface AnalyticsReport {
  totalClips: number;
  totalViews: number;
  avgViewDuration: number;
  topClips: ClipAnalytics[];
  viewsByDay: DailyViews[];
  platformBreakdown: PlatformStats[];
}

interface ClipAnalytics {
  id: string;
  title: string;
  views: number;
  avgWatchTime: number;
  completionRate: number;
  shares: number;
}

class AnalyticsService {
  private clipfm: ClipFM;

  constructor(apiKey: string) {
    this.clipfm = new ClipFM({ apiKey });
  }

  async generateReport(userId: string, days: number = 30): Promise<AnalyticsReport> {
    const endDate = new Date();
    const startDate = subDays(endDate, days);

    // Fetch all clips for the period
    const clips = await this.fetchAllClips(userId, startDate, endDate);
    
    // Fetch analytics for each clip
    const analyticsPromises = clips.map(clip => 
      this.clipfm.analytics.getClipStats(clip.id, {
        start_date: startDate.toISOString(),
        end_date: endDate.toISOString()
      })
    );
    
    const allAnalytics = await Promise.all(analyticsPromises);

    // Aggregate the data
    const report: AnalyticsReport = {
      totalClips: clips.length,
      totalViews: this.sumViews(allAnalytics),
      avgViewDuration: this.calculateAvgDuration(allAnalytics),
      topClips: this.getTopClips(clips, allAnalytics, 10),
      viewsByDay: this.aggregateViewsByDay(allAnalytics, startDate, endDate),
      platformBreakdown: this.getPlatformBreakdown(allAnalytics)
    };

    return report;
  }

  private async fetchAllClips(userId: string, startDate: Date, endDate: Date) {
    const clips = [];
    let offset = 0;
    const limit = 100;

    while (true) {
      const batch = await this.clipfm.clips.list({
        user_id: userId,
        created_after: startDate.toISOString(),
        created_before: endDate.toISOString(),
        limit,
        offset
      });

      clips.push(...batch.data);

      if (batch.data.length < limit) break;
      offset += limit;
    }

    return clips;
  }

  private sumViews(analytics: any[]): number {
    return analytics.reduce((sum, stat) => sum + stat.views, 0);
  }

  private calculateAvgDuration(analytics: any[]): number {
    const totalDuration = analytics.reduce(
      (sum, stat) => sum + stat.total_watch_time, 
      0
    );
    const totalViews = this.sumViews(analytics);
    return totalViews > 0 ? totalDuration / totalViews : 0;
  }

  private getTopClips(clips: any[], analytics: any[], limit: number): ClipAnalytics[] {
    return clips
      .map((clip, index) => ({
        id: clip.id,
        title: clip.title,
        views: analytics[index].views,
        avgWatchTime: analytics[index].avg_watch_time,
        completionRate: analytics[index].completion_rate,
        shares: analytics[index].shares
      }))
      .sort((a, b) => b.views - a.views)
      .slice(0, limit);
  }

  private aggregateViewsByDay(
    analytics: any[], 
    startDate: Date, 
    endDate: Date
  ): DailyViews[] {
    const dayMap = new Map<string, number>();

    // Initialize all days with 0 views
    for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
      dayMap.set(d.toISOString().split('T')[0], 0);
    }

    // Aggregate views by day
    analytics.forEach(stat => {
      stat.daily_views.forEach((dv: any) => {
        const date = dv.date.split('T')[0];
        dayMap.set(date, (dayMap.get(date) || 0) + dv.views);
      });
    });

    return Array.from(dayMap.entries()).map(([date, views]) => ({
      date,
      views
    }));
  }

  private getPlatformBreakdown(analytics: any[]): PlatformStats[] {
    const platformMap = new Map<string, PlatformStats>();

    analytics.forEach(stat => {
      stat.platforms.forEach((platform: any) => {
        const existing = platformMap.get(platform.name) || {
          name: platform.name,
          views: 0,
          avgWatchTime: 0,
          shares: 0
        };

        existing.views += platform.views;
        existing.shares += platform.shares;
        
        platformMap.set(platform.name, existing);
      });
    });

    return Array.from(platformMap.values())
      .sort((a, b) => b.views - a.views);
  }
}

// Usage example
async function generateMonthlyReport(userId: string) {
  const analytics = new AnalyticsService(process.env.CLIPFM_API_KEY!);
  const report = await analytics.generateReport(userId, 30);

  console.log(`šŸ“Š Monthly Report for User ${userId}`);
  console.log(`Total Clips: ${report.totalClips}`);
  console.log(`Total Views: ${report.totalViews.toLocaleString()}`);
  console.log(`Avg Watch Time: ${report.avgViewDuration.toFixed(1)}s`);
  
  console.log('\nšŸ† Top 5 Clips:');
  report.topClips.slice(0, 5).forEach((clip, i) => {
    console.log(`${i + 1}. ${clip.title} - ${clip.views} views`);
  });

  // Export to CSV, send email report, update dashboard, etc.
  await exportToCSV(report);
  await sendEmailReport(userId, report);
}

Need more examples?

Check out our GitHub repository for more examples and starter templates.