Skip to main content
Glama
by clipsense
client.ts•3.98 kB
/** * ClipSense API Client * * Handles communication with ClipSense backend for video analysis. */ import axios, { AxiosInstance } from "axios"; import { createReadStream, statSync } from "fs"; import { basename } from "path"; const API_BASE_URL = "https://api.clipsense.app/api/v1"; const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB export class ClipSenseClient { private client: AxiosInstance; private apiKey: string; constructor(apiKey: string) { this.apiKey = apiKey; this.client = axios.create({ baseURL: API_BASE_URL, headers: { Authorization: `Bearer ${apiKey}`, }, timeout: 300000, // 5 minutes (video analysis takes time) }); } /** * Analyze a video file */ async analyzeVideo( videoPath: string, question: string ): Promise<{ jobId: string; analysis: string }> { // Validate file size const stats = statSync(videoPath); if (stats.size > MAX_FILE_SIZE) { throw new Error(`Video file too large. Max size: 500MB (${stats.size} bytes provided)`); } // Step 1: Get presigned upload URL const { data: presignData } = await this.client.post("/upload/presign", { filename: basename(videoPath), content_type: this.getContentType(videoPath), file_size: stats.size, }); const { upload_url, video_key } = presignData; // Step 2: Upload video to Firebase Storage using PUT const fileStream = createReadStream(videoPath); await axios.put(upload_url, fileStream, { headers: { "Content-Type": this.getContentType(videoPath), }, timeout: 120000, // 2 minutes for upload maxBodyLength: MAX_FILE_SIZE, maxContentLength: MAX_FILE_SIZE, }); // Step 3: Start analysis job const { data: jobData } = await this.client.post("/analyze/start", { video_key: video_key, filename: basename(videoPath), question, analysis_type: "mobile_bug", }); const jobId = jobData.id; // Step 4: Poll for results const result = await this.pollJobStatus(jobId); return { jobId, analysis: this.formatAnalysis(result), }; } /** * Poll job status until complete */ private async pollJobStatus(jobId: string): Promise<any> { const maxAttempts = 120; // 10 minutes max (5s interval) let attempts = 0; while (attempts < maxAttempts) { const { data } = await this.client.get(`/analyze/jobs/${jobId}/status`); if (data.status === "completed") { // Get full job details const { data: fullJob } = await this.client.get(`/analyze/jobs/${jobId}`); return fullJob; } if (data.status === "failed") { throw new Error(`Analysis failed: ${data.error_message || "Unknown error"}`); } // Wait 5 seconds before next poll await new Promise((resolve) => setTimeout(resolve, 5000)); attempts++; } throw new Error("Analysis timeout after 10 minutes"); } /** * Format analysis result for display */ private formatAnalysis(job: any): string { const { result, cost_total, tokens_used } = job; if (!result?.response) { throw new Error("No analysis result found"); } return ` ## Mobile Bug Analysis ${result.response} --- **Analysis Details:** - Frames analyzed: ${job.frames_extracted || "N/A"} - Tokens used: ${tokens_used || 0} - Cost: $${(cost_total || 0).toFixed(4)} `.trim(); } /** * Get content type from file extension */ private getContentType(filepath: string): string { const ext = filepath.toLowerCase().split(".").pop(); const types: Record<string, string> = { mp4: "video/mp4", mov: "video/quicktime", webm: "video/webm", avi: "video/x-msvideo", mkv: "video/x-matroska", flv: "video/x-flv", mpeg: "video/mpeg", mpg: "video/mpeg", "3gp": "video/3gpp", wmv: "video/x-ms-wmv", }; return types[ext || ""] || "video/mp4"; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/clipsense/-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server