fablecut 0.0.0-stage → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CLAUDE.md +1198 -0
  2. package/LICENSE +21 -0
  3. package/README.md +571 -2
  4. package/analyze.js +289 -0
  5. package/app.js +13339 -0
  6. package/audio-fx.js +241 -0
  7. package/cli.js +31 -0
  8. package/color.js +1043 -0
  9. package/denoise.js +120 -0
  10. package/ducking.js +127 -0
  11. package/edit-ops.js +962 -0
  12. package/encode-profiles.js +302 -0
  13. package/encoding-profiles.json +68 -0
  14. package/export-jobs.js +163 -0
  15. package/favicon.svg +14 -0
  16. package/fx-worklet.js +162 -0
  17. package/icons/favicon-32.png +0 -0
  18. package/import-url.js +293 -0
  19. package/index.html +420 -0
  20. package/library/README.md +15 -0
  21. package/library/elements/.gitkeep +0 -0
  22. package/library/elements/bokeh-drift.svg +19 -0
  23. package/library/elements/light-leak-cool.svg +16 -0
  24. package/library/elements/light-leak-warm.svg +22 -0
  25. package/library/elements/vhs-scanlines.svg +18 -0
  26. package/library/elements/vignette-frame.svg +14 -0
  27. package/library/fonts/.gitkeep +0 -0
  28. package/library/fonts/Abril Fatface.woff2 +0 -0
  29. package/library/fonts/Anton.woff2 +0 -0
  30. package/library/fonts/Archivo Black.woff2 +0 -0
  31. package/library/fonts/Bangers.woff2 +0 -0
  32. package/library/fonts/Barlow.woff2 +0 -0
  33. package/library/fonts/Bebas Neue.woff2 +0 -0
  34. package/library/fonts/Bungee.woff2 +0 -0
  35. package/library/fonts/Caveat.woff2 +0 -0
  36. package/library/fonts/DM Sans.woff2 +0 -0
  37. package/library/fonts/Inter.woff2 +0 -0
  38. package/library/fonts/LICENSES.md +31 -0
  39. package/library/fonts/Lato.woff2 +0 -0
  40. package/library/fonts/Montserrat.woff2 +0 -0
  41. package/library/fonts/Oswald.woff2 +0 -0
  42. package/library/fonts/Pacifico.woff2 +0 -0
  43. package/library/fonts/Playfair Display.woff2 +0 -0
  44. package/library/fonts/Poppins.woff2 +0 -0
  45. package/library/fonts/Righteous.woff2 +0 -0
  46. package/library/fonts/Roboto.woff2 +0 -0
  47. package/library/fonts/Rubik.woff2 +0 -0
  48. package/library/fonts/Teko.woff2 +0 -0
  49. package/library/sfx/README.md +18 -0
  50. package/library/svg/ai-star.svg +17 -0
  51. package/library/svg/arrow-bounce.svg +18 -0
  52. package/library/svg/arrow-scribble.svg +13 -0
  53. package/library/svg/burst-lines.svg +30 -0
  54. package/library/svg/checkmark-success.svg +19 -0
  55. package/library/svg/confetti-burst.svg +23 -0
  56. package/library/svg/countdown-ring.svg +92 -0
  57. package/library/svg/equalizer.svg +18 -0
  58. package/library/svg/glow-orbs.svg +29 -0
  59. package/library/svg/heart-pop.svg +28 -0
  60. package/library/svg/loading-dots.svg +16 -0
  61. package/library/svg/location-pin.svg +25 -0
  62. package/library/svg/lower-third.svg +17 -0
  63. package/library/svg/new-badge.svg +18 -0
  64. package/library/svg/progress-bar.svg +16 -0
  65. package/library/svg/radial-pulse.svg +19 -0
  66. package/library/svg/rating-stars.svg +25 -0
  67. package/library/svg/rec-indicator.svg +41 -0
  68. package/library/svg/sparkles.svg +17 -0
  69. package/library/svg/spawning-hearts.svg +39 -0
  70. package/library/svg/speech-bubble.svg +23 -0
  71. package/library/svg/subscribe-bell.svg +29 -0
  72. package/library/svg/subscribe-pill.svg +22 -0
  73. package/library/svg/swipe-up.svg +46 -0
  74. package/library/svg/underline-swoosh.svg +15 -0
  75. package/loudness.js +135 -0
  76. package/mcp-server.js +1421 -0
  77. package/meter-worklet.js +229 -0
  78. package/package.json +45 -4
  79. package/paths.js +106 -0
  80. package/ruler-worker.js +120 -0
  81. package/server.js +775 -0
  82. package/style.css +3563 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ronak-create
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,572 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="docs/fablecut-banner.png" alt="FableCut" width="800">
4
+
5
+
6
+ **A browser video editor that AI agents can drive.**
7
+
8
+ <a href="https://trendshift.io/repositories/77702?utm_source=trendshift-badge&amp;utm_medium=badge&amp;utm_campaign=badge-trendshift-77702" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/77702/daily?language=JavaScript" alt="ronak-create%2FFableCut | Trendshift" width="250" height="55"/></a>
9
+
10
+ [![Hacker News — front page](https://img.shields.io/badge/Hacker%20News-front%20page-ff6600?logo=ycombinator&logoColor=white)](https://news.ycombinator.com/item?id=48845422)
11
+ [![DEV — Top 7 of the week](https://img.shields.io/badge/DEV-Top%207%20of%20the%20week-0A0A0A?logo=devdotto&logoColor=white)](https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-815)
12
+ [![Official MCP registry](https://img.shields.io/badge/MCP%20registry-io.github.ronak--create%2Ffablecut-7b6cff?logo=modelcontextprotocol&logoColor=white)](https://registry.modelcontextprotocol.io/v0/servers?search=fablecut)
13
+ [![Mentioned in Awesome MCP Servers](https://awesome.re/mentioned-badge.svg)](https://github.com/punkpeye/awesome-mcp-servers)
14
+ [![Glama score](https://glama.ai/mcp/servers/ronak-create/FableCut/badges/score.svg)](https://glama.ai/mcp/servers/ronak-create/FableCut)
15
+ [![Glama — #18 Best Browser Automation MCP Servers](https://img.shields.io/badge/Glama-%2318%20Best%20Browser%20Automation-0e1618)](https://glama.ai/mcp/best/browser-automation)
16
+ [![Docs](https://img.shields.io/badge/Docs-fablecut.space%2Fdocs-7b6cff)](https://fablecut.space/docs/)
17
+ [![Discord](https://img.shields.io/badge/Discord-join%20the%20community-5865F2?logo=discord&logoColor=white)](https://discord.gg/EFMQH7d6Tv)
18
+
19
+ **English** · [简体中文](docs/i18n/README.zh-CN.md) · [日本語](docs/i18n/README.ja.md) · [Español](docs/i18n/README.es.md) · [Português (BR)](docs/i18n/README.pt-BR.md)
20
+
21
+ </div>
22
+
23
+ <https://github.com/user-attachments/assets/2430b854-168b-4a9a-af2e-489e5efa7543>
24
+
25
+ FableCut is a Premiere-style non-linear video editor that runs entirely in your
26
+ browser — and exposes its whole timeline as one JSON document. Edit it by hand,
27
+ from the UI, or let an AI agent (Claude Code, Claude Desktop, or anything that
28
+ speaks MCP/REST) cut your video for you while you watch the timeline update
29
+ live.
30
+
31
+ Zero npm dependencies. One `node server.js`. That's it.
32
+
33
+ ![FableCut editor](docs/screenshot.png)
34
+
35
+ ## Why it's interesting
36
+
37
+ Most "AI video" tools hide the edit behind an API. FableCut flips that: the
38
+ **project file is the interface**. `project.json` describes media, clips,
39
+ tracks, effects, keyframes and transitions — any process that can write JSON
40
+ can edit video, and the open browser UI hot-reloads within ~150 ms via
41
+ server-sent events. A human and an agent can work on the same timeline at the
42
+ same time.
43
+
44
+ ## Features
45
+
46
+ **Editing**
47
+
48
+ - Video + audio tracks (default 3+4; add more with **+V** / **+A** in the track
49
+ header; right-click an empty header → **Remove track**), drag/trim/split/snap,
50
+ undo/redo
51
+ - **Settings** (cog in the top bar) — optional prefs stored in this browser via
52
+ `localStorage`. Enable **Link timeline and Project bin selection** so picking a
53
+ timeline clip highlights its media in Project, and clicking a Project item
54
+ selects every timeline clip that uses it (off by default).
55
+ - Video + audio tracks (default 3+4), drag/trim/split/snap, undo/redo
56
+ - **+V** / **+A** in the track header add a video or audio lane (up to 16 each)
57
+ - Right-click an empty track header → **Remove track** (disabled if the lane
58
+ has clips, or if it would leave you with zero video/audio tracks)
59
+ - Track header **S** solos that lane (mutes all others); click again to restore
60
+ the previous mute state. Using the mute toggle while soloed exits solo.
61
+ - **Track targeting** — click a track's name to target / untarget it (lit =
62
+ targeted). Split, insert, ripple, close gap and jump-to-cut touch targeted
63
+ tracks only; the eye / speaker toggle is output-only (hide from preview and
64
+ export) and no longer blocks edits. A selected clip is edited wherever it sits.
65
+ - **Track lock** — the padlock in the track header. Nothing on a locked track
66
+ can be moved, trimmed, split or deleted, and ripples leave it in place while
67
+ the other tracks shift — lock the music bed and re-cut the picture freely.
68
+ - **Clip lock, disable and unlink** — right-click a clip (or use the toggles at
69
+ the top of the inspector): **Lock** pins it like a locked track; **Disable**
70
+ (<kbd>⇧E</kbd>) keeps it on the timeline but hides it from preview and export;
71
+ **Unlink** (<kbd>Ctrl/Cmd+L</kbd>) splits a video from its audio so they move
72
+ separately, and **Link** re-joins them once they line up again. Agents see
73
+ locks too: `fablecut_patch_project` refuses to edit locked clips unless the
74
+ op says `force: true`.
75
+ - Dropping a video with more audio channels than A-tracks **adds the missing
76
+ lanes automatically** (up to 16) and toasts how many were added; each channel
77
+ becomes a linked stem on its own A-track
78
+ - **Direct manipulation on the monitor** — click a clip or title on the preview to
79
+ move, resize (corner handles), or rotate (top handle, Shift-snap) it directly
80
+ - **Timeline multi-select** — rubber-band marquee (drag on empty track area),
81
+ <kbd>Ctrl/Cmd/Shift+click</kbd> to add/remove clips, <kbd>Ctrl+A</kbd> to
82
+ select all, <kbd>Esc</kbd> to deselect. Drag any selected clip to move the
83
+ whole group; <kbd>Delete</kbd> removes all selected; <kbd>S</kbd> splits all
84
+ selected at the playhead. Inspector shows an "N clips selected" banner.
85
+ - Beat & cue markers (tap <kbd>m</kbd> on the beat during playback) — name and colour them, drag them on the ruler, jump with <kbd>⇧m</kbd> / <kbd>Alt+⇧m</kbd> or the **Markers** list; snapping targets (clip edges, playhead, markers, IN/OUT, keyframes, frame grid) are picked from the **▾** beside Snap
86
+ - Press <kbd>Alt+t</kbd> to add an in/out transition based on the playhead position over the selected clip. The last used transition is remembered as the default. Drag the overlay triangle to adjust duration; <kbd>Delete</kbd> clears the focused transition.
87
+ - Real decoded audio waveforms on clips
88
+ - **Mixer** — the **Mixer** tab beside the Inspector has a strip per audio
89
+ track (fader, pan, mute, solo, meter) and a master fader with a live LUFS
90
+ readout. Preview and export run through the same mix, so what you hear is
91
+ what renders.
92
+ - **Clip gain, channels and Normalize** — each audio clip has a **Gain** (dB,
93
+ before volume and keyframes), a **Channels** mode (stereo, mono, left,
94
+ right, swap) and **Normalize**: measure the selected clips (ITU-R BS.1770
95
+ loudness or sample peak) and set their gain to hit −14 / −16 / −23 LUFS or
96
+ −1 dBFS. Linked stems share one gain so a stereo pair stays balanced.
97
+ - **Volume line, fades and crossfades** — every audio clip shows its volume as
98
+ a line you drag; Ctrl/Cmd-click adds a keyframe, Alt-click removes one. Corner
99
+ grips drag fades in and out, drawn with their real curve (constant power,
100
+ constant gain or exponential). **Shift+D** crossfades the cut next to the
101
+ selection, borrowing spare media from both sides so nothing else moves.
102
+ - **Auto-duck** — select the music, pick the dialogue track and an amount, and
103
+ the music dips wherever someone speaks (ramping down just before, back up
104
+ after). It rides on top of the music's own volume, so re-running or clearing
105
+ it never touches your levels. Agents use `fablecut_auto_duck`.
106
+ - **Audio effects and presets** — EQ, high/low-pass, compressor, limiter,
107
+ noise gate, delay, reverb, distortion, stereo width and pitch shift, chained
108
+ per clip, per track (Mixer → **FX**), per bus and on the master. Presets:
109
+ Clean voice, Podcast, Radio, Deep voice, Telephone, Cinematic, Wide and
110
+ Muffled, all tweakable afterwards. Preview and export run the same effects.
111
+ Agents use the `setFx` patch op.
112
+ - **Effect automation** — the ◆ beside an effect setting keys it at the
113
+ playhead, like clip keyframes: sweep a filter into the drop, open up a
114
+ reverb at the end. Agents use `setFxKeys`.
115
+ - **Submix buses** — **+ Bus** in the Mixer adds a bus with its own effects,
116
+ fader, pan, mute and meter; route tracks into it from the menu under each
117
+ track's name (all the dialogue through one compressor and one fader).
118
+ - **Noise reduction** — the inspector's **Noise** control (Light / Medium /
119
+ Strong) measures the file's noise floor and renders a cleaned copy with
120
+ ffmpeg; the clip's audio switches to it and **Off** switches back. Agents
121
+ use `fablecut_denoise`.
122
+ - **Project bin folders** — tree view with expand/collapse; drag media or folders to nest; right-click the **Project** tab → New folder; drop files onto a folder to import into it
123
+ - **Import from URL** — **+ URL** downloads an HTTPS video/audio/image into
124
+ `./media/` (same-origin after import). Remote SVG is refused. Agents use
125
+ `fablecut_import_media` with an `https://` path. The URL is not kept as
126
+ `media.src` — that would taint the canvas and break export.
127
+ - **Audio Hold** — timeline toolbar toggle: while paused, loops **one frame** of
128
+ audio at the playhead (useful when stepping frame-by-frame). Scrubbing or
129
+ frame-step retargets the held slice; meters stay live. **Play** / **Pause**
130
+ turns it off.
131
+ - Canvas aspect presets (16:9, 9:16 reels, 4:5, 1:1) + project FPS select
132
+ (24 / 25 / 30 / 50 / 60; non-preset rates show as Custom) + safe-area guides
133
+ - **Export frame / reframing** — composition canvas can be larger than the delivery
134
+ crop (`exportFrame` in `project.json`). Preview dims the overscan; drag the
135
+ **Export frame** handle to reframe (e.g. 16:9 canvas → 9:16 export). Fast export
136
+ crops to the frame; WebCodecs and Realtime export are disabled while a frame is set
137
+ - **Program Monitor zoom** — mouse-wheel over the preview zooms the composition
138
+ toward the cursor (fit → up to **2 screen pixels per canvas pixel**). Magnified
139
+ view uses **native scrollbars** so overflow stays reachable; middle-click or
140
+ <kbd>Alt</kbd>+drag pans. The **Fit** button (shown while zoomed) resets to the
141
+ fit-to-stage baseline
142
+ - Full **J**/**K**/**L** shuttle — <kbd>L</kbd> plays forward, <kbd>J</kbd> in reverse; tap
143
+ again for 1.5×/2×/4×, the other key turns around, <kbd>K</kbd> stops. Hold <kbd>K</kbd> and
144
+ tap <kbd>J</kbd>/<kbd>L</kbd> to step a frame, or hold both to crawl at ¼ speed. Preview
145
+ only, never the export
146
+ - Typed timecode — click the playhead readout (or type digits) to jump: `01:15:00`,
147
+ `1500`, `+30`, `12.5`; the IN / OUT readouts take typed times too
148
+ - Resizable workspace: drag the divider between monitor and timeline (double-click resets), plus S/M/L timeline track-density presets (S hides thumbnails for compact tracks)
149
+ - **Zoom to selection** (<kbd>⇧Z</kbd>) frames all selected clips, not just one
150
+ - **IN/OUT work area** — set markers with <kbd>i</kbd> and <kbd>o</kbd> (<kbd>⇧I</kbd> / <kbd>⇧O</kbd> to clear). The Program Monitor shows playhead as `current / sequence duration`; when markers are set, IN, marked duration, and OUT stack on the right. **Export** has a Range dropdown (Entire timeline / IN–OUT; defaults to IN–OUT when markers exist) so you can keep markers for split/trim and still export the full sequence. Enabling **Limit** constrains playback to the marked range and maps <kbd>Home</kbd> / <kbd>End</kbd> to the IN and OUT positions rather than the full timeline. <kbd>t</kbd> splits clips at the markers; <kbd>⇧t</kbd> trims clips to the work (between marker in and marker out) area.
151
+ - **Find & close gaps** — a gap is a stretch where every targeted track is empty (black frames). <kbd>g</kbd> jumps the playhead to the next shared gap (wraps; respects IN/OUT when both are set). <kbd>⇧G</kbd> closes the gap under the playhead by pulling later clips left on all targeted tracks (locked clips stay put).
152
+ - **Trim tools** — a tool picker in the timeline toolbar (or <kbd>V</kbd> <kbd>B</kbd> <kbd>R</kbd> <kbd>Y</kbd> <kbd>U</kbd>): **Selection**, **Ripple edit** (drag a clip edge and everything after it follows, so no gap opens), **Rolling edit** (drag a cut between two clips; nothing else moves), **Slip** (change which part of the source a clip shows without moving it) and **Slide** (move a clip while its neighbours trim to make room). Every tool respects source length, targeting, locks and linked audio, and shows the offset beside the pointer while you drag. Agents run the same edits — split, ripple delete, lift / extract, insert / overwrite, ripple / roll / slip / slide and crossfade — as `fablecut_patch_project` ops, on the same code.
153
+ - **Lift / Extract** — <kbd>;</kbd> removes the IN→OUT range on the targeted tracks and leaves the gap; <kbd>'</kbd> removes it and closes the gap. Both also sit in the timeline toolbar.
154
+ - **Jump to cut** — <kbd>↑</kbd> / <kbd>↓</kbd> move the playhead to the previous / next edit (clip In or Out). With a clip selected, the first taps land on that clip’s start then end (Premiere-style); with no selection they walk cuts on targeted tracks. In the Source monitor they jump among 0, In, Out, and duration. Left/right still step frames; Home/End still go to the sequence (or IN/OUT with Limit).
155
+ - **Ripple delete** — the timeline **Ripple delete** button (or <kbd>⇧Del</kbd>) removes the selection and pulls later clips left on each **targeted** track to close the gap; plain <kbd>Del</kbd> still lifts (leaves a gap). Linked AV partners always move together, even on untargeted tracks (sync lock); locked clips are never deleted or moved.
156
+ - **Reset a property** — <kbd>Ctrl/Cmd+click</kbd> an inspector **label** or **slider** restores that effect/prop to its default *and* clears every keyframe on the channel (scale → 1, opacity → 1, paired fields like Crop L/R reset together; transition labels clear the in/out transition). <kbd>Shift+click</kbd> a label is playhead-local: if you are parked on a keyframe it removes **that** keyframe only; otherwise it sets the value at the playhead to the default (auto-keys if the channel is already animated).
157
+ - **Replace media** — the inspector's **Source** button (any video/audio/image/svg
158
+ clip) swaps the underlying file while keeping position, trim, keyframes,
159
+ transitions and every effect. Pick another item already in the bin or
160
+ **Browse file…** to import and replace in one step. A video's linked L/R
161
+ audio companions are swapped along with it; a shorter replacement clamps the
162
+ trim to fit and toasts that it did so.
163
+ - **Multi-channel video audio** — a video with more than 2 audio channels gets
164
+ a linked audio clip per channel, not just L/R (5.1, 7.1…). Extra audio
165
+ tracks (A5, A6, …, capped at 16) are created automatically as needed;
166
+ replacing a clip's media re-syncs the linked channel clips to the new
167
+ source's channel count, adding/dropping extras and new tracks as needed.
168
+
169
+ **Look**
170
+
171
+ - 14 one-click filter presets (cinematic, teal-orange, noir, vintage, cyberpunk, sunset, midnight…)
172
+ - **Adjustment layers** — one clip grades everything below it, Premiere-style
173
+ - **Color workspace** (top bar → **Color**): live **waveform, RGB parade,
174
+ vectorscope and histogram** beside the program monitor, and a **Color** tab
175
+ that grades the selected clip on the GPU — **lift / gamma / gain / offset
176
+ wheels**, exposure, white balance with a **picker** (click something white),
177
+ blacks / shadows / midtones / highlights / whites, log-style contrast with a
178
+ pivot, soft rolloff and saturation, plus **curves** — luma and R / G / B,
179
+ hue vs hue, hue vs saturation, hue vs luma and saturation vs luma, with a
180
+ picker that marks a colour from the monitor, and **layers** (secondaries):
181
+ a correction limited by an HSL **qualifier** and / or an ellipse, rect or
182
+ polygon **mask** (feathered, invertible, keyframable, dragged on the
183
+ monitor), with a matte view. Copy / paste a grade across shots.
184
+ Agents grade with the `setGrade` patch op and read the result as numbers
185
+ with `fablecut_scopes` (levels, clipping, colour cast)
186
+ - Quick filter sliders: brightness/contrast/saturation/hue, **temperature & tint**,
187
+ blur, grayscale/sepia/invert, **vignette**, animated **film grain**
188
+ - Blend modes (screen, multiply, overlay…), fit modes (contain/cover/stretch),
189
+ per-edge cropping, corner radius, flip H/V
190
+ - **Chroma key** (green screen) with tolerance/softness + spill suppression
191
+ - **AI background removal** (person cut-out, in-browser via MediaPipe)
192
+
193
+ **Motion**
194
+
195
+ - Keyframe animation on ~25 properties with easing
196
+ - **Keyframe markers on clips** — diamonds on the clip body at each unique
197
+ keyframe time (tooltip lists channels; a count badge when several share a
198
+ time). <kbd>Ctrl/Cmd+←</kbd> / <kbd>Ctrl/Cmd+→</kbd> jumps the playhead to
199
+ the previous / next keyframe (selected clips first, else clips under the
200
+ playhead). Inspector fields show the interpolated value at the playhead;
201
+ changing one updates the keyframe you’re on, or inserts one if that channel
202
+ is already keyed. Dragging a clip in the program monitor (move / scale /
203
+ rotate) writes the same way. The ◆ button adds a keyframe at the playhead, or removes
204
+ the one you’re parked on; ✕ clears the whole channel. When the playhead is
205
+ outside the selected clip, keyframed fields and ◆ buttons are disabled (they
206
+ show the nearest edge value) — keyframe edits only apply where the playhead
207
+ actually is; unanimated properties stay editable anywhere
208
+ - **Keyframe graphs** — toggle a property’s curve in the inspector to show an
209
+ interpolated value graph beside the program monitor; click the graph to seek
210
+ - **Speed ramps** — keyframe `speed` and the engine time-remaps video *and* the
211
+ export audio mix (the fast-into-slow-mo reel move)
212
+ - **Camera shake** and **RGB-split/chromatic aberration**, both animatable
213
+ - 17 transitions: fades, slides, wipes (4 directions), zoom, iris, spin, blur,
214
+ whip-pan, **glitch**, **pop**
215
+
216
+ **Text**
217
+
218
+ - **Title styles** — one-tap cohesive looks (Impact, Elegant, Kinetic cut, Neon,
219
+ Handwritten, Luxury, and more); new titles vary the font, placement and animation
220
+ automatically instead of defaulting to one flat style
221
+ - Kinetic captions: typewriter, word-pop, word-slide, karaoke, **letter-pop**,
222
+ **wave**, **bounce**, **shake**, **clip-reveal**, **zoom-in**, **font-cut**
223
+ (rhythmic typeface cuts), **rise-mask**
224
+ - **Neon glow** for that TikTok caption look
225
+ - Font editor: system fonts, drop-in custom fonts (`library/fonts/`), and **any
226
+ Google Font by name** — loaded automatically
227
+ - Gradient fills, outline, background pills, letter-spacing, line-height,
228
+ weights, italic, uppercase, soft shadows
229
+ - **Text layout** — horizontal Align: left / center / right / **justify**
230
+ (extra spaces between words). Drag a title’s corner handles to create a
231
+ **text box** (`boxW` / `boxH`); further corner drags resize it (opposite
232
+ corner stays fixed; <kbd>Ctrl/Cmd</kbd> resizes from center; <kbd>Shift</kbd>
233
+ locks aspect). Inside a box, text wraps at the fixed font size by default;
234
+ enable **Scale to fit** to shrink the font so the whole block fits. **V-align**
235
+ (top / middle / bottom) places the text block vertically in the box. Set Box
236
+ W/H to `0` to return to hug-content sizing.
237
+
238
+ **Animated SVG clips**
239
+
240
+ - A first-class `svg` clip kind: CSS-`@keyframes`-animated SVGs render
241
+ **frame-accurately** in preview and export (the compositor freezes the
242
+ animation at any time). Agents can author their own vector overlays —
243
+ lower-thirds, confetti, sparkles — as plain `.svg` files. Starters included.
244
+
245
+ **Remake a reference video**
246
+
247
+ - Give it a reference edit (a reel you like) and get back an **edit blueprint**:
248
+ shot boundaries, music beats + BPM, a loudness curve, per-shot energy, the
249
+ drop — plus the reference's **music track extracted** into your media, ready
250
+ to rebuild the same idea with your own footage. Zero extra dependencies
251
+ (ffmpeg does the decoding; onset/tempo detection is plain Node).
252
+ `node analyze.js ref.mp4`, `POST /api/analyze`, or the
253
+ `fablecut_analyze_reference` MCP tool.
254
+
255
+ **Asset library**
256
+
257
+ - `library/` folders surface as tabs in the UI: **Elements** (overlay art),
258
+ **Sound FX**, **SVG** — drop files in, the open editor refreshes live
259
+
260
+ **Export**
261
+
262
+ - Fast export: browser renders every frame + an offline audio mix; ffmpeg
263
+ encodes **JPEG frames** via an **encoding profile** from
264
+ `encoding-profiles.json` (keeps rendering if you switch tabs). Encode and
265
+ upload run ahead of the compositor so a fast timeline is not stalled by
266
+ `toBlob`. The Export dialog has a profile selector; pin a project default
267
+ with `encodeProfile` in `project.json`
268
+ - WebCodecs export: the browser HW-encodes Annex-B H.264; the server
269
+ stream-copies and muxes audio. Faster uploads; bitrate/VBR-CBR in the
270
+ Export dialog. Unavailable while an export frame is set (use Fast)
271
+ - Realtime MediaRecorder fallback when ffmpeg or WebCodecs isn't available
272
+ - Agents export too: `fablecut_export` runs the Fast export in the open editor,
273
+ or in a headless Chrome / Edge the server starts, and returns the file path
274
+ - Export **Range** dropdown: Entire timeline or IN–OUT (defaults to IN–OUT when
275
+ markers are set). Effective IN/OUT export bounds are clamped to `projDur()`.
276
+
277
+ ## Quick start
278
+
279
+ ```bash
280
+ npx -y fablecut # → http://localhost:7777
281
+ ```
282
+
283
+ Your projects, media and exports go to `~/FableCut`. Or run it from a clone:
284
+
285
+ ```bash
286
+ git clone https://github.com/ronak-create/FableCut.git
287
+ cd FableCut
288
+ node server.js # → http://localhost:7777
289
+ ```
290
+
291
+ Requirements: **Node 18+** and a Chromium-based browser. **ffmpeg on PATH** is
292
+ optional but recommended (fast export + upload remuxing). AI background
293
+ removal fetches its model from a CDN on first use.
294
+
295
+ The server binds **127.0.0.1 only** (v1.3.1+). To use it from another device on
296
+ your LAN, opt in explicitly: `HOST=0.0.0.0 FABLECUT_ALLOWED_HOSTS=<your-ip> node server.js`.
297
+
298
+ Drop media into the window (or `./media/`), paste an HTTPS URL via **+ URL**,
299
+ drag clips onto the timeline, edit, export.
300
+
301
+ To keep your work outside the checkout, set **`FABLECUT_DATA_DIR`** — it moves
302
+ `project.json`, `media/`, `exports/`, `analysis/` and `library/` to a directory
303
+ you choose. Leave it unset and everything stays in the repo, exactly as before.
304
+
305
+ ### Or install it as a Claude Code plugin
306
+
307
+ ```
308
+ /plugin marketplace add ronak-create/FableCut
309
+ /plugin install fablecut@fablecut
310
+ ```
311
+
312
+ That registers the MCP server for you and adds two skills — `edit-video` and
313
+ `remake-reel`. Your timeline and footage live in the plugin's own data
314
+ directory, so an update never touches them. Node 18+ and (optionally) ffmpeg
315
+ still need to be on your machine.
316
+
317
+ ## Driving it with an AI agent
318
+
319
+ Everything an agent needs is in **[CLAUDE.md](CLAUDE.md)** — the complete
320
+ schema, semantics and recipes. Point any capable model at that file and it can
321
+ operate the editor end to end.
322
+
323
+ > 📖 **Browsable docs:** the same manual as web pages, one page per topic
324
+ > (MCP tools, the `project.json` schema, clip props, recipes, REST API,
325
+ > export): **[fablecut.space/docs](https://fablecut.space/docs/)**.
326
+
327
+ Three equivalent control surfaces:
328
+
329
+ 1. **MCP** (best for Claude Code / Claude Desktop) — register the bundled
330
+ zero-dependency MCP server once:
331
+
332
+ ```bash
333
+ claude mcp add -s user fablecut -- npx -y fablecut mcp
334
+ ```
335
+
336
+ No clone or path needed; your work lives in `~/FableCut`. From a clone,
337
+ point at the file instead:
338
+
339
+ ```bash
340
+ claude mcp add -s user fablecut -- node "<path-to>/fablecut/mcp-server.js"
341
+ ```
342
+
343
+ **OpenCode** can use the same stdio server from its project or global
344
+ `opencode.json` configuration:
345
+
346
+ ```json
347
+ {
348
+ "$schema": "https://opencode.ai/config.json",
349
+ "mcp": {
350
+ "fablecut": {
351
+ "type": "local",
352
+ "command": ["node", "/absolute/path/to/FableCut/mcp-server.js"],
353
+ "enabled": true
354
+ }
355
+ }
356
+ }
357
+ ```
358
+
359
+ For another MCP client, register a local stdio server with this equivalent
360
+ command. The exact key names vary by client, but the command and arguments
361
+ do not:
362
+
363
+ ```json
364
+ {
365
+ "name": "fablecut",
366
+ "transport": "stdio",
367
+ "command": "npx",
368
+ "args": ["-y", "fablecut", "mcp"]
369
+ }
370
+ ```
371
+
372
+ (From a clone: `"command": "node"`, `"args": ["/absolute/path/to/FableCut/mcp-server.js"]`.)
373
+
374
+ The server is intentionally client-neutral. It speaks MCP over stdio and
375
+ does not require Claude-specific environment variables. With a clone, keep
376
+ the path absolute. Node 18 or newer either way.
377
+
378
+ Tools: `fablecut_status` (auto-starts the editor), `fablecut_docs`,
379
+ `fablecut_get_project`, `fablecut_set_project`, `fablecut_patch_project`,
380
+ `fablecut_import_media`, `fablecut_analyze_reference`,
381
+ `fablecut_encode_profiles`, `fablecut_normalize_audio`, `fablecut_auto_duck`,
382
+ `fablecut_denoise`, `fablecut_export`, `fablecut_scopes`.
383
+
384
+ FableCut is also published on the **official MCP registry** as
385
+ [`io.github.ronak-create/fablecut`](https://registry.modelcontextprotocol.io/v0/servers?search=fablecut)
386
+ — each release ships an MCPB bundle (`fablecut.mcpb`) that MCPB-capable
387
+ clients can install directly.
388
+
389
+ The surface is **token-efficient by design**: agents patch the timeline with
390
+ small ops (`fablecut_patch_project`) instead of round-tripping the whole
391
+ document, read a compact one-line-per-clip summary
392
+ (`fablecut_get_project {compact:true}`), and fetch only the manual sections
393
+ they need (`fablecut_docs {section:"props"}`).
394
+ 2. **The file** — read `project.json`, modify, bump `revision`, write. The UI
395
+ live-reloads.
396
+ 3. **REST** — `GET/PUT /api/project`, `POST /api/upload`, `POST /api/import-url`,
397
+ `GET /api/library`, `GET /api/export/profiles`, SSE at `/api/events`. See
398
+ CLAUDE.md for the full list.
399
+
400
+ Example: ask Claude Code *"cut these six clips to the beat markers, add a
401
+ teal-orange grade, put a word-pop caption on top and a whoosh on every cut"* —
402
+ and watch the timeline rebuild itself.
403
+
404
+ Or hand it a reference: *"here's a reel I like — analyze it and remake it with
405
+ my clips, same music"*. The agent calls `fablecut_analyze_reference`, gets the
406
+ blueprint (cuts, beats, BPM, energy, drop, extracted music), and rebuilds the
407
+ structure shot-for-shot with your footage.
408
+
409
+ **Conflict-safe concurrent editing**: the UI, the MCP tools, and direct
410
+ `project.json` writes all agree on a `revision` counter. If you edit a clip in
411
+ the UI while an agent is mid-task, the agent's next write is rejected (409 from
412
+ the REST API / a conflict error from `fablecut_set_project`) instead of
413
+ silently overwriting your change. The UI similarly detects when an agent write
414
+ supersedes a not-yet-saved local tweak and tells you with a toast instead of
415
+ dropping it silently.
416
+
417
+ ## Project layout
418
+
419
+ ```
420
+ server.js zero-dependency HTTP server: static hosting, REST API, SSE,
421
+ ffmpeg export pipeline
422
+ app.js the editor: timeline UI, compositor, keyframes, text engine,
423
+ SVG rasterizer, chroma key, exporters
424
+ index.html single-page UI
425
+ style.css dark editor theme
426
+ mcp-server.js stdio MCP server exposing the editor to AI agents
427
+ analyze.js reference-video analyzer: shots, beats/BPM, energy, drop,
428
+ music extraction (module + CLI)
429
+ CLAUDE.md the agent manual (schema + recipes) — also served by fablecut_docs
430
+ encoding-profiles.json
431
+ Fast-export ffmpeg presets (hot-reloaded)
432
+ project.json your timeline (created on first run; gitignored)
433
+ media/ project footage (gitignored)
434
+ analysis/ cached edit blueprints from /api/analyze (gitignored)
435
+ library/ default assets: elements/ sfx/ svg/ fonts/
436
+ exports/ finished renders (gitignored)
437
+ ```
438
+
439
+ ## Architecture Overview
440
+
441
+ ```mermaid
442
+ flowchart TD
443
+
444
+ subgraph group_interaction["Editing Experience"]
445
+ node_browser_ui["Browser Editor<br/>[app.js]"]
446
+ node_timeline_model["Timeline Model<br/>[app.js]"]
447
+ node_ruler_worker["Ruler Worker<br/>[ruler-worker.js]"]
448
+ end
449
+
450
+ subgraph group_project_media["Project And Media"]
451
+ node_project_store[("Project Store<br/>[paths.js]")]
452
+ node_media_store[("Media Store<br/>[paths.js]")]
453
+ node_asset_library[("Asset Library<br/>[server.js]")]
454
+ node_url_importer["URL Importer<br/>[import-url.js]"]
455
+ end
456
+
457
+ subgraph group_agent_api["Agent Control"]
458
+ node_rest_api["REST Server<br/>[server.js]"]
459
+ node_mcp_server["MCP Server<br/>[mcp-server.js]"]
460
+ node_sse_hub["Change Notifications<br/>[server.js]"]
461
+ end
462
+
463
+ subgraph group_playback_export["Playback And Export"]
464
+ node_compositor["Canvas Compositor<br/>[app.js]"]
465
+ node_audio_engine["Audio Engine<br/>[app.js]"]
466
+ node_audio_meter["Audio Meter<br/>[meter-worklet.js]"]
467
+ node_export_engine["Export Engine<br/>[server.js]"]
468
+ node_encoding_profiles["Encoding Profiles<br/>[encode-profiles.js]"]
469
+ end
470
+
471
+ subgraph group_analysis["Analysis Tools"]
472
+ node_reference_analyzer["Reference Analyzer<br/>[analyze.js]"]
473
+ end
474
+
475
+ node_human(("Human Editor"))
476
+ node_ai_agent(("AI Agent"))
477
+ node_ffmpeg["FFmpeg"]
478
+
479
+ node_human -->|"edits timeline"| node_browser_ui
480
+ node_ai_agent -->|"sends tools"| node_mcp_server
481
+ node_browser_ui -->|"updates project"| node_timeline_model
482
+ node_timeline_model -->|"persists JSON"| node_project_store
483
+ node_browser_ui -->|"calls REST"| node_rest_api
484
+ node_rest_api -->|"reads writes"| node_project_store
485
+ node_rest_api -->|"serves media"| node_media_store
486
+ node_rest_api -->|"lists assets"| node_asset_library
487
+ node_mcp_server -->|"checks server"| node_rest_api
488
+ node_mcp_server -->|"patches project"| node_project_store
489
+ node_mcp_server -->|"imports media"| node_url_importer
490
+ node_url_importer -->|"stores downloads"| node_media_store
491
+ node_rest_api -->|"downloads URLs"| node_url_importer
492
+ node_rest_api -->|"broadcasts changes"| node_sse_hub
493
+ node_sse_hub -->|"pushes changes"| node_browser_ui
494
+ node_timeline_model -->|"renders timeline"| node_compositor
495
+ node_timeline_model -->|"routes clips"| node_audio_engine
496
+ node_audio_engine -->|"measures audio"| node_audio_meter
497
+ node_browser_ui -->|"uploads frames"| node_rest_api
498
+ node_rest_api -->|"streams export"| node_export_engine
499
+ node_export_engine -->|"loads profile"| node_encoding_profiles
500
+ node_export_engine -->|"encodes video"| node_ffmpeg
501
+ node_reference_analyzer -->|"analyzes media"| node_ffmpeg
502
+ node_reference_analyzer -->|"writes music"| node_media_store
503
+ node_ai_agent -.->|"requests analysis"| node_reference_analyzer
504
+ node_browser_ui -.->|"draws ruler"| node_ruler_worker
505
+
506
+ click node_browser_ui "https://github.com/ronak-create/fablecut/blob/main/app.js"
507
+ click node_timeline_model "https://github.com/ronak-create/fablecut/blob/main/app.js"
508
+ click node_compositor "https://github.com/ronak-create/fablecut/blob/main/app.js"
509
+ click node_audio_engine "https://github.com/ronak-create/fablecut/blob/main/app.js"
510
+ click node_audio_meter "https://github.com/ronak-create/fablecut/blob/main/meter-worklet.js"
511
+ click node_rest_api "https://github.com/ronak-create/fablecut/blob/main/server.js"
512
+ click node_mcp_server "https://github.com/ronak-create/fablecut/blob/main/mcp-server.js"
513
+ click node_sse_hub "https://github.com/ronak-create/fablecut/blob/main/server.js"
514
+ click node_project_store "https://github.com/ronak-create/fablecut/blob/main/paths.js"
515
+ click node_media_store "https://github.com/ronak-create/fablecut/blob/main/paths.js"
516
+ click node_asset_library "https://github.com/ronak-create/fablecut/blob/main/server.js"
517
+ click node_url_importer "https://github.com/ronak-create/fablecut/blob/main/import-url.js"
518
+ click node_export_engine "https://github.com/ronak-create/fablecut/blob/main/server.js"
519
+ click node_encoding_profiles "https://github.com/ronak-create/fablecut/blob/main/encode-profiles.js"
520
+ click node_reference_analyzer "https://github.com/ronak-create/fablecut/blob/main/analyze.js"
521
+ click node_ruler_worker "https://github.com/ronak-create/fablecut/blob/main/ruler-worker.js"
522
+
523
+ classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
524
+ classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
525
+ classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
526
+ classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
527
+ classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
528
+ classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
529
+ classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
530
+ class node_browser_ui,node_timeline_model,node_ruler_worker toneBlue
531
+ class node_project_store,node_media_store,node_asset_library,node_url_importer toneAmber
532
+ class node_rest_api,node_mcp_server,node_sse_hub toneMint
533
+ class node_compositor,node_audio_engine,node_audio_meter,node_export_engine,node_encoding_profiles toneRose
534
+ class node_reference_analyzer,node_human,node_ai_agent,node_ffmpeg toneIndigo
535
+ ```
536
+
537
+ ## Authoring animated SVG overlays
538
+
539
+ SVGs animate with plain CSS `@keyframes`. One convention: never hardcode
540
+ `animation-delay` — set `--d: 0.4s` instead, and the compositor drives time by
541
+ pausing all animations and rebasing their delays. Full rules + a skeleton in
542
+ [CLAUDE.md](CLAUDE.md#authoring-animated-svgs-the-svg-clip-kind); working
543
+ examples in [`library/svg/`](library/svg/).
544
+
545
+ ## Notes
546
+
547
+ - The repo ships with **20 Google Fonts** (`library/fonts/`, OFL — see
548
+ `LICENSES.md` there) and a set of self-authored SVG overlays and animated
549
+ elements (`library/elements/`, `library/svg/`, MIT like the rest of the repo).
550
+ - `library/sfx/` is yours to fill (gitignored): sound-effect sites typically
551
+ don't allow redistributing their files in a public repo, so FableCut doesn't —
552
+ `library/sfx/README.md` lists good free sources.
553
+ - Export runs in the browser because the compositor *is* the browser; agents
554
+ ask you to click Export (or render directly with ffmpeg from `media/`).
555
+
556
+ ## Sponsors
557
+
558
+ <a href="https://fluxionai.world/register?source=github&amp;campaign=github-fablecut&amp;promo=FABLECUT" target="_blank" rel="noopener noreferrer sponsored">
559
+ <img src="docs/sponsors/fluxion-ai-banner.png" alt="Fluxion AI — unified access to GPT, Claude and other leading AI models" width="640">
560
+ </a>
561
+
562
+ **[Fluxion AI](https://fluxionai.world/register?source=github&campaign=github-fablecut&promo=FABLECUT)** provides reliable, cost-efficient access to GPT, Claude, and other leading AI models through one unified API. Save up to 70% compared with official API pricing — and get $3 in API credits when you sign up through this link.
563
+
564
+ ## Community
565
+
566
+ Questions, ideas, showing off an edit, or want to help shape what's next? Join
567
+ the **[FableCut Discord](https://discord.gg/EFMQH7d6Tv)**. Bugs and feature
568
+ requests are still best filed as [GitHub issues](https://github.com/ronak-create/FableCut/issues).
569
+
570
+ ## License
571
+
572
+ [MIT](LICENSE)