Project File Export

Convert a gtrk v1 timeline structure into project files for multiple non-linear editors (NLE) in a single call: Premiere/FCP7 XML, Final Cut Pro X, OpenTimelineIO, JianYing draft, CapCut draft, and the Tonghe gtrk project file. Ideal for importing an algorithmically arranged video structure straight into an editor for further refinement.

ℹ️ Note: Unlike other endpoints, this one requires no file upload — the request body itself is the timeline structure. The asset paths in materials[].path are local references in the caller's editing environment; the server never reads the asset files, it only converts the structure. materials[].id is the platform-returned file ID (file_id) in cloud scenarios.

Create Task

Basic Information

ItemValue
Request MethodPOST
Request Path/task/video_project_struct
Content-Typeapplication/json
AuthenticationRaw API key in the Authorization header

Request Body

The body is a gtrk v1 timeline structure. All time fields are in seconds.

ParameterTypeRequiredDefaultDescription
versionstringYes-Contract version, fixed at "v1"
video_sizearrayYes-Canvas size [width, height] in pixels, e.g. [1920, 1080]
video_ratenumberYes-Overall frame rate (fps, integer), e.g. 60
durationnumberYes-Total output duration (seconds)
materialsarrayYes-Asset list; each entry has an id plus metadata (see below)
video_trackarrayYes-Video tracks (layered), see below
audio_trackarrayYes-Audio tracks (layered), see below
beat_trackarrayNo[]HTML particle overlay layers. This endpoint ignores beat_track; to render particles use html_animate_render
struct_metaobjectNo{}Contains nle_draft_dir (draft folder path), see below
project_formatsarrayNoallSubset of formats to produce; omit to produce all public formats. The legacy name output_formats is still accepted as an alias

materials[] entry

Each asset is identified by a single id — the platform file_id in cloud scenarios, or a local ID in purely local scenarios. Clips in video_track / audio_track / beat_track reference this id via their material field.

Each entry uses one of two reference forms, and both may be mixed within one request:

  • Local reference: the entry carries a non-empty path (absolute path recommended, always forward slashes /). The id may then be any local identifier — it only serves as the clip reference key and no platform file check is performed. The path is written into the generated project file as-is for your local editing software to load, so make sure it is valid in your environment;
  • Cloud reference: the entry has no path. The id must then be a platform file ID (file_id) of an existing file, otherwise 6004 (file not found) is returned.
FieldTypeRequiredDescription
idstringYesUnique asset identifier; any local ID when path is present, must be a platform file ID (file_id) when it is not; referenced by clips via material
pathstringNoLocal asset path (absolute + forward slashes recommended) for NLE project files / offline ffmpeg; never read by the server; when present, the entry is exempt from the platform file check
durationnumberNoTotal asset duration (seconds)
video_sizearrayNoOriginal asset size [width, height] (video assets)
video_ratenumberNoAsset frame rate (video assets)
audio_channelstringNoChannel layout, "stereo" or "mono" (audio assets)

💡 Tip: Fill in as much metadata per asset as you can, so that a client holding the gtrk can rebuild the project offline with ffmpeg without resolving IDs back through the cloud. A missing field does not break serialization — it is simply omitted.

Track object in video_track[] / audio_track[]

Both video and audio tracks are layered by track: track_index is an explicit integer z-order, and the track with the smallest track_index is the base track (main); the rest are overlays (larger track_index is closer to the front).

FieldTypeRequiredDescription
track_indexnumberYesExplicit layer; the smallest is the base track (main)
track_sizearrayNo(video track only) Original asset size [width, height] of this track, for scaling against the canvas video_size
mutedbooleanNo(video track only) Track-level default mute, can be overridden by a clip-level muted
volumenumberNo(audio track only) Track-level default volume, can be overridden by a clip-level volume
track_timelinearrayYesSequence of clip objects on the track. Array order does not matter (the server places clips by track_st)

Clip object in track_timeline[] (video / audio)

Every non-gap clip carries both the source-trim timecodes (clip_st/clip_ed) and the on-track timecodes (track_st/track_ed/duration). The two sets are kept redundantly to serve both "start + duration" and "start + end" consumers. The array order of track_timeline does not matter: every clip is placed absolutely on the timeline by its track_st (the server always positions clips by track_st), so out-of-order submissions produce the same output as time-ordered ones, and real gaps between clips are preserved as-is.

FieldTypeRequiredDescription
clip_idstringYesClip identifier; an empty string "" means a Gap (a gap omits material / clip_st / clip_ed)
materialstringYesReferences a materials[].id (omitted for gaps)
clip_stnumberYesSource in-point within the asset (seconds)
clip_ednumberYesSource out-point within the asset (seconds); for retimed clips clip_st + duration × speed
track_stnumberYesClip in-point on the timeline (seconds)
track_ednumberYesClip out-point on the timeline (seconds)
durationnumberYesClip duration on the timeline (seconds)
mutedbooleanNo(video) Clip-level mute, overrides the track-level muted
volumenumberNo(audio) Clip-level volume, overrides the track-level volume
clip_transformobjectNo(video) Static clip transform, see "clip_transform & clip_keyframes" below
clip_keyframesobjectNoKeyframe animation channels (all channels on video clips / volume only on audio clips), see "clip_transform & clip_keyframes" below
border_radiusnumberNo(video) Corner radius of the clip's displayed rectangle, canvas pixels, ≥ 0, see "border_radius & clip_mask" below
clip_maskobjectNo(video) Shape mask (circle / rounded rectangle / heart / diamond / star), see "border_radius & clip_mask" below
speednumberNoConstant playback speed, 0.01–100, default 1, see "speed & keep_pitch" below
keep_pitchbooleanNoWhether to keep the original pitch when retimed, default false, see "speed & keep_pitch" below

ℹ️ Two-layer mute / volume semantics: video uses muted, audio uses volume, each at both the track level and the clip level. Precedence: clip-level if present, otherwise track-level, otherwise the default (muted defaults to false, volume defaults to 1.0).

clip_transform & clip_keyframes (optional: static transform & keyframe animation)

Both fields are honored only by the JianYing / CapCut drafts; the other formats (Premiere / FCPX / OTIO) ignore them without error. Omitting them means no transform / no animation.

clip_transform (static transform, video clips): {position_x, position_y, scale_x, scale_y, rotation, alpha}. Units: position_x/y in canvas pixels (canvas center is the origin, +x right / +y down); scale_x/y as ratios (1.0 = 100%, negative = flip); rotation in clockwise degrees; alpha opacity 0..1.

clip_keyframes (keyframe animation): an object whose keys are property names and whose values are non-empty [{t, v}] arrays. Video clips support seven channels: position_x / position_y / scale_x / scale_y / rotation / alpha / volume; audio clips support volume only (volume ramps). Rules:

  • t is in seconds relative to the clip's on-track in-point, must fall within [0, duration], and must be strictly increasing within a channel;
  • v uses the same unit as the matching clip_transform property (volume is linear gain, must be ≥ 0, 1.0 = original volume);
  • adjacent keyframes are linearly interpolated (JianYing draft keyframes are linear);
  • at most 3000 keyframes per channel;
  • when a property is keyframed, set the matching clip_transform static value to the first keyframe's value (inside JianYing the curve wins; the static value serves as a fallback for formats that do not honor animation);
  • a scale_x / scale_y channel containing any negative value is not honored (JianYing's flip is a boolean and cannot be keyframed); the clip's other channels convert as usual. Express a static flip via a negative scale in clip_transform.

speed & keep_pitch (optional: constant speed)

Allowed on video and audio clips. Gaps and HTML particles in beat_track cannot be retimed; requests that try are rejected. Omitted means normal speed.

  • speed: constant playback speed, a finite number from 0.01 to 100. > 1 speeds up, < 1 slows down;
  • duration is the clip's length on the timeline; the clip consumes duration × speed of source, i.e. clip_ed = clip_st + duration × speed (in whole milliseconds, .5 rounds up);
  • keep_pitch: boolean, true keeps the original pitch after retiming, default false (pitch follows speed);
  • t in clip_keyframes is still in timeline seconds and is not scaled by speed.

Format support: Jianying drafts and FCPX carry both speed and pitch; CapCut drafts, Premiere XML and OTIO carry speed, and when a pitch setting cannot be expressed it is counted per format in the returned warnings (kind = speed_keep_pitch_not_carried); gtrk keeps both fields as-is.

// Occupies 3 s on the timeline, plays source 10–16 s at 2x with pitch kept
{ "clip_id": "c1", "material": "F100", "clip_st": 10.0, "clip_ed": 16.0,
  "track_st": 0.0, "track_ed": 3.0, "duration": 3.0, "speed": 2.0, "keep_pitch": true }

border_radius & clip_mask (optional: rounded corners & shape mask)

Both fields are accepted on video clips only (an audio clip carrying either is rejected), and are honored only by the JianYing / CapCut drafts; the other formats ignore them without error. Omitting them means no rounding / no mask. The typical use is picture-in-picture: scale and position the clip with clip_transform, then round its corners with border_radius or open a circular window with clip_mask.

border_radius (rounded corners): a number ≥ 0 in canvas pixels, applied to the four corners of the clip's displayed rectangle (after scaling) and rotating with the clip. Inside JianYing it is expressed as a full-size rounded-rectangle mask.

clip_mask (shape mask, at most one per clip): {shape, center_x, center_y, width, height, rotation, corner_radius, feather, invert}.

  • shape: rectangle (rectangle, optionally with corner_radius) / ellipse (equal width and height gives a circle) / heart / diamond / star;
  • center_x / center_y: mask center, normalized to the clip's displayed rectangle with the origin at its center — 0 is the center, ±0.5 the edges, +x right / +y down, values outside the edges are allowed; default 0;
  • width / height: mask size as a ratio of the clip's displayed width / height (> 0, may exceed 1); default 0.5;
  • rotation: clockwise degrees around the mask center;
  • corner_radius: rectangle only, 0..1, corner radius as a fraction of half the mask's shorter side (1 = fully rounded);
  • feather: 0..100, feather width as a percentage of the mask's shorter side (0 = hard edge);
  • invert: boolean, inverts the mask (keeps the outside, hides the inside).

Conversion notes: JianYing's ellipse / heart / star masks have a fixed aspect ratio, so when width and height do not match, height is honored; diamond has no JianYing counterpart, so that clip's mask is skipped while its other fields convert as usual; when a clip carries both border_radius and clip_mask, only clip_mask is applied in JianYing (a segment holds a single mask); a material without video_size cannot be converted and the mask is skipped.

{
  "clip_id": "c1", "material": "F100",
  "clip_st": 0.0, "clip_ed": 4.0, "track_st": 0.0, "track_ed": 4.0, "duration": 4.0,
  "clip_transform": { "alpha": 0.0 },
  "clip_keyframes": {
    "alpha":      [ { "t": 0.0, "v": 0.0 }, { "t": 1.0, "v": 1.0 } ],
    "position_y": [ { "t": 0.0, "v": 270 }, { "t": 1.0, "v": 0 } ]
  }
}

The example above is a "fade in + slide up from 270px below": the exported JianYing clip carries 4 keyframes that remain editable inside JianYing.

struct_meta object

FieldTypeRequiredDescription
nle_draft_dirstringNoAbsolute path to the JianYing / CapCut draft folder. The legacy name capcut_draft_path is still accepted as an alias
client_visual_elementsobjectNoMirror of client-side overlay elements such as subtitles. Subtitle (text) elements are assembled into a text track inside the JianYing draft — selectable and editable in JianYing. See "Subtitle text track" below

⚠️ What nle_draft_dir actually is: it is the absolute path of the draft folder where the JianYing / CapCut draft should land on disk. JianYing / CapCut recognize a draft by the pair of draft_content.json + draft_meta_info.json sitting in that folder. If you omit this path, the endpoint only produces draft_content.json (the content file); without draft_meta_info.json (the meta file) the editor may fail to open the draft. So to open the finished draft in JianYing / CapCut, always pass nle_draft_dir.

Subtitle text track (struct_meta.client_visual_elements)

When this field is present, its subtitle elements are assembled into a text track in the JianYing draft, layered above every video track. In JianYing you can select them, edit the wording, and keep adjusting the styling; they arrive as real subtitles (usable with JianYing's bulk subtitle panel), and line wrapping is left to JianYing.

Only JianYing carries the subtitle text track. Every other output format skips it, for different reasons:

FormatCarries subtitlesReason
jianying✅ Yes—
capcut❌ NoTarget library limitation (no shadow API, required CJK font missing from its font table)
xml (Premiere)❌ NoThis endpoint's Premiere output path has no text vocabulary yet; may be added later
fcpxml❌ NoSame as above, and titles must reference machine-local template resources
otio❌ NoThe format itself has no text/subtitle structure

⚠️ The time unit must be declared explicitly: the object's time_unit field must be "second". With any other value (or none), the endpoint will not assemble a subtitle track — this is deliberate: interpreting the times under the wrong unit yields a draft that is structurally valid but temporally wrong (it opens fine, yet every subtitle sits far past the end of the video).

Size limits: at most 200 overlay lanes / 2000 elements / 2 MB. Exceeding any of them returns a parameter error.

Styling follows the parameters as they stand in your client (styles you tweaked by hand carry over). Dimensions JianYing cannot express (e.g. non-uniform glyph scaling) are skipped and recorded rather than faked with a different field. Stickers, graphics and effects do not enter the draft, but their skipped counts are recorded.

beat_track[] particle overlays

beat_track carries HTML animation particle overlay layers. This Project File Export endpoint ignores beat_track — NLE project files do not carry HTML particles. To actually render particles into the picture, use html_animate_render instead. The request body may still include beat_track to keep the gtrk structure complete, but it does not affect this endpoint's output.

project_formats values

There are 6 public output formats:

ValueOutputSaved as
xmlPremiere / FCP7 XML.xml
fcpxmlFinal Cut Pro X.xml
otioOpenTimelineIO.json
jianyingJianYing draft (produced as a pair automatically)draft_content.json + draft_meta_info.json
capcutCapCut draft (produced as a pair automatically)draft_content.json + draft_meta_info.json
gtrkTonghe project file (single-index JSON, for import into the Tonghe client).gtrk

ℹ️ Note: JianYing / CapCut drafts require both the content and meta files placed together in the draft folder to be recognized, so selecting jianying / capcut automatically produces both. files[].format in the response still identifies individual files (e.g. jianying_draft / jianying_meta). Compatibility: the legacy fine-grained values (jianying_draft, etc.) are still accepted with their original single-file semantics; new integrations should use the table above.

🆕 Drafts open in "free layer" mode by default: the JianYing / CapCut drafts produced by this endpoint default to free-layer mode, so clip layers can be reordered freely instead of being bound to the traditional main-track-plus-overlay model. Multi-track stacking order is unchanged.

⚠️ Previously exported drafts cannot be switched: JianYing locks the layer mode at import time, and it cannot be changed from the project settings afterwards. If an older draft needs free-layer mode, export it again.

Request Example

The example below shows cutting multiple segments from one source: materials declares a single video source, video_track cuts several segments from the same asset using different clip_st / clip_ed and stitches them together, then layers an audio_track, and specifies project_formats.

curl -X POST https://api.ai-mcn.tv:10000/task/video_project_struct \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "v1",
    "video_size": [1920, 1080],
    "video_rate": 60,
    "duration": 9.0,
    "materials": [
      {
        "id": "F100",
        "path": "C:/clips/source.mp4",
        "duration": 120.0,
        "video_size": [1920, 1080],
        "video_rate": 60,
        "audio_channel": "stereo"
      },
      {
        "id": "F200",
        "path": "C:/clips/bgm.mp3",
        "duration": 180.0,
        "audio_channel": "stereo"
      }
    ],
    "video_track": [
      {
        "track_index": 0,
        "track_size": [1920, 1080],
        "muted": false,
        "track_timeline": [
          {
            "clip_id": "c1", "material": "F100",
            "clip_st": 10.0, "clip_ed": 13.0,
            "track_st": 0.0, "track_ed": 3.0, "duration": 3.0
          },
          {
            "clip_id": "c2", "material": "F100",
            "clip_st": 55.0, "clip_ed": 59.0,
            "track_st": 3.0, "track_ed": 7.0, "duration": 4.0,
            "muted": true
          },
          {
            "clip_id": "c3", "material": "F100",
            "clip_st": 88.0, "clip_ed": 90.0,
            "track_st": 7.0, "track_ed": 9.0, "duration": 2.0
          }
        ]
      }
    ],
    "audio_track": [
      {
        "track_index": 0,
        "volume": 0.8,
        "track_timeline": [
          {
            "clip_id": "a1", "material": "F200",
            "clip_st": 0.0, "clip_ed": 9.0,
            "track_st": 0.0, "track_ed": 9.0, "duration": 9.0
          }
        ]
      }
    ],
    "struct_meta": {
      "nle_draft_dir": "C:/Users/Me/AppData/Local/JianyingPro/User Data/Projects/com.lveditor.draft/20260626"
    },
    "project_formats": ["xml", "gtrk", "jianying"]
  }'

Success Example

{
  "code": 200,
  "msg": "success",
  "data": {
    "task_id": "537489015178247",
    "task_type": "video_project_struct",
    "status": "queued"
  }
}

Query Task Result

Basic Information

ItemValue
Request MethodGET
Request Path/task/video_project_struct/{task_id}
AuthenticationRaw API key in the Authorization header

Response Fields (output_result)

FieldTypeDescription
filesarrayList of successfully produced project files
files[].formatstringOutput format: xml / fcpxml / otio / gtrk / jianying_draft / jianying_meta / capcut_draft / capcut_meta
files[].file_idstringOutput file ID
files[].download_urlstringDownload path
files[].filenamestringSuggested file name (e.g. draft_content.json; the editor only recognizes it once placed in a draft folder)
errorsobjectFailed formats and reasons (non-empty on partial success)
warningsarrayNon-blocking warnings; may be empty. A completed task can still have warnings. These are distinct from format-generation failures in errors

ℹ️ Partial success: each format is generated independently; a failure in one format does not affect the others, and the task is still marked completed with the failures recorded under errors. The task is marked failed only when all formats fail.

Missing subtitle content key

If a text element considered for subtitle parsing lacks the params.content key, warnings includes an entry such as:

{"kind": "text_content_key_missing", "count": 2, "total": 10}
  • count: number of text segments missing the content key. They are skipped as empty text, so the draft can still be generated successfully while those subtitles are absent.
  • total: total number of text segments considered for this subtitle parse, not the number of subtitles ultimately generated.
  • Action: check the sending client's text-parameter mapping and retain the params.content key. Do not ignore this warning just because the task completed.

An existing but empty content value does not trigger this warning. A format's normal lack of subtitle support does not trigger it either. Successfully carrying subtitles does not produce a separate success notice.

Success Example

{
  "code": 200,
  "msg": "success",
  "data": {
    "task_id": "537489015178247",
    "status": "completed",
    "progress": 100,
    "output_result": {
      "files": [
        {"format": "xml", "file_id": "537489015178248", "download_url": "/download/a1/537489015178248.xml", "filename": "premiere.xml"},
        {"format": "gtrk", "file_id": "537489015178249", "download_url": "/download/a2/537489015178249.gtrk", "filename": "project.gtrk"},
        {"format": "jianying_draft", "file_id": "537489015178250", "download_url": "/download/a3/537489015178250.json", "filename": "draft_content.json"},
        {"format": "jianying_meta", "file_id": "537489015178251", "download_url": "/download/a4/537489015178251.json", "filename": "draft_meta_info.json"}
      ],
      "errors": {}
    },
    "create_time": "2026-06-01T08:00:00Z",
    "update_time": "2026-06-01T08:00:03Z"
  }
}

Error Codes

CodeHTTP StatusDescriptionResolution
6013400Missing required parameter (video_size/video_rate/duration/materials/video_track/audio_track)Provide the required parameters
6004400A cloud-referenced asset does not exist (an entry without path whose id matches no platform file)Verify the file_id, or add a path to switch the entry to a local reference
6016400Invalid parameter type or structure (e.g. a material not in the materials list, an illegal value in project_formats)Check the timeline structure and values
6502401Authentication failedCheck the Authorization header
6201402Quota insufficientBuy a quota pack or top up
6202402Balance insufficientTop up in the dashboard

Limitations

  • Structure-only conversion: the server does not read the files referenced by materials[].path; it only writes the paths into the project files. Make sure those paths are valid in your editing environment.
  • Particles are not rendered here: beat_track is ignored by this endpoint; render HTML particles via html_animate_render.
  • Per-call billing: billed once per request, regardless of how many formats are produced.
  • Draft version compatibility: the generated JianYing / CapCut drafts must be opened with a compatible client version (the draft schema evolves with the app). Place draft_content.json and draft_meta_info.json into the corresponding draft folder to open and keep editing.
  • Draft meta: jianying_meta / capcut_meta are produced only when struct_meta.nle_draft_dir is provided; without it only the content file is produced and the editor may fail to open the draft.