Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6655244
docs: update README — merge security sections, add format table & siz…
Mar 23, 2026
6838a41
docs: split format table into container vs codec, drop FFmpeg from li…
Mar 23, 2026
4a2d806
feat: add keyboard shortcut registry with NLE presets
Mar 23, 2026
2da63bd
feat: migrate keyboard handlers to shortcut registry
Mar 23, 2026
edb51d0
feat: add onboarding hint overlay to AI Chat tab
Mar 23, 2026
b32fa3a
feat: add Shortcuts settings tab with preset selector and key recorder
Mar 23, 2026
6f14ab9
feat: apply shortcut preset on onboarding NLE selection
Mar 23, 2026
8883892
fix: shortcut list not updating on preset change + consolidate settings
Mar 23, 2026
34ac0a2
feat: add video/audio toggle to effects panel
Mar 23, 2026
f5173f9
fix: filter effects by video/audio mode and render audio effect params
Mar 23, 2026
5ff4eac
refactor: unify audio controls into Effects tab, remove separate Volu…
Mar 23, 2026
bd97621
feat: enable effects during scrubbing
Mar 23, 2026
9a2ee13
feat: show shortcut confirmation after NLE selection in onboarding
Mar 23, 2026
9bcba50
feat: add 'Where are you coming from?' to Info menu
Mar 23, 2026
512cc0b
feat: add continuous save mode as default — project saves on every ch…
Mar 23, 2026
5079b8c
feat: add right-click context menu with Add items in media panel empt…
Mar 23, 2026
7b5cfae
fix: make continuous save more reliable
Mar 23, 2026
57c4362
feat: Three.js 3D layer system, model import, effect reordering & UX …
Mar 23, 2026
d0d6276
fix: correct keyboard shortcut presets from official NLE documentation
Mar 23, 2026
042a771
docs: update keyboard shortcuts with NLE preset reference table
Mar 23, 2026
d2b449d
fix: 3D export, opacity, blend, scale.z keyframes & docs
Mar 23, 2026
02b1144
docs: add 3D model formats to README, update feature docs
Mar 23, 2026
974b550
feat: add Mesh submenu with 3D primitive meshes (Cube, Sphere, Plane,…
Mar 23, 2026
b899335
fix: submenu not visible due to overflow:hidden on context menu and a…
Mar 23, 2026
ef0ec3b
fix: 3D models in nested compositions + mediaFileId linking
Mar 23, 2026
fc9bc46
docs: update documentation for Mesh primitive feature
Mar 23, 2026
bd6242e
fix: 3D models in nested comps after refresh without navigation
Mar 23, 2026
b07e4bf
chore: bump version to 1.4.2, 3D release notice
Mar 23, 2026
79c66da
chore: shortcut presets, preview & mask overlay updates
Mar 23, 2026
5f50460
docs: bold README with updated stats — 138k TS, 14 deps, 660KB gzip, 3D
Mar 23, 2026
ef495f0
chore: remove temp files from repo root
Mar 23, 2026
f5b811f
docs: make initial load badge prominent, remove stats table
Mar 23, 2026
11ba682
docs: massive initial load display in README
Mar 23, 2026
4114b64
docs: simplify initial load badge text, reduce spacing
Mar 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 45 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,31 @@

# MasterSelects

<h3>Browser-based Video Compositor</h3>
<h3>Browser-based Video Compositor & 3D Engine</h3>

<br>

<table><tr><td align="center" style="border:none;background:#0d1117;">
<h1>&#9889; 660 KB <sub>gzip</sub></h1>
<sup><b>initial load</b></sup>
</td></tr></table>


<p>
GPU-first editing with <b>30 effects</b>, <b>37 blend modes</b>, <b>76 AI tools</b>, and only <b>13 dependencies</b>.<br>
Built from scratch in <b>2,500+ lines of WGSL</b> and <b>120k lines of TypeScript</b>.
GPU-first editing with <b>30 effects</b>, <b>37 blend modes</b>, <b>76 AI tools</b>, <b>real 3D via Three.js</b>, and only <b>14 dependencies</b>.<br>
Built from scratch in <b>2,400+ lines of WGSL</b> and <b>138k lines of TypeScript</b>.<br>
Import <b>OBJ, glTF, GLB, FBX</b> models directly into the timeline.
</p>

<p>
<a href="https://github.com/Sportinger/MasterSelects/releases"><img src="https://img.shields.io/badge/version-1.4.0-blue.svg" alt="Version"></a>
<a href="https://github.com/Sportinger/MasterSelects/releases"><img src="https://img.shields.io/badge/version-1.4.2-blue.svg" alt="Version"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
<a href="https://app.fossa.com/projects/custom%2b61097%2fmasterselects"><img src="https://app.fossa.com/api/projects/custom%2b61097%2fmasterselects.svg?type=shield" alt="FOSSA Status"></a>
</p>

<p>
<a href="#"><img src="https://img.shields.io/badge/WebGPU-990000?style=flat-square&logo=webgpu&logoColor=white" alt="WebGPU"></a>
<a href="#"><img src="https://img.shields.io/badge/Three.js-000000?style=flat-square&logo=threedotjs&logoColor=white" alt="Three.js"></a>
<a href="#"><img src="https://img.shields.io/badge/React_19-61DAFB?style=flat-square&logo=react&logoColor=black" alt="React 19"></a>
<a href="#"><img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript"></a>
<a href="#"><img src="https://img.shields.io/badge/Vite-646CFF?style=flat-square&logo=vite&logoColor=white" alt="Vite"></a>
Expand All @@ -31,29 +41,36 @@

---

## Security Model
## Supported Formats

MasterSelects is a **local-first editor**. Your timeline, media processing, rendering, and most AI-adjacent operations stay on your machine unless you explicitly call an external API. The project now has explicit trust boundaries instead of relying on "it's just localhost".
Decoding depends on what the **browser** supports — the container is just the wrapper, the codec inside is what matters.

**Current protections:**
- **Native Helper bridge:** Binds to `127.0.0.1` only and requires a random startup Bearer token for HTTP and WebSocket bridge operations
- **Dev bridge hardening:** Vite `/api/ai-tools` and local file routes require a per-session token and reject non-loopback browser origins
- **Path restrictions:** Local file reads, listings, uploads, and locate/search operations are restricted to explicit allowed roots instead of arbitrary disk access
- **AI tool policy:** External bridge calls run through caller restrictions and approval/confirmation gates instead of getting unrestricted editor control
- **Secret handling:** API keys are stored in encrypted IndexedDB, `.keys.enc` import/export is disabled, and logs redact common secret/token patterns
- **Security verification:** CI includes secret scanning plus JS and Rust security checks, and the repo has dedicated tests for bridge auth, file access, and tool policy behavior
<table>
<tr><th colspan="2">Import (Decode)</th></tr>
<tr><td><b>Containers</b></td><td>MP4, MOV, WebM, MKV, AVI, M4V</td></tr>
<tr><td><b>Video codecs</b></td><td>H.264 (AVC), H.265 (HEVC)¹, VP8, VP9, AV1</td></tr>
<tr><td><b>Audio codecs</b></td><td>AAC, MP3, Opus, Vorbis, FLAC, WAV/PCM</td></tr>
<tr><td><b>Image</b></td><td>PNG, JPG, WebP, GIF, BMP, AVIF, SVG</td></tr>
<tr><td><b>3D Models</b></td><td>OBJ, glTF, GLB, FBX — rendered via Three.js with lighting</td></tr>
<tr><td><b>Download</b></td><td>YouTube, TikTok, Instagram, Twitter/X, Vimeo + <a href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md">all yt-dlp sites</a> via Native Helper</td></tr>
<tr><th colspan="2">Export (Encode)</th></tr>
<tr><td><b>Containers</b></td><td>MP4, WebM</td></tr>
<tr><td><b>Video codecs</b></td><td>H.264, H.265¹, VP9, AV1 — GPU-accelerated via WebCodecs</td></tr>
<tr><td><b>Audio codecs</b></td><td>AAC (MP4), Opus (WebM)</td></tr>
<tr><td><b>Interchange</b></td><td>FCPXML (Final Cut Pro / DaVinci Resolve), PNG sequence</td></tr>
</table>

**Known boundary:** this is still not "perfect sandboxing". Same-user local processes, malicious browser extensions, and compromised same-origin code can still be dangerous. The goal here is **clear, test-covered local trust boundaries**, not pretending a browser app is magically zero-risk.
¹ H.265 decode/encode depends on OS & hardware — full support on Windows, partial on macOS/Linux.

See [Security.md](docs/Features/Security.md) for the full trust model, secret handling, bridge details, and current limitations.
> **MOV** files work because they share the same ISO BMFF container as MP4 — any MOV with H.264/H.265 inside plays fine. **MKV** works if it contains browser-decodable codecs (H.264, VP9, etc.). Files with unsupported codecs (e.g. ProRes in MOV) fall back to the Native Helper decode path when available.

---

## What Makes This Different

Most browser-based video editors share a pattern: Canvas 2D compositing, heavyweight dependency trees, and CPU-bound rendering that falls apart at scale. This project takes a fundamentally different approach.

**GPU-first architecture.** Preview, scrubbing, and export all run through the same **WebGPU ping-pong compositor**. Video textures are imported as `texture_external` (**zero-copy**, no CPU roundtrip). **37 blend modes**, 3D rotation, and inline color effects all execute in a **single WGSL composite shader** per layer. No Three.js, no GSAP, no Canvas 2D fallback in the hot path.
**GPU-first architecture.** Preview, scrubbing, and export all run through the same **WebGPU ping-pong compositor**. Video textures are imported as `texture_external` (**zero-copy**, no CPU roundtrip). **37 blend modes**, 3D rotation, and inline color effects all execute in a **single WGSL composite shader** per layer. **Three.js** is lazily loaded only for 3D model rendering — no GSAP, no Canvas 2D fallback in the hot path.

**Zero-copy export pipeline.** Frames are captured as `new VideoFrame(offscreenCanvas)` directly from the GPU canvas. **No `readPixels()`**, no `getImageData()`, no staging buffers in the default path. The GPU renders, **WebCodecs encodes**. That's it.

Expand Down Expand Up @@ -161,23 +178,25 @@ cargo run --release # WebSocket :9876, HTTP :9877
| **AI Control** | Local HTTP bridge for external agents to steer the running editor |
| **Download** | yt-dlp integration for YouTube, TikTok, Instagram, Twitter/X, Vimeo, and other supported sites |

**Export codecs:** H.264, H.265, VP9, AV1 via WebCodecs (production). ProRes, DNxHR, FFV1, UTVideo, MJPEG via experimental FFmpeg WASM path (single-threaded, requires custom build).

**Platforms:** Windows, Linux, macOS. Building the Native Helper requires Rust. Downloads also require `yt-dlp`. See [Native Helper docs](tools/native-helper/README.md) for platform-specific setup.

---

## Security Model
## Security

MasterSelects is a **local-first editor**. Editing, rendering, caching, and most analysis stay in the browser unless you explicitly invoke an external provider or the Native Helper.

MasterSelects is local-first: editing, rendering, caching, and most analysis stay in the browser unless you explicitly invoke an external provider or the Native Helper.
- **API keys:** stored in IndexedDB with per-browser Web Crypto encryption
- **Native Helper:** binds to `127.0.0.1` only, requires a random startup Bearer token for HTTP and WebSocket
- **Dev bridge:** Vite `/api/ai-tools` and local file routes require a per-session token and reject non-loopback origins
- **Local file access:** restricted to explicit allowed roots (project root, temp, Desktop, Documents, Downloads, Videos)
- **AI tool policy:** external bridge calls run through caller restrictions and approval gates
- **Secret handling:** logs redact common secret/token patterns; `.keys.enc` export disabled
- **CI checks:** secret scanning, JS and Rust security audits, dedicated tests for bridge auth and file access policy

- **API keys:** stored in IndexedDB with per-browser Web Crypto encryption. This protects against casual inspection, not against same-origin script execution.
- **Dev bridge:** sensitive Vite routes require a per-session Bearer token and only accept localhost browser origins.
- **Native Helper:** binds to `127.0.0.1` and requires a random startup token for HTTP and WebSocket bridge operations.
- **Local file access:** limited to explicit roots such as the project root, temp, Desktop, Documents, Downloads, and Videos, plus optional `MASTERSELECTS_ALLOWED_FILE_ROOTS`.
- **Key export:** `.keys.enc` export/import is disabled until passphrase-based encryption is implemented.
**Known boundary:** this is not perfect sandboxing. Same-user local processes, malicious browser extensions, and compromised same-origin code can still be dangerous. The goal is **clear, test-covered local trust boundaries**.

See [Security docs](docs/Features/Security.md) for the trust model and known limitations, and [Native Helper docs](tools/native-helper/README.md) for auth examples.
See [Security.md](docs/Features/Security.md) for the full trust model and limitations.

---

Expand Down
124 changes: 124 additions & 0 deletions docs/Features/3D-Layers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 3D Layer System

MasterSelects supports After Effects-style 3D layers via an integrated Three.js renderer. Layers can be toggled between 2D and 3D mode, 3D model files (OBJ, glTF, GLB, FBX) can be imported directly as timeline clips, and primitive 3D meshes can be created from the Media Panel.

## Architecture

```
[2D Layers] --> Existing WebGPU Compositor (unchanged)
|
[3D Layers] --> Three.js Scene --> OffscreenCanvas --> copyExternalImageToTexture --> Compositor
^
[Camera + Lighting]
```

Three.js renders all 3D-flagged layers into an OffscreenCanvas. The result is imported into the existing WebGPU compositor as a single texture layer. 2D layers continue through the existing pipeline unchanged. Zero overhead when no 3D layers exist (Three.js is lazily loaded via dynamic import).

## Features

### Per-Layer 3D Toggle
- Any video/image layer can be toggled to 3D via the **"2D/3D" button** in the Transform panel
- 3D layers become textured planes in a Three.js scene with perspective camera
- Toggling back to 2D resets Position Z, Rotation X/Y to 0

### 3D Model Import
- Drag **OBJ, glTF, GLB, FBX** files into the timeline
- Model clips are automatically set to 3D (cannot be switched to 2D)
- Models are auto-centered and normalized to fit the viewport
- Default lighting: Ambient (0.6) + Directional (0.8)
- OBJ without MTL: gets default gray MeshStandardMaterial
- Wireframe debug toggle: **"Wire" button** in Transform panel (blue wireframe)

### Primitive Mesh Creation
Create 3D mesh primitives from the Media Panel via **+ Add > Mesh** or right-click context menu:

| Primitive | Three.js Geometry | Default Size |
|-----------|------------------|--------------|
| Cube | `BoxGeometry` | 0.6 x 0.6 x 0.6 |
| Sphere | `SphereGeometry` | radius 0.35, 32x24 segments |
| Plane | `PlaneGeometry` | 0.8 x 0.8 |
| Cylinder | `CylinderGeometry` | radius 0.25, height 0.6 |
| Torus | `TorusGeometry` | radius 0.3, tube 0.1 |
| Cone | `ConeGeometry` | radius 0.3, height 0.6 |

- Mesh items are stored in a "Meshes" folder in the Media Panel
- Drag to timeline creates a clip with `is3D: true` and `meshType`
- Default material: `MeshStandardMaterial` (color #aaaaaa, metalness 0.3, roughness 0.6)
- Wireframe toggle supported
- Default clip duration: 10 seconds (max 1 hour)
- All transform properties (position, rotation, scale) and keyframe animation supported

### Transform Controls (3D Mode)
| Property | 2D Mode | 3D Mode |
|----------|---------|---------|
| Position | X, Y | X, Y, Z |
| Scale | All, X, Y | All, X, Y (+ Z for models) |
| Rotation | Z | X, Y, Z (AE-style: `Nx + remainder`) |
| Opacity | Yes | Yes (compositor-level) |
| Blend Mode | Yes | Yes (all 37 modes) |

### AE-Style Rotation Display
Rotation values are displayed as `2x +30.0°` (2 revolutions + 30 degrees = 750°):
- **Multiplier (`2x`)**: Drag to change in 360° increments
- **Remainder (`+30.0°`)**: Fine rotation within the revolution
- Both are independently draggable and keyframeable

### Composition Camera
Per-composition camera with configurable properties:
- Position (x, y, z)
- Target / Look-at (x, y, z)
- FOV (default: 50°)
- Near/Far planes

Camera distance is auto-calculated so default-transform layers fill the viewport exactly.

## Keyframe Animation
All 3D properties are fully keyframeable:
- `position.z`, `rotation.x`, `rotation.y`, `rotation.z`, `scale.z`
- Keyframe lanes for 3D properties are hidden when clip is in 2D mode
- `scale.z` keyframes work for model clips

## Export
3D layers are included in video export. The export pipeline uses the same `engine.render()` → `RenderDispatcher` → `process3DLayers()` → Three.js path as the preview.

## Effect Support
GPU effects (blur, color correction, etc.) are applied as post-processing on the 3D scene output. They work identically to 2D layers since Three.js renders to a texture first.

## Effect Reordering
Effects can be reordered via drag-and-drop:
- Drag the **≡ handle** on each effect to reorder
- Only the handle initiates drag (sliders/controls are not blocked)
- Order affects render output (effects are chained sequentially)
- Undo/redo supported

## Key Files

| File | Purpose |
|------|---------|
| `src/engine/three/ThreeSceneRenderer.ts` | Three.js scene renderer (HMR singleton) |
| `src/engine/three/types.ts` | Layer3DData, CameraConfig types |
| `src/engine/render/RenderDispatcher.ts` | 3D layer routing (`process3DLayers`) |
| `src/engine/render/LayerCollector.ts` | Model layer passthrough |
| `src/stores/timeline/clip/addModelClip.ts` | Model clip creation (file-based) |
| `src/stores/timeline/meshClipSlice.ts` | Primitive mesh clip creation |
| `src/services/layerBuilder/LayerBuilderService.ts` | Model layer builder |
| `src/engine/export/ExportLayerBuilder.ts` | Export 3D layer support |
| `src/components/panels/properties/TransformTab.tsx` | 3D transform UI |
| `src/engine/featureFlags.ts` | `use3DLayers` flag |

## Supported File Formats

| Format | Loader | Notes |
|--------|--------|-------|
| `.obj` | OBJLoader | Blender default export, no materials without .mtl |
| `.gltf` | GLTFLoader | Khronos standard, text-based |
| `.glb` | GLTFLoader | Binary glTF, most common for web |
| `.fbx` | GLTFLoader (fallback) | Autodesk format, limited support |

## Limitations

- No PBR material editor yet (models use default or embedded materials)
- No shadow casting between layers
- Camera is per-composition, not keyframeable yet
- Single 3D layer: opacity/blend handled by compositor. Multiple 3D layers: opacity via Three.js material
- Model clips need file re-authorization after page refresh (same as video clips)
Loading
Loading