Skip to main content
Glama
video-plugin.ts16.4 kB
/** * Video Plugin - Video Content Element * Implements video-1 content element for multimedia demonstration */ import { BaseContentElementPlugin, RecognitionPattern, EducationalMetadata } from './base-plugin.js'; import { Video1Widget } from '../composer-widget-types.js'; export interface VideoProperties { title: string; description?: string; url?: string; placeholder: boolean; settings: { autoplay: boolean; controls: boolean; loop: boolean; muted: boolean; startTime?: number; // seconds endTime?: number; // seconds playbackSpeed: number; showCaptions: boolean; }; dimensions?: { width: number; height: number; aspectRatio: '16:9' | '4:3' | '1:1' | 'custom'; }; interaction?: { enablePause: boolean; showProgress: boolean; allowSeeking: boolean; showVolume: boolean; enableFullscreen: boolean; }; educational?: { hasTranscript: boolean; keyMoments: VideoMoment[]; followUpQuestions: string[]; relatedResources: string[]; }; } export interface VideoMoment { time: number; // seconds title: string; description: string; type: 'concept' | 'example' | 'transition' | 'summary'; } export class VideoPlugin extends BaseContentElementPlugin { type = 'video-1' as const; name = 'Video Player'; description = 'Interactive video player for multimedia content and demonstrations'; version = '1.0.0'; recognitionPatterns: RecognitionPattern[] = [ { pattern: /\b(video|watch|view|play)\b/i, weight: 1.0, context: ['demonstration', 'comprehension'], examples: ['Show a video about...', 'Watch the demonstration', 'Video explanation'] }, { pattern: /\b(demonstrate|show|display|present|exhibit)\b/i, weight: 0.9, context: ['demonstration'], examples: ['Demonstrate the process', 'Show how to...', 'Display the technique'] }, { pattern: /\b(visual|visually|see|observe|look)\b/i, weight: 0.7, context: ['demonstration', 'comprehension'], examples: ['Visual explanation', 'See the example', 'Observe the process'] }, { pattern: /\b(tutorial|walkthrough|step-by-step|how\s+to)\b/i, weight: 0.8, context: ['demonstration'], examples: ['Tutorial video', 'Step-by-step guide', 'How to do...'] }, { pattern: /\b(explanation|example|illustration|demo)\b/i, weight: 0.6, context: ['comprehension', 'demonstration'], examples: ['Video explanation', 'Example demonstration', 'Illustrated concept'] }, { pattern: /\b(youtube|vimeo|mp4|webm|streaming)\b/i, weight: 0.5, context: ['demonstration'], examples: ['YouTube video', 'MP4 file', 'Streaming content'] } ]; generateWidget(content: string, properties?: Partial<VideoProperties>): Video1Widget { const videoProps = this.generateVideoProperties(content, properties); return { id: this.generateId('video'), type: this.type, content_title: null, padding_top: 35, padding_bottom: 35, background_color: '#FFFFFF', video: videoProps.url || 'https://pocs.digitalpages.com.br/rdpcomposer/media/video-1/video-1.mp4', dam_assets: [] }; } getEducationalValue(): EducationalMetadata { return { learningTypes: ['demonstration', 'comprehension'], complexity: 'intermediate', interactivity: 'medium', timeEstimate: 15, // 15 minutes average including video + reflection prerequisites: [], }; } private generateVideoProperties(content: string, userProps?: Partial<VideoProperties>): VideoProperties { const title = this.extractVideoTitle(content); const description = this.extractDescription(content); const url = this.extractVideoUrl(content); const purpose = this.determineVideoPurpose(content); const settings = this.generateSettings(purpose, userProps?.settings); const dimensions = this.generateDimensions(purpose, userProps?.dimensions); const interaction = this.generateInteractionSettings(purpose, userProps?.interaction); const educational = this.generateEducationalFeatures(content, userProps?.educational); return { title, description, url, placeholder: !url, settings, dimensions, interaction, educational, ...userProps, }; } private extractVideoTitle(content: string): string { // Look for explicit video titles const titlePatterns = [ /(?:video|watch|view):\s*([^\n.!?]+)/i, /title:\s*([^\n.!?]+)/i, /^([^.!?]+(?:video|demonstration|tutorial))/i, /"([^"]+)"/g, // Quoted titles ]; for (const pattern of titlePatterns) { const match = content.match(pattern); if (match && match[1]) { return match[1].trim().replace(/"/g, ''); } } // Generate title from content topic const firstSentence = content.split(/[.!?]/)[0]; const topic = this.extractMainTopic(firstSentence); return topic ? `${topic} Video` : 'Educational Video'; } private extractDescription(content: string): string { // Look for description patterns const descPatterns = [ /description:\s*([^\n]+)/i, /about:\s*([^\n]+)/i, /this\s+video\s+(?:shows|demonstrates|explains|covers):\s*([^\n.!?]+)/i, /(?:demonstrates|shows|explains)\s+([^.!?]+)/i, ]; for (const pattern of descPatterns) { const match = content.match(pattern); if (match && match[1]) { return match[1].trim(); } } // Generate description from content const purpose = this.determineVideoPurpose(content); const topic = this.extractMainTopic(content); const purposeMap = { tutorial: `Learn how to ${topic}`, explanation: `Understand the concepts of ${topic}`, demonstration: `See ${topic} in action`, example: `Examples and applications of ${topic}`, overview: `Overview of ${topic}`, }; return purposeMap[purpose as keyof typeof purposeMap] || `Educational content about ${topic}`; } private extractVideoUrl(content: string): string | undefined { // Look for video URLs const urlPatterns = [ /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[\w-]+/g, /https?:\/\/youtu\.be\/[\w-]+/g, /https?:\/\/(?:www\.)?vimeo\.com\/\d+/g, /https?:\/\/[^\s]+\.(?:mp4|webm|ogg|m4v)/g, ]; for (const pattern of urlPatterns) { const match = content.match(pattern); if (match && match[0]) { return match[0]; } } return undefined; } private determineVideoPurpose(content: string): string { const lowerContent = content.toLowerCase(); if (lowerContent.includes('tutorial') || lowerContent.includes('how to') || lowerContent.includes('step')) { return 'tutorial'; } if (lowerContent.includes('explain') || lowerContent.includes('understand') || lowerContent.includes('concept')) { return 'explanation'; } if (lowerContent.includes('demo') || lowerContent.includes('show') || lowerContent.includes('display')) { return 'demonstration'; } if (lowerContent.includes('example') || lowerContent.includes('instance') || lowerContent.includes('case')) { return 'example'; } if (lowerContent.includes('overview') || lowerContent.includes('introduction') || lowerContent.includes('summary')) { return 'overview'; } return 'explanation'; // Default } private generateSettings(purpose: string, userSettings?: Partial<VideoProperties['settings']>): VideoProperties['settings'] { const baseSettings = { tutorial: { autoplay: false, controls: true, loop: false, muted: false, playbackSpeed: 1.0, showCaptions: true, }, explanation: { autoplay: false, controls: true, loop: false, muted: false, playbackSpeed: 1.0, showCaptions: true, }, demonstration: { autoplay: false, controls: true, loop: true, // Loop demonstrations for repeated viewing muted: false, playbackSpeed: 1.0, showCaptions: false, }, example: { autoplay: false, controls: true, loop: false, muted: false, playbackSpeed: 1.0, showCaptions: false, }, overview: { autoplay: true, // Auto-start overviews controls: true, loop: false, muted: true, // Start muted for auto-play compliance playbackSpeed: 1.0, showCaptions: true, }, }; return { ...baseSettings[purpose as keyof typeof baseSettings] || baseSettings.explanation, ...userSettings, }; } private generateDimensions(purpose: string, userDimensions?: Partial<VideoProperties['dimensions']>): VideoProperties['dimensions'] { const baseDimensions = { tutorial: { width: 1280, height: 720, aspectRatio: '16:9' as const }, explanation: { width: 1280, height: 720, aspectRatio: '16:9' as const }, demonstration: { width: 1280, height: 720, aspectRatio: '16:9' as const }, example: { width: 1280, height: 720, aspectRatio: '16:9' as const }, overview: { width: 1280, height: 720, aspectRatio: '16:9' as const }, }; return { ...baseDimensions[purpose as keyof typeof baseDimensions] || baseDimensions.explanation, ...userDimensions, }; } private generateInteractionSettings(purpose: string, userInteraction?: Partial<VideoProperties['interaction']>): VideoProperties['interaction'] { const baseInteraction = { tutorial: { enablePause: true, showProgress: true, allowSeeking: true, showVolume: true, enableFullscreen: true, }, explanation: { enablePause: true, showProgress: true, allowSeeking: true, showVolume: true, enableFullscreen: true, }, demonstration: { enablePause: true, showProgress: false, // Hide progress for focused viewing allowSeeking: false, // Prevent skipping in demonstrations showVolume: true, enableFullscreen: true, }, example: { enablePause: true, showProgress: true, allowSeeking: true, showVolume: true, enableFullscreen: true, }, overview: { enablePause: true, showProgress: true, allowSeeking: true, showVolume: true, enableFullscreen: false, // Keep in context for overviews }, }; return { ...baseInteraction[purpose as keyof typeof baseInteraction] || baseInteraction.explanation, ...userInteraction, }; } private generateEducationalFeatures(content: string, userEducational?: Partial<VideoProperties['educational']>): VideoProperties['educational'] { const keyMoments = this.extractKeyMoments(content); const followUpQuestions = this.generateFollowUpQuestions(content); const relatedResources = this.extractRelatedResources(content); return { hasTranscript: false, // Default to false, can be enabled keyMoments, followUpQuestions, relatedResources, ...userEducational, }; } private extractKeyMoments(content: string): VideoMoment[] { const moments: VideoMoment[] = []; // Look for time stamps in content const timestampPattern = /(\d{1,2}):(\d{2})\s*[-–]\s*([^.\n]+)/g; let match; while ((match = timestampPattern.exec(content)) !== null) { const minutes = parseInt(match[1]); const seconds = parseInt(match[2]); const time = minutes * 60 + seconds; const description = match[3].trim(); moments.push({ time, title: this.extractSnippet(description, 30), description, type: this.classifyMomentType(description), }); } // If no explicit timestamps, generate key moments from content structure if (moments.length === 0) { const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 20); sentences.slice(0, 3).forEach((sentence, index) => { moments.push({ time: index * 30, // Distribute evenly title: `Key Point ${index + 1}`, description: sentence.trim(), type: 'concept', }); }); } return moments; } private classifyMomentType(description: string): VideoMoment['type'] { const lower = description.toLowerCase(); if (lower.includes('example') || lower.includes('instance') || lower.includes('case')) { return 'example'; } if (lower.includes('summary') || lower.includes('conclusion') || lower.includes('recap')) { return 'summary'; } if (lower.includes('next') || lower.includes('now') || lower.includes('moving')) { return 'transition'; } return 'concept'; // Default } private generateFollowUpQuestions(content: string): string[] { const topic = this.extractMainTopic(content); const purpose = this.determineVideoPurpose(content); const questionTemplates = { tutorial: [ `What are the key steps shown in this ${topic} tutorial?`, `How would you apply these ${topic} techniques?`, `What challenges might you face when implementing ${topic}?`, ], explanation: [ `What is the main concept explained about ${topic}?`, `How does ${topic} relate to what you already know?`, `What questions do you still have about ${topic}?`, ], demonstration: [ `What did you observe in the ${topic} demonstration?`, `How could you modify this ${topic} approach?`, `What would happen if you changed the ${topic} parameters?`, ], example: [ `What makes this a good example of ${topic}?`, `Can you think of similar examples of ${topic}?`, `How would you explain this ${topic} example to someone else?`, ], overview: [ `What are the main points covered about ${topic}?`, `Which aspect of ${topic} interests you most?`, `What would you like to explore further about ${topic}?`, ], }; return questionTemplates[purpose as keyof typeof questionTemplates] || questionTemplates.explanation; } private extractRelatedResources(content: string): string[] { const resources: string[] = []; // Look for explicit resource mentions const resourcePatterns = [ /(?:see also|reference|link|resource):\s*([^\n.!?]+)/gi, /(?:more information|additional|further reading):\s*([^\n.!?]+)/gi, ]; resourcePatterns.forEach(pattern => { let match; while ((match = pattern.exec(content)) !== null) { resources.push(match[1].trim()); } }); // Generate default related resources based on topic if (resources.length === 0) { const topic = this.extractMainTopic(content); resources.push( `Additional resources about ${topic}`, `Practice exercises for ${topic}`, `Advanced concepts in ${topic}` ); } return resources.slice(0, 5); // Limit to 5 resources } private extractMainTopic(content: string): string { // Simple topic extraction - same as quiz plugin const words = content.toLowerCase() .replace(/[^\w\s]/g, ' ') .split(/\s+/) .filter(word => word.length > 3); const frequency: Record<string, number> = {}; words.forEach(word => { frequency[word] = (frequency[word] || 0) + 1; }); const commonWords = ['the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'its', 'may', 'new', 'now', 'old', 'see', 'two', 'who', 'boy', 'did', 'man', 'run', 'say', 'she', 'too', 'use', 'this', 'that', 'with', 'have', 'from', 'they', 'know', 'want', 'been', 'good', 'much', 'some', 'time', 'very', 'when', 'come', 'here', 'just', 'like', 'long', 'make', 'many', 'over', 'such', 'take', 'than', 'them', 'well', 'were']; const topWords = Object.entries(frequency) .filter(([word]) => !commonWords.includes(word)) .sort(([,a], [,b]) => b - a); return topWords.length > 0 ? topWords[0][0] : 'content'; } }

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/rkm097git/euconquisto-composer-mcp-poc'

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