• English
  • Edit JSON

    preview and apply take one JSON object with exactly one key: range (cut or restore speech), speed (play parts faster or slower), track (tracks and clips), placement (camera and screen layout), canvas (the size of the video frame), offset (the sync of one recording to the reference audio) or gain (the volume of an inserted video). Unknown keys are refused with invalid_edit.

    Always preview first. It uses the same checks as apply and writes nothing.

    Range edits: cut and restore

    Cut the lunch sentence, using output times from cutzal transcript:

    { "range": { "action": "cut", "coordinate": "output", "ranges": [{ "start": 2.84, "end": 4.84 }] } }

    Bring a cut part back. Restores must use source time, because cut parts have no output time:

    { "range": { "action": "restore", "coordinate": "source", "ranges": [{ "start": 4.5, "end": 6.5 }] } }
    • action: cut or restore.
    • coordinate: output or source (see coordinates.md).
    • ranges: 1 to 1000 items of { "start", "end" } in seconds, start at least 0 and less than end.
    • Put the edges on word boundaries from transcript (a word's start and end). An edge inside a word can keep part of it (partial: true in transcript).
    • One apply is one checkpoint. Put all ranges of one decision (for example every filler word) in one edit.
    • Optional rippleTrackIds on cut or restore lists the overlay visual tracks that should move with the edit. Use IDs from context.tracks; duplicates, audio/source tracks, and this field on swap are refused. Without it, output-anchored clips keep their existing times.
    { "range": { "action": "cut", "coordinate": "output", "ranges": [{ "start": 0.3, "end": 0.5 }], "rippleTrackIds": ["graphics"] } }

    The participating clips follow the actual change in rendered output, including padding and existing cuts. Internal cuts split video graphics and advance sourceInS so the remaining footage stays synchronized. Content-anchored clips and native camera/audio tracks retain their existing projection rules. Affected locks, missing media, unsupported linked sound-effect trims, or fragments that only hold an exhausted video frame reject the entire cut. Preview shows those conflicts without writing. Undo/rewind restores both the cut and graphics. In the CutZal app, every edit that only shortens or only lengthens the video (keys, timeline, phone script and chat) includes every additional visual track automatically when the project has an output-anchored clip. cutzal commands keep the default above: only the tracks you list move.

    A restore with rippleTrackIds pushes later participating clips back by the actual increase in rendered output, and a clip that runs to the end of the video grows to the new end. This restores the speech that the cut above removed and moves the graphics back with it:

    { "range": { "action": "restore", "coordinate": "source", "ranges": [{ "start": 0.3, "end": 0.5 }], "rippleTrackIds": ["graphics"] } }

    When the restored speech lands inside a clip, an image or text clip continues through it, and a video clip splits there and leaves the restored part empty. A video clip is refilled instead where an earlier ripple cut split that same graphic (restoring the same speech with the same tracks puts its footage back), keeps playing when it runs to the end of the video, and simply grows while it is already holding its last frame. A linked sound effect that ends after the restored spot inside its parent rejects the whole restore.

    An output-anchored clip that runs to the end of the video (a background, watermark, frame or a header text clip) makes a shortening cut without rippleTrackIds fail with track.outOfRange, because the clip would pass the new end. List its track in rippleTrackIds whenever you cut, so the clip shrinks with the video and later clips move back with the speech.

    Speed edits: fast-forward or slow down

    speed plays parts of the video faster or slower. The screen, the camera and clips that follow the speech (content anchors) change speed together, the edited video gets shorter (or longer), and later speech and clips move with it. Use it for a long process that should be seen but not heard in full, such as reading a prompt aloud, typing or waiting for a result. Cut what nobody needs to see.

    Fast-forward the lunch sentence 4 times, using output times from cutzal transcript. Its sound is muted:

    { "speed": { "coordinate": "output", "ranges": [{ "start": 2.4, "end": 4.6 }], "rate": 4 } }

    Speed up the last sentence a little and keep its sound:

    { "speed": { "coordinate": "source", "ranges": [{ "start": 8, "end": 10 }], "rate": 1.5, "audio": "keep" } }

    Bring the last sentence back to its original speed with rate: 1:

    { "speed": { "coordinate": "source", "ranges": [{ "start": 8, "end": 10 }], "rate": 1 } }

    Speed up the whole video a little. It multiplies the speed of every part, so the 4x part above now plays at 4.8x:

    { "speed": { "global": 1.2 } }
    • rate: 0.25 to 16 with up to two decimals. Above 1 is faster, below 1 is slower, and 1 removes the speed from those ranges.
    • coordinate: output or source (see coordinates.md). Output ranges cover only what is in the edited video now. Source ranges also cover parts that are cut, so speech you restore inside them later plays at the same speed.
    • audio: keep (the speech speeds up, pitch preserved) or mute. Without it, 0.5 to 2 keeps the sound and other speeds mute it. keep is allowed from 0.5 to 4. A muted part has no captions. Sound effect clips on audio tracks still play, so add a fast-forward sound effect there if the person wants one.
    • Setting a speed on a range again replaces it only where the ranges overlap. Several ranges in one edit get the same speed and are one checkpoint.
    • global: the speed of the whole video, 0.5 to 2 with up to two decimals. It multiplies every part's speed (1.2 on a 4x part plays it at 4.8x), so parts keep their relative speed. A part whose resulting speed is above 4 or below 0.5 plays without sound. { "speed": { "global": 1 } } brings the whole video back to its original speed. In chat it needs a whole-video request.
    • Clips pinned to output time (anchor.kind: "output") move with the speech around the change. Over a part whose speed changes, an image or text clip gets shorter or longer with it, while a video clip or sound effect keeps its length and plays at normal speed. A clip that ran to the end of the video still runs to the new end. An edit that would move a clip on a locked track, or make a clip pass the new end or overlap another, is refused as a whole with blocked and the clipIds.
    • context.speed lists the current speeds: spans in source time and rate for the whole video. context.output.segments carries rate (and mute when the sound is off) on those parts. transcript and capture already use the new output times.
    • A project with a speed is saved as file version 19. Older CutZal versions refuse to open it instead of playing it at normal speed.

    Track edits

    context returns the track document in result.tracks:

    • tracks: in drawing order, bottom first. A later track is drawn on top of an earlier one.
      • binding.kind: "source": the recording (screen, camera, microphone, system audio).
      • binding.kind: "transcript": the subtitle track.
      • binding.kind: "overlay": tracks for your clips. kind: "video" holds image and video clips, kind: "audio" holds sound effects.
    • clips: image and video clips. audioClips: sound effect clips (only when an audio track exists).

    The accepted track edits are track.add, track.update, track.remove, track.reorder, clip.put, clip.remove and track.batch. Use rewind to undo; there is no undo edit.

    Add a track

    A video track for images and B-roll:

    { "track": { "kind": "track.add", "track": { "id": "graphics", "kind": "video", "name": "Graphics", "locked": false, "visible": true, "binding": { "kind": "overlay" } } } }

    An audio track for sound effects. Audio tracks need "muted" and are always "visible": true:

    { "track": { "kind": "track.add", "track": { "id": "sfx", "kind": "audio", "name": "Sound effects", "locked": false, "visible": true, "muted": false, "binding": { "kind": "overlay" } } } }
    • id: your choice, unique, up to 200 characters. name: what the person sees.
    • Optional "beforeTrackId": "<id>" inserts the track before that one (so it is drawn below it). Without it the track goes last (on top).
    • Images can be registered with asset add before adding a track. Registration preserves the existing tracks and clips; clip.put places the image on a video track.
    • Sound files (asset add of an audio file, sfx use) are refused until the project has an audio track. Add the audio track first.

    Put a clip

    clip.put adds a clip, or replaces the clip with the same id. To change a clip, copy it from context, change fields, and put it again.

    An image over the whole frame from 1 s to 2 s of the output:

    { "track": { "kind": "clip.put", "clip": { "id": "title-card", "trackId": "graphics", "assetId": "title", "sourceInS": 0, "anchor": { "kind": "output", "startS": 1, "endS": 2 }, "placement": "full", "endBehavior": "hold" } } }

    B-roll in a box at the top right from 3 s to 4.5 s, starting 2 s into the B-roll file:

    { "track": { "kind": "clip.put", "clip": { "id": "broll-1", "trackId": "graphics", "assetId": "city", "sourceInS": 2, "anchor": { "kind": "output", "startS": 3, "endS": 4.5 }, "placement": "frame", "frame": { "pos": "tr", "sizePct": 0.35, "radiusPx": 12, "aspect": "media", "mirror": false }, "endBehavior": "return" } } }

    A sound effect at 1 s, 9 dB quieter than the file:

    { "track": { "kind": "clip.put", "clip": { "id": "ding-1", "trackId": "sfx", "assetId": "ding", "sourceInS": 0, "gainDb": -9, "anchor": { "kind": "output", "startS": 1, "endS": 1.6 } } } }

    Image and video clip fields:

    • id: your choice, unique across all clips, up to 200 characters.
    • trackId: an overlay video track. assetId: an image or video asset from context.assets or asset add.
    • sourceInS: where in the file to start, in seconds. Always 0 for images.
    • anchor: { "kind": "output", "startS", "endS" } pins the clip to output time for ordinary range edits. Explicit cuts with rippleTrackIds (and cuts made in the CutZal app) update participating clips in that transaction. Opening a project never repositions them. Clips the person placed in CutZal may have { "kind": "content", ... } anchors that follow the speech; keep those as they are when you put such a clip again.
    • placement: full (fills the frame), screen (where the screen recording sits in the layout) or frame (a box set by frame).
    • frame (needed for placement: "frame"): pos is a 9-grid spot (tl, tc, tr, ml, mc, mr, bl, bc, br) or { "x", "y" } from 0 to 1 measured from the top left. sizePct is the box width as a fraction of the frame width (0.35 is 35%). radiusPx rounds the corners. aspect is media (the file's own shape) or 1:1, 4:3, 3:4, 16:9. mirror flips it horizontally. focus (optional) picks which part of the file shows when the box crops it (coordinates.md).
    • endBehavior: what happens when the file ends before endS. return goes back to what was showing before; hold keeps its last frame until endS.

    Sound effect clip fields: id, trackId (an overlay audio track), assetId (an audio asset), sourceInS, gainDb (from -40 to 12, one decimal place) and anchor as above.

    Other track edits

    { "track": { "kind": "clip.remove", "clipId": "title-card" } }
    { "track": { "kind": "track.update", "trackId": "sfx", "changes": { "muted": true } } }
    • track.update changes takes at least one of name, locked, visible, muted (audio tracks, and the main voice track: the microphone, or the main video when there is no microphone), solo (the same tracks: when any track is soloed, only soloed tracks play, in previews and exports), gainDb (system audio tracks), denoise and denoiseMix (the microphone track). Muting the voice or soloing another track exports the video with no speech.
    • track.remove ({ "kind": "track.remove", "trackId": "<id>" }) removes an overlay track that has no clips left.
    • track.reorder ({ "kind": "track.reorder", "trackIds": [...] }) takes every track id in the new order, bottom first. The recording tracks (binding.kind: "source") keep their order among themselves, so the camera stays above the screen. Overlay tracks can go anywhere, for example between the screen and the camera so the camera stays on top of a B-roll clip.

    Background noise removal

    Turn on denoise on the microphone recording track (binding.sourceId is the microphone source, usually source:microphone) to remove background noise such as fans and air conditioners from the voice:

    { "track": { "kind": "track.update", "trackId": "source:microphone", "changes": { "denoise": true } } }
    • It applies only while the microphone is the master audio. false turns it off. Sound effects and system audio are never processed.
    • The app processes the whole microphone recording on this computer in the background (a long recording can take a few minutes, no cost). Preview plays the original sound until it is ready, and export waits for it. Transcripts and AI cut decisions keep using the original sound.

    denoiseMix sets the strength, an integer from 1 to 10 (like Premiere's Mix Amount). 10 is the default and uses only the cleaned voice. Lower values blend in more of the original sound, which keeps a voice natural when the cleaned result sounds over-processed:

    { "track": { "kind": "track.update", "trackId": "source:microphone", "changes": { "denoiseMix": 7 } } }
    • Changing the strength does not process the recording again. Turning denoise off keeps the strength for the next time it is turned on. 10 removes the field.

    Several changes at once

    track.batch applies up to 100 track edits together as one checkpoint. Batches cannot be nested.

    { "track": { "kind": "track.batch", "edits": [
      { "kind": "clip.put", "clip": { "id": "ding-2", "trackId": "sfx", "assetId": "ding", "sourceInS": 0, "gainDb": -9, "anchor": { "kind": "output", "startS": 5, "endS": 5.6 } } },
      { "kind": "clip.put", "clip": { "id": "ding-3", "trackId": "sfx", "assetId": "ding", "sourceInS": 0, "gainDb": -9, "anchor": { "kind": "output", "startS": 6.5, "endS": 7.1 } } }
    ] } }

    Layout edits: camera and screen

    placement changes how the screen recording and the camera are arranged, like the layout (화면 구성) controls in CutZal. It needs a project with a screen and a camera recording. context.placement names the source tracks that hold the screen and the camera (null when there is none), and the clips on those tracks hold the current layout: placement (full fills the picture, frame uses frame) and frame.

    Show only the camera while the person talks from 2 s to 4 s of the edited video:

    { "placement": { "preset": "cam-full", "coordinate": "output", "ranges": [{ "start": 2, "end": 4 }] } }

    Show the screen with the camera in the top left corner for the whole video:

    { "placement": { "preset": "cam-pip", "corner": "tl" } }

    Make the camera a little bigger everywhere:

    { "placement": { "camera": { "sizePct": 0.3 } } }

    Shrink the screen and round its corners for one part, using source time:

    { "placement": { "screen": { "sizePct": 0.9, "radiusPx": 16 }, "coordinate": "source", "ranges": [{ "start": 5, "end": 7 }] } }

    The face sits left of the middle of the camera recording, so a narrow camera frame shows it off center. Keep the face at the center of the frame for the whole video (focus is the face's position in the recording, before mirroring):

    { "placement": { "camera": { "focus": { "x": 0.42, "y": 0.5 } } } }
    • Exactly one of these:
      • preset: cam-pip (screen with the camera in a corner; corner picks it, default br), split (screen left, camera right), cam-full (camera only) or screen-full (screen only).
      • Presets look at the shape of each recording and of the frame. A recording whose orientation differs from the frame (a vertical screen recording in a horizontal video, a horizontal camera in a vertical short) is shown whole with screen-full, cam-full and the screen part of cam-pip, with the background around it, instead of being cropped. In a horizontal frame with a vertical screen recording, split puts the camera on the left and the whole vertical screen on the right.
      • camera or screen: only the frame fields to change. The other fields keep their value in each part. The fields are in coordinates.md.
    • coordinate and ranges go together, with the same rules as range edits. Leave both out to change the whole video. Parts cut out between your ranges are included, the same as selecting blocks in CutZal.
    • The layout follows the speech: later cuts and restores keep it on the same words.
    • camera.mirror also works where the camera fills the picture (cam-full). Position, size, rounding and aspect changes still skip full-picture camera parts; a full-picture-only range without a mirror change is refused with invalid_edit. A range with no camera is also refused.
    • Switching between cam-pip, split and cam-full preserves each existing clip's mirror setting. Newly created clips without an existing camera segment use mirror: false; screen-full removes the camera from the selected range.
    • focus follows the same rules as the other frame fields and also carries over when cam-pip or split rebuilds the camera frame. A full-picture camera part has no focus, so a part that goes through cam-full comes back centered.
    • When nothing would change, apply returns noop and preview shows changed: false.
    • One edit is one checkpoint. To pick a preset and then change the camera size, send two edits.
    • A clip that fills the frame can carry "conform": "fit" (track edits, placement: "full" only): the whole recording is shown inside the frame instead of filling it and cropping the edges. Clips without conform fill the frame as before.

    Canvas: the size of the video frame

    canvas sets the frame size of the whole video: "1920x1080" (horizontal), "1080x1920" (vertical) or "original" (the size of the screen recording, the default). One video has one frame size; vertical and horizontal recordings are then placed inside it with placement.

    Make a horizontal video from a project whose screen was recorded on a vertical monitor:

    { "canvas": "1920x1080" }
    • Layouts that showed a recording whole keep showing it whole: a full-frame clip that would now be cropped gets conform: "fit", and a box that would now run off the frame shrinks in place. Clips that were already cropped stay as they are, and locked tracks are not changed. apply returns how many clips it changed as fixed.
    • Then pick the layout per part with placement presets, which follow the new frame shape.
    • preview only says whether the size changes (changed). Check the result with capture after apply.
    • One edit is one checkpoint, and rewinding to an earlier checkpoint also restores the frame size. Same size is noop.

    Offset: sync a recording to the reference audio

    offset moves one take of one source in time against the reference audio. sourceTime = masterTime + offsetS, so a positive value shows later parts of that recording at the same moment. Read the current values from context.sources (each source has isMaster and its takes with id, offsetS and syncStatus).

    The camera shows the mouth about 0.3 seconds before the voice is heard, so move the camera 0.3 seconds back from its current value (here from 0.27):

    { "offset": { "sourceId": "camera", "takeId": "camera-1", "offsetS": -0.03 } }
    • offsetS is the new value in seconds, not a change (within ±3600). Compute it from context.sources. Sending the same value again is noop.
    • takeId can be left out when the source has one take. With several takes it is required, and a missing or unknown take is refused with the list of takes.
    • The reference audio source (isMaster: true) always has offset 0 and is refused. Move the other sources instead.
    • The take becomes syncStatus: "manual". The person can undo it in CutZal. It is listed in checkpoints, but rewind does not bring offsets back: to go back, send another offset edit with the previous value. Works before transcription too.
    • preview only says that the value changes. Check the result with capture at a moment where the mouth and the voice are clear.

    Inserted videos

    cutzal insert adds a video with its own sound to the edited video (see commands.md). Its part of source time comes after the end of the recording, and the edited video plays it at the insert point. Cut and restore it like any other part: a range cut with coordinate: "output" over the inserted block takes it out of the edited video. A content clip can be anchored to the inserted part in source time, and it follows that part when the person moves it.

    Gain: the volume of an inserted video

    gain sets the volume of one inserted video in dB. Inserted takes are the ones with inserted: true in context.sources, and their gainDb is the current value (0 is the original volume of the file). It starts at the value CutZal matched to the main audio when the video was inserted.

    The inserted interview plays a little loud next to the talk, so make it 3 dB quieter than its current 0 dB:

    { "gain": { "takeId": "interview", "gainDb": -3 } }
    • gainDb is the new value, not a change: from −40 to +20 dB in steps of 0.1. 0 brings back the original volume. Sending the current value again is noop.
    • Only inserted videos take a volume. Any other take is refused with invalid_edit and the list of inserted takes. To change the loudness of the whole video, use the person's target loudness setting in CutZal instead.
    • The same volume is used by playback in CutZal, by exports, and by the loudness measurement for the target loudness. The person can undo it in CutZal. It is listed in checkpoints. preview only says that the value changes.

    What gets refused

    • Clips on the same track that overlap in output time.
    • Clip changes on a locked track.
    • An asset kind that does not match the track (images and videos go on video tracks, audio on audio tracks).
    • A rewind-like edit (track.restore) or a drag transform (clip.transform): use rewind, or put the clip again.

    When an edit would change clips in a way the person should decide, preview shows status: "blocked" with conflicts, and apply returns the error blocked without applying. Change the edit or ask the person.

    A full example: title card on a new project

    1. cutzal context --project clip.mp4 gives revision: 0 on a project that was just opened.
    2. Add a video track (the track.add above) with apply --revision 0 --id add-graphics. The result has revision: 1.
    3. cutzal asset add --project clip.mp4 title.png registers asset title. The revision is now 2 (run context to see it).
    4. Put the title card (the first clip.put above) with apply --revision 2 --id put-title --label "Title card at 1 s".
    5. cutzal capture --project clip.mp4 --at 1.5 and look at the PNG.

    Captions on video

    Use the existing transcript track and a track.update edit. Captions are off until explicitly enabled. Read the track ID from inspect rather than inventing a second track. The full caption object is required:

    { "track": { "kind": "track.update", "trackId": "transcript", "changes": { "caption": { "enabled": true, "x": 0.5, "y": 0.88, "size": 0.045, "appearance": "outline" } } } }

    x and y are fractions of the canvas (0.05–0.95); size is a fraction of canvas height (0.02–0.09). appearance is outline, box, or plain (text only). Locked transcript tracks reject caption changes. Placement is clamped to keep the text in frame. Both preview and the composed video use these settings; source stems and SRT/VTT files remain separate. Burning captions re-encodes the composed video. No transcription or model call is started by this edit.

    The caption look uses the same words as title text. These fields are optional; omit them to keep the default look (white text, #111111 outline or a black box at 80%):

    • weight: semibold or black. The outline is thicker with black.
    • color: text color, #RRGGBB.
    • edgeColor: outline color, or box color with box, #RRGGBB.
    • boxOpacity: box opacity with box, 0.2–1.
    • shadow: true adds a soft drop shadow.

    The app offers four looks and names them by appearance, weight, and shadow only (colors and opacity can change without changing the look). To match one, send its values:

    Lookappearanceweightshadowcolors
    Outlineoutlinesemiboldfalsecolor #FFFFFF, edgeColor #111111
    Boxboxsemiboldfalsecolor #FFFFFF, edgeColor #000000, boxOpacity 1
    Bold outlineoutlineblackfalsecolor #FFFFFF, edgeColor #000000
    Shadowplainsemiboldtruecolor #FFFFFF

    Lines wrap between words. A word longer than the line, or text without spaces, wraps by character.

    Title text on the video

    On-screen titles (for example the two-line header of a vertical short) are text clips, not images, so the person can edit the words in CutZal. Put one with clip.put on a video overlay track, with title instead of assetId: { "kind": "clip.put", "clip": { "id": "header", "trackId": "<overlay track>", "placement": "full", "anchor": { "kind": "output", "startS": 0, "endS": <end> }, "title": { "text": "first line\nsecond line", "style": { "x": 0.5, "y": 0.24, "size": 0.058, "colors": ["#F3EFE8", "#EC8A62"], "weight": "black", "shadow": true } } } }.

    • text: up to 3 lines (\n), 40 characters per line. colors is one #RRGGBB per line; extra lines reuse the last color. weight is black or semibold.
    • x and y are the center of the text block as fractions of the canvas (0.02–0.98); size is the letter height as a fraction of canvas height (0.02–0.12). A line wider than 88% of the canvas shrinks on its own.
    • Text clips take placement: "full" only, and no assetId, sourceInS, endBehavior, frame or mirror. To change the words, send the same clip again with a new title. Preview, capture and export draw the same image.
    • A project with a text clip is saved as file version 16. Older CutZal versions refuse to open it instead of showing the video without the title.
    • Optional style fields: sizes sets the size of each line (same range as size; lines without a value use size). Wrap words in asterisks, like *word*, to draw them in accent (#RRGGBB, default #FF3B30); the asterisks are not drawn and do not count toward the 40 characters, and they must come in pairs. progress: { "y": <top of the bar as a fraction of canvas height>, "color": "#RRGGBB", "track": "#RRGGBB" } adds a bar that fills from left to right while the text clip is on screen. CutZal's Properties panel sets track to the bar color darkened (each channel x 0.48).
    • Example: a two-line short header (small top line, big bottom line, key words in the accent color, progress bar under the text) on a 1080x1920 canvas is { "x": 0.5, "y": 0.2318, "size": 0.074, "sizes": [0.0344, 0.074], "colors": ["#F3F3EF"], "accent": "#FF3B30", "weight": "black", "shadow": false, "progress": { "y": 0.2943, "color": "#FF3B30", "track": "#7A1C17" } } with text like first line\n*key words* rest.

    Subtitle term fixes

    Transcripts spell product names the way they sound (for example "클라우드 코드"). To show the standard spelling in subtitles, set captionTerms on the transcript track with track.update: { "kind": "track.update", "trackId": "transcript", "changes": { "captionTerms": [{ "from": "클라우드 코드", "to": "Claude Code" }, { "from": "깃허브", "to": "GitHub" }] } }.

    • Both sides need the same number of words. The last word keeps what follows it, so "클라우드 코드에서" becomes "Claude Code에서". Longer terms match first, up to 100 terms.
    • Preview, burned-in captions and SRT/VTT use the same list. Word timings do not change. "captionTerms": null removes the list.