<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MartinDelophy</title>
    <description>The latest articles on DEV Community by MartinDelophy (@martindelophy).</description>
    <link>https://dev.to/martindelophy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4029567%2Fc8d453a4-d3ea-4275-b7ce-fbece2440c21.png</url>
      <title>DEV Community: MartinDelophy</title>
      <link>https://dev.to/martindelophy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martindelophy"/>
    <language>en</language>
    <item>
      <title>Building editable beat templates for a browser video editor</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Mon, 09 Nov 2026 06:13:03 +0000</pubDate>
      <link>https://dev.to/martindelophy/building-editable-beat-templates-for-a-browser-video-editor-388p</link>
      <guid>https://dev.to/martindelophy/building-editable-beat-templates-for-a-browser-video-editor-388p</guid>
      <description>&lt;p&gt;A beat template in Timeline Studio starts with a set of media slots and ends with clips you can select on the timeline. In the Memory Vortex example, photos and video stills first appear as panes of a rotating glass tunnel. At 2.70 seconds, the tunnel cuts to the first full-screen shot. The next 48 shots use the same set of media bindings. &lt;a href="https://github.com/MartinDelophy/ai-video-editor/releases/tag/v1.0.12" rel="noopener noreferrer"&gt;Release notes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2dokvp7kbr5qc7njtie.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2dokvp7kbr5qc7njtie.webp" alt="A still from the Memory Vortex Beats opening, showing photos arranged in concentric glass panes" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That reuse creates an interesting constraint. If a video shot starts partway through a source file or plays at a different rate, the pane in the opening should show a frame from the shot that actually follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  One set of bindings, two outputs
&lt;/h2&gt;

&lt;p&gt;The template stores a cut schedule measured on a 30 fps reference. The reference tempo is 106 BPM. Applying the template scales the measured offsets by the ratio between the reference tempo and the selected tempo, so the gaps between cuts and their markers move together. At the default tempo, frame 81 becomes the first full-screen shot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secondsPerReferenceFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;selectedBpm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timelineTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;referenceFrame&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;secondsPerReferenceFrame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template resolves its media slots into actual shot ranges before preparing the opening. The same resolver supplies the full-screen clips. Those clips, the opening effect, beat markers, and optional soundtrack are added as ordinary editor objects. The smoke project for the example contained 49 visual clips, one opening effect, and one music track. &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.12/src/lib/referenceTemplates.js#L63-L99" rel="noopener noreferrer"&gt;Shot preparation and timing&lt;/a&gt; · &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.12/src/lib/projectedBeatTemplates.js#L98-L117" rel="noopener noreferrer"&gt;Cut and transition schedule&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare the stills once
&lt;/h2&gt;

&lt;p&gt;For a video slot, the atlas builder converts the midpoint of the timeline shot into source time. When multiple slots refer to the same source, it groups them, sorts their requested timestamps, and removes duplicates before extracting frames. The extraction path tries WebCodecs and can fall back to seeking a video element. Photos are decoded and center-cropped into their assigned tiles.&lt;/p&gt;

&lt;p&gt;The result is a WebP texture atlas with &lt;strong&gt;8 columns × 6 rows&lt;/strong&gt;. Each tile is &lt;strong&gt;224 × 280&lt;/strong&gt;, which gives a &lt;strong&gt;1792 × 1680&lt;/strong&gt; image. A decoded RGBA copy of that image is about 11.5 MiB. This bounds the size of the texture the effect samples, while the timeline clips still reference their original media. &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.12/src/lib/memoryVortexIntro.js" rel="noopener noreferrer"&gt;Atlas builder&lt;/a&gt; · &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.12/src/lib/media.js#L389-L427" rel="noopener noreferrer"&gt;Frame extraction&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During playback or export, the Canvas 2D effect computes its pane geometry from the current time within the clip. It clips to each visible polygon and uses the source-rectangle form of &lt;code&gt;drawImage()&lt;/code&gt; to paint the corresponding atlas tile. The drawing step can then render a frame at any requested timestamp, including a jump during scrubbing, using the prepared image and the effect settings. &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.12/src/lib/projectedBeatEffects.js#L43-L117" rel="noopener noreferrer"&gt;Canvas renderer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the next beat in place when replacing a clip
&lt;/h2&gt;

&lt;p&gt;A replacement is a local edit. If the timeline clip lasts two seconds and the user selects three seconds from another video, the replacement plays at 1.5×. The editor keeps the clip ID and timeline duration, then updates its media and source range. Reapplying the template with new bindings is the separate operation that rebuilds the opening atlas. &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.12/src/lib/visualReplacement.js#L18-L27" rel="noopener noreferrer"&gt;Clip replacement&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To reproduce the sequence, open &lt;strong&gt;Templates → Memory Vortex Beats&lt;/strong&gt; in the &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;browser editor&lt;/a&gt;, keep the sample media and default tempo, and apply the template. At 30 fps, inspect frames 80 and 81 in the export to see where the opening hands off to the first shot. The &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Timeline Studio repository&lt;/a&gt; contains the editor and the template definitions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: AI drafted this article using the released source, project records, and exported frames.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building ChatCut: Conversational Video Editing in the Browser with Cloud Models</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Fri, 06 Nov 2026 10:20:45 +0000</pubDate>
      <link>https://dev.to/martindelophy/building-chatcut-conversational-video-editing-in-the-browser-with-cloud-models-43h9</link>
      <guid>https://dev.to/martindelophy/building-chatcut-conversational-video-editing-in-the-browser-with-cloud-models-43h9</guid>
      <description>&lt;p&gt;“Shorten the second shot, then move the narration and its captions together.”&lt;/p&gt;

&lt;p&gt;That request sounds simple until you try to connect a language model to an actual video project. The model needs to identify the right clips, understand their timing, preserve links, and avoid applying a plan after the user has changed the timeline.&lt;/p&gt;

&lt;p&gt;I am building &lt;strong&gt;ChatCut&lt;/strong&gt;, the conversational editing workspace in the Anna edition of my open-source project, &lt;strong&gt;Timeline Studio&lt;/strong&gt;. The goal is to let users edit through conversation without installing desktop editing software or setting up their own model inference environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; · &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/codex/anna-validation/docs/anna-chatcut.md" rel="noopener noreferrer"&gt;Anna integration notes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is an architecture walkthrough, including cloud video generation and the music integration under development. Availability depends on the Anna release, account grants, and selected model. The standalone editor and the Anna edition have different capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What runs in the browser, and what runs in the cloud?
&lt;/h2&gt;

&lt;p&gt;ChatCut combines a conversation panel with the live preview and editable timeline. The cloud model interprets requests and chooses tools. The browser owns the editing state, validates operations, previews changes, and performs composition/export.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User request
     |
Browser: inspect project and available assets
     |
Cloud LLM: choose a supported tool
     |
Browser: validate and execute
     |                        |
Timeline edit          Cloud generation job
     |                        |
Review / apply         Download and decode result
     |                        |
Editable timeline &amp;lt;--- Explicit placement request
     |
Browser composition and export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“No desktop install” does not mean “every operation runs on a server.” This division keeps direct editing and playback close to the user while using platform-hosted models for planning and supported generation tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bounded tool loop around the editor
&lt;/h2&gt;

&lt;p&gt;ChatCut uses Anna's llm.complete host API. The browser maintains the tool loop; it does not create a separate cloud Agent session or expose an unrestricted standard MCP server.&lt;/p&gt;

&lt;p&gt;Each turn starts by inspecting the project. The model receives a filtered catalog of registered editor tools, their input schemas, bounded conversation context, and the user's request.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timeline_project_inspect
timeline_track_inspect
timeline_clip_inspect
timeline_assets_inspect
timeline_edit_preview
timeline_video_generate
timeline_export_prepare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop has a hard limit of 16 model calls per turn. Only registered operations are dispatched. Model-generated JavaScript, shell commands, and arbitrary media URLs are not executed as editing instructions.&lt;/p&gt;

&lt;p&gt;The model proposes actions. The application enforces their meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect the edits made while the model is thinking
&lt;/h2&gt;

&lt;p&gt;A conversation turn captures an initial project state token. Any generated edit preview must still match that state before it can be committed.&lt;/p&gt;

&lt;p&gt;If the user trims a shot while the model is planning, the old result becomes stale. Re-reading the project inside the same turn does not silently authorize a plan based on a different initial state.&lt;/p&gt;

&lt;p&gt;The shared review engine checks asset and clip IDs, supported operations, timing, media availability, and locked tracks.&lt;/p&gt;

&lt;p&gt;When automatic application is enabled, ordinary validated edits can be committed by the host, with inline &lt;strong&gt;Undo&lt;/strong&gt; and &lt;strong&gt;View changes&lt;/strong&gt; receipts. New narration and audio-processing results require listening review and remain pending. Historical receipts cannot apply or undo an unrelated transaction.&lt;/p&gt;

&lt;p&gt;This is what makes the conversation part of an editable application rather than a detached suggestion box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual evidence has a budget
&lt;/h2&gt;

&lt;p&gt;A filename is weak evidence about a video. ChatCut can inspect selected source frames with permission, using an imported asset ID and explicit source times.&lt;/p&gt;

&lt;p&gt;The decoder is independent of the visible preview, so sampling does not move the user's playback position. A call can return at most six bounded JPEG frames, with eighteen frames allowed per conversation request.&lt;/p&gt;

&lt;p&gt;These are source frames. They exclude the editor's rendered captions, overlays, and effects.&lt;/p&gt;

&lt;p&gt;Sparse samples can help answer a local question about composition. They cannot establish complete scene understanding, exact cut detection, or audio transcription. The model should not claim to have watched the whole video when it has only seen a handful of frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video generation is an asynchronous media pipeline
&lt;/h2&gt;

&lt;p&gt;The Anna connector uses the real video.generate and video.getJob host APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explicit generation request
        |
Validate model parameters and app permission
        |
Submit once -&amp;gt; retain job ID -&amp;gt; poll existing job
        |
Download actual media bytes
        |
Decode and commit to My assets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An app-level media grant is required. Selecting a video model alone does not establish permission, and submitting a job consumes the user's Anna CU.&lt;/p&gt;

&lt;p&gt;A recorded live text-to-video check submitted a five-second, 480p, 16:9 request. It returned a playable 832x480 video, approximately 5.184 seconds long, with H.264 video and AAC audio. The downloaded asset was stored and the existing timeline was unchanged.&lt;/p&gt;

&lt;p&gt;That validates one concrete path. Image-conditioned generation and other model/resolution combinations need their own checks; the adapter's existence is not evidence that every provider combination works.&lt;/p&gt;

&lt;p&gt;Stopping local waiting also does not imply that a remote generation job was canceled or refunded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generation and placement are separate decisions
&lt;/h2&gt;

&lt;p&gt;A generated video might need another iteration. Committing it directly to the timeline would conflate “the provider returned a file” with “the user accepted the result.”&lt;/p&gt;

&lt;p&gt;Completed video outputs are downloaded, decoded, and saved in My assets. Placement uses a separate inspected editing plan when the user requests it.&lt;/p&gt;

&lt;p&gt;This separation preserves the original footage and makes recovery easier. Download failure, generation failure, and placement failure become distinct states instead of one ambiguous “try again” button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Music generation starts with model capability discovery
&lt;/h2&gt;

&lt;p&gt;The Anna music connector queries the official catalog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;serviceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;music_gen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The selected model determines prompt length, lyrics support, instrumental mode, available formats, and whether duration is adjustable. A fixed-duration model receives no invented duration parameter. Lyrics go through the official lyrics field when supported.&lt;/p&gt;

&lt;p&gt;No provider secret is embedded in the frontend; requests use the platform's permissioned host interface.&lt;/p&gt;

&lt;p&gt;The current ChatCut music tool prepares a prompt and opens the official generation form. It does not silently submit a billed music job: the user clicks Generate in that form. Conversation-driven music preparation and beat-aware montage are working-draft features, not a promise that every published edition already includes them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recover a music result without composing—and paying—again
&lt;/h2&gt;

&lt;p&gt;The implemented connector persists the exact request and an idempotency key before submission. An ambiguous outcome keeps its receipt; recovery looks for the same request rather than immediately inventing a new key.&lt;/p&gt;

&lt;p&gt;If a successful job's signed download URL expires and returns 403, the connector refreshes the result through getJob. It does not call generate again.&lt;/p&gt;

&lt;p&gt;Downloaded audio is decoded to establish duration and waveform data, marked as music, and committed to My assets for explicit Music-track placement. Playback uses the downloaded object URL rather than a short-lived signed URL.&lt;/p&gt;

&lt;p&gt;The repository documents build, contract, and local-browser checks for the music connector. Those checks do not establish live paid-generation audio quality, which still needs an authorized end-to-end run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Music-aware cutting needs evidence beyond BPM
&lt;/h2&gt;

&lt;p&gt;The montage work under development treats templates as starting points. Uploaded and generated music should feed the same analysis path: decoded waveform, beat and accent candidates, then a model-planned sequence of cuts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Music -&amp;gt; waveform and timing evidence
                    |
Template + source clips + user direction
                    |
              Cut plan
                    |
Validate times, source IDs, and available media
                    |
            Editable sequence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A constant BPM grid alone does not prove that the cuts follow the track's actual accents or sections. This is why analysis, planning, and validation are separate stages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export through the existing compositor
&lt;/h2&gt;

&lt;p&gt;ChatCut can prepare export of the full timeline, a selected visual or overlay clip, or an explicit time interval. The visible export action starts rendering after the user clicks it.&lt;/p&gt;

&lt;p&gt;Exports reuse the editor's composition pipeline, including the relevant captions, effects, and audio. This avoids maintaining a separate renderer for conversational edits.&lt;/p&gt;

&lt;p&gt;The browser still does real work. Performance depends on the source media, browser capabilities, device, and composition complexity; cloud planning does not remove those constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to explore the code
&lt;/h2&gt;

&lt;p&gt;Start with the &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;open-source repository&lt;/a&gt;, and the Anna implementation on the codex/anna-validation branch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;src/lib/annaChatCut.js — bounded conversation loop and state guards&lt;/li&gt;
&lt;li&gt;src/components/ChatCut.jsx — conversation UI and generation handoffs&lt;/li&gt;
&lt;li&gt;src/lib/annaRuntime.js — host API adaptation&lt;/li&gt;
&lt;li&gt;src/plugins/generation/providers/anna/ — official generation adapters&lt;/li&gt;
&lt;li&gt;docs/anna-chatcut.md — execution contract and workflow&lt;/li&gt;
&lt;li&gt;docs/anna-video-generation.md — video integration and live validation&lt;/li&gt;
&lt;li&gt;docs/anna-music-generation.md — model capabilities and recovery behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;standalone browser editor&lt;/a&gt; lets you explore Timeline Studio's editing foundation. ChatCut and the platform-hosted generation paths described here belong to the Anna edition; use the integration notes to understand their release and permission requirements.&lt;/p&gt;

&lt;p&gt;What interests me most about this architecture is the connection between an accessible conversation and a durable editing model. Users can ask for a change, inspect the result, undo it, and continue manually—all inside the same browser workspace.&lt;/p&gt;

&lt;p&gt;If you build agent tools, browser media applications, or generation pipelines, I would welcome feedback and reproducible failure cases through the &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I maintain the project discussed here. This article was drafted by an AI assistant using the current source code and implementation records.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Turning Video into Line Art in the Browser with WebGL 2 and Sobel</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Wed, 04 Nov 2026 06:40:49 +0000</pubDate>
      <link>https://dev.to/martindelophy/turning-video-into-line-art-in-the-browser-with-webgl-2-and-sobel-4cmm</link>
      <guid>https://dev.to/martindelophy/turning-video-into-line-art-in-the-browser-with-webgl-2-and-sobel-4cmm</guid>
      <description>&lt;p&gt;Depth maps and line art preserve different parts of a video. Depth describes spatial relationships; contours emphasize shape, composition, and visible changes in pose.&lt;/p&gt;

&lt;p&gt;After adding depth-video conversion to &lt;strong&gt;Timeline Studio&lt;/strong&gt;, my open-source browser video editor, I added a line-art asset generator. This version runs locally with WebGL 2 and Sobel edge extraction. It does not download or run a neural line-art model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Source code&lt;/a&gt; · &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Try the editor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is how the image-processing pipeline connects to actual video editing: previews, trimmed clips, speed curves, source audio, encoding, and cancellation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two passes on the GPU
&lt;/h2&gt;

&lt;p&gt;The pipeline is deliberately small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Image or decoded video frame
          |
   Grayscale + 3x3 smoothing
          |
   Intermediate GPU texture
          |
   Sobel + soft threshold
          |
    Output canvas
          |
      PNG or WebM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smoothing pass renders into a texture attached to a framebuffer. The edge pass samples that texture and draws to the output canvas. Intermediate pixels stay on the GPU rather than being read back into JavaScript between passes.&lt;/p&gt;

&lt;p&gt;Shader programs, buffers, textures, and the framebuffer are reused across frames. That reduces setup work, although decoding and encoding still contribute to total processing time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert to luminance, then smooth
&lt;/h2&gt;

&lt;p&gt;The shader computes a weighted RGB luminance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;texture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7152&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;0722&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first pass uses this weighted 3x3 kernel, divided by 16:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1  2  1
2  4  2
1  2  1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smoothing suppresses some high-frequency texture before edge extraction. It is a tradeoff: small details can also become weaker.&lt;/p&gt;

&lt;p&gt;Because detection operates on luminance, different colors with similar luminance may produce weak boundaries. Conversely, a shadow or reflection can produce a strong edge without marking an object boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extract Sobel gradients
&lt;/h2&gt;

&lt;p&gt;The second pass estimates horizontal and vertical gradients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gx                  Gy
-1  0  1            -1 -2 -1
-2  0  2             0  0  0
-1  0  1             1  2  1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Their combined magnitude becomes the edge strength:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The letters represent neighboring luminance samples. Each output pixel performs the same local calculation, which maps naturally to a fragment shader.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the parameters useful
&lt;/h2&gt;

&lt;p&gt;The inspector exposes detail, line weight, and inversion. Defaults are 55% detail, 1x line weight, and black lines on white.&lt;/p&gt;

&lt;p&gt;Detail maps to an edge threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;035&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smoothstep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inverted&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ink&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Higher detail lowers the threshold, retaining weaker edges. The soft threshold avoids an abrupt binary transition around the cutoff. For busy footage, increasing detail can make the result less useful by retaining background texture.&lt;/p&gt;

&lt;p&gt;Line weight changes the sampling distance. It uses the longest dimension as a 1080px reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scaledThickness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;thickness&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps keep the edge-sampling scale comparable across output sizes. It is not morphological dilation: changing the sampling distance also changes which details respond. The initial blur itself remains a one-pixel 3x3 neighborhood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview and asset generation share one renderer
&lt;/h2&gt;

&lt;p&gt;Both paths call the same implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLineArtRenderer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Release resources when finished:&lt;/span&gt;
&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The preview redraws a sampled frame when parameters or the local playhead time change. Asset generation traverses output frames through that same renderer.&lt;/p&gt;

&lt;p&gt;Sharing the implementation reduces differences in thresholds, sampling, and inversion between preview and generated assets. It does not make processing instantaneous.&lt;/p&gt;

&lt;p&gt;Output dimensions preserve aspect ratio, cap the longest edge at 1080 by default, avoid enlarging smaller media, and normalize dimensions to even numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video has to follow the timeline's clock
&lt;/h2&gt;

&lt;p&gt;A selected clip may already be trimmed or retimed. Sampling the original file from zero would produce the wrong result.&lt;/p&gt;

&lt;p&gt;There are separate coordinates for output time, clip-local time, and source-media time. For constant speed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sourceTime = sourceIn + clipLocalTime * speed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For speed curves, the implementation reuses the editor's mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getVisualSourceTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator outputs 24 fps. Each local sample time is i / 24; it is mapped to source time before decoding the frame. The processed frame is then encoded at its output timestamp.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output frame time
       |
Source-time mapping, including trim and retiming
       |
Decode source frame -&amp;gt; draw contours -&amp;gt; encode output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mediabunny handles media input and timestamp-based canvas sampling. The final frame duration is limited to the remaining clip duration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24 fps is the output rate, not a claim of real-time generation speed.&lt;/strong&gt; Actual throughput depends on the browser, hardware, source media, and encoder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalize still-image orientation
&lt;/h2&gt;

&lt;p&gt;One integration detail had nothing to do with Sobel: image orientation.&lt;/p&gt;

&lt;p&gt;The image path decodes a Blob to an ImageBitmap, then draws it to a 2D canvas before uploading it to WebGL. ImageBitmap does not follow the same UNPACK_FLIP_Y_WEBGL behavior as the other upload path, so the intermediate canvas normalizes orientation.&lt;/p&gt;

&lt;p&gt;Without that step, still images and video frames could disagree about which side is up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encode a reusable asset, with corresponding source audio
&lt;/h2&gt;

&lt;p&gt;Images become PNG. Videos become WebM using VP9 at approximately 4 Mbps. When corresponding source audio exists, the output uses Opus at 192 kbps.&lt;/p&gt;

&lt;p&gt;Audio preparation reuses the editor's source-audio and offline-mixing helpers so trimming and retiming follow the selected segment. This preserves the segment's corresponding source sound; it does not mix every music and narration track from the whole project.&lt;/p&gt;

&lt;p&gt;The completed asset goes to &lt;strong&gt;My assets&lt;/strong&gt;. It is not automatically inserted into the timeline, making it possible to retain the original and compare versions before placement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation includes cleanup
&lt;/h2&gt;

&lt;p&gt;An AbortController carries cancellation through source reading, frame processing, and output finalization. The renderer releases its textures, framebuffer, shaders, programs, and vertex buffer. Media input resources are disposed, and encoding is canceled when necessary.&lt;/p&gt;

&lt;p&gt;Frame processing reports progress up to 99%. Finalization and the asset commit must finish before the UI reports completion. Processing the last frame is not the same as having a usable saved file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What structural reference means here
&lt;/h2&gt;

&lt;p&gt;Depth video and contour video provide complementary reference assets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Asset&lt;/th&gt;
&lt;th&gt;Emphasizes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Depth video&lt;/td&gt;
&lt;td&gt;Relative depth and foreground/background layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Line-art video&lt;/td&gt;
&lt;td&gt;Visible contours, composition, and changes in shape&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A downstream generation workflow can use these only if it accepts the corresponding conditioning input and its preprocessing, timing, resolution, and frame-rate requirements are satisfied.&lt;/p&gt;

&lt;p&gt;This feature creates the reference asset. It does not imply automatic integration with every image or video generator, nor guarantee that a generator will preserve the desired structure.&lt;/p&gt;

&lt;p&gt;The current Sobel route also lacks semantic simplification and explicit temporal tracking. Fine textures, noise, and motion can make lines fluctuate across frames. A learned line-art model or additional temporal processing would need separate evaluation rather than being claimed as part of this implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it or read the code
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Effects -&amp;gt; Line art&lt;/strong&gt;, select an image or video clip, adjust parameters, and generate the asset. The feature includes 13 interface languages and a mobile layout.&lt;/p&gt;

&lt;p&gt;Implementation entry points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/main/src/lib/lineArt.js" rel="noopener noreferrer"&gt;src/lib/lineArt.js&lt;/a&gt;: renderer, source sampling, encoding, and cleanup&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/main/src/components/LineArtPanel.jsx" rel="noopener noreferrer"&gt;src/components/LineArtPanel.jsx&lt;/a&gt;: preview, controls, and job state&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/main/src/i18nLineArt.js" rel="noopener noreferrer"&gt;src/i18nLineArt.js&lt;/a&gt;: localized copy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work on browser graphics or video tools, I would welcome difficult examples: busy backgrounds, small contours, and fast motion are particularly useful for understanding where this approach needs improvement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; · &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Open the editor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I maintain the project discussed here. This article was drafted by an AI assistant using the current repository source and implementation notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built an Editable Timeline for AI Agents: Skills, MCP, and Transactional Video Editing</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Tue, 03 Nov 2026 07:20:35 +0000</pubDate>
      <link>https://dev.to/martindelophy/i-built-an-editable-timeline-for-ai-agents-skills-mcp-and-transactional-video-editing-15og</link>
      <guid>https://dev.to/martindelophy/i-built-an-editable-timeline-for-ai-agents-skills-mcp-and-transactional-video-editing-15og</guid>
      <description>&lt;p&gt;An AI agent can generate an FFmpeg command. But what happens when the next request is: “Shorten the second shot, move the voiceover and its captions together, and give me a project I can keep editing”? &lt;/p&gt;

&lt;p&gt;That is the problem I have been working on in &lt;strong&gt;Timeline Studio&lt;/strong&gt;, an open-source, local-first browser video editor with an Agent Skill and a shared command layer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Try the editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post focuses on the editing architecture: how an agent turns a request into an inspectable plan, how that plan changes project state, and where rendering still has explicit limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with an editable project
&lt;/h2&gt;

&lt;p&gt;A rendered MP4 does not preserve the decisions needed for the next edit: clip identity, source ranges, timing, transforms, and caption/audio relationships.&lt;/p&gt;

&lt;p&gt;Timeline Studio uses an editable &lt;strong&gt;.timeline&lt;/strong&gt; project as the source of truth. In the local command workflow, the archive contains project.json and embedded media. Asset import probes necessary metadata, computes SHA-256 integrity information, and embeds the media bytes.&lt;/p&gt;

&lt;p&gt;This gives both the user and the agent a concrete state to return to. The user can reopen the project in the editor. The agent can inspect it before making another change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate editorial decisions from state changes
&lt;/h2&gt;

&lt;p&gt;The model can decide that a voiceover should start at three seconds. The application should enforce whether the clip exists, whether the time is valid, and how linked captions follow it.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User request + media evidence
             |
     Skill: analyze and plan
             |
     Structured edit plan
             |
 CLI / local MCP / browser WebMCP
             |
 Shared command registry and reducers
             |
 Editable project + verified output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transports have different environments. The CLI operates on local archives. The local STDIO MCP adapter exposes that command runner to an agent. Browser WebMCP, when exposed by the host, operates on the open editor and commits through its normal history.&lt;/p&gt;

&lt;p&gt;The important constraint is to reuse editing rules rather than implement a separate editor for each transport.&lt;/p&gt;

&lt;h2&gt;
  
  
  An edit plan needs preconditions
&lt;/h2&gt;

&lt;p&gt;Here is a simplified plan that moves an existing voiceover to project time 3s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects/input.timeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"baseRevision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dryRun"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"move-voice-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"timed.move"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"track"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"clipId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voice-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects/output.timeline"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The revision and clip ID are illustrative: they must come from inspecting the actual project.&lt;/p&gt;

&lt;p&gt;Each field has a job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;baseRevision&lt;/strong&gt; checks the state on which the plan was based.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operation IDs&lt;/strong&gt; support idempotent handling of retries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;clipId&lt;/strong&gt; identifies an existing project object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;type&lt;/strong&gt; restricts the action to a supported operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;output.project&lt;/strong&gt; declares a new output artifact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this audio move, still-linked captions move by the same delta. That behavior belongs in the editing rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview a semantic diff before applying
&lt;/h2&gt;

&lt;p&gt;The workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inspect -&amp;gt; plan -&amp;gt; validate -&amp;gt; diff -&amp;gt; apply -&amp;gt; inspect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI exposes it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.inspect /projects/input.timeline

node skills/edit-timeline-studio/scripts/validate_edit_plan.mjs &lt;span class="se"&gt;\&lt;/span&gt;
  /projects/plan.json

npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.diff /projects/plan.json
npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.run /projects/plan.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The standalone validator checks transport shape, finite time fields, and duplicate operation IDs. It does not prove that a plan is valid for a particular project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;project.diff is the project-aware dry run.&lt;/strong&gt; It uses the same command registry and reducers as application, without writing an archive. It checks supported operations and preconditions, then reports project fields, clip additions/removals, changed fields, ordering, and duration changes.&lt;/p&gt;

&lt;p&gt;This makes accidental scope expansion visible. A request to move narration should not quietly change the visual sequence duration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries and concurrent edits are normal cases
&lt;/h2&gt;

&lt;p&gt;Three mechanisms matter here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Idempotency
&lt;/h3&gt;

&lt;p&gt;If an operation completes but its response is lost, retrying the same operation ID should not insert the same clip twice. Previously applied IDs are treated as no-ops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Revision checks
&lt;/h3&gt;

&lt;p&gt;If the agent inspected revision 12 and the user subsequently edited the project, a stale plan containing new operations must fail with a revision conflict. The agent needs to inspect again and reconsider the plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactional application
&lt;/h3&gt;

&lt;p&gt;A failed batch should not leave a partially edited output archive. Local CLI and MCP writes also require a new output path and reject overwriting the input or an existing output.&lt;/p&gt;

&lt;p&gt;These are familiar application-engineering techniques. They become especially useful when the caller is an agent that may retry or reason from outdated state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video editing has more than one clock
&lt;/h2&gt;

&lt;p&gt;A timestamp can refer to source media, a clip-local position, or the final project.&lt;/p&gt;

&lt;p&gt;For a constant-speed clip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sourceTime = sourceIn + (timelineTime - clipStart) * speed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a speed curve, source-time mapping must accumulate the changing playback speed over time.&lt;/p&gt;

&lt;p&gt;An agent identifying an action at source time 8s cannot assume that the cut belongs at project time 8s. Trimming, splitting, keyframes, thumbnails, and effect analysis all depend on maintaining that mapping.&lt;/p&gt;

&lt;p&gt;Tracks also have different semantics. Main visuals form a contiguous sequence; narration, captions, and overlays have explicit timing. Whether a duration change shifts other tracks depends on ripple mode, links, and track locks.&lt;/p&gt;

&lt;p&gt;These rules need to be explicit application behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills and MCP serve different purposes
&lt;/h2&gt;

&lt;p&gt;The Skill describes the workflow: inspect material, plan the edit, choose an execution path, and verify the result.&lt;/p&gt;

&lt;p&gt;The MCP adapter exposes concrete tools, including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timeline_project_inspect
timeline_track_inspect
timeline_clip_inspect
timeline_project_diff
timeline_project_apply
timeline_project_render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local adapter is a thin transport over the existing command runner. It does not duplicate timeline editing logic.&lt;/p&gt;

&lt;p&gt;To install the Skill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add MartinDelophy/ai-video-editor &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skill&lt;/span&gt; edit-timeline-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To explore the repository and local MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/MartinDelophy/ai-video-editor.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-video-editor
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installing the Skill does not install the complete runtime. Local MCP needs the checkout and Node dependencies; media operations need their corresponding tools. The Skill includes a host dependency-check workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  An edited project is not yet a verified video
&lt;/h2&gt;

&lt;p&gt;The current local project.render path supports the documented portable Visuals, Voiceover, and Music subset. It uses FFmpeg to produce H.264/AAC MP4 and ffprobe to check dimensions, duration, and audio-track presence.&lt;/p&gt;

&lt;p&gt;It rejects unsupported composition features rather than silently dropping visible captions, overlays, transitions, or effects. Richer compositions require the browser renderer.&lt;/p&gt;

&lt;p&gt;Verification needs several levels: inspect the project, reopen it and resolve archived media, check the output container, and actually play the important visual and audio boundaries. A valid project structure alone cannot prove that the final mix sounds right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to read the implementation
&lt;/h2&gt;

&lt;p&gt;Useful entry points in the &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;repository&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;src/lib/projectCommandEngine.js — shared project command engine&lt;/li&gt;
&lt;li&gt;scripts/timeline-command.mjs — local archive operations and rendering entry point&lt;/li&gt;
&lt;li&gt;skills/edit-timeline-studio/mcp/server.mjs — STDIO MCP adapter&lt;/li&gt;
&lt;li&gt;src/hooks/useWebMcpEditor.js — browser editor integration&lt;/li&gt;
&lt;li&gt;skills/edit-timeline-studio/SKILL.md — agent workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/main/skills/edit-timeline-studio/docs/command-reference.md" rel="noopener noreferrer"&gt;command reference&lt;/a&gt; documents the current contract and limits.&lt;/p&gt;

&lt;p&gt;The larger lesson for me is that agent reliability depends on the application interface: explicit state, constrained operations, reviewable changes, and verifiable outputs.&lt;/p&gt;

&lt;p&gt;If you are building agent tools or working on browser media, I would welcome feedback on the architecture. Real editing failures and reproducible cases are particularly useful. You can explore the &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;source&lt;/a&gt;, &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;try the editor&lt;/a&gt;, or browse the &lt;a href="https://github.com/MartinDelophy/timeline-studio-handbook" rel="noopener noreferrer"&gt;editing handbook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Writing disclosure: this article was drafted with AI assistance using the project's source code and documentation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Rehearsing AI Video in the Browser: A Three.js Blockout Workspace</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Tue, 20 Oct 2026 07:35:21 +0000</pubDate>
      <link>https://dev.to/martindelophy/rehearsing-ai-video-in-the-browser-a-threejs-blockout-workspace-3go</link>
      <guid>https://dev.to/martindelophy/rehearsing-ai-video-in-the-browser-a-threejs-blockout-workspace-3go</guid>
      <description>&lt;p&gt;A generated video can look beautiful and still tell the wrong story. A character enters from the wrong direction. A close-up replaces the shot that should establish the room. A prop removed in one scene reappears in the next.&lt;/p&gt;

&lt;p&gt;In Timeline Studio, our open-source, local-first video editor, we built a usable browser-based 3D blockout workspace to make those directing decisions visible before final generation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Source code on GitHub&lt;/a&gt; · &lt;a href="https://anna.partners/store/@martindelophy/timeline-studio" rel="noopener noreferrer"&gt;Try the Anna edition&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article covers the architecture and lessons from a separate AI short-film experiment that helped motivate it. The experiment's blockout footage was made independently; the editor did not generate that finished film in one click.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a blockout makes explicit
&lt;/h2&gt;

&lt;p&gt;A blockout is a simplified rehearsal: basic characters, primitive props, camera positions, and timed movement. Materials and detailed appearance can wait while we check where people stand, what the camera sees, and whether an action has enough time to finish.&lt;/p&gt;

&lt;p&gt;“Walk to the desk, put down the milk, and return a hair clip” hides several decisions: which side of the desk, when the hand releases the carton, where the handoff happens, and when to cut to the other character's reaction.&lt;/p&gt;

&lt;p&gt;The goal is to give these decisions an editable representation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxzklgbnuips4gx6semh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxzklgbnuips4gx6semh6.png" alt="Timeline Studio browser-based 3D blockout workspace" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The workspace is directly editable. The voice-binding area shown in this screenshot is future work.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate scene data from rendering
&lt;/h2&gt;

&lt;p&gt;The workspace uses Three.js and loads separately from the main editor. Its scene is a versioned, serializable document with objects, timed motions, camera shots, script segments, and a total duration.&lt;/p&gt;

&lt;p&gt;A simplified view of the data model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;revision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;motions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;script&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three layers share responsibility:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scene commands&lt;/td&gt;
&lt;td&gt;Validate operations and produce the next scene snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three.js viewport&lt;/td&gt;
&lt;td&gt;Selection, transforms, playback, and camera-view output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workspace and chat&lt;/td&gt;
&lt;td&gt;Editing state, timeline, script, and proposal review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scene command layer is the common boundary for manual controls and AI proposals. The renderer displays the scene; it does not define a second set of editing rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  One clock for actors, cameras, and script
&lt;/h2&gt;

&lt;p&gt;At playback time &lt;code&gt;t&lt;/code&gt;, the workspace computes actor poses, selects the current camera shot, and finds the corresponding script segment.&lt;/p&gt;

&lt;p&gt;For a linear move between &lt;code&gt;P0&lt;/code&gt; and &lt;code&gt;P1&lt;/code&gt; over &lt;code&gt;[t0, t1]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;u = clamp((t - t0) / (t1 - t0), 0, 1)
P(t) = P0 + (P1 - P0) * u
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preview and video export use the same actor-pose and camera-selection helpers. That reduces the risk of a preview showing one arrangement while an export uses another.&lt;/p&gt;

&lt;p&gt;Clicking an action, shot, or script segment seeks to its start. Script text and time ranges remain editable. Changing the total scene duration scales timed content together. Script timing is an editable estimate, not automatic alignment to recorded speech.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI proposes edits through a constrained command layer
&lt;/h2&gt;

&lt;p&gt;In the Anna integration, the model receives the current scene metadata and a scene-edit catalog. It submits structured proposals rather than arbitrary executable code.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inspect scene snapshot
→ Propose structured operations
→ Validate commands
→ Review the proposed edit
→ Check that the scene still matches
→ Apply as one undoable edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation checks object identities, values, and supported timing constraints. Unknown objects, unsupported operations, arbitrary code, and external assets are rejected. If the scene changes while a proposal is pending, that old proposal cannot overwrite the newer edits.&lt;/p&gt;

&lt;p&gt;This keeps natural-language assistance connected to an inspectable, editable project. Manual editing works locally; remote planning needs a working Anna host session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export a rehearsal you can actually watch
&lt;/h2&gt;

&lt;p&gt;The workspace exports scene JSON and a real 1280 × 720 WebM camera-view video, with progress and cancellation. Video capture uses the browser canvas stream and &lt;code&gt;MediaRecorder&lt;/code&gt;; available codecs depend on the browser.&lt;/p&gt;

&lt;p&gt;Scene changes persist per project in the same browser. This does not imply cross-device synchronization. The blockout workspace is also separate from the main video timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned from a short-film experiment
&lt;/h2&gt;

&lt;p&gt;In a campus short-film experiment, we compared independent blockout footage against AI-generated shots. The comparison images accompanying this article show corresponding narrative moments sampled proportionally from clips of different lengths. They are not exact timestamp matches or proof of frame-by-frame adherence. All realistic character images are AI-generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hrso49n48a3k3u1u69x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hrso49n48a3k3u1u69x.jpg" alt="Blockout: establishing characters and space at 1.27 seconds" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftc655sn2xnbbgdlqsrds.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftc655sn2xnbbgdlqsrds.jpg" alt="AI-generated comparison at 1.25 seconds" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;G02 r5: blockout above, AI-generated image below. Proportional narrative-moment sampling, not frame-exact alignment.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Similar composition does not mean the whole shot passed
&lt;/h3&gt;

&lt;p&gt;Comparisons made it easier to inspect character positions, desk layout, occlusion, and camera direction. But a matching still did not establish that a whole sequence followed the rehearsal.&lt;/p&gt;

&lt;p&gt;One generated segment preserved the broad order—enter, approach, put down milk, return the hair clip—while changing medium shots into close-ups or over-the-shoulder views and delaying a reaction shot. We did not mark it as strict adherence.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Sometimes the rehearsal contains the mistake
&lt;/h3&gt;

&lt;p&gt;A character needed to return to his seat before a segment ended. Initially, we blamed generation for not completing the action. Reviewing the blockout showed that the original staging itself had left too little time.&lt;/p&gt;

&lt;p&gt;We moved the return approximately 0.7 seconds earlier and adjusted the ending shot. The lesson was practical: inspect the reference before adding stronger wording to the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Prop interactions need motion review
&lt;/h3&gt;

&lt;p&gt;Putting down milk requires a stable contact with the table before release. Returning a hair clip requires an actual handoff and the correct resulting prop state.&lt;/p&gt;

&lt;p&gt;A still helps locate a problem. It cannot establish whether the fingers, object, and movement remain coherent throughout the action. Full playback is still necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Continuity references can propagate errors
&lt;/h3&gt;

&lt;p&gt;Using the previous clip's real final frame helped carry appearance and pose forward. It also carried an incorrect hair clip into the next segment. A conflicting environment reference contributed to a window being replaced by a blackboard.&lt;/p&gt;

&lt;p&gt;We preserved the original final frame, made a corrected reference copy, and removed the conflicting environment image.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;th&gt;Main information&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blockout video&lt;/td&gt;
&lt;td&gt;Space, movement, cameras, and pacing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Character image&lt;/td&gt;
&lt;td&gt;Appearance and clothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Previous final frame&lt;/td&gt;
&lt;td&gt;State at the end of the preceding segment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment image&lt;/td&gt;
&lt;td&gt;Setting and layout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;More references help only when their information is compatible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this contributes—and what remains open
&lt;/h2&gt;

&lt;p&gt;The clearest benefits we observed were earlier staging checks, a concrete comparison baseline after generation, and more precise discussions about revisions.&lt;/p&gt;

&lt;p&gt;We have not measured a reliable percentage reduction in retries or cost. A blockout also cannot guarantee that a generation model will execute its camera plan.&lt;/p&gt;

&lt;p&gt;The browser workspace already supports direct editing, timed actions and shots, script timing, reviewed AI edits, and JSON/WebM export. Voice binding, complex facial animation, collision and IK, external mesh imports, and full integration with the video timeline remain unfinished. There is no automatic end-to-end blockout-to-final-video pipeline.&lt;/p&gt;

&lt;p&gt;The direction is to keep directing decisions visible and revisable before committing to expensive final imagery.&lt;/p&gt;

&lt;p&gt;If you work on browser 3D, AI filmmaking, or agent-assisted editors, I'd welcome a concrete shot-control problem or an implementation discussion.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Explore the source and star the project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://anna.partners/store/@martindelophy/timeline-studio" rel="noopener noreferrer"&gt;Try Timeline Studio on Anna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Open the public editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feature availability can differ between the public editor and the Anna edition.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building an AI Video Editor: Reviewable Commands, State Tokens, and Browser Export</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:51:33 +0000</pubDate>
      <link>https://dev.to/martindelophy/building-an-ai-video-editor-reviewable-commands-state-tokens-and-browser-export-3hhc</link>
      <guid>https://dev.to/martindelophy/building-an-ai-video-editor-reviewable-commands-state-tokens-and-browser-export-3hhc</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I work on Timeline Studio, the open-source editor discussed below. This article was prepared with AI assistance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A generated video shot can be beautiful and still need another cut. The opening lingers, the narration arrives too late, or the sequence needs a different order.&lt;/p&gt;

&lt;p&gt;We are building Timeline Studio around that next creative decision: a browser video editor where generated media and agent-assisted operations become part of an editable multitrack project.&lt;/p&gt;

&lt;p&gt;Here are the architectural choices behind its command interface, with a small example you can follow in the repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg8yvsav37hv955vfd8dj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg8yvsav37hv955vfd8dj.png" alt="Timeline Studio multitrack editor" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Repository screenshot; interface details may differ in the current release.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Separate a source asset from a timeline clip
&lt;/h2&gt;

&lt;p&gt;A source video can appear several times in one project, each time with a different trim, playback speed, transform, or effect. The asset identifies the media; the clip identifies one use of it.&lt;/p&gt;

&lt;p&gt;When a user says “move the second shot to the beginning,” an agent first needs to resolve that description against the current sequence. Execution then targets a stable clip ID, rather than a filename or screen coordinate.&lt;/p&gt;

&lt;p&gt;Timeline Studio exposes project, track, and clip inspection. An agent can read the overall structure and then request the relevant objects. Metadata inspection is distinct from understanding the picture or transcribing speech; those require their own processing routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Turn intent into an explicit plan
&lt;/h2&gt;

&lt;p&gt;The local command interface accepts a versioned JSON plan. This example is illustrative: the revision, clip ID, and source range must match an inspected project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects/input.timeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"baseRevision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dryRun"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trim-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"visual.trim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"clipId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"visual-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sourceIn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sourceOut"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects/output.timeline"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The envelope states which project revision the plan assumes, identifies operations, and specifies a new output path. The operation states exactly which source interval to retain.&lt;/p&gt;

&lt;p&gt;Planning interprets the request. Execution enforces the supported operations and project constraints. This boundary also lets non-conversational tools produce the same plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Validate meaning, not just JSON
&lt;/h2&gt;

&lt;p&gt;Valid JSON can describe an impossible edit. The clip may no longer exist, the source range may be invalid, or the track may be locked.&lt;/p&gt;

&lt;p&gt;We separate transport-shape validation from project-aware validation. A semantic dry run uses the shared command registry and state transformations to predict the actual result without writing an archive.&lt;/p&gt;

&lt;p&gt;The local workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.inspect /projects/input.timeline
npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.diff /projects/plan.json
npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.run /projects/plan.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff reports predicted changes to project fields, clips, timing, and duration. It gives review a concrete connection to execution. The local writer requires a new output path, preserving the input project.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reject stale plans in a live editor
&lt;/h2&gt;

&lt;p&gt;There is a concurrency problem even with a single human user:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent inspects the project.&lt;/li&gt;
&lt;li&gt;The user splits or moves a clip.&lt;/li&gt;
&lt;li&gt;The agent returns a plan based on the earlier state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The browser interface uses a state token to bind preparation to the inspected state. A reviewed edit receives a preview ID. Applying it checks that the editor still matches the reviewed snapshot; otherwise, the agent must inspect again and replan.&lt;/p&gt;

&lt;p&gt;Successful edits enter the editor's normal history mechanism, so the user can continue adjusting and undoing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Reuse editing rules across transports
&lt;/h2&gt;

&lt;p&gt;The project provides a CLI, a bundled STDIO MCP adapter, and a browser WebMCP interface. Separate implementations of trimming, sorting, and link handling would invite inconsistencies.&lt;/p&gt;

&lt;p&gt;The CLI and MCP adapter use the same application service. The browser adapter compiles reviewed operations into the shared command engine and commits through the editor.&lt;/p&gt;

&lt;p&gt;Capability boundaries still matter. The browser has live media, inference jobs, and the full editor export workflow. The local renderer supports a defined portable subset and rejects unsupported compositions.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep generation and placement separate
&lt;/h2&gt;

&lt;p&gt;Generation providers differ in authorization, job protocols, and output formats. Source-integrated adapters handle service-specific transport; a shared host receives downloaded media and commits it to My assets.&lt;/p&gt;

&lt;p&gt;Completed generation does not automatically insert footage into the timeline. A creator may want to compare candidates first. This also prevents a long-running job from unexpectedly modifying an active edit.&lt;/p&gt;

&lt;p&gt;Track submission, generation, download, and asset commit separately. A successful generation followed by a failed download needs a different recovery path from a failed generation. Stopping polling also does not by itself cancel a remote task.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Put preview and export on the same timeline clock
&lt;/h2&gt;

&lt;p&gt;A source interval from 10 to 18 seconds, played at 2×, occupies four timeline seconds. For constant speed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source time = source in + clip-local time × playback speed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Speed curves require accumulating the changing rate over time. Preview, splitting, thumbnails, and export need to agree on that mapping.&lt;/p&gt;

&lt;p&gt;Timeline Studio uses native media paths for responsive playback and a separate deterministic offline rendering path for export, with WebCodecs-based composition and a compatibility fallback. Availability depends on the browser and device.&lt;/p&gt;

&lt;p&gt;Resource scheduling matters too: thumbnail preparation is progressive, visible regions receive priority, and scrubbing pauses background refinement. Bounded processing queues keep throughput improvements from becoming unbounded memory growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is available now?
&lt;/h2&gt;

&lt;p&gt;ChatCut is usable, and the Anna edition includes video-generation integrations. Its setup and experience entry points are documented in the repository. Hosted generation is distinct from browser-local processing.&lt;/p&gt;

&lt;p&gt;The command contract and browser adapter are useful starting points if you are building a similar system. The goal is to preserve an understandable project at every handoff: from request to plan, plan to reviewed operations, and editable state to rendered output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Explore the Timeline Studio source and documentation on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What would you put in an agent-facing editing contract first: timing, media identity, or transaction handling?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cutting Browser Video Export Time by 31%: An 8-Minute WebCodecs Case Study</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Mon, 12 Oct 2026 05:54:01 +0000</pubDate>
      <link>https://dev.to/martindelophy/cutting-browser-video-export-time-by-31-an-8-minute-webcodecs-case-study-m0l</link>
      <guid>https://dev.to/martindelophy/cutting-browser-video-export-time-by-31-an-8-minute-webcodecs-case-study-m0l</guid>
      <description>&lt;p&gt;An eight-minute video is long enough to expose work that is easy to miss in short browser-editor demos: audio preparation, repeated frame conversions, background thumbnail decoding, and idle time between decode, composition, and encoding.&lt;/p&gt;

&lt;p&gt;We recently optimized these paths in &lt;strong&gt;Timeline Studio&lt;/strong&gt;, an open-source browser video editor. In a controlled comparison against the code before the first optimization round, average export time fell from &lt;strong&gt;192.0 seconds to 131.9 seconds&lt;/strong&gt;—a &lt;strong&gt;31.3% reduction&lt;/strong&gt;, saving about &lt;strong&gt;60 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Source code on GitHub&lt;/a&gt; · &lt;a href="https://video-editor.ai-creator.top" rel="noopener noreferrer"&gt;Try the editor&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark: what we actually measured
&lt;/h2&gt;

&lt;p&gt;Both versions ran on the same machine with the same approximately eight-minute synthetic stress-test video. The input was H.264 at 1440 × 1080 and approximately 29.88 fps, with stereo AAC audio at 44.1 kHz.&lt;/p&gt;

&lt;p&gt;Output settings were identical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;H.264, 1440 × 1080, 30 fps&lt;/li&gt;
&lt;li&gt;10 Mbps target video bitrate&lt;/li&gt;
&lt;li&gt;AAC audio at 192 kbps&lt;/li&gt;
&lt;li&gt;No effects or overlays in this benchmark&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Timing started immediately before embedded-audio preparation and ended when export completed. Import and timeline-thumbnail preparation were excluded.&lt;/p&gt;

&lt;p&gt;We ran old → new → new → old, starting a fresh browser process for each run.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;First run&lt;/th&gt;
&lt;th&gt;Second run&lt;/th&gt;
&lt;th&gt;Mean total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before the first optimization&lt;/td&gt;
&lt;td&gt;203.7 s&lt;/td&gt;
&lt;td&gt;180.4 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;192.0 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current implementation&lt;/td&gt;
&lt;td&gt;141.6 s&lt;/td&gt;
&lt;td&gt;122.3 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;131.9 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Before, mean&lt;/th&gt;
&lt;th&gt;After, mean&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Audio preparation&lt;/td&gt;
&lt;td&gt;9.3 s&lt;/td&gt;
&lt;td&gt;0.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video export stage&lt;/td&gt;
&lt;td&gt;182.7 s&lt;/td&gt;
&lt;td&gt;131.0 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are two runs per version, with visible runtime variation. The input is synthetic, so the 31.3% result should not be generalized to every camera recording, device, or effect stack. We also checked decoding and compatibility with a native Sony ZV-1 4K camera sample, but that sample is &lt;strong&gt;not&lt;/strong&gt; the source of the eight-minute benchmark result.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Avoid decoding the entire audio track just to prepare it
&lt;/h2&gt;

&lt;p&gt;The old preparation path used FFmpeg to decode embedded audio into WAV before subsequent processing. For long inputs, this adds whole-track decoding, PCM allocation, and intermediate-data handling.&lt;/p&gt;

&lt;p&gt;For eligible AAC tracks, the new path extracts encoded packets and remuxes them instead. Final output audio is still encoded according to export settings; this change removes unnecessary work in the preparation stage.&lt;/p&gt;

&lt;p&gt;Timestamp handling matters. AAC can contain preroll and negative timestamps. Simply copying packets while discarding that information can shift audio relative to the picture. The implementation preserves and compensates for the source offset and distinguishes storage duration from logical segment duration.&lt;/p&gt;

&lt;p&gt;Other formats retain the fallback path. In this benchmark, mean audio preparation dropped from &lt;strong&gt;9.3 seconds to 0.9 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Remove an intermediate frame conversion only when it is safe
&lt;/h2&gt;

&lt;p&gt;At 30 fps, an eight-minute output contains approximately 14,400 frames. Even a small repeated operation is worth examining at that scale.&lt;/p&gt;

&lt;p&gt;The new eligible path sends decoded VideoFrame objects directly to the shared compositor, avoiding an additional intermediate Canvas rasterization step.&lt;/p&gt;

&lt;p&gt;Eligibility is deliberately narrow: original-size, unrotated, square-pixel frames with no relevant transformations or effects that require the existing rasterization path. Scaling, grading, animation, masks, and other affected cases keep their compatible Canvas path.&lt;/p&gt;

&lt;p&gt;This restriction came from validation: different rasterization routes produced small pixel differences when resizing. We therefore did not extend the direct-frame optimization to those cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Overlap work with bounded queues
&lt;/h2&gt;

&lt;p&gt;A fully serial loop can leave resources waiting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Decode frame → Compose frame → Submit/wait for encoding → Repeat&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The updated pipeline overlaps a limited amount of work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prepare one decoded frame ahead.&lt;/li&gt;
&lt;li&gt;Compose the next frame while a video submission is in flight, subject to backpressure.&lt;/li&gt;
&lt;li&gt;Process audio and video concurrently.&lt;/li&gt;
&lt;li&gt;Release frames promptly after use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The queue bounds are essential. Unrestricted prefetching can turn a throughput problem into a memory problem on long videos.&lt;/p&gt;

&lt;p&gt;The exporter also probes supported hardware-encoding configurations and falls back when necessary. Actual hardware acceleration depends on the browser, operating system, and device.&lt;/p&gt;

&lt;p&gt;Cancellation needs the same discipline: stop submitting new work, let bounded in-flight operations settle, and then release output resources. Closing everything immediately can leave asynchronous waits stranded.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Give playback priority over background thumbnails
&lt;/h2&gt;

&lt;p&gt;Export was only part of the original problem. Playback and seeking also competed with background timeline work.&lt;/p&gt;

&lt;p&gt;We changed scheduling so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Playback pauses background thumbnail refinement.&lt;/li&gt;
&lt;li&gt;Scrubbing cancels background thumbnail work for the pointer gesture.&lt;/li&gt;
&lt;li&gt;Releasing the pointer resumes viewport-prioritized refinement.&lt;/li&gt;
&lt;li&gt;Starting playback prioritizes the latest seek instead of stale scrub requests.&lt;/li&gt;
&lt;li&gt;The playback clock pauses while decoding is seeking or buffering, reducing cases where the timeline advances while the picture is frozen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For live thumbnail updates, confirmed decoded frames are drawn directly rather than repeatedly going through JPEG encoding and decoding. Source timestamps remain aligned with timeline sampling.&lt;/p&gt;

&lt;p&gt;For locally imported media processed in the browser, these are client-side resource and coordination issues worth measuring alongside network behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation: speed with the same output settings
&lt;/h2&gt;

&lt;p&gt;We kept output resolution, frame rate, and target bitrate unchanged. Validation covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Output video/audio tracks, duration, and dimensions.&lt;/li&gt;
&lt;li&gt;AAC packet extraction and source-offset handling.&lt;/li&gt;
&lt;li&gt;Pixel comparisons for eligible direct-frame cases.&lt;/li&gt;
&lt;li&gt;Canvas fallback behavior for scaling, grading, and animation.&lt;/li&gt;
&lt;li&gt;Encoded-packet consistency for a fixed effects case across the pipeline scheduling change.&lt;/li&gt;
&lt;li&gt;Cancellation and resource cleanup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not establish byte-identical output between the original version and the final version, or cover every possible project. It checks the specific paths changed by this optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the implementation
&lt;/h2&gt;

&lt;p&gt;The main gains came from reducing intermediate work and overlapping bounded tasks while retaining the processing paths required by effects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Repository and documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor/commit/4f9930af204a8fecb727f92b4432a63c02a836d2" rel="noopener noreferrer"&gt;Optimization commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor/compare/9514d6579eb8e1dfd27d737dbf0393d80372116f...4f9930af204a8fecb727f92b4432a63c02a836d2" rel="noopener noreferrer"&gt;Before/after code comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://video-editor.ai-creator.top" rel="noopener noreferrer"&gt;Live editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you reproduce a performance issue with your own footage, include your device, browser version, source codec parameters, export settings, and effects in a GitHub issue. Separating preparation time from export time makes the bottleneck much easier to discuss.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Video Depth Estimation in the Browser: Building with WebGPU and Depth Anything V2</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Thu, 08 Oct 2026 06:15:19 +0000</pubDate>
      <link>https://dev.to/martindelophy/video-depth-estimation-in-the-browser-building-with-webgpu-and-depth-anything-v2-4o0m</link>
      <guid>https://dev.to/martindelophy/video-depth-estimation-in-the-browser-building-with-webgpu-and-depth-anything-v2-4o0m</guid>
      <description>&lt;p&gt;An ordinary video records color and motion, but it does not explicitly tell an editor which pixels are near the camera and which belong to the background.&lt;/p&gt;

&lt;p&gt;I added browser-local depth estimation to &lt;strong&gt;Timeline Studio&lt;/strong&gt;, my open-source video editor, to turn that missing information into an editable resource. The current implementation supports depth-aware blur, depth-based relighting, and exporting an independent grayscale depth video.&lt;/p&gt;

&lt;p&gt;This post covers the implementation and what happened when I used the resulting depth videos as references for AI video generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model actually predicts
&lt;/h2&gt;

&lt;p&gt;The implementation uses &lt;a href="https://huggingface.co/depth-anything/Depth-Anything-V2-Small" rel="noopener noreferrer"&gt;Depth Anything V2 Small&lt;/a&gt; to estimate relative depth from RGB images. In our default visualization, brighter pixels generally represent nearer surfaces, and darker pixels represent farther ones. The display can also be inverted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relative depth is not a distance measurement.&lt;/strong&gt; A grayscale value does not tell us that an object is exactly two meters from the camera. It is useful for visual layering and effects, but it is not a calibrated 3D reconstruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The browser pipeline
&lt;/h2&gt;

&lt;p&gt;The processing flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Video segment
  -&amp;gt; map timeline time to source time
  -&amp;gt; decode and resize a frame
  -&amp;gt; run depth inference in a Web Worker
  -&amp;gt; normalize and refine depth pixels
  -&amp;gt; preview an effect or encode a depth video
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker loads the model through Transformers.js with WebGPU and a Q4F16 configuration. The essential setup looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;estimator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;depth-estimation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;modelPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webgpu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;q4f16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;progress_callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reportProgress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production setup also handles pinned model revisions, mirrored downloads, progress reporting, optional caching, and failures.&lt;/p&gt;

&lt;p&gt;A worker keeps inference work off the main JavaScript thread. It does not eliminate resource contention: decoding, inference, and editor rendering still share the device. That is why bounded work matters.&lt;/p&gt;

&lt;p&gt;Video frames stay local during this depth-analysis path. Sending the exported depth video to a remote generation service is a separate operation that uploads reference material.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline time is not source time
&lt;/h2&gt;

&lt;p&gt;This was one of the most important integration details.&lt;/p&gt;

&lt;p&gt;An editor segment may be trimmed, sped up, slowed down, or assigned a speed curve. Sampling the original file at the same numerical timestamp as the timeline would produce incorrect depth frames.&lt;/p&gt;

&lt;p&gt;The analysis uses the editor's shared source-time mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getVisualSourceTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preview and depth-video output use the same mapping. Analysis cache signatures include the source, trim, playback rate, speed curve, quality setting, and model revision. Relevant changes invalidate the old analysis.&lt;/p&gt;

&lt;p&gt;A good depth estimate attached to the wrong video frame is still a broken effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bounding inference and memory
&lt;/h2&gt;

&lt;p&gt;The current analysis presets are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preset&lt;/th&gt;
&lt;th&gt;Target sample rate&lt;/th&gt;
&lt;th&gt;Maximum input dimension&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;8 fps&lt;/td&gt;
&lt;td&gt;392 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balanced&lt;/td&gt;
&lt;td&gt;16 fps&lt;/td&gt;
&lt;td&gt;504 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;24 fps&lt;/td&gt;
&lt;td&gt;518 px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are sampling settings, &lt;strong&gt;not measured real-time inference speeds&lt;/strong&gt;. Actual throughput depends on the browser, GPU, and workload.&lt;/p&gt;

&lt;p&gt;Each analysis has a 720-sample budget. Longer clips reduce their effective sampling density to cover the segment within that budget.&lt;/p&gt;

&lt;p&gt;The implementation overlaps inference for the current frame with preparation of the next frame. It keeps only a limited number of tasks in flight instead of decoding a whole video into memory.&lt;/p&gt;

&lt;p&gt;Independent depth-video output runs at 24 fps. Fast and Balanced modes use RIFE interpolation between depth samples. Interpolation can make transitions smoother, but it does not add genuine depth observations and can still fail around rapid motion or occlusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning depth into an editable effect
&lt;/h2&gt;

&lt;p&gt;For depth-aware blur, the renderer prepares a sharp layer, a blurred layer, and a depth-derived mask.&lt;/p&gt;

&lt;p&gt;Pixels close to the selected focus depth stay sharp. Pixels outside the focus band progressively mix in the blurred layer, with a smooth transition at the boundary.&lt;/p&gt;

&lt;p&gt;This is a compositing approximation rather than a physically exact lens simulation. Hair, transparent materials, and complex occlusion remain challenging.&lt;/p&gt;

&lt;p&gt;The depth output also receives a small edge-guided smoothing pass. Neighboring pixels contribute less when their depth or source-color differences are large. This reduces unwanted mixing across foreground/background boundaries; image texture guides smoothing rather than becoming new geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exporting a reusable depth video
&lt;/h2&gt;

&lt;p&gt;The editor can encode depth results as a new asset in My assets. The current browser path uses WebM with VP9 video, preserves the source aspect ratio, and caps the longest output dimension at 1080 pixels. Where source audio exists, the export prepares audio matched to the segment timing.&lt;/p&gt;

&lt;p&gt;Output size and inference resolution are different. Encoding a larger image does not recover geometry the model never predicted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment: depth references for AI video generation
&lt;/h2&gt;

&lt;p&gt;I also tested a workflow combining three inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A depth video for silhouettes, relative depth, occlusion, and framing changes.&lt;/li&gt;
&lt;li&gt;Character images for appearance, clothing, and materials.&lt;/li&gt;
&lt;li&gt;Text describing the action stages, environment, and visual style.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is an important qualification: &lt;strong&gt;these experiments submitted the depth video as an ordinary reference video, not through a dedicated depth-conditioning API.&lt;/strong&gt; The generation service had to interpret the grayscale reference itself.&lt;/p&gt;

&lt;p&gt;Some broad movements survived, including turning toward the camera and moving into a closer shot. But the results were not frame-exact. In one experiment, a foreground limb shape was interpreted as a different body action, and head/shoulder proportions drifted from the reference.&lt;/p&gt;

&lt;p&gt;A separate two-character sleeve-transition experiment followed the main action structure more closely when I supplied the original RGB video instead. Sampled-frame inspection showed the sleeve raise, full-frame occlusion, and subsequent close-up, while hand details still differed.&lt;/p&gt;

&lt;p&gt;The practical lesson is to choose reference inputs according to the task. Depth emphasizes spatial structure, while RGB retains semantic information about gestures, fabric, and objects.&lt;/p&gt;

&lt;p&gt;A relative depth map also does not directly provide a calibrated camera FOV. Framing fidelity needs to be checked through screen-space proportions, crop boundaries, and camera changes. Uploading a depth reference alone is not proof of accurate reconstruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is working, and what still needs work
&lt;/h2&gt;

&lt;p&gt;The browser implementation connects depth analysis to editable effects and independent video output. The engineering work is as much about timing, cancellation, progress, cache validity, and bounded resources as it is about running a model.&lt;/p&gt;

&lt;p&gt;The generation experiments are useful evidence of possibilities and limitations, not a promise of precise motion transfer. Better temporal stability, difficult boundaries, and dedicated depth-conditioning integrations remain areas to improve.&lt;/p&gt;

&lt;p&gt;If you are building browser AI tools or experimenting with depth-guided editing, I would be interested in how you handle these issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it or explore the source
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Open Timeline Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Source code on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/timeline-studio-handbook" rel="noopener noreferrer"&gt;AI video editing Skills handbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Timeline Studio is free and open source. Feedback, reproducible issues, and contributions are welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Depth-Assisted Video Relighting in the Browser with WebGPU and WebGL</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Sun, 04 Oct 2026 14:53:58 +0000</pubDate>
      <link>https://dev.to/martindelophy/depth-assisted-video-relighting-in-the-browser-with-webgpu-and-webgl-2nl3</link>
      <guid>https://dev.to/martindelophy/depth-assisted-video-relighting-in-the-browser-with-webgpu-and-webgl-2nl3</guid>
      <description>&lt;p&gt;I added an editable video-relighting effect to &lt;strong&gt;Timeline Studio&lt;/strong&gt;, my open-source browser video editor. It lets you move a virtual light around a scene, adjust its color and softness, and compare the result with the original while keeping the effect editable on the timeline.&lt;/p&gt;

&lt;p&gt;The useful part is the division of work: a depth model analyzes the footage, then a small GPU shader handles lighting adjustments. Moving the light does not require another inference pass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Try Timeline Studio&lt;/a&gt; · &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;Explore the source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5z5wnm0br8oteeij8vxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5z5wnm0br8oteeij8vxw.png" alt="The light-position illustration used by Timeline Studio's video-relighting effect" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Light-position artwork from the editor. The interactive control uses a sphere to make light direction easier to understand.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From cinematic depth to editable lighting
&lt;/h2&gt;

&lt;p&gt;The editor already used depth estimation for its cinematic depth-of-field effect and grayscale depth-map videos. Relighting builds on that same scene-depth information.&lt;/p&gt;

&lt;p&gt;The pipeline is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode sampled frames from the selected clip.&lt;/li&gt;
&lt;li&gt;Run Depth Anything V2 Small locally through Transformers.js and a WebGPU inference path.&lt;/li&gt;
&lt;li&gt;Normalize and refine the depth maps using source-image edges.&lt;/li&gt;
&lt;li&gt;Blend neighboring depth samples at the current source time.&lt;/li&gt;
&lt;li&gt;Render the original frame and the depth textures through a WebGL lighting shader.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;WebGPU runs the model; WebGL renders the relighting effect. Keeping those responsibilities separate lets the expensive analysis be reused while lighting parameters remain interactive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning a depth map into an approximate surface
&lt;/h2&gt;

&lt;p&gt;A grayscale depth map tells us which visible regions are nearer or farther away. Differences between neighboring depth pixels provide an approximate surface orientation.&lt;/p&gt;

&lt;p&gt;The shader estimates a normal from those depth gradients. In simplified GLSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;normal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;lightDirection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lightPosition&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;surface&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;diffuse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lightDirection&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation combines that directional response with a spatial falloff controlled by the light's range and distance. Color and strength determine the added illumination; softness changes the response and the neighborhood used to estimate the surface.&lt;/p&gt;

&lt;p&gt;It also approximates a rear-light rim using foreground depth discontinuities. A single camera view does not provide the hidden geometry behind the subject, so this is a screen-space silhouette estimate.&lt;/p&gt;

&lt;p&gt;The shader adds a restrained fill to the existing image rather than replacing its original shading. It uses an approximate gamma-to-linear conversion before adding light, then converts the result back for display.&lt;/p&gt;

&lt;h2&gt;
  
  
  A sphere instead of mysterious coordinates
&lt;/h2&gt;

&lt;p&gt;The inspector exposes a draggable light-position sphere, horizontal and vertical angles, distance, strength, color, range, and softness. Warm, neutral, and cool presets provide quick starting points.&lt;/p&gt;

&lt;p&gt;Internally, the sphere's angles become a three-dimensional light position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;azimuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elevation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azimuth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elevation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elevation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;azimuth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elevation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The effect also has a &lt;strong&gt;hold-to-view-original&lt;/strong&gt; button. Comparison is transient preview state: it does not change the saved effect, create an undo entry, or disable lighting in an export.&lt;/p&gt;

&lt;p&gt;The controls are localized in all 13 interface languages supported by the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video needs source-time alignment
&lt;/h2&gt;

&lt;p&gt;A still image needs one depth result. Video needs the right result at the right source time, including after trimming or changing playback speed.&lt;/p&gt;

&lt;p&gt;The analysis signature includes the source, trim information, duration, playback rate, speed curve, quality, and model revision. Changing the source-time mapping invalidates reuse of an incompatible analysis.&lt;/p&gt;

&lt;p&gt;At playback time, neighboring depth maps are blended. This softens sample-to-sample steps, although it is not motion-compensated interpolation and cannot guarantee flicker-free results on every clip.&lt;/p&gt;

&lt;p&gt;Depth refinement uses a small edge-guided smoothing pass. Source-image edges discourage mixing across boundaries, while a depth-difference gate helps preserve foreground/background separation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seek bug that mattered for editing
&lt;/h2&gt;

&lt;p&gt;A lighting shader can be correct and still display the wrong video frame.&lt;/p&gt;

&lt;p&gt;Seeking a video element is asynchronous. Updating the playhead time does not mean the target frame has finished decoding. Painting too early can leave the effect canvas showing the previous frame after a drag.&lt;/p&gt;

&lt;p&gt;The preview now avoids painting while the source is seeking and refreshes after decoded-frame events. The main video also uses video-frame callbacks where available. That connects repainting to the actual media frame rather than relying only on a UI state update.&lt;/p&gt;

&lt;p&gt;Preview and export both call the shared relighting renderer. They use the same lighting math, though resolution, decoding, and encoding can still affect the final appearance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your own footage
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Timeline Studio&lt;/a&gt; and add a video to the timeline.&lt;/li&gt;
&lt;li&gt;Select the clip and open &lt;strong&gt;Effects → Video lighting&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Analyze the depth, then drag the light on the sphere.&lt;/li&gt;
&lt;li&gt;Adjust strength and color first; use range, softness, and distance to refine the look.&lt;/li&gt;
&lt;li&gt;Hold the original-comparison button to judge the change, then export when you are happy with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this effect, footage and depth inference stay in the browser. The model files need to download, and the current analysis route requires WebGPU support. Speed depends on the browser, GPU, clip length, and analysis quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this approach can and cannot do
&lt;/h2&gt;

&lt;p&gt;This is an editable approximation based on visible scene depth. It does not reconstruct a complete 3D scene, recover material properties, remove baked-in shadows, or simulate physically accurate cast shadows and reflections.&lt;/p&gt;

&lt;p&gt;Its practical role is adding a directional fill, a color accent, or a restrained edge-light impression to existing footage. Strong motion, difficult depth boundaries, and reflective or transparent surfaces can expose the approximation.&lt;/p&gt;

&lt;p&gt;The implementation is open source. Start with the &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/cd45ecdb61a73d1f7daf74bab26ba04d4cbd060a/src/lib/videoRelighting.js" rel="noopener noreferrer"&gt;relighting renderer&lt;/a&gt;, &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/cd45ecdb61a73d1f7daf74bab26ba04d4cbd060a/src/lib/depthOfField.js" rel="noopener noreferrer"&gt;depth rendering integration&lt;/a&gt;, and &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/cd45ecdb61a73d1f7daf74bab26ba04d4cbd060a/src/hooks/useDepthOfFieldAnalysis.js" rel="noopener noreferrer"&gt;analysis hook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was prepared with AI writing assistance and checked against the feature's source code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Timeline Studio: From Approved Storyboard to Editable Video with Sanity</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Sat, 26 Sep 2026 04:08:38 +0000</pubDate>
      <link>https://dev.to/martindelophy/timeline-studio-from-approved-storyboard-to-editable-video-with-sanity-2o2m</link>
      <guid>https://dev.to/martindelophy/timeline-studio-from-approved-storyboard-to-editable-video-with-sanity-2o2m</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A storyboard is useful only if the editor can turn it into an actual sequence of shots. I wanted to connect those two steps without making creators upload all their footage to a cloud service.&lt;/p&gt;

&lt;p&gt;I develop and maintain &lt;strong&gt;Timeline Studio&lt;/strong&gt;, an open-source browser video editor. For this challenge, I built a Sanity-powered storyboard workflow: write a shot plan, review it, approve a specific version, and turn that version into editable timeline clips using local media.&lt;/p&gt;

&lt;p&gt;Sanity stores the shared creative decisions. The browser keeps the source footage and the edit.&lt;/p&gt;

&lt;p&gt;The workflow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An author creates a brief and ordered shots, each with a duration, visual direction, optional narration draft, and asset hint.&lt;/li&gt;
&lt;li&gt;A reviewer requests changes with a note or approves the saved version.&lt;/li&gt;
&lt;li&gt;Approval creates a separate snapshot of that version.&lt;/li&gt;
&lt;li&gt;In the editor, the creator chooses an approved storyboard and matches its shots to local images or videos.&lt;/li&gt;
&lt;li&gt;The editor checks that approval is still current, then appends editable clips and shot annotations. One undo reverses the import.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The new workbench uses &lt;strong&gt;Astro with an interactive React/Sanity Studio custom tool&lt;/strong&gt;. It shares the schema and workflow components with the editor integration. The editor retains its Vite build, and the two outputs are assembled into one static deployment. &lt;code&gt;/&lt;/code&gt; opens the editor directly; &lt;code&gt;/storyboard/&lt;/code&gt; opens the workbench.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sanity-storyboards--web-player-ai-voice-editor.netlify.app/" rel="noopener noreferrer"&gt;Try the editor — no login required&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sanity-storyboards--web-player-ai-voice-editor.netlify.app/storyboard/?lang=en" rel="noopener noreferrer"&gt;Open the storyboard workbench&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sanity-storyboards--web-player-ai-voice-editor.netlify.app/challenge-demo/sanity-storyboard-demo-2026-09-26.timeline" rel="noopener noreferrer"&gt;Download the editable demo project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/codex/sanity-storyboard-workflow/docs/sanity-challenge/reviewer-guide.md" rel="noopener noreferrer"&gt;Step-by-step reviewer guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faeij8floljofl03tafmt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faeij8floljofl03tafmt.png" alt="An approved storyboard imported as editable clips and timeline annotations in Timeline Studio" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The demo is a &lt;strong&gt;13-second editable sequence&lt;/strong&gt;, with three shots lasting 4, 5, and 4 seconds. Its local media are screenshots of the editor. Open the downloaded &lt;code&gt;.timeline&lt;/code&gt; file through the editor's Open project action to inspect the clips and annotations. This is a project archive, not an encoded video or a recording of the review process.&lt;/p&gt;

&lt;p&gt;To exercise the cloud handoff, upload local media, open &lt;strong&gt;File → Storyboards&lt;/strong&gt;, load the approved demo board, match each shot, and append it. Reading approved boards and editing locally do not require a Sanity login.&lt;/p&gt;

&lt;p&gt;Writing and reviewing in the workbench require an account authorized for the selected Sanity project. The reviewer guide also explains how to use your own project and dataset through the connection form. No shared owner password is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/MartinDelophy/ai-video-editor/tree/codex/sanity-storyboard-workflow" rel="noopener noreferrer"&gt;Source code and feature branch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main workflow lives in &lt;code&gt;src/sanity/&lt;/code&gt;. The Astro entry at &lt;code&gt;storyboard-site/pages/storyboard/index.astro&lt;/code&gt; mounts the shared workbench. Approval and timeline import each have one implementation, rather than separate copies for different frontends.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;p&gt;I built this integration with &lt;strong&gt;Codex&lt;/strong&gt;, iterating on the data contract, interface, and behavior in the actual browser.&lt;/p&gt;

&lt;p&gt;My initial instruction was to integrate Sanity on an independent branch. The useful follow-up constraints were concrete: keep media local, preserve the direct editor experience, import only approved content, and protect existing timeline edits. These are a summary of the working constraints, not a verbatim prompt transcript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the handoff explicit
&lt;/h3&gt;

&lt;p&gt;A cloud storyboard cannot assume that a creator's local file has a particular URL or duration. We made media matching a deliberate step. For video, a source in-point and shot duration must fit the source; the importer does not quietly loop or stretch footage.&lt;/p&gt;

&lt;p&gt;The append operation uses the editor's existing ripple-editing behavior and undo system. Shot direction and narration become range-marker notes. They remain editable and do not extend the rendered media duration. Importing a narration draft does not automatically generate speech or captions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat approval as a versioned decision
&lt;/h3&gt;

&lt;p&gt;A status label alone was insufficient. A reviewer might approve one version while another browser still holds older changes.&lt;/p&gt;

&lt;p&gt;We used revision-guarded writes and a separate approved snapshot. Conflicting saves preserve the user's local edits, and unsaved drafts can recover after reload. Revising an approved board removes it from new imports until it is approved again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify the real integration
&lt;/h3&gt;

&lt;p&gt;Isolated checks helped exercise revision conflicts and withdrawn approvals, but those checks did not establish that authentication and real cloud writes worked.&lt;/p&gt;

&lt;p&gt;We then used my signed-in Chrome session to create, save, submit, request changes, recover an unsaved edit after reload, resubmit, and approve a real board. The editor fetched the actual approved snapshot, matched local assets, and created three clips and three annotations. The exported project reopened in a fresh browser.&lt;/p&gt;

&lt;p&gt;The deployed preview was checked separately: the Astro workbench loaded the approved board, and anonymous cloud read → local import → undo passed without intercepting network responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work through build issues
&lt;/h3&gt;

&lt;p&gt;The first Astro configuration used &lt;code&gt;publicDir: false&lt;/code&gt;, which the build rejected. A dedicated public directory fixed that. An Astro 7 build then exposed a cookie-module compatibility error. Pinning Astro 6.4.8 with its matching React integration produced a working build.&lt;/p&gt;

&lt;p&gt;The combined production build and TypeScript check pass. Lint reports zero errors; the repository still has pre-existing warnings. The Sanity schema has also been deployed through the official CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;rgq98xsq&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt; — public&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo board:&lt;/strong&gt; &lt;code&gt;ts-board-19e4f5ec-b9be-4a0b-9a8d-8f577f929e12&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved snapshot:&lt;/strong&gt; &lt;code&gt;ts-release-37ad30b2-04d4-4a10-a23a-652efb239341&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The schema has two document types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tsStoryboard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Brief, language, ordered shots, review note, workflow status, actor/time, and approved-release reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tsStoryboardRelease&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Approved content snapshot, source revision, storyboard reference, and approval actor/time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each shot has a stable key, duration, visual direction, narration draft, and asset hint. Approval creates the release and updates the board in one transaction guarded by &lt;code&gt;ifRevisionId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The editor reads through the non-CDN API and rechecks the approved snapshot immediately before appending. This establishes approval at fetch time; it is not a distributed lock against a later cloud revision. The imported annotations retain the release identity for provenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Choices and Limits
&lt;/h2&gt;

&lt;p&gt;This uses ordinary Content Lake documents and an application-defined review workflow. It does not require paid workflow features, and it is a Studio custom tool rather than an App SDK application.&lt;/p&gt;

&lt;p&gt;The dataset is public, including demo script drafts and review notes, so it contains only shareable material. Source images, videos, voice profiles, and local project archives are not uploaded by this feature. Official Sanity Studio handles authentication; no developer write token is embedded in the editor.&lt;/p&gt;

&lt;p&gt;Approval is an application workflow, not an access-control barrier against a dataset administrator. Imported timelines are independent local edits: later storyboard changes do not silently rewrite a creator's project.&lt;/p&gt;

&lt;p&gt;The integration's controls support 13 languages. The result is a small but complete bridge from a shared, reviewed shot plan to a local, editable video sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;I used Codex for implementation, debugging, validation, and assistance preparing this writeup. No public agent-session transcript is attached; the code and reviewer guide document the implementation and reproducible workflow.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Turning a README into a Playable Game Catalog</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Wed, 09 Sep 2026 03:13:17 +0000</pubDate>
      <link>https://dev.to/martindelophy/turning-a-readme-into-a-playable-game-catalog-2g4h</link>
      <guid>https://dev.to/martindelophy/turning-a-readme-into-a-playable-game-catalog-2g4h</guid>
      <description>&lt;p&gt;GitHub: &lt;a href="https://github.com/MartinDelophy/awesome-gpt-6-astra" rel="noopener noreferrer"&gt;awesome-gpt-6-astra&lt;/a&gt;&lt;br&gt;
Live gallery: &lt;a href="https://astragames.aigccreative.com/" rel="noopener noreferrer"&gt;Astra Games&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building a game with AI leaves another problem to solve: helping people find it, try it, and understand how it was made.&lt;/p&gt;

&lt;p&gt;I maintain Awesome GPT-6 Astra, a community collection of games and interactive projects. As the collection grew, a Markdown list became the content source for a visual gallery with screenshots and playable links.&lt;/p&gt;

&lt;p&gt;This post explains a few design choices behind that workflow: structured entries, a README-driven catalog, real screenshots, and multilingual maintenance.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Give each entry a predictable structure
&lt;/h2&gt;

&lt;p&gt;A collection becomes difficult to browse when every submission uses a different format. One project links to source code, another has a screenshot, and a third hides its demo inside a long development log.&lt;/p&gt;

&lt;p&gt;We use a small, consistent entry format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Project name → playable demo
  - Creator → public profile
  - Platform → browser requirements, controls, access conditions
  - GPT-6 Astra → model contribution statement or development record
  - Resources → source code and setup instructions, when available
  - Preview → an actual gameplay screenshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A demo answers “Can I try it?” Source code answers “Can I study or run it?” A development record answers “How was it built?” These fields serve different purposes.&lt;/p&gt;

&lt;p&gt;Public source code is optional. A playable project can still be useful to explore, provided its creator is credited and the available information is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Let the README supply the website's catalog
&lt;/h2&gt;

&lt;p&gt;Maintaining a GitHub list and a separate frontend array creates duplicate work. A contributor can update the README while the gallery continues showing an old description—or misses the project entirely.&lt;/p&gt;

&lt;p&gt;Our gallery reads the public upstream README and parses it into structured records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;README entries
      ↓
Parse titles, categories, authors, links, and screenshots
      ↓
Structured catalog records
      ↓
Gallery cards and playable links
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Contributors can work with Markdown and pull requests without learning the frontend. The website handles presentation and periodically checks for catalog updates.&lt;/p&gt;

&lt;p&gt;The tradeoff is that Markdown is now a content interface. Formatting conventions matter, and the parser needs to handle edits predictably.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Parse project entries, not every link
&lt;/h2&gt;

&lt;p&gt;A README also contains navigation, language selectors, contribution instructions, licenses, and creator profiles. Treating every link as a game would produce a noisy catalog.&lt;/p&gt;

&lt;p&gt;The parser needs to distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project titles from navigation.&lt;/li&gt;
&lt;li&gt;Demo destinations from source-code references.&lt;/li&gt;
&lt;li&gt;Creator metadata from descriptions.&lt;/li&gt;
&lt;li&gt;Games from interactive experiments.&lt;/li&gt;
&lt;li&gt;Valid entries from incomplete or duplicate content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The existing tests cover adding, deleting, and editing entries, duplicate handling, malformed documents, caching, and recovery from failed reads.&lt;/p&gt;

&lt;p&gt;For a project like this, the useful guarantee is straightforward: a normal README contribution should produce the expected catalog update.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make screenshots part of the entry
&lt;/h2&gt;

&lt;p&gt;A short description rarely communicates how a game feels. An actual screenshot helps readers understand its visual style and interface before opening it.&lt;/p&gt;

&lt;p&gt;We embed screenshots in the README entries so the gallery can use them as covers. For captured images stored in the repository, we also record their source, capture date, and the scope of the check.&lt;/p&gt;

&lt;p&gt;That last detail matters. Entering the first level is not a full playthrough. A manual describing touch controls is not evidence that someone tested the game on a phone.&lt;/p&gt;

&lt;p&gt;Keeping those distinctions explicit makes the catalog more useful to both readers and future maintainers. Screenshots should represent the running project, and third-party images retain their creators' rights.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Multilingual maintenance includes metadata
&lt;/h2&gt;

&lt;p&gt;The collection has 12 README language versions. Translating a description is only one part of keeping them aligned.&lt;/p&gt;

&lt;p&gt;For a new submission, we also check that every version includes the entry, uses the same demo and creator links, references an existing screenshot, and has consistent counts and maintenance dates.&lt;/p&gt;

&lt;p&gt;Model attribution needs the same care. If one version says “awaiting creator confirmation,” another should not describe the contribution as independently verified.&lt;/p&gt;

&lt;p&gt;The website's current catalog source is the default README. The translated READMEs are additional documentation that we maintain alongside it; translating them does not imply that every language is a separate runtime data source.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Describe AI involvement precisely
&lt;/h2&gt;

&lt;p&gt;“Made with GPT-6 Astra” can describe very different development processes: implementing one mechanic, helping design an interface, iterating on game rules, or contributing across a larger project.&lt;/p&gt;

&lt;p&gt;We aim to preserve those distinctions. Attribution follows creator or submitter statements, and missing confirmation is marked explicitly. Inclusion is not an independent model benchmark or an endorsement from OpenAI.&lt;/p&gt;

&lt;p&gt;A useful submission explains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the player actually does.&lt;/li&gt;
&lt;li&gt;Which parts Astra helped build.&lt;/li&gt;
&lt;li&gt;What required iteration or manual changes.&lt;/li&gt;
&lt;li&gt;What limitations remain in the current version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These details give other developers something to learn from beyond a finished screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share something people can play
&lt;/h2&gt;

&lt;p&gt;The goal of this project is to give creators a place to show working games and connect their demos with useful development context.&lt;/p&gt;

&lt;p&gt;Playable prototypes are welcome. If you have built a game with Astra, you can submit it through the repository with a demo, creator credit, a real screenshot, and a description of the model's contribution.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: This article was drafted and translated with AI assistance from the maintainer's project notes and repository context.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
    </item>
    <item>
      <title>Making a React Video Editor Agent-Friendly with Timeline Markers and MCP</title>
      <dc:creator>MartinDelophy</dc:creator>
      <pubDate>Tue, 08 Sep 2026 06:51:06 +0000</pubDate>
      <link>https://dev.to/martindelophy/making-a-react-video-editor-agent-friendly-with-timeline-markers-and-mcp-k7h</link>
      <guid>https://dev.to/martindelophy/making-a-react-video-editor-agent-friendly-with-timeline-markers-and-mcp-k7h</guid>
      <description>&lt;p&gt;A video editor needs to remember more than cuts. It also needs to remember why a cut matters.&lt;/p&gt;

&lt;p&gt;“Start the second chapter here.” “Review this section before publishing.” “Leave a little more room at the end.” These instructions become much easier to act on when their timestamps and context live inside the project.&lt;/p&gt;

&lt;p&gt;I recently added persistent timeline markers to Timeline Studio, the open-source browser video editor I maintain, and exposed them through its CLI, MCP adapter, and Agent Skill. The interesting engineering work was making those annotations precise, inspectable, and safe to edit in a batch.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;try the editor in your browser&lt;/a&gt; or &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;browse the source on GitHub&lt;/a&gt;. The implementation discussed here is included in Skill v1.0.7. Timeline Studio uses React and Vite and follows a local-first approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give annotations their own data model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The editor supports four annotation types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Timing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;marker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A beat cue, action, or cut reference&lt;/td&gt;
&lt;td&gt;&lt;code&gt;time&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chapter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The beginning of a named section&lt;/td&gt;
&lt;td&gt;&lt;code&gt;time&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;range&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An interval to review or work on&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;time&lt;/code&gt;, &lt;code&gt;endTime&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;note&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Feedback tied to a precise moment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;time&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A review range looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product-review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"endTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Product reveal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check whether the caption appears with the product."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"violet"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Times are absolute project seconds. Titles and notes preserve Unicode, and annotations are serialized in &lt;code&gt;timelineMarkers&lt;/code&gt; inside the portable &lt;code&gt;.timeline&lt;/code&gt; project.&lt;/p&gt;

&lt;p&gt;One boundary is especially useful: &lt;strong&gt;annotations do not contribute to rendered media duration&lt;/strong&gt;. Adding a planning marker at 90 seconds to a 60-second edit must not produce another 30 seconds of video.&lt;/p&gt;

&lt;p&gt;Chapter markers also do not automatically become on-screen titles or MP4 chapters. They remain editable project annotations. A marker-only task can therefore finish by writing a new project without re-encoding unchanged media.&lt;/p&gt;

&lt;p&gt;The tradeoff is explicit timing: annotations stay at their absolute project positions during trims, reordering, and ripple edits. If they need to follow moved content, the edit plan must update them too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the React interaction compact&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An always-visible annotation lane takes space away from media tracks. The default view now merges compact flags into the ruler. A toolbar chevron expands a separate lane with titles and range spans when more detail is useful.&lt;/p&gt;

&lt;p&gt;Pressing &lt;code&gt;M&lt;/code&gt; adds a marker at the playhead; &lt;code&gt;Shift + M&lt;/code&gt; opens the manager. The marker UI is localized in all 13 supported interface languages.&lt;/p&gt;

&lt;p&gt;During a drag, React holds a temporary preview. Releasing the pointer commits the edit; Escape or a canceled gesture discards the preview. This separates continuous pointer feedback from the final project mutation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure snapping in pixels, then convert to time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A fixed temporal threshold such as 0.2 seconds feels very different at different zoom levels. The implementation converts a 10-pixel tolerance into project seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thresholdSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;railWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;timelineDuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;railWidth&lt;/code&gt; is the timeline rail width and &lt;code&gt;timelineDuration&lt;/code&gt; is its corresponding time span. This keeps the screen-space snapping distance approximately consistent as the timeline zoom changes.&lt;/p&gt;

&lt;p&gt;Markers reuse the shared snapping targets: the playhead, clip boundaries, and other marker edges. The editor shows a common alignment guide, and holding Alt temporarily bypasses snapping.&lt;/p&gt;

&lt;p&gt;Ranges need an additional invariant: moving a range must preserve its length. If the end of a three-second range snaps to 12 seconds, its start must become 9 seconds.&lt;/p&gt;

&lt;p&gt;The core calculation, simplified, is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;movingEdge&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;targetTime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextEnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextStart&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual implementation checks both edges and chooses the nearer valid snap candidate. Resizing an edge is handled separately from moving the whole range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expose one command engine through CLI and MCP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent-facing architecture is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Skill: workflow, timing evidence, verification
                         |
CLI / MCP: structured operation entry points
                         |
Shared command engine: validation, edits, semantic diff
                         |
A new .timeline project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP adapter invokes the existing CLI runner. It does not maintain its own marker reducers. Fixing a validation rule therefore fixes both entry points.&lt;/p&gt;

&lt;p&gt;From the repository root, an agent can inspect the project and its annotations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.inspect /projects/input.timeline
npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; marker.inspect /projects/input.timeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MCP exposes the read-only &lt;code&gt;timeline_marker_inspect&lt;/code&gt; tool. Writes use &lt;code&gt;marker.add&lt;/code&gt;, &lt;code&gt;marker.update&lt;/code&gt;, and &lt;code&gt;marker.delete&lt;/code&gt; through the existing project diff/apply workflow.&lt;/p&gt;

&lt;p&gt;For example, suppose inspection reports revision &lt;code&gt;0&lt;/code&gt; and a &lt;code&gt;product-review&lt;/code&gt; range spanning 5–8 seconds. This plan moves it to 9 seconds and adds a note at 11 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects/input.timeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"baseRevision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"move-product-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"marker.update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"markerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product-review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add-ending-note-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"marker.add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"markerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ending-note"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"markerType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ending rhythm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reviewer request: leave a little more breathing room."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rose"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/projects/output-marked.timeline"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The paths, revision, IDs, and review request are illustrative. A real plan must use inspected values and actual feedback.&lt;/p&gt;

&lt;p&gt;Two distinctions matter: &lt;code&gt;id&lt;/code&gt; identifies an operation for retry handling, while &lt;code&gt;markerId&lt;/code&gt; identifies the annotation. Likewise, &lt;code&gt;type&lt;/code&gt; selects the command, while &lt;code&gt;markerType&lt;/code&gt; selects the annotation kind.&lt;/p&gt;

&lt;p&gt;Updating only the time of a range preserves its length. The example moves 5–8 seconds to 9–12 seconds. An explicit &lt;code&gt;endTime&lt;/code&gt; lets the plan change the end instead.&lt;/p&gt;

&lt;p&gt;Save the plan as &lt;code&gt;/projects/markers-plan.json&lt;/code&gt;, validate its structure, and preview the semantic diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node skills/edit-timeline-studio/scripts/validate_edit_plan.mjs /projects/markers-plan.json
npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.diff /projects/markers-plan.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reviewing the result, apply the same plan and inspect the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; project.run /projects/markers-plan.json
npm run agent &lt;span class="nt"&gt;--&lt;/span&gt; marker.inspect /projects/output-marked.timeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff reports additions, removals, and modifications under &lt;code&gt;changes.markers&lt;/code&gt;, including before/after values. A marker-only edit should not unexpectedly modify a media track.&lt;/p&gt;

&lt;p&gt;The Skill instructs the agent to inspect, review the diff, apply, and inspect again. The engine enforces concrete constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mismatched revision returns &lt;code&gt;REVISION_CONFLICT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Previously applied operation IDs are not applied again.&lt;/li&gt;
&lt;li&gt;A failed operation rejects the batch without writing a partially edited archive.&lt;/li&gt;
&lt;li&gt;Output must go to a new path; input files and existing outputs cannot be overwritten.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A revision conflict requires a fresh inspection and a reconsidered plan. Merely replacing the revision number would skip the reason for having that check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tricky bugs were identity and floating-point boundaries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider imported annotations with three identical IDs: &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, and &lt;code&gt;x&lt;/code&gt;. Inspection normalizes them to &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;x-2&lt;/code&gt;, and &lt;code&gt;x-3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If normalization runs after every individual operation, deleting the first annotation can change the identities of the remaining entries. A subsequent update to &lt;code&gt;x-2&lt;/code&gt; may then hit the wrong annotation.&lt;/p&gt;

&lt;p&gt;The fix was to normalize once on the transaction copy before marker writes, then resolve every operation against those fixed IDs. Duplicate-ID suffixes also reserve space within the 160-character limit so generated IDs remain valid.&lt;/p&gt;

&lt;p&gt;The second bug involved a one-millisecond range near 1,000 seconds. Subtracting its endpoints can produce a value slightly below &lt;code&gt;0.001&lt;/code&gt;. A strict comparison can reject a valid move toward zero. The implementation now accounts for floating-point error at the minimum range boundary while continuing to reject genuinely invalid intervals.&lt;/p&gt;

&lt;p&gt;Both bugs were easy to miss when testing a single ordinary drag. Batch operations and imported data made them visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An agent still needs evidence for its timestamps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UI snapping is a pointer interaction. CLI and MCP commands take exact seconds; they do not apply pixel-distance snapping.&lt;/p&gt;

&lt;p&gt;For an event in source footage at constant playback speed, the mapping is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;projectTime = clipStart
            + (sourceTime - sourceStart) / playbackRate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a speed curve, the corresponding source-time mapping is required. Dividing by an average speed can place the cue incorrectly.&lt;/p&gt;

&lt;p&gt;Music cues have a similar boundary. The marker commands store annotations; they do not perform automatic beat detection. An agent needs supplied cue times or a verified analysis before writing a beat grid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What was verified&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The checks covered real CLI and MCP calls, plus an independent agent following the Skill to move a range from 5–8 to 9–12 seconds and add a Unicode note at 11 seconds.&lt;/p&gt;

&lt;p&gt;Verification checked the range length, unrelated annotations, the original archive hash, embedded media bytes, media duration, and the unchanged rendering plan for annotation-only edits. Skill validation, type checking, the production build, and GitHub CI passed. Existing lint warnings remain.&lt;/p&gt;

&lt;p&gt;The result is a project that can carry editorial intent between people and agents. A human can leave a review note, an agent can act on its exact location, and the next person can inspect the resulting project and its annotations.&lt;/p&gt;

&lt;p&gt;If you want to try the flags and range snapping, open the &lt;a href="https://video-editor.ai-creator.top/" rel="noopener noreferrer"&gt;Timeline Studio editor&lt;/a&gt;. If you are building a React editor or exposing a creative tool through MCP, the &lt;a href="https://github.com/MartinDelophy/ai-video-editor" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; and &lt;a href="https://github.com/MartinDelophy/ai-video-editor/blob/v1.0.7/skills/edit-timeline-studio/references/timeline-markers.md" rel="noopener noreferrer"&gt;marker workflow reference&lt;/a&gt; contain the implementation and command contract. Stars help you follow updates; reproducible issues and design feedback are welcome.&lt;/p&gt;

&lt;p&gt;To install Skill v1.0.7 for Codex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh skill &lt;span class="nb"&gt;install &lt;/span&gt;MartinDelophy/ai-video-editor edit-timeline-studio &lt;span class="nt"&gt;--pin&lt;/span&gt; v1.0.7 &lt;span class="nt"&gt;--agent&lt;/span&gt; codex &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Skill installs the workflow. Running local commands still requires a Timeline Studio checkout with its Node dependencies, and the MCP adapter should run from that checkout. See the &lt;a href="https://github.com/MartinDelophy/ai-video-editor/releases/tag/v1.0.7" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; for the published version.&lt;/p&gt;

</description>
      <category>react</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
