One JSON document describes one edit: the media it uses, the clips on each track, captions, titles, cross dissolves and markers. CutConvert turns it into a project that opens in CapCut, Premiere Pro, DaVinci Resolve or Final Cut Pro, with captions and titles as text you can edit there.
The JSON Schema is schema.json. A test keeps this page, that schema and the validator in step: every field below is in both, with the type, whether it is required and the rules given here, and nothing else is.
Rules that hold everywhere
- Times are seconds, as decimals, from 0 to 1,000,000, and no clip ends later. Each time is moved to the nearest frame of the timeline, half a frame at most. A clip's out point follows from its in point and its length in frames, so it carries both their roundings. A file ends with its last whole frame: a file with a picture, with its own last frame at its own rate, even when its
durationruns on with its sound. When a clip would run past that end, its in point moves back, and when the in point is already at 0, the clip ends early by whole frames, leaving a gap before the next. That happens to a clip used to the end of its file above 100%, where each frame of the clip takes several of the file's, and also at 100% when the file's rate isn't the timeline's (2 s of a 25 fps file on a 29.97 timeline is 59.94 frames, and the clip ends on frame 59). A clip with a start, end, in or out point that moves by more than half a frame, or that ends early, gets oneedit-time-snappedwarning saying what moved and why. Anything that would last less than a frame is an error, dissolves and fades included. - Positions are fractions of the frame, from −10 to 10.
0.5, 0.5is the centre,xruns right andyruns down. A clip's position is where the centre of its picture lands. - Font sizes and stroke widths are pixels on the timeline's canvas.
- Volume is in dB. 0 leaves a clip as it is. −96 or lower is silent.
- Names, paths and fonts are one line. Caption and title text may hold line breaks (
\n) and tabs. No other control character is allowed anywhere, U+007F to U+009F included. - Lengths count characters, as JSON Schema does: an emoji is one character, though it takes two UTF-16 units.
- Unknown fields are errors. A misspelt field fails validation rather than being ignored.
- A document places up to 20,000 clips, captions and titles in all, a video clip whose file has sound counting twice, for its sound.
- A document that fails validation fails with every problem listed, each with its path, such as
video[0].clips[2].out: must be after in (6 s). One error lists the first 50, then how many more. Anything of yours a message or anedit-warning repeats, such as an unknown field, a media id, a file name or a track name, is quoted as JSON and cut to about 60 characters, with invisible characters (U+0085, bidi controls, zero-width spaces) written as\uXXXX. The editors' own warnings repeat your names as they are, with invisible characters written the same way. - Version 1 only grows. Later versions add optional fields. No field is renamed or removed, and a version 1 document stays valid.
The document
{
"cutconvert": "edit/1",
"name": "Episode 41 clip 3",
"timeline": { "fps": 29.97, "width": 1080, "height": 1920, "startTimecode": "00:00:00:00" },
"media": [
{ "id": "src", "name": "episode-41.mp4", "path": "/Users/sam/Movies/episode-41.mp4",
"duration": 3612.4, "fps": 29.97, "width": 1920, "height": 1080,
"audio": { "streams": [2], "sampleRate": 48000 } },
{ "id": "bed", "name": "bed.mp3", "duration": 184.0, "audio": { "streams": [2], "sampleRate": 44100 } }
],
"video": [
{ "name": "V1", "clips": [
{ "media": "src", "start": 0, "in": 812.4, "out": 847.1,
"position": { "x": 0.5, "y": 0.5, "keyframes": [ { "t": 0, "x": 0.44, "y": 0.5 }, { "t": 12.5, "x": 0.56, "y": 0.5 } ] },
"scale": 178, "volume": 0 },
{ "media": "src", "start": 34.7, "in": 1201.0, "out": 1211.0, "scale": 178 }
] }
],
"audio": [
{ "name": "A2", "clips": [ { "media": "bed", "start": 0, "in": 0, "out": 44.7, "volume": -18, "fadeIn": 1.0, "fadeOut": 2.0 } ] }
],
"captions": {
"style": { "font": "Inter", "fontSize": 72, "color": "#ffffff", "bold": true, "y": 0.78,
"stroke": { "color": "#000000", "opacity": 1, "width": 6 },
"background": { "color": "#000000", "opacity": 0.6 } },
"items": [ { "start": 0.5, "end": 1.8, "text": "nobody tells you this part" } ]
},
"titles": [
{ "start": 0, "end": 3, "text": "Part 3", "font": "Inter", "fontSize": 120, "color": "#ffdd00", "x": 0.5, "y": 0.2 }
],
"transitions": [ { "track": "V1", "at": 34.7, "kind": "crossDissolve", "duration": 0.5 } ],
"markers": [ { "t": 12.0, "name": "hook" } ]
}Fields
The table lists every field. A path ending in [] is an array, and the rows under it describe each item. Rules are what schema.json checks on the field alone: a range of numbers ("16 to 8,192", "above 0", "at most 24"), a length ("1 to 200 characters", "at most 10,000 items"), the values allowed ("one of: …") and a pattern in italics, listed after the tables. Rules the schema can't state are in Meaning, and CutConvert checks them too: those that need other fields or the timeline's frames, and a few on one field, such as the eight timeline rates and no more than 64 channels in all.
| Field | Type | Required | Rules | Meaning |
|---|---|---|---|---|
cutconvert | string | yes | exactly "edit/1" | The format and version. |
name | string | yes | 1 to 200 characters; one line | The project and timeline name. |
timeline | object | yes | The timeline's frame rate, size and start. | |
timeline.fps | number | yes | above 0 | One of 23.976, 24, 25, 29.97, 30, 50, 59.94 or 60. 23.976, 29.97 and 59.94 are the exact NTSC rates (24000/1001 and so on). |
timeline.width | integer | yes | 16 to 8,192 | Canvas width in pixels. |
timeline.height | integer | yes | 16 to 8,192 | Canvas height in pixels. |
timeline.startTimecode | string | no | timecode | Drop frame (;) only at 29.97 or 59.94, and a frame the rate has. Defaults to 00:00:00:00. |
media[] | array | yes | 1 to 1,000 items | The files the edit uses. |
media[].id | string | yes | id | Your id for the file, unique in the document. |
media[].name | string | yes | 1 to 200 characters; one line | The file name, such as episode-41.mp4. The editors look for this name when they relink. |
media[].path | string | no | at most 4,096 characters; absolute path | The file's path on the editing computer, such as /Users/sam/Movies/episode-41.mp4, D:\Footage\episode-41.mp4 or \\server\share\episode-41.mp4. Leave it out and each editor asks for the file by name. |
media[].duration | number | yes | above 0, up to 1,000,000 | The file's length in seconds. |
media[].fps | number | for video | 1 to 1,000 | The file's frame rate. 23.976, 23.98, 29.97 and 59.94 are the exact NTSC rates. Give it, with width and height, for any file with a picture. |
media[].width | integer | for video | 1 to 16,384 | The picture's width in pixels. |
media[].height | integer | for video | 1 to 16,384 | The picture's height in pixels. |
media[].audio | object | for sound | The file's sound. Leave it out only when the file has no sound. A file has a picture, sound or both. | |
media[].audio.streams | integer[] | yes | 1 to 16 items; each 1 to 64 | Channels per sound stream, and no more than 64 channels in all. [2] is one stereo stream. |
media[].audio.sampleRate | integer | yes | 8,000 to 192,000 | Samples per second. |
video[] | array | no | at most 100 items | Video tracks, bottom first. |
video[].name | string | no | 1 to 200 characters; one line | The track's name, which transitions[].track uses; of the four editors only DaVinci Resolve shows it. Defaults to V1, V2 and so on. Unique among video tracks. |
video[].clips[] | array | yes | at most 10,000 items | The track's clips. They may leave gaps but must not overlap. |
video[].clips[].media | string | yes | The id of a media file with a picture. If the file has sound, the sound comes too, on an audio track of its own. | |
video[].clips[].start | number | yes | 0 to 1,000,000 | Where the clip starts on the timeline, in seconds. |
video[].clips[].in | number | yes | 0 to 1,000,000 | Where the clip starts in its file, in seconds. |
video[].clips[].out | number | yes | 0 to 1,000,000 | Where the clip ends in its file, in seconds. After in, and no later than the file's duration. |
video[].clips[].position | object | no | Where the picture's centre sits. Leave it out to centre the picture. | |
video[].clips[].position.x | number | yes | −10 to 10 | Fraction of the frame width. When there are keyframes, they set the position instead. |
video[].clips[].position.y | number | yes | −10 to 10 | Fraction of the frame height. |
video[].clips[].position.keyframes[] | array | no | at most 1,000 items | Movement over the clip, in time order. Between keyframes the position moves in a straight line. |
video[].clips[].position.keyframes[].t | number | yes | 0 to 1,000,000 | Seconds from the clip's start on the timeline, no later than the clip's length. |
video[].clips[].position.keyframes[].x | number | yes | −10 to 10 | Fraction of the frame width. |
video[].clips[].position.keyframes[].y | number | yes | −10 to 10 | Fraction of the frame height. |
video[].clips[].scale | number | no | above 0, up to 10,000 | Size as a percentage of the file's own pixels: 100 shows a 1920×1080 picture at 1920×1080. Leave it out to fit the picture inside the frame. |
video[].clips[].volume | number | no | at most 24 | The clip's sound level in dB. Defaults to 0. |
video[].clips[].speed | number | no | 10 to 10,000 | Constant speed as a percentage: 200 plays twice as fast and 50 at half speed. Defaults to 100. |
audio[] | array | no | at most 100 items | Audio tracks, after the tracks that carry the video clips' sound. |
audio[].name | string | no | 1 to 200 characters; one line | The track's name, which transitions[].track uses; of the four editors only DaVinci Resolve shows it. Defaults to its place among all the audio tracks, counting the video clips' sound first: after one track of it, audio[0] is A2. Unique among audio tracks, the tracks of the video clips' sound (A1…) included. |
audio[].clips[] | array | yes | at most 10,000 items | The track's clips. They may leave gaps but must not overlap. |
audio[].clips[].media | string | yes | The id of a media file with sound. | |
audio[].clips[].start | number | yes | 0 to 1,000,000 | Where the clip starts on the timeline, in seconds. |
audio[].clips[].in | number | yes | 0 to 1,000,000 | Where the clip starts in its file, in seconds. |
audio[].clips[].out | number | yes | 0 to 1,000,000 | Where the clip ends in its file, in seconds. After in, and no later than the file's duration. |
audio[].clips[].volume | number | no | at most 24 | The clip's level in dB. Defaults to 0. |
audio[].clips[].fadeIn | number | no | 0 to 1,000,000 | A fade up from silence at the clip's start, in seconds. Not where a cross dissolve starts the clip. |
audio[].clips[].fadeOut | number | no | 0 to 1,000,000 | A fade down to silence at the clip's end, in seconds. fadeIn and fadeOut together are no longer than the clip. Not where a cross dissolve ends the clip. |
audio[].clips[].speed | number | no | 10 to 10,000 | Constant speed as a percentage. Defaults to 100. |
captions | object | no | Captions, written as editable text. | |
captions.style | style | no | The look every caption shares. | |
captions.items[] | array | yes | at most 20,000 items | The captions, in time order, not overlapping. |
captions.items[].start | number | yes | 0 to 1,000,000 | When the caption appears, in seconds. |
captions.items[].end | number | yes | 0 to 1,000,000 | When it goes, in seconds. |
captions.items[].text | string | yes | 1 to 2,000 characters; text | The words. \n starts a new line. |
captions.items[].style | style | no | Changes to the shared style for this caption only. Each field given replaces the shared one; stroke and background change field by field. | |
titles[] | array | no | at most 1,000 items | Text on screen, such as a title card. Titles may overlap. |
titles[].start | number | yes | 0 to 1,000,000 | When the title appears, in seconds. |
titles[].end | number | yes | 0 to 1,000,000 | When it goes, in seconds. |
titles[].text | string | yes | 1 to 2,000 characters; text | The words. \n starts a new line. |
titles[].font … titles[].background | style fields | no | The title's look, using the style fields below. | |
transitions[] | array | no | at most 10,000 items | Cross dissolves between clips that meet. |
transitions[].track | string | yes | 1 to 200 characters; one line | The name of the video or audio track. A name that a video track and an audio track share is an error here: rename one. A track of the video clips' sound (A1…) can't be named: a dissolve on its video track crossfades that sound. |
transitions[].at | number | yes | 0 to 1,000,000 | The time where one clip ends and the next starts, in seconds. One transition per cut. |
transitions[].kind | string | yes | one of: "crossDissolve" | The kind of transition. |
transitions[].duration | number | yes | above 0, up to 1,000,000 | The dissolve's length in seconds. |
markers[] | array | no | at most 10,000 items | Timeline markers. |
markers[].t | number | yes | 0 to 1,000,000 | The marker's time in seconds. |
markers[].name | string | yes | at most 200 characters; one line | The marker's name. |
Style fields
captions.style, captions.items[].style and each title use these fields. All are optional.
| Field | Type | Rules | Meaning |
|---|---|---|---|
font | string | 1 to 200 characters; one line | The font family, such as Inter. The editing computer needs the font installed. Left out, each editor uses its own: Helvetica in Premiere Pro and Final Cut Pro, Arial in DaVinci Resolve, CapCut's default in CapCut. |
fontSize | number | above 0, up to 1,000 | Pixels on the timeline's canvas, and no more than 4 × the canvas width or about 9.26 × its height (10,000 on Final Cut's 1080-line title canvas). Defaults to 1/20 of the canvas's shorter side: 54 on 1920×1080 or 1080×1920. |
color | string | #rrggbb | Defaults to white. |
bold | boolean | Bold text. | |
x | number | −10 to 10 | Where the text's centre sits, as a fraction of the frame width. Defaults to 0.5. |
y | number | −10 to 10 | Where the text's centre sits, as a fraction of the frame height. Captions default to 0.85, near the bottom, and titles to 0.5. |
stroke | object | An outline round the letters. | |
stroke.color | string | #rrggbb | Defaults to black. |
stroke.opacity | number | 0 to 1 | Defaults to 1. |
stroke.width | number | 0 to 1,000 | Pixels on the timeline's canvas. Defaults to 4. |
background | object | A box behind the text. | |
background.color | string | #rrggbb | Defaults to black. |
background.opacity | number | 0 to 1 | Defaults to 1. |
Patterns
Each pattern in the Rules column, with the regular expression edit-schema.json gives it. A string that also holds half of a surrogate pair on its own is refused, which no pattern can say.
- one line: no line break or other control character.
^[^\u0000-\u001f\u007f-\u009f]*$ - text: line breaks and tabs, but no other control character.
^[^\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]*$ - id: 1 to 64 letters, digits,
_,.or-.^[A-Za-z0-9_.-]{1,64}$ - timecode:
HH:MM:SS:FF, orHH:MM:SS;FFfor drop frame, at a time of day: hours up to 23, minutes and seconds up to 59.^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d[:;]\d\d$ - absolute path: one line that starts with
/, a drive such asD:\orD:/, or\\server\.^(?:\/|[A-Za-z]:[\\/]|\\\\[^\\/\u0000-\u001f\u007f-\u009f]+[\\/])[^\u0000-\u001f\u007f-\u009f]*$ - #rrggbb: a colour as six hex digits, such as
#ffdd00.^#[0-9a-fA-F]{6}$
How the document becomes a project
- Tracks. Video tracks stack in order,
video[0]at the bottom. Titles go on tracks above them and captions on one track at the top. The sound of the video clips comes first among the audio tracks, thenaudio[]in order. - A video clip brings its sound. When its media has
audio, the clip's sound is placed under it at the same times, from the same file, at the clip'svolume. Whether the two come in linked depends on the editor (see the table below). To keep the picture without the sound, setvolumeto −96: the sound stays in the project, silent. Premiere Pro and CapCut bring it in at their lowest level, ready to raise; Final Cut Pro and DaVinci Resolve bring it in as a disabled clip, to enable and then raise. - Cross dissolves. A dissolve sits across the cut at
at, half before and half after (an odd frame goes to the side with room). It needs spare footage on both sides: the outgoing clip's file must run on past itsout, and the incoming clip's file must start before itsin. When one side has too little for its half and the other has room for the whole dissolve, it moves wholly to that side of the cut (edit-transition-moved). Otherwise it is shortened to the longest that fits, centred or to one side (edit-transition-shortened), or left out when there is no spare footage at all (edit-transition-dropped). A dissolve also stays inside its two clips, clear of a fade or dissolve at their other ends. A dissolve on a video track also crossfades the clips' sound, when both have sound. - Fades on audio clips become each editor's own fade, not keyframes.
- Speed changes the clip's length on the timeline: a clip from 10 s to 20 s in its file at 200% lasts 5 s.
- Position keyframes count from the clip's first frame on the timeline, whatever its speed.
What each editor gets
Version 1 asks for nothing the four editors can't open, but a few things change on the way. This table lists what each editor does; where what is lost depends on the document, such as a start timecode CapCut can't hold or a dissolve without spare footage, it also comes back as a warning, with a code and a sentence.
| CapCut | Premiere Pro | DaVinci Resolve | Final Cut Pro | |
|---|---|---|---|---|
| Captions | CapCut captions on a caption track, each in its own look | Text generator clips on the top track | Text+ clips on the top track | Basic Titles on the top lane |
| Titles | Text | Text generator clips | Text+ | Basic Titles |
| Stroke and box | Both | Neither | Both | Stroke, not the box |
| Bold | Carried | Not carried | Carried | Carried |
| Audio fades | The clip's fade in and fade out | Cross Fade (+3dB) at the clip's edge | An edge fade; where another clip meets the faded edge, the clip's volume | The clip's own fade handles |
| Sound under a cross dissolve | The two sounds overlap, each fading: CapCut has no crossfade | Cross Fade (+3dB) | Cross Fade +3 dB | Audio Crossfade when centred with footage on both sides; otherwise each clip fades on its own side |
Media with no path | The file by name: CapCut's Link media window lists it on opening | Offline, by name | Offline, by name | Offline, in /CutConvert Offline Media; File > Relink Files finds it |
| Picture and sound | One clip that plays its own sound, except at a cross dissolve or for media with no path, where the sound is a separate clip on an audio track | Linked clips; stereo sound is two mono clips | Separate clips at the same times | Separate connected clips, the picture above the storyline and the sound below it, not linked |
| Sound at −96 dB or lower | Volume 0 | Level 0 (−∞ dB) | A disabled clip | A disabled clip at −96 dB |
| Track names | Not shown: CapCut's tracks have no names | Not carried: tracks are numbered V1… and A1… | Kept | Not carried: Final Cut has lanes, not tracks |
| Start timecode | Not carried: CapCut timelines start at 00:00:00:00 (edit-capcut-start-timecode); drop-frame display is kept | Carried | Carried | Carried |
Each editor has a page with what it showed when the test documents were opened in it: CapCut, Premiere Pro, DaVinci Resolve and Final Cut Pro.
Not in version 1
Variable speed, nested timelines, transitions other than cross dissolve, effects, colour, multicam, still images, scale or opacity keyframes, marker colours, and trimming media on the server.