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);
}