Skip to main content
Glama

Remotion Video Editing MCP Server

by smilish67
mediaUtils.ts6.56 kB
// 미디어 파일 처리 유틸리티 import { random } from 'remotion'; export interface MediaMetadata { duration?: number; width?: number; height?: number; fps?: number; fileSize: number; mimeType: string; createdAt: Date; } export interface MediaFile { id: string; name: string; url: string; type: 'video' | 'audio' | 'image'; metadata: MediaMetadata; thumbnail?: string; } // 미디어 파일 업로드 및 처리 export class MediaFileManager { private mediaFiles: Map<string, MediaFile> = new Map(); private uploadPath: string; constructor(uploadPath: string = './public/uploads') { this.uploadPath = uploadPath; } // 파일 업로드 처리 async uploadFile(file: File): Promise<MediaFile> { const fileId = this.generateId(); const fileName = `${fileId}_${file.name}`; // 파일 타입 감지 const mediaType = this.detectMediaType(file.type); // 메타데이터 추출 const metadata = await this.extractMetadata(file); // 썸네일 생성 (비디오/이미지) const thumbnail = await this.generateThumbnail(file, mediaType); const mediaFile: MediaFile = { id: fileId, name: file.name, url: fileName, type: mediaType, metadata, thumbnail }; this.mediaFiles.set(fileId, mediaFile); return mediaFile; } // 미디어 파일 목록 조회 getMediaFiles(): MediaFile[] { return Array.from(this.mediaFiles.values()); } // 특정 미디어 파일 조회 getMediaFile(id: string): MediaFile | undefined { return this.mediaFiles.get(id); } // 미디어 파일 삭제 deleteMediaFile(id: string): boolean { return this.mediaFiles.delete(id); } // 파일 타입 감지 private detectMediaType(mimeType: string): 'video' | 'audio' | 'image' { if (mimeType.startsWith('video/')) return 'video'; if (mimeType.startsWith('audio/')) return 'audio'; if (mimeType.startsWith('image/')) return 'image'; throw new Error(`Unsupported media type: ${mimeType}`); } // 메타데이터 추출 private async extractMetadata(file: File): Promise<MediaMetadata> { const metadata: MediaMetadata = { fileSize: file.size, mimeType: file.type, createdAt: new Date() }; if (file.type.startsWith('video/')) { const videoMetadata = await this.extractVideoMetadata(file); Object.assign(metadata, videoMetadata); } else if (file.type.startsWith('image/')) { const imageMetadata = await this.extractImageMetadata(file); Object.assign(metadata, imageMetadata); } return metadata; } // 비디오 메타데이터 추출 private async extractVideoMetadata(file: File): Promise<Partial<MediaMetadata>> { return new Promise((resolve) => { const video = document.createElement('video'); video.preload = 'metadata'; video.onloadedmetadata = () => { resolve({ duration: video.duration, width: video.videoWidth, height: video.videoHeight, fps: 30 // 기본값, 실제 구현에서는 더 정확한 방법 필요 }); URL.revokeObjectURL(video.src); }; video.src = URL.createObjectURL(file); }); } // 이미지 메타데이터 추출 private async extractImageMetadata(file: File): Promise<Partial<MediaMetadata>> { return new Promise((resolve) => { const img = new Image(); img.onload = () => { resolve({ width: img.width, height: img.height }); URL.revokeObjectURL(img.src); }; img.src = URL.createObjectURL(file); }); } // 썸네일 생성 private async generateThumbnail(file: File, type: 'video' | 'audio' | 'image'): Promise<string | undefined> { if (type === 'image') { return URL.createObjectURL(file); } if (type === 'video') { return this.generateVideoThumbnail(file); } return undefined; } // 비디오 썸네일 생성 private async generateVideoThumbnail(file: File): Promise<string> { return new Promise((resolve) => { const video = document.createElement('video'); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d')!; video.onloadedmetadata = () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; video.currentTime = 1; // 1초 지점의 프레임 캡처 }; video.onseeked = () => { ctx.drawImage(video, 0, 0); const thumbnailUrl = canvas.toDataURL('image/jpeg', 0.7); resolve(thumbnailUrl); URL.revokeObjectURL(video.src); }; video.src = URL.createObjectURL(file); }); } // ID 생성 (Remotion의 deterministic random 사용) private generateId(): string { return Date.now().toString(36) + random(null).toString(36).substr(2); } } // 미디어 변환 유틸리티 export class MediaConverter { // 파일 형식 변환 static async convertFormat(_file: File, _targetFormat: string): Promise<Blob> { // 실제 구현에서는 FFmpeg.js 등을 사용 throw new Error('Media conversion not implemented'); } // 비디오 압축 static async compressVideo(_file: File, _quality: number): Promise<Blob> { // 실제 구현에서는 FFmpeg.js 등을 사용 throw new Error('Video compression not implemented'); } // 오디오 추출 static async extractAudio(_videoFile: File): Promise<Blob> { // 실제 구현에서는 FFmpeg.js 등을 사용 throw new Error('Audio extraction not implemented'); } } // 시간 유틸리티 export class TimeUtils { // 초를 프레임으로 변환 static secondsToFrames(seconds: number, fps: number = 30): number { return Math.round(seconds * fps); } // 프레임을 초로 변환 static framesToSeconds(frames: number, fps: number = 30): number { return frames / fps; } // 시간 포맷팅 (HH:MM:SS) static formatTime(seconds: number): string { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // 프레임을 시간 포맷으로 변환 static formatFrameTime(frame: number, fps: number = 30): string { return this.formatTime(this.framesToSeconds(frame, fps)); } }

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/smilish67/rodumani'

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