diff --git a/TECHNICAL_SPECIFICATION.md b/TECHNICAL_SPECIFICATION.md new file mode 100644 index 0000000..1af5666 --- /dev/null +++ b/TECHNICAL_SPECIFICATION.md @@ -0,0 +1,1507 @@ +# **Movie Creator Web App - Technical Specification** + +## **Executive Summary** + +A browser-based video editing platform built with Next.js 14+, React, TailwindCSS, Zustand, and Remotion that enables users to create, edit, and render video projects with a timeline-based editor similar to CapCut or iMovie. + +--- + +## **1. Technical Stack** + +### **Core Technologies** +- **Framework**: Next.js 14+ (App Router) +- **UI Library**: React 18+ +- **Styling**: TailwindCSS + shadcn/ui components +- **State Management**: Zustand (with middleware for persistence) +- **Video Engine**: Remotion 4.x +- **Storage**: + - Primary: IndexedDB (via Dexie.js) + - Optional: Supabase (PostgreSQL + Storage) +- **File Handling**: Browser File System Access API (with fallbacks) +- **Rendering**: Remotion Lambda (cloud) or local rendering +- **Deployment**: Vercel + +### **Supporting Libraries** +- **Drag & Drop**: @dnd-kit/core, @dnd-kit/sortable +- **Media Processing**: FFmpeg.wasm (browser-based video manipulation) +- **Timeline UI**: Custom implementation with Framer Motion +- **Icons**: Lucide React +- **Form Validation**: Zod +- **Date/Time**: date-fns +- **Video Playback**: React Player (for preview) +- **Audio Visualization**: WaveSurfer.js + +--- + +## **2. System Architecture** + +### **2.1 High-Level Architecture** + +``` +┌─────────────────────────────────────────────────────┐ +│ Next.js App Router │ +├─────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ +│ │ Dashboard │ │ Editor │ │ Render │ │ +│ │ Page │ │ Page │ │ Page │ │ +│ └──────────────┘ └──────────────┘ └──────────┘ │ +│ │ +├─────────────────────────────────────────────────────┤ +│ Zustand State Management │ +│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐│ +│ │ Project │ │ Timeline │ │ Playback ││ +│ │ Store │ │ Store │ │ Store ││ +│ └─────────────┘ └──────────────┘ └──────────────┘│ +├─────────────────────────────────────────────────────┤ +│ Service Layer │ +│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐│ +│ │ Storage │ │ Asset │ │ Remotion ││ +│ │ Service │ │ Manager │ │ Renderer ││ +│ └─────────────┘ └──────────────┘ └──────────────┘│ +├─────────────────────────────────────────────────────┤ +│ Data Persistence Layer │ +│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐│ +│ │ IndexedDB │ │ Supabase │ │ File System ││ +│ │ (Dexie) │ │ (Optional) │ │ API ││ +│ └─────────────┘ └──────────────┘ └──────────────┘│ +└─────────────────────────────────────────────────────┘ +``` + +### **2.2 Folder Structure** + +``` +movie-creator/ +├── app/ +│ ├── (auth)/ # Auth-related pages (future) +│ ├── (marketing)/ # Landing, pricing pages +│ ├── dashboard/ +│ │ ├── page.tsx # Project list dashboard +│ │ └── layout.tsx +│ ├── editor/ +│ │ └── [projectId]/ +│ │ ├── page.tsx # Main editor +│ │ └── layout.tsx +│ ├── render/ +│ │ └── [projectId]/ +│ │ └── page.tsx # Render queue page +│ ├── api/ +│ │ ├── projects/ +│ │ │ ├── route.ts # CRUD operations +│ │ │ └── [id]/route.ts +│ │ ├── assets/ +│ │ │ └── route.ts # Asset upload/management +│ │ └── render/ +│ │ └── route.ts # Trigger rendering +│ ├── layout.tsx +│ └── page.tsx # Landing page +│ +├── components/ +│ ├── dashboard/ +│ │ ├── project-card.tsx +│ │ ├── project-grid.tsx +│ │ └── create-project-dialog.tsx +│ ├── editor/ +│ │ ├── toolbar/ +│ │ │ ├── left-toolbar.tsx +│ │ │ ├── asset-library.tsx +│ │ │ ├── text-tool.tsx +│ │ │ └── upload-asset.tsx +│ │ ├── canvas/ +│ │ │ ├── preview-canvas.tsx +│ │ │ ├── remotion-player.tsx +│ │ │ └── playback-controls.tsx +│ │ ├── timeline/ +│ │ │ ├── timeline-container.tsx +│ │ │ ├── timeline-track.tsx +│ │ │ ├── timeline-element.tsx +│ │ │ ├── timeline-ruler.tsx +│ │ │ ├── timeline-cursor.tsx +│ │ │ └── property-panel.tsx +│ │ └── editor-layout.tsx +│ ├── render/ +│ │ ├── render-queue.tsx +│ │ ├── render-progress.tsx +│ │ └── render-settings.tsx +│ └── ui/ # shadcn/ui components +│ ├── button.tsx +│ ├── dialog.tsx +│ ├── slider.tsx +│ ├── input.tsx +│ └── ... +│ +├── lib/ +│ ├── stores/ +│ │ ├── project-store.ts # Project CRUD state +│ │ ├── timeline-store.ts # Timeline state (tracks, elements) +│ │ ├── playback-store.ts # Playback controls state +│ │ └── asset-store.ts # Asset management state +│ ├── services/ +│ │ ├── storage/ +│ │ │ ├── indexeddb.ts # Dexie wrapper +│ │ │ ├── supabase.ts # Supabase client +│ │ │ └── storage-adapter.ts +│ │ ├── asset-manager.ts # Asset upload/processing +│ │ ├── remotion-renderer.ts # Rendering logic +│ │ └── export-manager.ts # Export/download +│ ├── remotion/ +│ │ ├── Root.tsx # Remotion root +│ │ ├── Composition.tsx # Main composition +│ │ ├── sequences/ +│ │ │ ├── VideoSequence.tsx +│ │ │ ├── ImageSequence.tsx +│ │ │ ├── AudioSequence.tsx +│ │ │ └── TextSequence.tsx +│ │ └── utils/ +│ │ ├── fps-calculator.ts +│ │ └── frame-converter.ts +│ ├── types/ +│ │ ├── project.ts +│ │ ├── timeline.ts +│ │ ├── asset.ts +│ │ └── render.ts +│ ├── hooks/ +│ │ ├── use-timeline.ts +│ │ ├── use-playback.ts +│ │ ├── use-keyboard-shortcuts.ts +│ │ └── use-drag-drop.ts +│ └── utils/ +│ ├── timeline-helpers.ts +│ ├── file-helpers.ts +│ └── format-helpers.ts +│ +├── public/ +│ └── assets/ # Default templates/assets +│ +├── remotion.config.ts +├── tailwind.config.ts +├── next.config.js +└── package.json +``` + +--- + +## **3. Data Models** + +### **3.1 Project Model** + +```typescript +interface Project { + id: string; // UUID + name: string; + description?: string; + thumbnail?: string; // Base64 or URL + createdAt: Date; + updatedAt: Date; + settings: ProjectSettings; + timeline: Timeline; + assets: Asset[]; + metadata: ProjectMetadata; +} + +interface ProjectSettings { + width: number; // Default: 1920 + height: number; // Default: 1080 + fps: number; // Default: 30 + durationInFrames: number; // Total project duration + backgroundColor: string; // Default: '#000000' +} + +interface ProjectMetadata { + version: string; // App version for compatibility + totalSize: number; // In bytes + assetCount: number; + trackCount: number; +} +``` + +### **3.2 Timeline Model** + +```typescript +interface Timeline { + tracks: Track[]; + currentFrame: number; // Current playback position + zoomLevel: number; // Timeline zoom (px per frame) + scrollPosition: number; +} + +interface Track { + id: string; + name: string; + type: 'video' | 'audio' | 'text' | 'overlay'; + order: number; // Vertical stacking order + elements: TimelineElement[]; + isLocked: boolean; + isMuted: boolean; + height: number; // Track height in pixels +} + +interface TimelineElement { + id: string; + trackId: string; + assetId?: string; // Reference to Asset (if applicable) + type: 'video' | 'image' | 'audio' | 'text' | 'shape'; + + // Timeline positioning + startFrame: number; // Start position on timeline + durationInFrames: number; // Visual duration on timeline + + // Media trimming + trimStart: number; // Trim from start (in frames) + trimEnd: number; // Trim from end (in frames) + + // Transformations + properties: ElementProperties; + + // Metadata + name: string; + layer: number; // Z-index within track +} + +interface ElementProperties { + // Visual + opacity: number; // 0-1 + scale: number; // 0.1-5 + rotation: number; // 0-360 + position: { x: number; y: number }; // Canvas position + + // Audio + volume: number; // 0-1 + + // Playback + speed: number; // 0.25-4x + playbackDirection: 'forward' | 'reverse'; + loop: boolean; + + // Text-specific + text?: TextProperties; + + // Transitions + transitionIn?: Transition; + transitionOut?: Transition; +} + +interface TextProperties { + content: string; + fontFamily: string; + fontSize: number; + color: string; + backgroundColor?: string; + alignment: 'left' | 'center' | 'right'; + fontWeight: number; + letterSpacing: number; + lineHeight: number; +} + +interface Transition { + type: 'fade' | 'slide' | 'zoom' | 'wipe'; + duration: number; // In frames + easing: string; // CSS easing function +} +``` + +### **3.3 Asset Model** + +```typescript +interface Asset { + id: string; + projectId: string; + name: string; + type: 'video' | 'audio' | 'image'; + + // File information + file: { + url: string; // Blob URL or remote URL + size: number; // In bytes + mimeType: string; + originalName: string; + }; + + // Media metadata + metadata: MediaMetadata; + + // Storage + storageType: 'indexeddb' | 'supabase' | 'url'; + storageKey?: string; // Key in storage + + // Timestamps + uploadedAt: Date; + lastAccessedAt: Date; +} + +interface MediaMetadata { + duration?: number; // In seconds (video/audio) + width?: number; // Video/image + height?: number; // Video/image + fps?: number; // Video + hasAudio?: boolean; // Video + thumbnail?: string; // Base64 preview + waveform?: number[]; // Audio waveform data +} +``` + +### **3.4 Render Job Model** + +```typescript +interface RenderJob { + id: string; + projectId: string; + status: 'pending' | 'rendering' | 'completed' | 'failed'; + + settings: RenderSettings; + + progress: { + currentFrame: number; + totalFrames: number; + percentage: number; + }; + + output?: { + url: string; + size: number; + duration: number; + }; + + error?: string; + + createdAt: Date; + startedAt?: Date; + completedAt?: Date; +} + +interface RenderSettings { + codec: 'h264' | 'h265' | 'vp8' | 'vp9'; + format: 'mp4' | 'webm' | 'mov'; + quality: number; // 0-100 + frameRange?: { + start: number; + end: number; + }; +} +``` + +--- + +## **4. State Management Architecture** + +### **4.1 Zustand Store Structure** + +**Project Store** (`project-store.ts`) +- Manages all projects +- CRUD operations +- Active project selection +- Persistence to IndexedDB + +```typescript +interface ProjectStoreState { + projects: Project[]; + activeProjectId: string | null; + isLoading: boolean; + error: string | null; + + // Actions + createProject: (name: string) => Promise; + updateProject: (id: string, updates: Partial) => Promise; + deleteProject: (id: string) => Promise; + setActiveProject: (id: string) => void; + loadProjects: () => Promise; +} +``` + +**Timeline Store** (`timeline-store.ts`) +- Manages timeline state for active project +- Track and element manipulation +- Undo/redo functionality +- Auto-save + +```typescript +interface TimelineStoreState { + timeline: Timeline; + selectedElements: string[]; + clipboard: TimelineElement[]; + history: TimelineHistory; + + // Track operations + addTrack: (type: Track['type']) => void; + removeTrack: (trackId: string) => void; + reorderTracks: (trackIds: string[]) => void; + + // Element operations + addElement: (trackId: string, element: Partial) => void; + updateElement: (elementId: string, updates: Partial) => void; + removeElement: (elementId: string) => void; + moveElement: (elementId: string, trackId: string, startFrame: number) => void; + + // Selection + selectElement: (elementId: string, multi?: boolean) => void; + clearSelection: () => void; + + // Clipboard + copy: () => void; + paste: (trackId: string, frame: number) => void; + + // History + undo: () => void; + redo: () => void; + + // Timeline controls + setCurrentFrame: (frame: number) => void; + setZoomLevel: (level: number) => void; +} +``` + +**Playback Store** (`playback-store.ts`) +- Controls playback state +- Synchronizes with Remotion Player +- Handles scrubbing and seeking + +```typescript +interface PlaybackStoreState { + isPlaying: boolean; + currentFrame: number; + fps: number; + loop: boolean; + + // Actions + play: () => void; + pause: () => void; + togglePlay: () => void; + seek: (frame: number) => void; + skipForward: (frames: number) => void; + skipBackward: (frames: number) => void; + toggleLoop: () => void; +} +``` + +**Asset Store** (`asset-store.ts`) +- Manages uploaded assets +- Handles file processing +- Manages asset library + +```typescript +interface AssetStoreState { + assets: Asset[]; + isUploading: boolean; + uploadProgress: { [key: string]: number }; + + // Actions + uploadAsset: (file: File) => Promise; + uploadFromUrl: (url: string) => Promise; + deleteAsset: (assetId: string) => Promise; + getAsset: (assetId: string) => Asset | undefined; + processAssetMetadata: (asset: Asset) => Promise; +} +``` + +### **4.2 State Persistence Strategy** + +**IndexedDB Schema** (via Dexie.js) + +```typescript +class MovieCreatorDB extends Dexie { + projects!: Table; + assets!: Table; + renderJobs!: Table; + + constructor() { + super('MovieCreatorDB'); + this.version(1).stores({ + projects: 'id, name, createdAt, updatedAt', + assets: 'id, projectId, type, uploadedAt', + renderJobs: 'id, projectId, status, createdAt' + }); + } +} +``` + +**Middleware for Auto-Persistence** + +```typescript +// Zustand middleware that syncs to IndexedDB +const persistMiddleware = (store) => { + store.subscribe((state) => { + // Debounced save to IndexedDB + debouncedSave(state); + }); +}; +``` + +--- + +## **5. Editor Components Architecture** + +### **5.1 Left Toolbar Component** + +**Features:** +- Collapsible panels +- Asset library with thumbnails +- Drag-to-timeline functionality +- Upload UI with progress +- Text/shape tools + +**Component Structure:** +``` +LeftToolbar +├── AssetLibraryPanel +│ ├── AssetUploadZone +│ ├── AssetGrid +│ │ └── AssetCard (draggable) +│ └── AssetFilters +├── TextToolPanel +│ ├── TextTemplates +│ └── TextStyleEditor +└── ShapeToolPanel + └── ShapeLibrary +``` + +**Key Implementation Details:** +- Use `@dnd-kit` for drag-from-library to timeline +- Lazy load asset thumbnails for performance +- Use virtual scrolling for large asset lists (react-window) +- Store blob URLs in memory, actual files in IndexedDB + +### **5.2 Canvas / Preview Component** + +**Features:** +- Live Remotion composition preview +- Synchronized playback with timeline +- Responsive sizing +- Safe area guides +- Element selection overlay + +**Component Structure:** +``` +PreviewCanvas +├── RemotionPlayer +├── PlaybackControls +│ ├── PlayButton +│ ├── TimeDisplay +│ ├── FrameScrubber +│ └── VolumeControl +└── CanvasOverlay + ├── SafeAreaGuides + └── SelectionIndicators +``` + +**Technical Approach:** + +1. **Remotion Player Integration:** + - Use `` component from `@remotion/player` + - Sync player's `currentFrame` with timeline store + - Handle player events (play, pause, seek, ended) + +2. **Dynamic Composition:** + - Remotion composition is generated from timeline state + - Each track becomes a layer of sequences + - Properties map directly to Remotion props + +3. **Performance Optimization:** + - Use `delayRender()` for asset loading + - Implement frame caching for repeated renders + - Use `` for better performance + +### **5.3 Timeline Component** + +**Features:** +- Multi-track timeline with layers +- Drag and drop elements +- Resize elements (trim) +- Snap to grid/cursor +- Zoom and pan +- Ruler with frame numbers +- Property inspector panel + +**Component Structure:** +``` +TimelineContainer +├── TimelineHeader +│ ├── TrackControls +│ └── TimelineRuler +├── TimelineBody +│ ├── TimelineTracks (virtualized) +│ │ └── TimelineTrack +│ │ └── TimelineElement (draggable, resizable) +│ └── TimelineCursor +└── PropertyPanel + └── ElementPropertyEditor +``` + +**Interaction Patterns:** + +1. **Drag and Drop:** + - Use `@dnd-kit/core` for DnD + - Support: drag from toolbar, drag between tracks, reorder + - Show drop indicators + - Implement snap-to-frame logic + +2. **Resize/Trim:** + - Click and drag element edges + - Update `durationInFrames` and `trimStart/trimEnd` + - Show tooltip with duration + +3. **Scrubbing:** + - Click/drag on ruler to seek + - Smooth animation with `requestAnimationFrame` + - Sync with Remotion Player + +4. **Zoom and Pan:** + - Zoom: Ctrl+Scroll (adjust `zoomLevel` in pixels per frame) + - Pan: Shift+Scroll or drag on ruler + - Implement smooth zoom to cursor position + +**Timeline Rendering Strategy:** + +```typescript +// Calculate visible timeline range based on scroll and zoom +const visibleStartFrame = scrollPosition / zoomLevel; +const visibleEndFrame = visibleStartFrame + (viewportWidth / zoomLevel); + +// Only render elements within visible range (virtual scrolling) +const visibleElements = elements.filter(el => + el.startFrame < visibleEndFrame && + (el.startFrame + el.durationInFrames) > visibleStartFrame +); +``` + +--- + +## **6. Remotion Integration Architecture** + +### **6.1 Timeline State → Remotion Mapping** + +**Core Concept:** +Transform timeline state into a Remotion composition tree where: +- Each timeline track = vertical layer +- Each timeline element = `` with specific props + +**Mapping Strategy:** + +```typescript +// Main composition that receives timeline state +export const DynamicComposition: React.FC<{ timeline: Timeline }> = ({ timeline }) => { + return ( + + {timeline.tracks.map((track, index) => ( + + ))} + + ); +}; + +// Individual track renderer +const TrackLayer: React.FC<{ track: Track; zIndex: number }> = ({ track, zIndex }) => { + return ( + + {track.elements.map(element => ( + + ))} + + ); +}; + +// Element renderer with all transformations +const ElementSequence: React.FC<{ element: TimelineElement }> = ({ element }) => { + const frame = useCurrentFrame(); + + // Calculate actual media playback frame accounting for speed and direction + const mediaFrame = calculateMediaFrame( + frame, + element.startFrame, + element.properties.speed, + element.properties.playbackDirection + ); + + return ( + + + {renderElementContent(element, mediaFrame)} + + + ); +}; +``` + +### **6.2 Element Type Renderers** + +**Video Element:** +```typescript +const VideoElement: React.FC<{ element: TimelineElement; frame: number }> = ({ element, frame }) => { + const asset = useAsset(element.assetId); + + // Calculate trimmed frame accounting for speed + const trimmedFrame = Math.floor( + (frame * element.properties.speed) + element.trimStart + ); + + return ( + + ); +}; +``` + +**Image Element:** +```typescript +const ImageElement: React.FC<{ element: TimelineElement }> = ({ element }) => { + const asset = useAsset(element.assetId); + + return ( + + ); +}; +``` + +**Audio Element:** +```typescript +const AudioElement: React.FC<{ element: TimelineElement; frame: number }> = ({ element, frame }) => { + const asset = useAsset(element.assetId); + + return ( +