@pixerate/editor 0.91.0 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,7 +20,7 @@ var VIDEO_BLEND_MODES = ["normal", "multiply", "screen", "overlay", "darken", "l
20
20
  var VIDEO_CLIP_ANIMATION_EFFECTS = ["fade", "rise", "drop", "slide-left", "slide-right", "zoom", "pop"];
21
21
  var VIDEO_FADE_CURVES = ["linear", "equal-power", "s-curve", "exponential", "logarithmic"];
22
22
  var VIDEO_PLACEMENT_MODES = ["refuse", "overwrite", "insert"];
23
- var VIDEO_CLIP_TYPES = ["media", "text", "color"];
23
+ var VIDEO_CLIP_TYPES = ["media", "text", "color", "sequence"];
24
24
 
25
25
  // src/video-editor/keyframes.ts
26
26
  var VIDEO_EASE_NAMES = ["linear", "hold", "ease-in", "ease-out", "ease-in-out"];
@@ -911,6 +911,20 @@ function parseVideoClip(v, path = "clip") {
911
911
  fadeInCurve: oneOf(v.fadeInCurve, VIDEO_FADE_CURVES, `${path}.fadeInCurve`),
912
912
  fadeOutCurve: oneOf(v.fadeOutCurve, VIDEO_FADE_CURVES, `${path}.fadeOutCurve`)
913
913
  });
914
+ if (type === "sequence") {
915
+ for (const key of ["speed", "timeRemap"])
916
+ if (v[key] !== void 0 && !(key === "speed" && v[key] === 1)) fail(`${path}.${key}`, 'is not supported on a compound clip (type "sequence"), which plays its sequence at normal speed; remove it, or change the speed of the clips inside the sequence');
917
+ return compact({
918
+ type,
919
+ ...base,
920
+ sequence: str(v.sequence, `${path}.sequence`),
921
+ in: num(v.in, `${path}.in`, { optional: true, min: 0 }),
922
+ volume: parseVideoAnimatable(v.volume, `${path}.volume`, { optional: true, min: 0, max: 4 }),
923
+ muted: bool(v.muted, `${path}.muted`),
924
+ fadeInCurve: oneOf(v.fadeInCurve, VIDEO_FADE_CURVES, `${path}.fadeInCurve`),
925
+ fadeOutCurve: oneOf(v.fadeOutCurve, VIDEO_FADE_CURVES, `${path}.fadeOutCurve`)
926
+ });
927
+ }
914
928
  if (type === "text")
915
929
  return compact({
916
930
  type,
@@ -939,6 +953,21 @@ function parseVideoTrack(v, path = "track") {
939
953
  clips: (_nullishCoalesce(v.clips, () => ( []))).map((c, i) => parseVideoClip(c, `${path}.clips[${i}]`))
940
954
  });
941
955
  }
956
+ function parseVideoSequence(v, path = "sequence") {
957
+ if (!isObject(v)) return fail(path, "must be an object { id, name, tracks }");
958
+ if (v.tracks !== void 0 && !Array.isArray(v.tracks)) fail(`${path}.tracks`, "must be an array");
959
+ const sequence = compact({
960
+ id: str(v.id, `${path}.id`),
961
+ name: str(v.name, `${path}.name`, true),
962
+ tracks: (_nullishCoalesce(v.tracks, () => ( []))).map((t, i) => parseVideoTrack(t, `${path}.tracks[${i}]`))
963
+ });
964
+ const seen = /* @__PURE__ */ new Set();
965
+ for (const t of sequence.tracks) {
966
+ if (seen.has(t.id)) throw new Error(`Two tracks in sequence "${sequence.id}" share the id "${t.id}"; ids must be unique.`);
967
+ seen.add(t.id);
968
+ }
969
+ return sequence;
970
+ }
942
971
  function parseVideoMarker(v, path = "marker") {
943
972
  if (!isObject(v)) return fail(path, "must be an object");
944
973
  return compact({ id: str(v.id, `${path}.id`), time: num(v.time, `${path}.time`, { min: 0 }), label: str(v.label, `${path}.label`, true), color: str(v.color, `${path}.color`, true) });
@@ -954,7 +983,7 @@ function parseVideoProject(input) {
954
983
  }
955
984
  if (!isObject(v)) return fail("The project", "must be an object");
956
985
  if (v.version !== void 0 && v.version !== 1) fail("version", "must be 1");
957
- for (const key of ["assets", "tracks", "markers"]) if (v[key] !== void 0 && !Array.isArray(v[key])) fail(key, "must be an array");
986
+ for (const key of ["assets", "tracks", "sequences", "markers"]) if (v[key] !== void 0 && !Array.isArray(v[key])) fail(key, "must be an array");
958
987
  const project = compact({
959
988
  version: 1,
960
989
  name: str(v.name, "name", true),
@@ -964,6 +993,7 @@ function parseVideoProject(input) {
964
993
  background: str(v.background, "background", true),
965
994
  assets: (_nullishCoalesce(v.assets, () => ( []))).map((a, i) => parseVideoAsset(a, `assets[${i}]`)),
966
995
  tracks: (_nullishCoalesce(v.tracks, () => ( []))).map((t, i) => parseVideoTrack(t, `tracks[${i}]`)),
996
+ sequences: v.sequences === void 0 ? void 0 : v.sequences.map((q, i) => parseVideoSequence(q, `sequences[${i}]`)),
967
997
  markers: v.markers === void 0 ? void 0 : v.markers.map((m, i) => parseVideoMarker(m, `markers[${i}]`))
968
998
  });
969
999
  const unique = (ids, what) => {
@@ -975,7 +1005,8 @@ function parseVideoProject(input) {
975
1005
  };
976
1006
  unique(project.assets.map((a) => a.id), "assets");
977
1007
  unique(project.tracks.map((t) => t.id), "tracks");
978
- unique(project.tracks.flatMap((t) => t.clips.map((c) => c.id)), "clips");
1008
+ unique((_nullishCoalesce(project.sequences, () => ( []))).map((q) => q.id), "sequences");
1009
+ unique([...project.tracks, ...(_nullishCoalesce(project.sequences, () => ( []))).flatMap((q) => q.tracks)].flatMap((t) => t.clips.map((c) => c.id)), "clips");
979
1010
  unique((_nullishCoalesce(project.markers, () => ( []))).map((m) => m.id), "markers");
980
1011
  return project;
981
1012
  }
@@ -1001,6 +1032,7 @@ var videoClipEnd = (clip) => clip.start + clip.duration;
1001
1032
  var videoTrackSyncLocked = (track) => track.syncLocked !== false;
1002
1033
  var videoClipSpeed = (clip) => clip.type === "media" ? _nullishCoalesce(clip.speed, () => ( 1)) : 1;
1003
1034
  function videoClipSourceRange(clip) {
1035
+ if (clip.type === "sequence") return { in: _nullishCoalesce(clip.in, () => ( 0)), out: (_nullishCoalesce(clip.in, () => ( 0))) + clip.duration };
1004
1036
  if (clip.type !== "media") return null;
1005
1037
  const start = _nullishCoalesce(clip.in, () => ( 0));
1006
1038
  if (_optionalChain([clip, 'access', _5 => _5.timeRemap, 'optionalAccess', _6 => _6.keys, 'access', _7 => _7.length])) return { in: start + clip.timeRemap.keys[0].v, out: start + clip.timeRemap.keys[clip.timeRemap.keys.length - 1].v };
@@ -1050,6 +1082,33 @@ var videoAssetHasSound = (asset) => !!asset && (asset.kind === "audio" || asset.
1050
1082
  function videoAssetMap(project) {
1051
1083
  return new Map(project.assets.map((a) => [a.id, a]));
1052
1084
  }
1085
+ function videoSequenceMap(project) {
1086
+ return new Map((_nullishCoalesce(project.sequences, () => ( []))).map((q) => [q.id, q]));
1087
+ }
1088
+ function videoSequenceDuration(sequence) {
1089
+ let end = 0;
1090
+ for (const track of sequence.tracks) for (const clip of track.clips) end = Math.max(end, videoClipEnd(clip));
1091
+ return end;
1092
+ }
1093
+ var videoSequenceChildren = (sequence) => [...new Set(sequence.tracks.flatMap((t) => t.clips.flatMap((c) => c.type === "sequence" ? [c.sequence] : [])))];
1094
+ function findVideoSequenceCycle(project, id) {
1095
+ const sequences = videoSequenceMap(project);
1096
+ const walk = (at, path) => {
1097
+ const seq = sequences.get(at);
1098
+ if (!seq) return null;
1099
+ for (const child of videoSequenceChildren(seq)) {
1100
+ if (child === id) return [...path, child];
1101
+ if (path.includes(child)) continue;
1102
+ const found = walk(child, [...path, child]);
1103
+ if (found) return found;
1104
+ }
1105
+ return null;
1106
+ };
1107
+ return walk(id, [id]);
1108
+ }
1109
+ function allVideoTracks(project) {
1110
+ return [...project.tracks.map((track) => ({ track })), ...(_nullishCoalesce(project.sequences, () => ( []))).flatMap((q) => q.tracks.map((track) => ({ sequence: q.id, track })))];
1111
+ }
1053
1112
  function sortedVideoClips(track) {
1054
1113
  return [...track.clips].sort((a, b) => a.start - b.start || a.duration - b.duration);
1055
1114
  }
@@ -1062,7 +1121,15 @@ function findVideoClip(project, id) {
1062
1121
  return null;
1063
1122
  }
1064
1123
  function allVideoClipIds(project) {
1065
- return project.tracks.flatMap((t) => t.clips.map((c) => c.id));
1124
+ return allVideoTracks(project).flatMap(({ track }) => track.clips.map((c) => c.id));
1125
+ }
1126
+ function findVideoSequenceClip(project, id) {
1127
+ for (const sequence of _nullishCoalesce(project.sequences, () => ( [])))
1128
+ for (const track of sequence.tracks) {
1129
+ const clip = track.clips.find((c) => c.id === id);
1130
+ if (clip) return { sequence, track, clip };
1131
+ }
1132
+ return null;
1066
1133
  }
1067
1134
  function videoProjectDuration(project) {
1068
1135
  let end = 0;
@@ -1078,19 +1145,24 @@ function videoClipAfter(track, clip) {
1078
1145
  for (const c of track.clips) if (c !== clip && Math.abs(c.start - videoClipEnd(clip)) <= VIDEO_TIME_EPSILON) return c;
1079
1146
  return null;
1080
1147
  }
1081
- function videoClipHandles(clip, assets) {
1148
+ function videoClipHandles(clip, assets, sequences) {
1149
+ if (clip.type === "sequence") {
1150
+ const seq = _optionalChain([sequences, 'optionalAccess', _8 => _8.get, 'call', _9 => _9(clip.sequence)]);
1151
+ const range2 = videoClipSourceRange(clip);
1152
+ return { before: range2.in, after: seq ? Math.max(0, videoSequenceDuration(seq) - range2.out) : Infinity };
1153
+ }
1082
1154
  if (clip.type !== "media") return { before: Infinity, after: Infinity };
1083
1155
  const asset = assets.get(clip.asset);
1084
1156
  const speed = videoClipSpeed(clip);
1085
1157
  const range = videoClipSourceRange(clip);
1086
1158
  const before = range.in / speed;
1087
- if (!asset || asset.kind === "image" || asset.duration === void 0) return { before: _optionalChain([asset, 'optionalAccess', _8 => _8.kind]) === "image" ? Infinity : before, after: Infinity };
1159
+ if (!asset || asset.kind === "image" || asset.duration === void 0) return { before: _optionalChain([asset, 'optionalAccess', _10 => _10.kind]) === "image" ? Infinity : before, after: Infinity };
1088
1160
  return { before, after: Math.max(0, (asset.duration - range.out) / speed) };
1089
1161
  }
1090
1162
  function videoClipOverhang(track, clip) {
1091
1163
  const lead = clip.transition && clip.transition.type !== "dip" && videoClipBefore(track, clip) ? clip.transition.duration / 2 : 0;
1092
1164
  const next = videoClipAfter(track, clip);
1093
- const tail = _optionalChain([next, 'optionalAccess', _9 => _9.transition]) && next.transition.type !== "dip" ? next.transition.duration / 2 : 0;
1165
+ const tail = _optionalChain([next, 'optionalAccess', _11 => _11.transition]) && next.transition.type !== "dip" ? next.transition.duration / 2 : 0;
1094
1166
  return { lead, tail };
1095
1167
  }
1096
1168
  function videoSnapPoints(project, options = {}) {
@@ -1174,7 +1246,7 @@ function videoClipAnimated(clip, project) {
1174
1246
  const spans = videoClipAnimationSpans(clip);
1175
1247
  const out = { ...clip, transform: { ...clip.transform } };
1176
1248
  for (const prop of PROPS) {
1177
- const current = prop === "opacity" ? clip.opacity : _optionalChain([clip, 'access', _10 => _10.transform, 'optionalAccess', _11 => _11[prop]]);
1249
+ const current = prop === "opacity" ? clip.opacity : _optionalChain([clip, 'access', _12 => _12.transform, 'optionalAccess', _13 => _13[prop]]);
1178
1250
  if (isVideoKeyframes(current)) continue;
1179
1251
  const base = _nullishCoalesce(current, () => ( (prop === "opacity" || prop === "scale" ? 1 : 0)));
1180
1252
  const away = (spec) => {
@@ -1210,13 +1282,15 @@ function videoClipAnimated(clip, project) {
1210
1282
  function videoClipKeyframes(clip) {
1211
1283
  const out = [
1212
1284
  { property: "opacity", keys: videoKeysOf(clip.opacity) },
1213
- { property: "volume", keys: clip.type === "media" ? videoKeysOf(clip.volume) : [] },
1214
- ...VIDEO_TRANSFORM_PROPERTIES.map((p) => ({ property: p, keys: videoKeysOf(_optionalChain([clip, 'access', _12 => _12.transform, 'optionalAccess', _13 => _13[p]])) }))
1285
+ { property: "volume", keys: clip.type === "media" || clip.type === "sequence" ? videoKeysOf(clip.volume) : [] },
1286
+ ...VIDEO_TRANSFORM_PROPERTIES.map((p) => ({ property: p, keys: videoKeysOf(_optionalChain([clip, 'access', _14 => _14.transform, 'optionalAccess', _15 => _15[p]])) }))
1215
1287
  ];
1216
1288
  return out.filter((o) => o.keys.length);
1217
1289
  }
1218
1290
  var VIDEO_ISSUE_CODES = [
1219
1291
  "missing-asset",
1292
+ "missing-sequence",
1293
+ "sequence-cycle",
1220
1294
  "wrong-track",
1221
1295
  "overlap",
1222
1296
  "source-overrun",
@@ -1229,23 +1303,41 @@ var VIDEO_ISSUE_CODES = [
1229
1303
  "keyframes-outside-clip",
1230
1304
  "unknown-duration",
1231
1305
  "unused-asset",
1306
+ "unused-sequence",
1232
1307
  "marker-past-end",
1233
1308
  "empty"
1234
1309
  ];
1235
- var videoIssueKey = (i) => [i.code, _nullishCoalesce(i.track, () => ( "")), _nullishCoalesce(i.clip, () => ( "")), _nullishCoalesce(i.asset, () => ( "")), _nullishCoalesce(i.marker, () => ( ""))].join("|");
1310
+ var videoIssueKey = (i) => [i.code, _nullishCoalesce(i.sequence, () => ( "")), _nullishCoalesce(i.track, () => ( "")), _nullishCoalesce(i.clip, () => ( "")), _nullishCoalesce(i.asset, () => ( "")), _nullishCoalesce(i.marker, () => ( ""))].join("|");
1236
1311
  function checkVideoProject(project) {
1237
1312
  const issues = [];
1238
1313
  const assets = videoAssetMap(project);
1314
+ const sequences = videoSequenceMap(project);
1239
1315
  const used = /* @__PURE__ */ new Set();
1316
+ const usedSequences = /* @__PURE__ */ new Set();
1317
+ const cycles = /* @__PURE__ */ new Map();
1240
1318
  for (const asset of project.assets)
1241
1319
  if ((asset.kind === "video" || asset.kind === "audio") && asset.duration === void 0)
1242
1320
  issues.push({ code: "unknown-duration", severity: "warning", asset: asset.id, message: `Asset "${asset.id}" has no duration, so clips of it cannot be checked against its length; set "duration" in ms.` });
1243
- for (const track of project.tracks) {
1321
+ const checkTrack = (track, sequence) => {
1244
1322
  const clips = sortedVideoClips(track);
1323
+ const where = sequence ? ` in sequence "${sequence}"` : "";
1245
1324
  for (let i = 0; i < clips.length; i++) {
1246
1325
  const clip = clips[i];
1247
- const at = { track: track.id, clip: clip.id };
1248
- if (clip.type === "media") {
1326
+ const at = { ...sequence ? { sequence } : {}, track: track.id, clip: clip.id };
1327
+ if (clip.type === "sequence") {
1328
+ usedSequences.add(clip.sequence);
1329
+ const seq = sequences.get(clip.sequence);
1330
+ if (!seq) issues.push({ code: "missing-sequence", severity: "error", ...at, message: `Compound clip "${clip.id}" plays sequence "${clip.sequence}", which is not in the project; add the sequence or delete the clip.` });
1331
+ else {
1332
+ if (!cycles.has(seq.id)) cycles.set(seq.id, findVideoSequenceCycle(project, seq.id));
1333
+ const cycle = cycles.get(seq.id);
1334
+ if (cycle) issues.push({ code: "sequence-cycle", severity: "error", ...at, message: `Compound clip "${clip.id}" plays sequence "${seq.id}", which contains itself (${cycle.join(" \u2192 ")}), so it is left out of the picture and sound; remove the compound clip that closes the loop.` });
1335
+ const length = videoSequenceDuration(seq);
1336
+ const range = videoClipSourceRange(clip);
1337
+ if (range.out > length + VIDEO_TIME_EPSILON)
1338
+ issues.push({ code: "source-overrun", severity: "error", ...at, message: `Compound clip "${clip.id}" plays to ${formatVideoSeconds(range.out)} of sequence "${seq.id}", which is only ${formatVideoSeconds(length)} long; shorten the clip or move its in point.` });
1339
+ }
1340
+ } else if (clip.type === "media") {
1249
1341
  used.add(clip.asset);
1250
1342
  const asset = assets.get(clip.asset);
1251
1343
  if (!asset) issues.push({ code: "missing-asset", severity: "error", ...at, asset: clip.asset, message: `Clip "${clip.id}" plays asset "${clip.asset}", which is not in the project; add the asset or delete the clip.` });
@@ -1259,7 +1351,7 @@ function checkVideoProject(project) {
1259
1351
  } else if (track.kind === "audio") issues.push({ code: "wrong-track", severity: "error", ...at, message: `Clip "${clip.id}" is a ${clip.type} clip on audio track "${track.id}"; move it to a video track.` });
1260
1352
  const next = clips[i + 1];
1261
1353
  if (next && next.start < videoClipEnd(clip) - VIDEO_TIME_EPSILON)
1262
- issues.push({ code: "overlap", severity: "error", ...at, message: `Clips "${clip.id}" and "${next.id}" overlap on track "${track.id}" from ${formatVideoSeconds(next.start)}; move one of them.` });
1354
+ issues.push({ code: "overlap", severity: "error", ...at, message: `Clips "${clip.id}" and "${next.id}" overlap on track "${track.id}"${where} from ${formatVideoSeconds(next.start)}; move one of them.` });
1263
1355
  if ((_nullishCoalesce(clip.fadeIn, () => ( 0))) + (_nullishCoalesce(clip.fadeOut, () => ( 0))) > clip.duration + VIDEO_TIME_EPSILON)
1264
1356
  issues.push({ code: "fades-too-long", severity: "warning", ...at, message: `Clip "${clip.id}"'s fades (${_nullishCoalesce(clip.fadeIn, () => ( 0))} + ${_nullishCoalesce(clip.fadeOut, () => ( 0))} ms) are longer than the clip (${clip.duration} ms); shorten them.` });
1265
1357
  const anim = (clip.animateIn ? _nullishCoalesce(clip.animateIn.duration, () => ( VIDEO_CLIP_ANIMATION_DURATION)) : 0) + (clip.animateOut ? _nullishCoalesce(clip.animateOut.duration, () => ( VIDEO_CLIP_ANIMATION_DURATION)) : 0);
@@ -1268,7 +1360,7 @@ function checkVideoProject(project) {
1268
1360
  for (const which of ["animateIn", "animateOut"]) {
1269
1361
  const a = clip[which];
1270
1362
  if (!a) continue;
1271
- const keyed = videoClipAnimationProperties(a).filter((p) => videoKeysOf(p === "opacity" ? clip.opacity : _optionalChain([clip, 'access', _14 => _14.transform, 'optionalAccess', _15 => _15[p]])).length);
1363
+ const keyed = videoClipAnimationProperties(a).filter((p) => videoKeysOf(p === "opacity" ? clip.opacity : _optionalChain([clip, 'access', _16 => _16.transform, 'optionalAccess', _17 => _17[p]])).length);
1272
1364
  if (keyed.length)
1273
1365
  issues.push({ code: "animation-keyed", severity: "note", ...at, message: `Clip "${clip.id}"'s ${a.effect} ${which === "animateIn" ? "entrance" : "exit"} leaves ${keyed.join(", ")} alone, because the clip keys ${keyed.length > 1 ? "them" : "it"} itself; remove those keyframes or the animation.` });
1274
1366
  }
@@ -1294,8 +1386,8 @@ function checkVideoProject(project) {
1294
1386
  continue;
1295
1387
  }
1296
1388
  if (clip.transition.type === "dip") continue;
1297
- const outgoing = videoClipHandles(before, assets).after;
1298
- const incoming = videoClipHandles(clip, assets).before;
1389
+ const outgoing = videoClipHandles(before, assets, sequences).after;
1390
+ const incoming = videoClipHandles(clip, assets, sequences).before;
1299
1391
  const half = d / 2;
1300
1392
  if (outgoing < half - VIDEO_TIME_EPSILON || incoming < half - VIDEO_TIME_EPSILON) {
1301
1393
  const fits = 2 * Math.min(outgoing, incoming);
@@ -1308,7 +1400,11 @@ function checkVideoProject(project) {
1308
1400
  }
1309
1401
  }
1310
1402
  }
1311
- }
1403
+ };
1404
+ for (const track of project.tracks) checkTrack(track);
1405
+ for (const sequence of _nullishCoalesce(project.sequences, () => ( []))) for (const track of sequence.tracks) checkTrack(track, sequence.id);
1406
+ for (const sequence of _nullishCoalesce(project.sequences, () => ( [])))
1407
+ if (!usedSequences.has(sequence.id)) issues.push({ code: "unused-sequence", severity: "note", sequence: sequence.id, message: `Sequence "${sequence.id}" is not played by any compound clip.` });
1312
1408
  for (const asset of project.assets) if (!used.has(asset.id)) issues.push({ code: "unused-asset", severity: "note", asset: asset.id, message: `Asset "${asset.id}" is not used by any clip.` });
1313
1409
  const end = videoProjectDuration(project);
1314
1410
  for (const marker of _nullishCoalesce(project.markers, () => ( [])))
@@ -1380,7 +1476,7 @@ function clipAnimatables(clip) {
1380
1476
  // A speed ramp is keys in clip time too, so trims and splits keep it on the same frames of source.
1381
1477
  { get: () => c.timeRemap, set: (v) => c.timeRemap = v }
1382
1478
  ];
1383
- for (const p of VIDEO_TRANSFORM_PROPERTIES) out.push({ get: () => _optionalChain([c, 'access', _16 => _16.transform, 'optionalAccess', _17 => _17[p]]), set: (v) => c.transform && (c.transform[p] = v) });
1479
+ for (const p of VIDEO_TRANSFORM_PROPERTIES) out.push({ get: () => _optionalChain([c, 'access', _18 => _18.transform, 'optionalAccess', _19 => _19[p]]), set: (v) => c.transform && (c.transform[p] = v) });
1384
1480
  return out;
1385
1481
  }
1386
1482
  function retimeClipKeys(clip, fn) {
@@ -1404,6 +1500,8 @@ var Editor = (_class2 = class {
1404
1500
  }
1405
1501
  clip(id) {
1406
1502
  const at = findVideoClip(this.p, id);
1503
+ const nested = at ? null : findVideoSequenceClip(this.p, id);
1504
+ if (nested) refuse(`Clip "${id}" is inside sequence "${nested.sequence.id}", and clips inside sequences cannot be edited yet; edit the compound clip that plays the sequence instead (list_video_sequences shows which).`);
1407
1505
  if (!at) {
1408
1506
  const ids = this.p.tracks.flatMap((t) => t.clips.map((c) => c.id));
1409
1507
  refuse(`There is no clip ${_chunkWAJQKWQUcjs.namedWithSuggestion.call(void 0, id, ids)}${ids.length ? "" : "; the project has no clips yet"}.`);
@@ -1427,8 +1525,13 @@ var Editor = (_class2 = class {
1427
1525
  }
1428
1526
  kindFits(track, clip) {
1429
1527
  const asset = clip.type === "media" ? this.asset(clip.asset) : null;
1528
+ if (clip.type === "sequence") {
1529
+ const ids = (_nullishCoalesce(this.p.sequences, () => ( []))).map((q) => q.id);
1530
+ if (typeof clip.sequence !== "string" || !ids.includes(clip.sequence)) refuse(`There is no sequence ${_chunkWAJQKWQUcjs.namedWithSuggestion.call(void 0, String(clip.sequence), ids)}; the sequences are ${ids.map((q) => `"${q}"`).join(", ") || "none"}.`);
1531
+ return;
1532
+ }
1430
1533
  if (track.kind === "audio" && (clip.type !== "media" || asset.kind === "image")) refuse(`Track "${track.id}" is an audio track; ${clip.type === "media" ? "images" : `${clip.type} clips`} go on a video track.`);
1431
- if (track.kind === "video" && _optionalChain([asset, 'optionalAccess', _18 => _18.kind]) === "audio") refuse(`Track "${track.id}" is a video track; audio asset "${asset.id}" goes on an audio track.`);
1534
+ if (track.kind === "video" && _optionalChain([asset, 'optionalAccess', _20 => _20.kind]) === "audio") refuse(`Track "${track.id}" is a video track; audio asset "${asset.id}" goes on an audio track.`);
1432
1535
  }
1433
1536
  /** Moves every clip on `track` starting at or after `from` by `delta`. */
1434
1537
  shift(track, from, delta, except = /* @__PURE__ */ new Set()) {
@@ -1440,7 +1543,7 @@ var Editor = (_class2 = class {
1440
1543
  return 1e3 / this.p.fps;
1441
1544
  }
1442
1545
  ids() {
1443
- return this.p.tracks.flatMap((t) => t.clips.map((c) => c.id));
1546
+ return allVideoClipIds(this.p);
1444
1547
  }
1445
1548
  /**
1446
1549
  * The tracks a ripple on `sources` moves, top to bottom: the sources, and every unlocked
@@ -1468,9 +1571,14 @@ var Editor = (_class2 = class {
1468
1571
  range(from, to) {
1469
1572
  if (!(to - from > VIDEO_TIME_EPSILON)) refuse(`"to" (${formatVideoSeconds(to)}) must be after "from" (${formatVideoSeconds(from)}).`);
1470
1573
  }
1471
- /** Moves a media clip's in point by `delta` of timeline time (stills, titles and colours have none). */
1574
+ /** Moves a media or compound clip's in point by `delta` of timeline time (stills, titles and colours have none). */
1472
1575
  moveIn(clip, delta) {
1473
- if (clip.type !== "media" || _optionalChain([videoAssetMap, 'call', _19 => _19(this.p), 'access', _20 => _20.get, 'call', _21 => _21(clip.asset), 'optionalAccess', _22 => _22.kind]) === "image") return;
1576
+ if (clip.type === "sequence") {
1577
+ clip.in = Math.max(0, (_nullishCoalesce(clip.in, () => ( 0))) + delta);
1578
+ if (clip.in < 1e-9) delete clip.in;
1579
+ return;
1580
+ }
1581
+ if (clip.type !== "media" || _optionalChain([videoAssetMap, 'call', _21 => _21(this.p), 'access', _22 => _22.get, 'call', _23 => _23(clip.asset), 'optionalAccess', _24 => _24.kind]) === "image") return;
1474
1582
  clip.in = Math.max(0, (_nullishCoalesce(clip.in, () => ( 0))) + delta * videoClipSpeed(clip));
1475
1583
  if (clip.in < 1e-9) delete clip.in;
1476
1584
  }
@@ -1497,7 +1605,7 @@ var Editor = (_class2 = class {
1497
1605
  clip.duration = at - clip.start;
1498
1606
  delete clip.fadeOut;
1499
1607
  delete clip.animateOut;
1500
- if (clip.type === "media") delete clip.fadeOutCurve;
1608
+ if (clip.type === "media" || clip.type === "sequence") delete clip.fadeOutCurve;
1501
1609
  track.clips.push(right);
1502
1610
  track.clips.sort((a, b) => a.start - b.start);
1503
1611
  return right;
@@ -1619,7 +1727,7 @@ var Editor = (_class2 = class {
1619
1727
  }
1620
1728
  case "remove_asset": {
1621
1729
  const asset = this.asset(e.asset);
1622
- const users = this.p.tracks.flatMap((t) => t.clips.filter((c) => c.type === "media" && c.asset === asset.id).map((c) => c.id));
1730
+ const users = allVideoTracks(this.p).flatMap(({ track: t }) => t.clips.filter((c) => c.type === "media" && c.asset === asset.id).map((c) => c.id));
1623
1731
  if (users.length) refuse(`Asset "${asset.id}" is used by clip${users.length > 1 ? "s" : ""} ${users.map((u) => `"${u}"`).join(", ")}; delete ${users.length > 1 ? "them" : "it"} first.`);
1624
1732
  this.p.assets = this.p.assets.filter((a) => a !== asset);
1625
1733
  return this.outcomes.push({ op: e.op, asset: asset.id });
@@ -1661,7 +1769,7 @@ var Editor = (_class2 = class {
1661
1769
  const mode = this.placement(e.mode, e.ripple);
1662
1770
  const draft = { ...e.clip, type: _nullishCoalesce(e.clip.type, () => ( "media")) };
1663
1771
  this.kindFits(track, draft);
1664
- const ids = this.p.tracks.flatMap((t) => t.clips.map((c) => c.id));
1772
+ const ids = this.ids();
1665
1773
  if (draft.id !== void 0 && ids.includes(draft.id)) refuse(`There is already a clip "${draft.id}".`);
1666
1774
  draft.id ??= freshId("c", ids);
1667
1775
  if (draft.duration === void 0) {
@@ -1671,6 +1779,10 @@ var Editor = (_class2 = class {
1671
1779
  else if (asset.duration === void 0) refuse(`Asset "${asset.id}" has no duration, so give the clip a "duration".`);
1672
1780
  else draft.duration = (asset.duration - (_nullishCoalesce(draft.in, () => ( 0)))) / (_nullishCoalesce(draft.speed, () => ( 1)));
1673
1781
  if (!(draft.duration > 0)) refuse(`"in" (${draft.in} ms) is at or past the end of "${asset.id}" (${asset.duration} ms).`);
1782
+ } else if (draft.type === "sequence") {
1783
+ const length = videoSequenceDuration(videoSequenceMap(this.p).get(draft.sequence)) - (_nullishCoalesce(draft.in, () => ( 0)));
1784
+ if (!(length > 0)) refuse(`"in" (${_nullishCoalesce(draft.in, () => ( 0))} ms) is at or past the end of sequence "${draft.sequence}".`);
1785
+ draft.duration = length;
1674
1786
  } else draft.duration = VIDEO_DEFAULT_TITLE_DURATION;
1675
1787
  }
1676
1788
  const end = sortedVideoClips(track).reduce((m, c) => Math.max(m, videoClipEnd(c)), 0);
@@ -1705,7 +1817,7 @@ var Editor = (_class2 = class {
1705
1817
  if (e.edge === "start") {
1706
1818
  const delta = e.to - clip.start;
1707
1819
  if (clip.duration - delta < minimum - VIDEO_TIME_EPSILON) refuse(`Trimming the start to ${formatVideoSeconds(e.to)} would leave "${clip.id}" shorter than a frame; it ends at ${formatVideoSeconds(end)}.`);
1708
- if (clip.type === "media" && !isRamped(clip)) {
1820
+ if (clip.type === "media" && !isRamped(clip) || clip.type === "sequence") {
1709
1821
  const sourceIn = (_nullishCoalesce(clip.in, () => ( 0))) + delta * speed;
1710
1822
  if (sourceIn < -VIDEO_TIME_EPSILON) refuse(`"${clip.id}" starts ${Math.round((_nullishCoalesce(clip.in, () => ( 0))) / speed)} ms into its source, so its start can move at most that far earlier (to ${formatVideoSeconds(clip.start - (_nullishCoalesce(clip.in, () => ( 0))) / speed)}).`);
1711
1823
  clip.in = Math.max(0, sourceIn);
@@ -1736,9 +1848,14 @@ var Editor = (_class2 = class {
1736
1848
  if (clip.type === "media" && !isRamped(clip)) {
1737
1849
  const asset = assets.get(clip.asset);
1738
1850
  const out = (_nullishCoalesce(clip.in, () => ( 0))) + duration * speed;
1739
- if (_optionalChain([asset, 'optionalAccess', _23 => _23.duration]) !== void 0 && out > asset.duration + VIDEO_TIME_EPSILON)
1851
+ if (_optionalChain([asset, 'optionalAccess', _25 => _25.duration]) !== void 0 && out > asset.duration + VIDEO_TIME_EPSILON)
1740
1852
  refuse(`"${clip.id}" can end at most at ${formatVideoSeconds(clip.start + (asset.duration - (_nullishCoalesce(clip.in, () => ( 0)))) / speed)}, where "${asset.id}" runs out.`);
1741
1853
  }
1854
+ if (clip.type === "sequence") {
1855
+ const seq = videoSequenceMap(this.p).get(clip.sequence);
1856
+ const length = seq ? videoSequenceDuration(seq) : Infinity;
1857
+ if ((_nullishCoalesce(clip.in, () => ( 0))) + duration > length + VIDEO_TIME_EPSILON) refuse(`"${clip.id}" can end at most at ${formatVideoSeconds(clip.start + length - (_nullishCoalesce(clip.in, () => ( 0))))}, where sequence "${clip.sequence}" ends.`);
1858
+ }
1742
1859
  if (e.ripple) {
1743
1860
  const tracks = this.rippleTracks([track]);
1744
1861
  let created = [];
@@ -1794,7 +1911,7 @@ var Editor = (_class2 = class {
1794
1911
  let stretch = 1;
1795
1912
  if (at.clip.type === "media" && !("duration" in set)) {
1796
1913
  const was = videoClipSpeed(at.clip);
1797
- const now = "speed" in set ? set.speed : _optionalChain([e, 'access', _24 => _24.remove, 'optionalAccess', _25 => _25.includes, 'call', _26 => _26("speed")]) ? 1 : was;
1914
+ const now = "speed" in set ? set.speed : _optionalChain([e, 'access', _26 => _26.remove, 'optionalAccess', _27 => _27.includes, 'call', _28 => _28("speed")]) ? 1 : was;
1798
1915
  if (now !== was) {
1799
1916
  set.duration = at.clip.duration * was / now;
1800
1917
  stretch = was / now;
@@ -1813,6 +1930,7 @@ var Editor = (_class2 = class {
1813
1930
  case "set_time_remap": {
1814
1931
  const { track, clip } = this.clip(e.clip);
1815
1932
  this.unlocked(track);
1933
+ if (clip.type === "sequence") refuse(`Clip "${clip.id}" is a compound clip, which plays its sequence at normal speed; ramp the clips inside the sequence instead.`);
1816
1934
  if (clip.type !== "media") refuse(`Clip "${clip.id}" is a ${clip.type === "text" ? "title" : "colour"}, so it has no source to ramp; only video and audio clips change speed.`);
1817
1935
  const asset = this.asset(clip.asset);
1818
1936
  if (asset.kind === "image") refuse(`Clip "${clip.id}" is a still ("${asset.id}"), so it has no motion to ramp.`);
@@ -1870,16 +1988,16 @@ var Editor = (_class2 = class {
1870
1988
  case "slip_clip": {
1871
1989
  const { track, clip } = this.clip(e.clip);
1872
1990
  this.unlocked(track);
1873
- if (clip.type !== "media") refuse(`Clip "${clip.id}" is a ${clip.type === "text" ? "title" : "colour"}, so it has no source to slip; only video and audio clips slip.`);
1874
- const asset = this.asset(clip.asset);
1875
- if (asset.kind === "image") refuse(`Clip "${clip.id}" is a still ("${asset.id}"), so it has no source to slip; only video and audio clips slip.`);
1876
- const handles = videoClipHandles(clip, videoAssetMap(this.p));
1991
+ if (clip.type !== "media" && clip.type !== "sequence") refuse(`Clip "${clip.id}" is a ${clip.type === "text" ? "title" : "colour"}, so it has no source to slip; only video, audio and compound clips slip.`);
1992
+ const source = clip.type === "sequence" ? `sequence "${clip.sequence}"` : `"${this.asset(clip.asset).id}"`;
1993
+ if (clip.type === "media" && this.asset(clip.asset).kind === "image") refuse(`Clip "${clip.id}" is a still ("${clip.asset}"), so it has no source to slip; only video and audio clips slip.`);
1994
+ const handles = videoClipHandles(clip, videoAssetMap(this.p), videoSequenceMap(this.p));
1877
1995
  const { lead, tail } = videoClipOverhang(track, clip);
1878
1996
  const delta = this.clamp(
1879
1997
  e.delta,
1880
1998
  lead - handles.before,
1881
1999
  handles.after - tail,
1882
- () => e.delta > 0 ? `"${clip.id}" already plays to the end of "${asset.id}"${tail ? ` (less the ${tail} ms the transition after it needs)` : ""}, so it cannot slip later; slip it earlier (a negative delta).` : `"${clip.id}" already starts at the beginning of "${asset.id}"${lead ? ` (plus the ${lead} ms its transition needs)` : ""}, so it cannot slip earlier; slip it later (a positive delta).`
2000
+ () => e.delta > 0 ? `"${clip.id}" already plays to the end of ${source}${tail ? ` (less the ${tail} ms the transition after it needs)` : ""}, so it cannot slip later; slip it earlier (a negative delta).` : `"${clip.id}" already starts at the beginning of ${source}${lead ? ` (plus the ${lead} ms its transition needs)` : ""}, so it cannot slip earlier; slip it later (a positive delta).`
1883
2001
  );
1884
2002
  this.moveIn(clip, delta);
1885
2003
  return this.outcomes.push({ op: e.op, clip: clip.id, track: track.id, delta });
@@ -1897,8 +2015,8 @@ var Editor = (_class2 = class {
1897
2015
  refuse(`"${L.id}" ends at ${formatVideoSeconds(videoClipEnd(L))} but "${R.id}" starts at ${formatVideoSeconds(R.start)}; a roll needs two clips that meet at a cut (use trim_clip to move one edge).`);
1898
2016
  }
1899
2017
  const assets = videoAssetMap(this.p);
1900
- const lo = Math.max(-(L.duration - this.frame), -(videoClipHandles(R, assets).before - videoClipOverhang(track, R).lead));
1901
- const hi = Math.min(R.duration - this.frame, videoClipHandles(L, assets).after - videoClipOverhang(track, L).tail);
2018
+ const lo = Math.max(-(L.duration - this.frame), -(videoClipHandles(R, assets, videoSequenceMap(this.p)).before - videoClipOverhang(track, R).lead));
2019
+ const hi = Math.min(R.duration - this.frame, videoClipHandles(L, assets, videoSequenceMap(this.p)).after - videoClipOverhang(track, L).tail);
1902
2020
  const delta = this.clamp(
1903
2021
  e.delta,
1904
2022
  lo,
@@ -1924,8 +2042,8 @@ var Editor = (_class2 = class {
1924
2042
  earlier: before ? before.duration - this.frame : clip.start - others.filter((c) => videoClipEnd(c) <= clip.start + VIDEO_TIME_EPSILON).reduce((m, c) => Math.max(m, videoClipEnd(c)), 0),
1925
2043
  later: after ? after.duration - this.frame : others.filter((c) => c.start >= end - VIDEO_TIME_EPSILON).reduce((m, c) => Math.min(m, c.start - end), Infinity)
1926
2044
  };
1927
- const lo = -Math.min(room.earlier, after ? videoClipHandles(after, assets).before - videoClipOverhang(track, after).lead : Infinity);
1928
- const hi = Math.min(room.later, before ? videoClipHandles(before, assets).after - videoClipOverhang(track, before).tail : Infinity);
2045
+ const lo = -Math.min(room.earlier, after ? videoClipHandles(after, assets, videoSequenceMap(this.p)).before - videoClipOverhang(track, after).lead : Infinity);
2046
+ const hi = Math.min(room.later, before ? videoClipHandles(before, assets, videoSequenceMap(this.p)).after - videoClipOverhang(track, before).tail : Infinity);
1929
2047
  const delta = this.clamp(
1930
2048
  e.delta,
1931
2049
  lo,
@@ -1940,7 +2058,7 @@ var Editor = (_class2 = class {
1940
2058
  this.trimHead(after, delta);
1941
2059
  }
1942
2060
  track.clips.sort((a, b) => a.start - b.start);
1943
- return this.outcomes.push({ op: e.op, clip: clip.id, clips: [_optionalChain([before, 'optionalAccess', _27 => _27.id]), clip.id, _optionalChain([after, 'optionalAccess', _28 => _28.id])].filter((x) => !!x), track: track.id, delta });
2061
+ return this.outcomes.push({ op: e.op, clip: clip.id, clips: [_optionalChain([before, 'optionalAccess', _29 => _29.id]), clip.id, _optionalChain([after, 'optionalAccess', _30 => _30.id])].filter((x) => !!x), track: track.id, delta });
1944
2062
  }
1945
2063
  case "lift_range":
1946
2064
  case "extract_range": {
@@ -1967,11 +2085,11 @@ var Editor = (_class2 = class {
1967
2085
  const { track, clip } = this.clip(e.clip);
1968
2086
  this.unlocked(track);
1969
2087
  if (!VIDEO_KEYFRAME_PROPERTIES.includes(e.property)) refuse(`"${e.property}" cannot be keyframed; use one of ${VIDEO_KEYFRAME_PROPERTIES.join(", ")}.`);
1970
- if (e.property === "volume" && clip.type !== "media") refuse(`Clip "${clip.id}" is a ${clip.type} clip and has no volume; volume keys go on media clips.`);
2088
+ if (e.property === "volume" && clip.type !== "media" && clip.type !== "sequence") refuse(`Clip "${clip.id}" is a ${clip.type} clip and has no volume; volume keys go on media and compound clips.`);
1971
2089
  const limits = VIDEO_KEYFRAME_LIMITS[e.property];
1972
2090
  const transform = VIDEO_TRANSFORM_PROPERTIES.includes(e.property);
1973
2091
  const c = clip;
1974
- const current = transform ? _optionalChain([c, 'access', _29 => _29.transform, 'optionalAccess', _30 => _30[e.property]]) : c[e.property];
2092
+ const current = transform ? _optionalChain([c, 'access', _31 => _31.transform, 'optionalAccess', _32 => _32[e.property]]) : c[e.property];
1975
2093
  let value;
1976
2094
  if (e.keys === null) {
1977
2095
  const v = videoValueAt(current, 0, limits.default);
@@ -2053,7 +2171,7 @@ function applyVideoEdits(project, edits) {
2053
2171
  editor.run(edit);
2054
2172
  } catch (error) {
2055
2173
  const message = error.message;
2056
- return { ok: false, error: `${list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _31 => _31.op])}): ` : ""}${message}${/Nothing was changed\.$/.test(message) ? "" : " Nothing was changed."}` };
2174
+ return { ok: false, error: `${list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _33 => _33.op])}): ` : ""}${message}${/Nothing was changed\.$/.test(message) ? "" : " Nothing was changed."}` };
2057
2175
  }
2058
2176
  }
2059
2177
  let next;
@@ -2072,7 +2190,7 @@ function applyVideoEdits(project, edits) {
2072
2190
 
2073
2191
  // src/video-editor/evaluate.ts
2074
2192
  function videoTransformAt(transform, clipTime = 0) {
2075
- return { x: videoValueAt(_optionalChain([transform, 'optionalAccess', _32 => _32.x]), clipTime, 0), y: videoValueAt(_optionalChain([transform, 'optionalAccess', _33 => _33.y]), clipTime, 0), scale: videoValueAt(_optionalChain([transform, 'optionalAccess', _34 => _34.scale]), clipTime, 1), rotation: videoValueAt(_optionalChain([transform, 'optionalAccess', _35 => _35.rotation]), clipTime, 0) };
2193
+ return { x: videoValueAt(_optionalChain([transform, 'optionalAccess', _34 => _34.x]), clipTime, 0), y: videoValueAt(_optionalChain([transform, 'optionalAccess', _35 => _35.y]), clipTime, 0), scale: videoValueAt(_optionalChain([transform, 'optionalAccess', _36 => _36.scale]), clipTime, 1), rotation: videoValueAt(_optionalChain([transform, 'optionalAccess', _37 => _37.rotation]), clipTime, 0) };
2076
2194
  }
2077
2195
  function placeVideoRect(frame, size, fit = "contain", transform = {}, clipTime = 0) {
2078
2196
  let w = size.width;
@@ -2118,13 +2236,13 @@ function videoFadeCurveGain(curve, x) {
2118
2236
  }
2119
2237
  function fadeGain(clip, time, sound = false) {
2120
2238
  let g = 1;
2121
- const shape = sound && clip.type === "media" ? clip : void 0;
2122
- if (clip.fadeIn) g *= videoFadeCurveGain(_optionalChain([shape, 'optionalAccess', _36 => _36.fadeInCurve]), (time - clip.start) / clip.fadeIn);
2123
- if (clip.fadeOut) g *= videoFadeCurveGain(_optionalChain([shape, 'optionalAccess', _37 => _37.fadeOutCurve]), (videoClipEnd(clip) - time) / clip.fadeOut);
2239
+ const shape = sound && (clip.type === "media" || clip.type === "sequence") ? clip : void 0;
2240
+ if (clip.fadeIn) g *= videoFadeCurveGain(_optionalChain([shape, 'optionalAccess', _38 => _38.fadeInCurve]), (time - clip.start) / clip.fadeIn);
2241
+ if (clip.fadeOut) g *= videoFadeCurveGain(_optionalChain([shape, 'optionalAccess', _39 => _39.fadeOutCurve]), (videoClipEnd(clip) - time) / clip.fadeOut);
2124
2242
  return g;
2125
2243
  }
2126
2244
  function videoClipSoundGain(clip, clipTime) {
2127
- const volume = clip.type === "media" ? videoValueAt(clip.volume, clipTime, 1) : 1;
2245
+ const volume = clip.type === "media" || clip.type === "sequence" ? videoValueAt(clip.volume, clipTime, 1) : 1;
2128
2246
  return volume * fadeGain(clip, clip.start + Math.min(Math.max(clipTime, 0), clip.duration), true);
2129
2247
  }
2130
2248
  function activeOnTrack(track, time) {
@@ -2133,7 +2251,7 @@ function activeOnTrack(track, time) {
2133
2251
  const before = clip.transition ? videoClipBefore(track, clip) : null;
2134
2252
  const lead = before ? clip.transition.duration / 2 : 0;
2135
2253
  const next = videoClipAfter(track, clip);
2136
- const tail = _optionalChain([next, 'optionalAccess', _38 => _38.transition]) ? next.transition.duration / 2 : 0;
2254
+ const tail = _optionalChain([next, 'optionalAccess', _40 => _40.transition]) ? next.transition.duration / 2 : 0;
2137
2255
  if (time < clip.start - lead || time >= videoClipEnd(clip) + tail) continue;
2138
2256
  const a = { clip };
2139
2257
  if (lead && time < clip.start + lead) a.incoming = { progress: clamp012((time - (clip.start - lead)) / (2 * lead)), type: clip.transition.type, transition: clip.transition };
@@ -2144,28 +2262,43 @@ function activeOnTrack(track, time) {
2144
2262
  }
2145
2263
  var sourceTimeAt = (clip, time) => clip.type === "media" ? videoClipSourceTime(clip, time - clip.start) : 0;
2146
2264
  function videoFrameAt(project, time) {
2147
- const assets = videoAssetMap(project);
2265
+ const { layers, audio } = tracksAt(project, project.tracks, time, { assets: videoAssetMap(project), sequences: videoSequenceMap(project), within: [], prefix: "" });
2266
+ return { time, width: project.width, height: project.height, background: _nullishCoalesce(project.background, () => ( "#000000")), layers, audio };
2267
+ }
2268
+ function tracksAt(project, tracks, time, scope) {
2269
+ const { assets, prefix } = scope;
2148
2270
  const layers = [];
2149
2271
  const audio = [];
2150
- for (let ti = project.tracks.length - 1; ti >= 0; ti--) {
2151
- const track = project.tracks[ti];
2272
+ for (let ti = tracks.length - 1; ti >= 0; ti--) {
2273
+ const track = tracks[ti];
2152
2274
  for (const { clip, incoming, outgoing } of activeOnTrack(track, time)) {
2153
- if (_optionalChain([incoming, 'optionalAccess', _39 => _39.type]) === "dip" && incoming.progress < 0.5) continue;
2154
- if (_optionalChain([outgoing, 'optionalAccess', _40 => _40.type]) === "dip" && outgoing.progress >= 0.5) continue;
2275
+ if (_optionalChain([incoming, 'optionalAccess', _41 => _41.type]) === "dip" && incoming.progress < 0.5) continue;
2276
+ if (_optionalChain([outgoing, 'optionalAccess', _42 => _42.type]) === "dip" && outgoing.progress >= 0.5) continue;
2155
2277
  const inside = Math.min(Math.max(time, clip.start), videoClipEnd(clip));
2156
2278
  const fade = fadeGain(clip, inside);
2157
2279
  const level = (t, transition, into) => videoFadeCurveGain(videoTransitionAudioCurve(transition), t.type === "dip" ? Math.abs(1 - 2 * t.progress) : into ? t.progress : 1 - t.progress);
2158
2280
  const mix = (incoming ? level(incoming, incoming.transition, true) : 1) * (outgoing ? level(outgoing, outgoing.next.transition, false) : 1);
2159
2281
  const asset = clip.type === "media" ? assets.get(clip.asset) : void 0;
2160
- const sourceTime = sourceTimeAt(clip, time);
2282
+ const sourceTime = clip.type === "sequence" ? (_nullishCoalesce(clip.in, () => ( 0))) + time - clip.start : sourceTimeAt(clip, time);
2161
2283
  const clipTime = time - clip.start;
2284
+ let nested = null;
2285
+ if (clip.type === "sequence") {
2286
+ const seq = scope.sequences.get(clip.sequence);
2287
+ if (!seq || scope.within.includes(seq.id)) continue;
2288
+ nested = tracksAt(project, seq.tracks, sourceTime, { ...scope, within: [...scope.within, seq.id], prefix: `${prefix}${clip.id}/` });
2289
+ }
2162
2290
  const audible = videoAssetHasSound(asset);
2163
2291
  if (audible && clip.type === "media" && !clip.muted && !track.muted) {
2164
2292
  const gain = videoValueAt(clip.volume, clipTime, 1) * (_nullishCoalesce(track.volume, () => ( 1))) * fadeGain(clip, inside, true) * mix;
2165
- audio.push({ clip: clip.id, track: track.id, asset: asset.id, src: asset.src, sourceTime, gain, speed: videoClipSpeedAt(clip, clipTime) });
2293
+ audio.push({ clip: prefix + clip.id, track: track.id, asset: asset.id, src: asset.src, sourceTime, gain, speed: videoClipSpeedAt(clip, clipTime) });
2294
+ }
2295
+ if (nested && clip.type === "sequence" && !clip.muted && !track.muted) {
2296
+ const gain = videoValueAt(clip.volume, clipTime, 1) * (_nullishCoalesce(track.volume, () => ( 1))) * fadeGain(clip, inside, true) * mix;
2297
+ for (const a of nested.audio) audio.push({ ...a, gain: a.gain * gain });
2166
2298
  }
2167
2299
  if (track.kind !== "video" || track.hidden) continue;
2168
2300
  if (clip.type === "media" && (!asset || asset.kind === "audio")) continue;
2301
+ if (nested && !nested.layers.length) continue;
2169
2302
  const shown = videoClipAnimated(clip, project);
2170
2303
  let opacity = videoValueAt(shown.opacity, clipTime, 1) * fade;
2171
2304
  let wipe;
@@ -2182,31 +2315,35 @@ function videoFrameAt(project, time) {
2182
2315
  shift = videoTransitionEase(incoming.progress) - 1;
2183
2316
  } else dip = { color: _nullishCoalesce(incoming.transition.color, () => ( "#000000")), opacity: (1 - incoming.progress) * 2 };
2184
2317
  }
2185
- if (_optionalChain([outgoing, 'optionalAccess', _41 => _41.type]) === "dip") dip = { color: _nullishCoalesce(outgoing.next.transition.color, () => ( "#000000")), opacity: outgoing.progress * 2 };
2186
- if (_optionalChain([outgoing, 'optionalAccess', _42 => _42.type]) === "push") {
2318
+ if (_optionalChain([outgoing, 'optionalAccess', _43 => _43.type]) === "dip") dip = { color: _nullishCoalesce(outgoing.next.transition.color, () => ( "#000000")), opacity: outgoing.progress * 2 };
2319
+ if (_optionalChain([outgoing, 'optionalAccess', _44 => _44.type]) === "push") {
2187
2320
  shiftDirection = _nullishCoalesce(outgoing.next.transition.direction, () => ( "right"));
2188
2321
  shift = videoTransitionEase(outgoing.progress);
2189
2322
  }
2190
2323
  const size = clip.type === "media" ? { width: _nullishCoalesce(asset.width, () => ( project.width)), height: _nullishCoalesce(asset.height, () => ( project.height)) } : { width: project.width, height: project.height };
2191
- const fit = _nullishCoalesce(clip.fit, () => ( (clip.type === "media" ? "contain" : "fill")));
2324
+ const fit = _nullishCoalesce(clip.fit, () => ( (clip.type === "media" || clip.type === "sequence" ? "contain" : "fill")));
2192
2325
  const placed = placeVideoRect(project, size, clip.type === "text" ? "fill" : fit, shown.transform, clipTime);
2193
2326
  const across = shiftDirection === "left" || shiftDirection === "right";
2194
2327
  const sign = shiftDirection === "left" || shiftDirection === "up" ? -1 : 1;
2195
2328
  const rect = shift ? { ...placed, ...across ? { x: placed.x + sign * shift * project.width } : { y: placed.y + sign * shift * project.height } } : placed;
2196
- const layer = { clip: clip.id, track: track.id, type: clip.type, opacity: clamp012(opacity), rect, rotation: videoValueAt(_optionalChain([shown, 'access', _43 => _43.transform, 'optionalAccess', _44 => _44.rotation]), clipTime, 0) };
2329
+ const layer = { clip: prefix + clip.id, track: track.id, type: clip.type, opacity: clamp012(opacity), rect, rotation: videoValueAt(_optionalChain([shown, 'access', _45 => _45.transform, 'optionalAccess', _46 => _46.rotation]), clipTime, 0) };
2197
2330
  if (wipe) layer.wipe = wipe;
2198
2331
  if (iris) layer.iris = iris;
2199
2332
  if (clip.blend && clip.blend !== "normal") layer.blend = clip.blend;
2200
2333
  const grade = normalizeVideoGrade(clip.grade);
2201
2334
  if (grade) layer.grade = grade;
2202
2335
  if (clip.type === "media") Object.assign(layer, { asset: asset.id, assetKind: asset.kind, src: asset.src, sourceTime });
2336
+ if (clip.type === "sequence") Object.assign(layer, { sequence: clip.sequence, sourceTime, layers: nested.layers });
2203
2337
  if (clip.type === "text") layer.text = videoTextStyle(clip);
2204
2338
  if (clip.type === "color") layer.color = clip.color;
2205
2339
  if (layer.opacity > 0) layers.push(layer);
2206
- if (dip && dip.opacity > 0) layers.push({ clip: `transition:${incoming ? clip.id : outgoing.next.id}`, track: track.id, type: "color", color: dip.color, opacity: clamp012(dip.opacity), rect: { x: 0, y: 0, width: project.width, height: project.height }, rotation: 0 });
2340
+ if (dip && dip.opacity > 0) layers.push({ clip: `transition:${prefix}${incoming ? clip.id : outgoing.next.id}`, track: track.id, type: "color", color: dip.color, opacity: clamp012(dip.opacity), rect: { x: 0, y: 0, width: project.width, height: project.height }, rotation: 0 });
2207
2341
  }
2208
2342
  }
2209
- return { time, width: project.width, height: project.height, background: _nullishCoalesce(project.background, () => ( "#000000")), layers, audio };
2343
+ return { layers, audio };
2344
+ }
2345
+ function videoFrameLeafLayers(layers) {
2346
+ return layers.flatMap((l) => l.layers ? videoFrameLeafLayers(l.layers) : [l]);
2210
2347
  }
2211
2348
 
2212
2349
  // src/video-editor/audio.ts
@@ -2864,8 +3001,8 @@ function videoTranscriptTrack(project, track) {
2864
3001
  return track;
2865
3002
  }
2866
3003
  const assets = videoAssetMap(project);
2867
- const transcribed = (t) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access', _45 => _45.get, 'call', _46 => _46(c.asset), 'optionalAccess', _47 => _47.transcript, 'optionalAccess', _48 => _48.length]));
2868
- return _nullishCoalesce(_optionalChain([(_nullishCoalesce(project.tracks.find((t) => t.kind === "audio" && transcribed(t)), () => ( project.tracks.find(transcribed)))), 'optionalAccess', _49 => _49.id]), () => ( null));
3004
+ const transcribed = (t) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access', _47 => _47.get, 'call', _48 => _48(c.asset), 'optionalAccess', _49 => _49.transcript, 'optionalAccess', _50 => _50.length]));
3005
+ return _nullishCoalesce(_optionalChain([(_nullishCoalesce(project.tracks.find((t) => t.kind === "audio" && transcribed(t)), () => ( project.tracks.find(transcribed)))), 'optionalAccess', _51 => _51.id]), () => ( null));
2869
3006
  }
2870
3007
  function videoTimelineTranscript(project, options = {}) {
2871
3008
  const id = videoTranscriptTrack(project, options.track);
@@ -2875,8 +3012,8 @@ function videoTimelineTranscript(project, options = {}) {
2875
3012
  const out = [];
2876
3013
  for (const clip of sortedVideoClips(track)) {
2877
3014
  if (clip.type !== "media") continue;
2878
- const words = _optionalChain([assets, 'access', _50 => _50.get, 'call', _51 => _51(clip.asset), 'optionalAccess', _52 => _52.transcript]);
2879
- if (!_optionalChain([words, 'optionalAccess', _53 => _53.length])) continue;
3015
+ const words = _optionalChain([assets, 'access', _52 => _52.get, 'call', _53 => _53(clip.asset), 'optionalAccess', _54 => _54.transcript]);
3016
+ if (!_optionalChain([words, 'optionalAccess', _55 => _55.length])) continue;
2880
3017
  const speed = videoClipSpeed(clip);
2881
3018
  const from = _nullishCoalesce(clip.in, () => ( 0));
2882
3019
  const to = from + clip.duration * speed;
@@ -2967,6 +3104,7 @@ function videoWordAt(words, time) {
2967
3104
  // src/video-editor/render.ts
2968
3105
  var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2969
3106
  var n = (v) => String(Math.round(v * 100) / 100);
3107
+ var n4 = (v) => String(Math.round(v * 1e4) / 1e4);
2970
3108
  var videoIrisRadius = (frame, progress) => progress * Math.hypot(frame.width / 2, frame.height / 2);
2971
3109
  function videoWipeRect(rect, wipe) {
2972
3110
  const { direction, progress } = wipe;
@@ -2979,15 +3117,18 @@ function videoWipeRect(rect, wipe) {
2979
3117
  }
2980
3118
  function videoFrameToSvg(frame, options = {}, assets = []) {
2981
3119
  const defs = [];
2982
- const body = [];
2983
3120
  const posters = new Map(assets.map((a) => [a.id, a.poster]));
2984
- frame.layers.forEach((layer, i) => {
3121
+ const draw = (layers, prefix) => layers.map((layer, index) => {
3122
+ const i = `${prefix}${index}`;
2985
3123
  const { rect } = layer;
2986
3124
  const attrs = [];
3125
+ const style = [];
2987
3126
  if (layer.opacity < 1) attrs.push(`opacity="${n(layer.opacity)}"`);
2988
- if (layer.blend) attrs.push(`style="mix-blend-mode:${layer.blend === "add" ? "plus-lighter" : layer.blend}"`);
3127
+ if (layer.blend) style.push(`mix-blend-mode:${layer.blend === "add" ? "plus-lighter" : layer.blend}`);
3128
+ if (layer.layers) style.push("isolation:isolate");
3129
+ if (style.length) attrs.push(`style="${style.join(";")}"`);
2989
3130
  if (layer.grade) {
2990
- defs.push(videoGradeSvgFilter(`g${i}`, layer.grade, layer.grade.lut ? _optionalChain([options, 'access', _54 => _54.lutFor, 'optionalCall', _55 => _55(layer.grade.lut.src)]) : null));
3131
+ defs.push(videoGradeSvgFilter(`g${i}`, layer.grade, layer.grade.lut ? _optionalChain([options, 'access', _56 => _56.lutFor, 'optionalCall', _57 => _57(layer.grade.lut.src)]) : null));
2991
3132
  attrs.push(`filter="url(#g${i})"`);
2992
3133
  }
2993
3134
  if (layer.rotation) attrs.push(`transform="rotate(${n(layer.rotation)} ${n(rect.x + rect.width / 2)} ${n(rect.y + rect.height / 2)})"`);
@@ -2998,7 +3139,9 @@ function videoFrameToSvg(frame, options = {}, assets = []) {
2998
3139
  }
2999
3140
  const box = `x="${n(rect.x)}" y="${n(rect.y)}" width="${n(rect.width)}" height="${n(rect.height)}"`;
3000
3141
  let inner = "";
3001
- if (layer.type === "color") inner = `<rect ${box} fill="${esc(_nullishCoalesce(layer.color, () => ( "#000000")))}"/>`;
3142
+ if (layer.layers) {
3143
+ inner = `<g transform="translate(${n(rect.x)} ${n(rect.y)}) scale(${n4(rect.width / frame.width)} ${n4(rect.height / frame.height)})">${draw(layer.layers, `${i}-`)}</g>`;
3144
+ } else if (layer.type === "color") inner = `<rect ${box} fill="${esc(_nullishCoalesce(layer.color, () => ( "#000000")))}"/>`;
3002
3145
  else if (layer.type === "text" && layer.text) {
3003
3146
  const t = layer.text;
3004
3147
  const lines = t.text.split("\n");
@@ -3026,9 +3169,10 @@ function videoFrameToSvg(frame, options = {}, assets = []) {
3026
3169
  defs.push(`<clipPath id="i${i}"><circle cx="${n(frame.width / 2)}" cy="${n(frame.height / 2)}" r="${n(videoIrisRadius(frame, layer.iris.progress))}"/></clipPath>`);
3027
3170
  item = `<g clip-path="url(#i${i})">${item}</g>`;
3028
3171
  }
3029
- body.push(item);
3030
- });
3031
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n(frame.width)} ${n(frame.height)}" width="${n(frame.width)}" height="${n(frame.height)}">${defs.length ? `<defs>${defs.join("")}</defs>` : ""}<rect width="100%" height="100%" fill="${esc(frame.background)}"/>${body.join("")}</svg>`;
3172
+ return item;
3173
+ }).join("");
3174
+ const body = draw(frame.layers, "");
3175
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n(frame.width)} ${n(frame.height)}" width="${n(frame.width)}" height="${n(frame.height)}">${defs.length ? `<defs>${defs.join("")}</defs>` : ""}<rect width="100%" height="100%" fill="${esc(frame.background)}"/>${body}</svg>`;
3032
3176
  }
3033
3177
  function videoProjectSvg(project, time, options = {}) {
3034
3178
  return videoFrameToSvg(videoFrameAt(project, time), { fps: project.fps, ...options }, project.assets);
@@ -3112,13 +3256,36 @@ function drawVideoFrame(ctx, frame, sourceFor, scale = 1, options = {}) {
3112
3256
  ctx.globalAlpha = 1;
3113
3257
  ctx.fillStyle = frame.background;
3114
3258
  ctx.fillRect(0, 0, frame.width, frame.height);
3259
+ drawVideoLayers(ctx, frame, frame.layers, sourceFor, scale, options, 0);
3260
+ ctx.restore();
3261
+ }
3262
+ function drawVideoLayers(ctx, frame, layers, sourceFor, scale, options, depth) {
3115
3263
  const cw = Math.max(1, Math.round(frame.width * scale));
3116
3264
  const ch = Math.max(1, Math.round(frame.height * scale));
3117
- for (const layer of frame.layers) {
3118
- const setting = _optionalChain([layer, 'access', _56 => _56.grade, 'optionalAccess', _57 => _57.lut]);
3119
- const lut = setting ? _optionalChain([options, 'access', _58 => _58.lutFor, 'optionalCall', _59 => _59(setting.src)]) : null;
3120
- const scratch = lut ? _optionalChain([options, 'access', _60 => _60.scratch, 'optionalCall', _61 => _61(cw, ch)]) : null;
3265
+ for (const layer of layers) {
3266
+ let source = sourceFor;
3267
+ if (layer.layers) {
3268
+ const group = _optionalChain([options, 'access', _58 => _58.scratch, 'optionalCall', _59 => _59(cw, ch, 2 * depth)]);
3269
+ if (!group) {
3270
+ drawVideoLayers(ctx, frame, layer.layers, sourceFor, scale, options, depth + 1);
3271
+ continue;
3272
+ }
3273
+ const g = group.ctx;
3274
+ g.save();
3275
+ g.setTransform(1, 0, 0, 1, 0, 0);
3276
+ g.clearRect(0, 0, cw, ch);
3277
+ g.setTransform(scale, 0, 0, scale, 0, 0);
3278
+ g.globalAlpha = 1;
3279
+ g.globalCompositeOperation = "source-over";
3280
+ drawVideoLayers(g, frame, layer.layers, sourceFor, scale, options, depth + 1);
3281
+ g.restore();
3282
+ source = (l) => l === layer ? group.canvas : sourceFor(l);
3283
+ }
3284
+ const setting = _optionalChain([layer, 'access', _60 => _60.grade, 'optionalAccess', _61 => _61.lut]);
3285
+ const lut = setting ? _optionalChain([options, 'access', _62 => _62.lutFor, 'optionalCall', _63 => _63(setting.src)]) : null;
3286
+ const scratch = lut ? _optionalChain([options, 'access', _64 => _64.scratch, 'optionalCall', _65 => _65(cw, ch, 2 * depth + 1)]) : null;
3121
3287
  ctx.save();
3288
+ ctx.setTransform(scale, 0, 0, scale, 0, 0);
3122
3289
  if (lut && scratch) {
3123
3290
  const s = scratch.ctx;
3124
3291
  s.save();
@@ -3127,7 +3294,7 @@ function drawVideoFrame(ctx, frame, sourceFor, scale = 1, options = {}) {
3127
3294
  s.setTransform(scale, 0, 0, scale, 0, 0);
3128
3295
  s.globalAlpha = 1;
3129
3296
  s.globalCompositeOperation = "source-over";
3130
- paintVideoLayer(s, frame, layer, sourceFor, options);
3297
+ paintVideoLayer(s, frame, layer, source, options);
3131
3298
  s.restore();
3132
3299
  const img = s.getImageData(0, 0, cw, ch);
3133
3300
  applyVideoLutToPixels(lut, img.data, _nullishCoalesce(setting.strength, () => ( 1)));
@@ -3139,15 +3306,14 @@ function drawVideoFrame(ctx, frame, sourceFor, scale = 1, options = {}) {
3139
3306
  } else {
3140
3307
  ctx.globalAlpha = layer.opacity;
3141
3308
  if (layer.blend && layer.blend !== "normal") ctx.globalCompositeOperation = layer.blend === "add" ? "lighter" : layer.blend;
3142
- paintVideoLayer(ctx, frame, layer, sourceFor, options);
3309
+ paintVideoLayer(ctx, frame, layer, source, options);
3143
3310
  }
3144
3311
  ctx.restore();
3145
3312
  }
3146
- ctx.restore();
3147
3313
  }
3148
3314
 
3149
3315
  // src/video-editor/export.ts
3150
- var reelName = (asset) => (_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _62 => _62.name]), () => ( _optionalChain([asset, 'optionalAccess', _63 => _63.id]))), () => ( "AX"))).replace(/\.[a-z0-9]+$/i, "").replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 8) || "AX";
3316
+ var reelName = (asset) => (_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _66 => _66.name]), () => ( _optionalChain([asset, 'optionalAccess', _67 => _67.id]))), () => ( "AX"))).replace(/\.[a-z0-9]+$/i, "").replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 8) || "AX";
3151
3317
  function toVideoEdl(project, options = {}) {
3152
3318
  const track = options.track !== void 0 ? project.tracks.find((t) => t.id === options.track) : [...project.tracks].reverse().find((t) => t.kind === "video");
3153
3319
  if (!track) throw new Error(options.track !== void 0 ? `There is no track "${options.track}".` : "The project has no video track.");
@@ -3182,9 +3348,10 @@ function toVideoEdl(project, options = {}) {
3182
3348
  lines.push(row(n2, reel, `${t.type === "crossfade" || t.type === "dip" ? "D" : "W001"} ${frames}`, sourceAt(clip, recIn), sourceAt(clip, recOut), recIn, recOut));
3183
3349
  } else lines.push(row(n2, reel, "C", sourceAt(clip, recIn), sourceAt(clip, recOut), recIn, recOut));
3184
3350
  if (clip.type === "media" && videoClipSpeed(clip) !== 1) lines.push(`M2 ${reel.padEnd(8)} ${(videoClipSpeed(clip) * fps).toFixed(1).padStart(6)} ${tc(sourceAt(clip, recIn))}`);
3185
- if (clip.type === "media") lines.push(`* FROM CLIP NAME: ${_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _64 => _64.name]), () => ( _optionalChain([asset, 'optionalAccess', _65 => _65.src]))), () => ( clip.asset))}`);
3351
+ if (clip.type === "media") lines.push(`* FROM CLIP NAME: ${_nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _68 => _68.name]), () => ( _optionalChain([asset, 'optionalAccess', _69 => _69.src]))), () => ( clip.asset))}`);
3186
3352
  if (clip.type === "text") lines.push(`* TITLE: ${clip.text.replace(/\n/g, " ")}`);
3187
3353
  if (clip.type === "color") lines.push(`* COLOR: ${clip.color}`);
3354
+ if (clip.type === "sequence") lines.push(`* COMPOUND CLIP: ${_nullishCoalesce(clip.name, () => ( clip.sequence))} (sequence ${clip.sequence} from ${tc(_nullishCoalesce(clip.in, () => ( 0)))}, not flattened)`);
3188
3355
  lines.push("");
3189
3356
  }
3190
3357
  return lines.join("\n");
@@ -3264,6 +3431,7 @@ function toVideoLoudnessMeasure(project, options = {}) {
3264
3431
  function buildFfmpeg(project, options, measure) {
3265
3432
  const notes = [];
3266
3433
  const assets = videoAssetMap(project);
3434
+ const sequences = videoSequenceMap(project);
3267
3435
  const D = videoProjectDuration(project);
3268
3436
  if (D <= 0) throw new Error("The project has no clips, so there is nothing to render.");
3269
3437
  const W = even(project.width);
@@ -3271,8 +3439,6 @@ function buildFfmpeg(project, options, measure) {
3271
3439
  const fps = project.fps;
3272
3440
  const inputs = [];
3273
3441
  const graph = measure ? [] : [`color=c=${color(_nullishCoalesce(project.background, () => ( "#000000")))}:s=${W}x${H}:r=${fps}:d=${sec(D)},format=yuva420p[base]`];
3274
- const audio = [];
3275
- let cur = "base";
3276
3442
  let usesDrawtext = false;
3277
3443
  const animated = /* @__PURE__ */ new Set();
3278
3444
  const ramped = /* @__PURE__ */ new Set();
@@ -3296,7 +3462,7 @@ function buildFfmpeg(project, options, measure) {
3296
3462
  const pts = videoTimeRemapBreakpoints(remap, Math.max(80, 2e3 / fps));
3297
3463
  const first = pts[0];
3298
3464
  const last = pts[pts.length - 1];
3299
- const n2 = audio.length;
3465
+ const n2 = ak;
3300
3466
  const fmtA = "aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo";
3301
3467
  const silence = (ms) => `anullsrc=r=48000:cl=stereo,atrim=duration=${sec(ms)},${fmtA}`;
3302
3468
  const pieces = [];
@@ -3345,180 +3511,228 @@ function buildFfmpeg(project, options, measure) {
3345
3511
  } else inputs.push(["-ss", sec((_nullishCoalesce(clip.in, () => ( 0))) - lead * speed), "-t", sec(span * speed), "-i", src]);
3346
3512
  return inputs.length - 1;
3347
3513
  };
3348
- for (const track of [...project.tracks].reverse()) {
3349
- for (const raw of sortedVideoClips(track)) {
3350
- const clip = videoClipAnimated(raw, project);
3351
- const { lead, tail } = videoClipOverhang(track, clip);
3352
- const t0 = clip.start - lead;
3353
- const span = clip.duration + lead + tail;
3354
- const end = videoClipEnd(clip);
3355
- const asset = clip.type === "media" ? assets.get(clip.asset) : void 0;
3356
- if (clip.type === "media" && !asset) {
3357
- notes.push(`Clip "${clip.id}" was left out: its asset "${clip.asset}" is missing.`);
3358
- continue;
3359
- }
3360
- let index;
3361
- if (!measure && asset && asset.kind !== "audio" && track.kind === "video" && !track.hidden) index = input(clip, asset, lead, tail);
3362
- const audible = videoAssetHasSound(asset);
3363
- if (audible && clip.type === "media" && !clip.muted && !track.muted) {
3364
- const ai = _nullishCoalesce(index, () => ( input(clip, asset, lead, tail)));
3365
- const trackGain = _nullishCoalesce(track.volume, () => ( 1));
3366
- const volume = isVideoKeyframes(clip.volume) ? `volume='${trackGain}*(${expr(clip.volume, `(t*1000${lead ? `-${Math.round(lead)}` : ""})`, 1, "volume")})':eval=frame` : `volume=${(_nullishCoalesce(clip.volume, () => ( 1))) * trackGain}`;
3367
- let source2 = `[${ai}:a]`;
3368
- if (clip.timeRemap) {
3369
- source2 = `[r${audio.length}]`;
3370
- graph.push(...rampedSound(clip.timeRemap, `${ai}:a`, source2, lead, clip.duration + tail));
3371
- ramped.add("sound");
3514
+ let ak = 0;
3515
+ let q = 0;
3516
+ const compose = (tracks, base, length, scope) => {
3517
+ let cur2 = base;
3518
+ const sounds = [];
3519
+ for (const track of [...tracks].reverse()) {
3520
+ for (const raw of sortedVideoClips(track)) {
3521
+ const clip = videoClipAnimated(raw, project);
3522
+ const { lead, tail } = videoClipOverhang(track, clip);
3523
+ const t0 = clip.start - lead;
3524
+ const span = clip.duration + lead + tail;
3525
+ const end = videoClipEnd(clip);
3526
+ const asset = clip.type === "media" ? assets.get(clip.asset) : void 0;
3527
+ if (clip.type === "media" && !asset) {
3528
+ notes.push(`Clip "${clip.id}" was left out: its asset "${clip.asset}" is missing.`);
3529
+ continue;
3372
3530
  }
3373
- const chain2 = [...clip.timeRemap ? [] : ["asetpts=PTS-STARTPTS", ...atempo(videoClipSpeed(clip))], volume];
3374
- const before2 = clip.transition ? videoClipBefore(track, clip) : null;
3375
- const next2 = sortedVideoClips(track).find((c) => c !== clip && Math.abs(c.start - end) < 0.5 && c.transition);
3376
- const inCurve = clip.transition ? afadeCurve(videoTransitionAudioCurve(clip.transition)) : "";
3377
- const outCurve = next2 ? afadeCurve(videoTransitionAudioCurve(next2.transition)) : "";
3378
- if (lead) chain2.push(`afade=t=in:st=0:d=${sec(2 * lead)}${inCurve}`);
3379
- if (tail) chain2.push(`afade=t=out:st=${sec(span - 2 * tail)}:d=${sec(2 * tail)}${outCurve}`);
3380
- if (before2 && clip.transition.type === "dip") chain2.push(`afade=t=in:st=0:d=${sec(clip.transition.duration / 2)}${inCurve}`);
3381
- if (_optionalChain([next2, 'optionalAccess', _66 => _66.transition, 'access', _67 => _67.type]) === "dip") chain2.push(`afade=t=out:st=${sec(span - next2.transition.duration / 2)}:d=${sec(next2.transition.duration / 2)}${outCurve}`);
3382
- if (clip.fadeIn) chain2.push(`afade=t=in:st=${sec(lead)}:d=${sec(clip.fadeIn)}${afadeCurve(clip.fadeInCurve)}`);
3383
- if (clip.fadeOut) chain2.push(`afade=t=out:st=${sec(lead + clip.duration - clip.fadeOut)}:d=${sec(clip.fadeOut)}${afadeCurve(clip.fadeOutCurve)}`);
3384
- chain2.push(`adelay=${Math.round(t0)}:all=1`);
3385
- graph.push(`${source2}${chain2.join(",")}[a${audio.length}]`);
3386
- audio.push(`[a${audio.length}]`);
3387
- }
3388
- if (measure || track.kind !== "video" || track.hidden) continue;
3389
- const enable = `enable='between(t,${sec(t0)},${sec(t0 + span)})'`;
3390
- const next = `v${k++}`;
3391
- const onTimeline = `(t*1000-${Math.round(clip.start)})`;
3392
- const onChain = (v) => `(${v}*1000${lead ? `-${Math.round(lead)}` : ""})`;
3393
- const tf = clip.transform;
3394
- const moving = VIDEO_TRANSFORM_PROPERTIES.some((p) => isVideoKeyframes(_optionalChain([tf, 'optionalAccess', _68 => _68[p]])));
3395
- if (clip.type === "text" && options.titleImage) {
3396
- inputs.push(["-loop", "1", "-framerate", String(fps), "-t", sec(span), "-i", options.titleImage(moving ? { ...clip, transform: void 0 } : clip, { width: project.width, height: project.height })]);
3397
- index = inputs.length - 1;
3398
- } else if (clip.type === "text") {
3399
- usesDrawtext = true;
3400
- const op = isVideoKeyframes(clip.opacity) ? `clip(${expr(clip.opacity, onTimeline, 1, "opacity")},0,1)` : String(_nullishCoalesce(clip.opacity, () => ( 1)));
3401
- const alpha = [op, clip.fadeIn ? `min(1,(t-${sec(clip.start)})/${sec(clip.fadeIn)})` : "", clip.fadeOut ? `min(1,(${sec(end)}-t)/${sec(clip.fadeOut)})` : ""].filter(Boolean).join("*");
3402
- const coord = (axis) => {
3403
- const v = _optionalChain([tf, 'optionalAccess', _69 => _69[axis]]);
3404
- const base = `(${axis === "x" ? "w-text_w" : "h-text_h"})/2`;
3405
- return isVideoKeyframes(v) ? `${axis}='${base}+(${expr(v, onTimeline, 0, "position")})'` : `${axis}=${base}+${_nullishCoalesce(v, () => ( 0))}`;
3531
+ const shows = scope.picture && track.kind === "video" && !track.hidden;
3532
+ let compound = null;
3533
+ if (clip.type === "sequence") {
3534
+ const seq = sequences.get(clip.sequence);
3535
+ if (!seq || scope.within.includes(seq.id)) {
3536
+ notes.push(`Compound clip "${clip.id}" was left out: its sequence "${clip.sequence}" ${seq ? "contains itself" : "is missing"}.`);
3537
+ continue;
3538
+ }
3539
+ const from = (_nullishCoalesce(clip.in, () => ( 0))) - lead;
3540
+ if (from < -0.5) notes.push(`Compound clip "${clip.id}"'s transition needs ${Math.round(-from)} ms of sequence "${seq.id}" before its start, which is not there; it shows nothing then.`);
3541
+ const length2 = Math.max(videoSequenceDuration(seq), from + span);
3542
+ const hears = scope.sound && !clip.muted && !track.muted;
3543
+ const n2 = q++;
3544
+ const nestedBase = shows ? `nb${n2}` : null;
3545
+ if (nestedBase) graph.push(`color=c=black@0:s=${W}x${H}:r=${fps}:d=${sec(length2)},format=yuva420p[${nestedBase}]`);
3546
+ const sub = compose(seq.tracks, nestedBase, length2, { within: [...scope.within, seq.id], picture: shows, sound: hears });
3547
+ compound = { picture: null, sound: null };
3548
+ if (sub.video) {
3549
+ graph.push(`[${sub.video}]trim=start=${sec(Math.max(0, from))}:duration=${sec(span)},setpts=PTS-STARTPTS[sq${n2}]`);
3550
+ compound.picture = `[sq${n2}]`;
3551
+ }
3552
+ if (sub.audio.length) {
3553
+ graph.push(`${sub.audio.join("")}amix=inputs=${sub.audio.length}:normalize=0:duration=longest,apad,atrim=start=${sec(Math.max(0, from))}:end=${sec(from + span)}[sm${n2}]`);
3554
+ compound.sound = `[sm${n2}]`;
3555
+ }
3556
+ }
3557
+ let index;
3558
+ if (shows && asset && asset.kind !== "audio") index = input(clip, asset, lead, tail);
3559
+ const audible = clip.type === "sequence" ? !!_optionalChain([compound, 'optionalAccess', _70 => _70.sound]) : videoAssetHasSound(asset);
3560
+ if (scope.sound && audible && (clip.type === "media" || clip.type === "sequence") && !clip.muted && !track.muted) {
3561
+ const trackGain = _nullishCoalesce(track.volume, () => ( 1));
3562
+ const volume = isVideoKeyframes(clip.volume) ? `volume='${trackGain}*(${expr(clip.volume, `(t*1000${lead ? `-${Math.round(lead)}` : ""})`, 1, "volume")})':eval=frame` : `volume=${(_nullishCoalesce(clip.volume, () => ( 1))) * trackGain}`;
3563
+ const ramp = clip.type === "media" ? clip.timeRemap : void 0;
3564
+ const ai = compound ? -1 : _nullishCoalesce(index, () => ( input(clip, asset, lead, tail)));
3565
+ let source2 = _nullishCoalesce(_optionalChain([compound, 'optionalAccess', _71 => _71.sound]), () => ( `[${ai}:a]`));
3566
+ if (ramp) {
3567
+ source2 = `[r${ak}]`;
3568
+ graph.push(...rampedSound(ramp, `${ai}:a`, source2, lead, clip.duration + tail));
3569
+ ramped.add("sound");
3570
+ }
3571
+ const chain2 = [...ramp ? [] : ["asetpts=PTS-STARTPTS", ...atempo(videoClipSpeed(clip))], volume];
3572
+ const before2 = clip.transition ? videoClipBefore(track, clip) : null;
3573
+ const next2 = sortedVideoClips(track).find((c) => c !== clip && Math.abs(c.start - end) < 0.5 && c.transition);
3574
+ const inCurve = clip.transition ? afadeCurve(videoTransitionAudioCurve(clip.transition)) : "";
3575
+ const outCurve = next2 ? afadeCurve(videoTransitionAudioCurve(next2.transition)) : "";
3576
+ if (lead) chain2.push(`afade=t=in:st=0:d=${sec(2 * lead)}${inCurve}`);
3577
+ if (tail) chain2.push(`afade=t=out:st=${sec(span - 2 * tail)}:d=${sec(2 * tail)}${outCurve}`);
3578
+ if (before2 && clip.transition.type === "dip") chain2.push(`afade=t=in:st=0:d=${sec(clip.transition.duration / 2)}${inCurve}`);
3579
+ if (_optionalChain([next2, 'optionalAccess', _72 => _72.transition, 'access', _73 => _73.type]) === "dip") chain2.push(`afade=t=out:st=${sec(span - next2.transition.duration / 2)}:d=${sec(next2.transition.duration / 2)}${outCurve}`);
3580
+ if (clip.fadeIn) chain2.push(`afade=t=in:st=${sec(lead)}:d=${sec(clip.fadeIn)}${afadeCurve(clip.fadeInCurve)}`);
3581
+ if (clip.fadeOut) chain2.push(`afade=t=out:st=${sec(lead + clip.duration - clip.fadeOut)}:d=${sec(clip.fadeOut)}${afadeCurve(clip.fadeOutCurve)}`);
3582
+ chain2.push(`adelay=${Math.round(t0)}:all=1`);
3583
+ graph.push(`${source2}${chain2.join(",")}[a${ak}]`);
3584
+ sounds.push(`[a${ak++}]`);
3585
+ }
3586
+ if (!shows || compound && !compound.picture) continue;
3587
+ const enable = `enable='between(t,${sec(t0)},${sec(t0 + span)})'`;
3588
+ const next = `v${k++}`;
3589
+ const onTimeline = `(t*1000-${Math.round(clip.start)})`;
3590
+ const onChain = (v) => `(${v}*1000${lead ? `-${Math.round(lead)}` : ""})`;
3591
+ const tf = clip.transform;
3592
+ const moving = VIDEO_TRANSFORM_PROPERTIES.some((p) => isVideoKeyframes(_optionalChain([tf, 'optionalAccess', _74 => _74[p]])));
3593
+ if (clip.type === "text" && options.titleImage) {
3594
+ inputs.push(["-loop", "1", "-framerate", String(fps), "-t", sec(span), "-i", options.titleImage(moving ? { ...clip, transform: void 0 } : clip, { width: project.width, height: project.height })]);
3595
+ index = inputs.length - 1;
3596
+ } else if (clip.type === "text") {
3597
+ usesDrawtext = true;
3598
+ if (scope.within.length) notes.push(`Title "${clip.id}" is inside a compound clip and drawn with drawtext, which leaves the sequence's transparent areas transparent, so it shows only over pictures in the sequence; pass titleImage to draw it anywhere.`);
3599
+ const op = isVideoKeyframes(clip.opacity) ? `clip(${expr(clip.opacity, onTimeline, 1, "opacity")},0,1)` : String(_nullishCoalesce(clip.opacity, () => ( 1)));
3600
+ const alpha = [op, clip.fadeIn ? `min(1,(t-${sec(clip.start)})/${sec(clip.fadeIn)})` : "", clip.fadeOut ? `min(1,(${sec(end)}-t)/${sec(clip.fadeOut)})` : ""].filter(Boolean).join("*");
3601
+ const coord = (axis) => {
3602
+ const v = _optionalChain([tf, 'optionalAccess', _75 => _75[axis]]);
3603
+ const base2 = `(${axis === "x" ? "w-text_w" : "h-text_h"})/2`;
3604
+ return isVideoKeyframes(v) ? `${axis}='${base2}+(${expr(v, onTimeline, 0, "position")})'` : `${axis}=${base2}+${_nullishCoalesce(v, () => ( 0))}`;
3605
+ };
3606
+ const fontSize = _nullishCoalesce(clip.fontSize, () => ( 64));
3607
+ const opts = [
3608
+ `text=${ffmpegFilterValue(clip.text)}`,
3609
+ options.fontFile ? `fontfile=${ffmpegFilterValue(options.fontFile)}` : `font=${ffmpegFilterValue(_nullishCoalesce(clip.fontFamily, () => ( "sans-serif")))}`,
3610
+ isVideoKeyframes(_optionalChain([tf, 'optionalAccess', _76 => _76.scale])) ? `fontsize='${fontSize}*(${expr(tf.scale, onTimeline, 1, "scale")})'` : `fontsize=${Math.round(fontSize * (_nullishCoalesce(_optionalChain([tf, 'optionalAccess', _77 => _77.scale]), () => ( 1))))}`,
3611
+ `fontcolor=${color(_nullishCoalesce(clip.color, () => ( "#ffffff")))}`,
3612
+ coord("x"),
3613
+ coord("y"),
3614
+ `alpha='${alpha}'`,
3615
+ ...clip.background ? ["box=1", `boxcolor=${color(clip.background)}`, `boxborderw=${Math.round((_nullishCoalesce(clip.fontSize, () => ( 64))) / 2)}`] : [],
3616
+ `enable='between(t,${sec(clip.start)},${sec(end)})'`
3617
+ ];
3618
+ if (_optionalChain([tf, 'optionalAccess', _78 => _78.rotation])) notes.push(`Title "${clip.id}" is drawn without its rotation (drawtext cannot rotate).`);
3619
+ if (videoGradeFfmpegFilters(clip.grade).length || normalizeVideoGradeLut(_optionalChain([clip, 'access', _79 => _79.grade, 'optionalAccess', _80 => _80.lut]))) notes.push(`Title "${clip.id}" is drawn without its grade (drawtext draws straight onto the picture); pass titleImage to grade titles.`);
3620
+ if (clip.blend && clip.blend !== "normal") notes.push(`Title "${clip.id}" is drawn without its blend mode (drawtext draws straight onto the picture); pass titleImage to blend titles.`);
3621
+ graph.push(`[${cur2}]drawtext=${opts.join(":")}[${next}]`);
3622
+ cur2 = next;
3623
+ continue;
3624
+ }
3625
+ const size = asset ? { width: _nullishCoalesce(asset.width, () => ( project.width)), height: _nullishCoalesce(asset.height, () => ( project.height)) } : { width: project.width, height: project.height };
3626
+ const placed = clip.type === "text" && !moving ? void 0 : tf;
3627
+ const still = Object.fromEntries(Object.entries(_nullishCoalesce(placed, () => ( {}))).filter(([, v]) => !isVideoKeyframes(v)));
3628
+ const rect = placeVideoRect(project, size, clip.type === "text" ? "fill" : _nullishCoalesce(clip.fit, () => ( (clip.type === "media" || clip.type === "sequence" ? "contain" : "fill"))), still);
3629
+ const chain = [];
3630
+ let source;
3631
+ if (clip.type === "color") source = `color=c=${color(clip.color)}:s=${even(rect.width)}x${even(rect.height)}:r=${fps}:d=${sec(span)}`;
3632
+ else if (_optionalChain([compound, 'optionalAccess', _81 => _81.picture])) {
3633
+ source = compound.picture;
3634
+ chain.push(`scale=${even(rect.width)}:${even(rect.height)}`);
3635
+ } else {
3636
+ source = `[${index}:v]`;
3637
+ if (clip.type === "media" && clip.timeRemap) chain.push(...rampedPicture(clip.timeRemap, lead, clip.duration + tail), `scale=${even(rect.width)}:${even(rect.height)}`);
3638
+ else chain.push(`setpts=${videoClipSpeed(clip) !== 1 ? `(PTS-STARTPTS)/${videoClipSpeed(clip)}` : "PTS-STARTPTS"}`, `fps=${fps}`, `scale=${even(rect.width)}:${even(rect.height)}`);
3639
+ }
3640
+ const grade = videoGradeFfmpegFilters(clip.grade);
3641
+ if (grade.length) chain.push("format=rgba", ...grade);
3642
+ const lut = normalizeVideoGradeLut(_optionalChain([clip, 'access', _82 => _82.grade, 'optionalAccess', _83 => _83.lut]));
3643
+ if (lut) {
3644
+ chain.push("format=gbrap");
3645
+ const path = options.resolveLut ? options.resolveLut(lut.src) : lut.src;
3646
+ if (/^(blob|data|https?):/i.test(path)) notes.push(`ffmpeg's lut3d reads only local files, and clip "${clip.id}"'s LUT is "${path.slice(0, 80)}"; save it and pass resolveLut to give its path.`);
3647
+ const file = ffmpegFilterValue(path);
3648
+ const into2 = `q${next}`;
3649
+ graph.push(`${source.startsWith("[") ? source : `${source},`}${chain.join(",")}[${into2}]`);
3650
+ const lut3d = `lut3d=file=${file}:interp=tetrahedral`;
3651
+ if ((_nullishCoalesce(lut.strength, () => ( 1))) >= 1) graph.push(`[${into2}]${lut3d}[r${next}]`);
3652
+ else graph.push(`[${into2}]split[o${next}][p${next}]`, `[p${next}]${lut3d}[s${next}]`, `[s${next}][o${next}]blend=all_mode=normal:all_opacity=${lut.strength}[r${next}]`);
3653
+ source = `[r${next}]`;
3654
+ chain.length = 0;
3655
+ }
3656
+ chain.push("format=yuva420p");
3657
+ if (isVideoKeyframes(clip.opacity)) chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='alpha(X,Y)*clip(${expr(clip.opacity, onChain("T"), 1, "opacity")},0,1)'`);
3658
+ else if (clip.opacity !== void 0 && clip.opacity < 1) chain.push(`colorchannelmixer=aa=${clip.opacity}`);
3659
+ if (clip.fadeIn) chain.push(`fade=t=in:st=${sec(lead)}:d=${sec(clip.fadeIn)}:alpha=1`);
3660
+ if (clip.fadeOut) chain.push(`fade=t=out:st=${sec(lead + clip.duration - clip.fadeOut)}:d=${sec(clip.fadeOut)}:alpha=1`);
3661
+ if (lead && clip.transition.type === "crossfade") chain.push(`fade=t=in:st=0:d=${sec(2 * lead)}:alpha=1`);
3662
+ if (lead && clip.transition.type === "wipe") {
3663
+ const d = sec(2 * lead);
3664
+ const p = `min(1,T/${d})`;
3665
+ const dir = _nullishCoalesce(clip.transition.direction, () => ( "right"));
3666
+ const test = dir === "right" ? `lt(X,W*${p})` : dir === "left" ? `gt(X,W*(1-${p}))` : dir === "down" ? `lt(Y,H*${p})` : `gt(Y,H*(1-${p}))`;
3667
+ chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='if(${test},alpha(X,Y),0)'`);
3668
+ }
3669
+ if (lead && clip.transition.type === "iris") {
3670
+ const r = videoIrisRadius({ width: W, height: H }, 1);
3671
+ chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='if(lte(hypot(X+${Math.round(rect.x)}-${W / 2},Y+${Math.round(rect.y)}-${H / 2}),${Math.round(r)}*min(1,T/${sec(2 * lead)})),alpha(X,Y),0)'`);
3672
+ }
3673
+ const rotation = _optionalChain([placed, 'optionalAccess', _84 => _84.rotation]);
3674
+ if (isVideoKeyframes(rotation)) chain.push(`rotate=a='(${expr(rotation, onChain("t"), 0, "rotation")})*PI/180':c=black@0:ow='hypot(iw,ih)':oh=ow`);
3675
+ else if (rotation) chain.push(`rotate=${rotation}*PI/180:c=black@0:ow=rotw(${rotation}*PI/180):oh=roth(${rotation}*PI/180)`);
3676
+ if (isVideoKeyframes(_optionalChain([placed, 'optionalAccess', _85 => _85.scale]))) {
3677
+ const s = expr(placed.scale, onChain("t"), 1, "scale");
3678
+ chain.push(`scale=w='max(2,trunc(iw*(${s})/2)*2)':h='max(2,trunc(ih*(${s})/2)*2)':eval=frame`);
3679
+ }
3680
+ chain.push(`setpts=PTS+${sec(t0)}/TB`);
3681
+ const cx = rect.x + rect.width / 2;
3682
+ const cy = rect.y + rect.height / 2;
3683
+ const shifts = { x: [], y: [] };
3684
+ const shiftBy = (direction, from, d, entering) => {
3685
+ const dir = _nullishCoalesce(direction, () => ( "right"));
3686
+ const axis = dir === "left" || dir === "right" ? "x" : "y";
3687
+ const size2 = axis === "x" ? W : H;
3688
+ const sign = dir === "left" || dir === "up" ? -1 : 1;
3689
+ const ease = `(1-cos(PI*clip((t-${sec(from)})/${sec(d)},0,1)))/2`;
3690
+ shifts[axis].push(`${sign * size2}*(${ease}${entering ? "-1" : ""})`);
3406
3691
  };
3407
- const fontSize = _nullishCoalesce(clip.fontSize, () => ( 64));
3408
- const opts = [
3409
- `text=${ffmpegFilterValue(clip.text)}`,
3410
- options.fontFile ? `fontfile=${ffmpegFilterValue(options.fontFile)}` : `font=${ffmpegFilterValue(_nullishCoalesce(clip.fontFamily, () => ( "sans-serif")))}`,
3411
- isVideoKeyframes(_optionalChain([tf, 'optionalAccess', _70 => _70.scale])) ? `fontsize='${fontSize}*(${expr(tf.scale, onTimeline, 1, "scale")})'` : `fontsize=${Math.round(fontSize * (_nullishCoalesce(_optionalChain([tf, 'optionalAccess', _71 => _71.scale]), () => ( 1))))}`,
3412
- `fontcolor=${color(_nullishCoalesce(clip.color, () => ( "#ffffff")))}`,
3413
- coord("x"),
3414
- coord("y"),
3415
- `alpha='${alpha}'`,
3416
- ...clip.background ? ["box=1", `boxcolor=${color(clip.background)}`, `boxborderw=${Math.round((_nullishCoalesce(clip.fontSize, () => ( 64))) / 2)}`] : [],
3417
- `enable='between(t,${sec(clip.start)},${sec(end)})'`
3418
- ];
3419
- if (_optionalChain([tf, 'optionalAccess', _72 => _72.rotation])) notes.push(`Title "${clip.id}" is drawn without its rotation (drawtext cannot rotate).`);
3420
- if (videoGradeFfmpegFilters(clip.grade).length || normalizeVideoGradeLut(_optionalChain([clip, 'access', _73 => _73.grade, 'optionalAccess', _74 => _74.lut]))) notes.push(`Title "${clip.id}" is drawn without its grade (drawtext draws straight onto the picture); pass titleImage to grade titles.`);
3421
- if (clip.blend && clip.blend !== "normal") notes.push(`Title "${clip.id}" is drawn without its blend mode (drawtext draws straight onto the picture); pass titleImage to blend titles.`);
3422
- graph.push(`[${cur}]drawtext=${opts.join(":")}[${next}]`);
3423
- cur = next;
3424
- continue;
3425
- }
3426
- const size = asset ? { width: _nullishCoalesce(asset.width, () => ( project.width)), height: _nullishCoalesce(asset.height, () => ( project.height)) } : { width: project.width, height: project.height };
3427
- const placed = clip.type === "text" && !moving ? void 0 : tf;
3428
- const still = Object.fromEntries(Object.entries(_nullishCoalesce(placed, () => ( {}))).filter(([, v]) => !isVideoKeyframes(v)));
3429
- const rect = placeVideoRect(project, size, clip.type === "text" ? "fill" : _nullishCoalesce(clip.fit, () => ( (clip.type === "media" ? "contain" : "fill"))), still);
3430
- const chain = [];
3431
- let source;
3432
- if (clip.type === "color") source = `color=c=${color(clip.color)}:s=${even(rect.width)}x${even(rect.height)}:r=${fps}:d=${sec(span)}`;
3433
- else {
3434
- source = `[${index}:v]`;
3435
- if (clip.type === "media" && clip.timeRemap) chain.push(...rampedPicture(clip.timeRemap, lead, clip.duration + tail), `scale=${even(rect.width)}:${even(rect.height)}`);
3436
- else chain.push(`setpts=${videoClipSpeed(clip) !== 1 ? `(PTS-STARTPTS)/${videoClipSpeed(clip)}` : "PTS-STARTPTS"}`, `fps=${fps}`, `scale=${even(rect.width)}:${even(rect.height)}`);
3437
- }
3438
- const grade = videoGradeFfmpegFilters(clip.grade);
3439
- if (grade.length) chain.push("format=rgba", ...grade);
3440
- const lut = normalizeVideoGradeLut(_optionalChain([clip, 'access', _75 => _75.grade, 'optionalAccess', _76 => _76.lut]));
3441
- if (lut) {
3442
- chain.push("format=gbrap");
3443
- const path = options.resolveLut ? options.resolveLut(lut.src) : lut.src;
3444
- if (/^(blob|data|https?):/i.test(path)) notes.push(`ffmpeg's lut3d reads only local files, and clip "${clip.id}"'s LUT is "${path.slice(0, 80)}"; save it and pass resolveLut to give its path.`);
3445
- const file = ffmpegFilterValue(path);
3446
- const into2 = `q${next}`;
3447
- graph.push(`${source.startsWith("[") ? source : `${source},`}${chain.join(",")}[${into2}]`);
3448
- const lut3d = `lut3d=file=${file}:interp=tetrahedral`;
3449
- if ((_nullishCoalesce(lut.strength, () => ( 1))) >= 1) graph.push(`[${into2}]${lut3d}[r${next}]`);
3450
- else graph.push(`[${into2}]split[o${next}][p${next}]`, `[p${next}]${lut3d}[s${next}]`, `[s${next}][o${next}]blend=all_mode=normal:all_opacity=${lut.strength}[r${next}]`);
3451
- source = `[r${next}]`;
3452
- chain.length = 0;
3453
- }
3454
- chain.push("format=yuva420p");
3455
- if (isVideoKeyframes(clip.opacity)) chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='alpha(X,Y)*clip(${expr(clip.opacity, onChain("T"), 1, "opacity")},0,1)'`);
3456
- else if (clip.opacity !== void 0 && clip.opacity < 1) chain.push(`colorchannelmixer=aa=${clip.opacity}`);
3457
- if (clip.fadeIn) chain.push(`fade=t=in:st=${sec(lead)}:d=${sec(clip.fadeIn)}:alpha=1`);
3458
- if (clip.fadeOut) chain.push(`fade=t=out:st=${sec(lead + clip.duration - clip.fadeOut)}:d=${sec(clip.fadeOut)}:alpha=1`);
3459
- if (lead && clip.transition.type === "crossfade") chain.push(`fade=t=in:st=0:d=${sec(2 * lead)}:alpha=1`);
3460
- if (lead && clip.transition.type === "wipe") {
3461
- const d = sec(2 * lead);
3462
- const p = `min(1,T/${d})`;
3463
- const dir = _nullishCoalesce(clip.transition.direction, () => ( "right"));
3464
- const test = dir === "right" ? `lt(X,W*${p})` : dir === "left" ? `gt(X,W*(1-${p}))` : dir === "down" ? `lt(Y,H*${p})` : `gt(Y,H*(1-${p}))`;
3465
- chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='if(${test},alpha(X,Y),0)'`);
3466
- }
3467
- if (lead && clip.transition.type === "iris") {
3468
- const r = videoIrisRadius({ width: W, height: H }, 1);
3469
- chain.push(`geq=lum='lum(X,Y)':cb='cb(X,Y)':cr='cr(X,Y)':a='if(lte(hypot(X+${Math.round(rect.x)}-${W / 2},Y+${Math.round(rect.y)}-${H / 2}),${Math.round(r)}*min(1,T/${sec(2 * lead)})),alpha(X,Y),0)'`);
3470
- }
3471
- const rotation = _optionalChain([placed, 'optionalAccess', _77 => _77.rotation]);
3472
- if (isVideoKeyframes(rotation)) chain.push(`rotate=a='(${expr(rotation, onChain("t"), 0, "rotation")})*PI/180':c=black@0:ow='hypot(iw,ih)':oh=ow`);
3473
- else if (rotation) chain.push(`rotate=${rotation}*PI/180:c=black@0:ow=rotw(${rotation}*PI/180):oh=roth(${rotation}*PI/180)`);
3474
- if (isVideoKeyframes(_optionalChain([placed, 'optionalAccess', _78 => _78.scale]))) {
3475
- const s = expr(placed.scale, onChain("t"), 1, "scale");
3476
- chain.push(`scale=w='max(2,trunc(iw*(${s})/2)*2)':h='max(2,trunc(ih*(${s})/2)*2)':eval=frame`);
3477
- }
3478
- chain.push(`setpts=PTS+${sec(t0)}/TB`);
3479
- const cx = rect.x + rect.width / 2;
3480
- const cy = rect.y + rect.height / 2;
3481
- const shifts = { x: [], y: [] };
3482
- const shiftBy = (direction, from, d, entering) => {
3483
- const dir = _nullishCoalesce(direction, () => ( "right"));
3484
- const axis = dir === "left" || dir === "right" ? "x" : "y";
3485
- const size2 = axis === "x" ? W : H;
3486
- const sign = dir === "left" || dir === "up" ? -1 : 1;
3487
- const ease = `(1-cos(PI*clip((t-${sec(from)})/${sec(d)},0,1)))/2`;
3488
- shifts[axis].push(`${sign * size2}*(${ease}${entering ? "-1" : ""})`);
3489
- };
3490
- const into = lead ? clip.transition.type : void 0;
3491
- if (into === "push" || into === "slide") shiftBy(clip.transition.direction, clip.start - lead, 2 * lead, true);
3492
- const after = tail ? sortedVideoClips(track).find((c) => c !== clip && Math.abs(c.start - end) < 0.5 && c.transition) : void 0;
3493
- if (_optionalChain([after, 'optionalAccess', _79 => _79.transition, 'access', _80 => _80.type]) === "push") shiftBy(after.transition.direction, end - tail, 2 * tail, false);
3494
- const at = (axis, c) => {
3495
- const v = _optionalChain([placed, 'optionalAccess', _81 => _81[axis]]);
3496
- const extra = shifts[axis].map((s) => `+${s}`).join("");
3497
- return isVideoKeyframes(v) ? `${axis}='${Math.round(axis === "x" ? project.width / 2 : project.height / 2)}+(${expr(v, onTimeline, 0, "position")})-${axis === "x" ? "w" : "h"}/2${extra}'` : `${axis}='${Math.round(c)}-${axis === "x" ? "w" : "h"}/2${extra}'`;
3498
- };
3499
- graph.push(`${source.startsWith("[") ? source : `${source},`}${chain.join(",")}[l${next}]`);
3500
- if (clip.blend && clip.blend !== "normal") {
3501
- graph.push(`color=c=black@0:s=${W}x${H}:r=${fps}:d=${sec(D)},format=yuva420p[t${next}]`);
3502
- graph.push(`[t${next}][l${next}]overlay=${at("x", cx)}:${at("y", cy)}:eof_action=pass:${enable},format=gbrap,split[f${next}][g${next}]`);
3503
- graph.push(`[${cur}]format=gbrap,split[c${next}][d${next}]`);
3504
- const layerFirst = clip.blend === "color-dodge" || clip.blend === "color-burn";
3505
- graph.push(`${layerFirst ? `[f${next}][c${next}]` : `[c${next}][f${next}]`}blend=all_mode=${FFMPEG_BLEND[clip.blend]}[b${next}]`);
3506
- graph.push(`[g${next}]alphaextract[m${next}]`);
3507
- graph.push(`[b${next}][m${next}]alphamerge[x${next}]`);
3508
- graph.push(`[d${next}][x${next}]overlay=format=gbrp,format=yuva420p[${next}]`);
3509
- blended.add(clip.blend);
3510
- } else graph.push(`[${cur}][l${next}]overlay=${at("x", cx)}:${at("y", cy)}:eof_action=pass:${enable}[${next}]`);
3511
- cur = next;
3512
- const before = clip.transition ? videoClipBefore(track, clip) : null;
3513
- if (before && clip.transition.type === "dip") {
3514
- const d = clip.transition.duration;
3515
- const dip = `d${k}`;
3516
- graph.push(`color=c=${color(_nullishCoalesce(clip.transition.color, () => ( "#000000")))}:s=${W}x${H}:r=${fps}:d=${sec(d)},format=yuva420p,fade=t=in:st=0:d=${sec(d / 2)}:alpha=1,fade=t=out:st=${sec(d / 2)}:d=${sec(d / 2)}:alpha=1,setpts=PTS+${sec(clip.start - d / 2)}/TB[l${dip}]`);
3517
- graph.push(`[${cur}][l${dip}]overlay=eof_action=pass:enable='between(t,${sec(clip.start - d / 2)},${sec(clip.start + d / 2)})'[${dip}]`);
3518
- cur = dip;
3692
+ const into = lead ? clip.transition.type : void 0;
3693
+ if (into === "push" || into === "slide") shiftBy(clip.transition.direction, clip.start - lead, 2 * lead, true);
3694
+ const after = tail ? sortedVideoClips(track).find((c) => c !== clip && Math.abs(c.start - end) < 0.5 && c.transition) : void 0;
3695
+ if (_optionalChain([after, 'optionalAccess', _86 => _86.transition, 'access', _87 => _87.type]) === "push") shiftBy(after.transition.direction, end - tail, 2 * tail, false);
3696
+ const at = (axis, c) => {
3697
+ const v = _optionalChain([placed, 'optionalAccess', _88 => _88[axis]]);
3698
+ const extra = shifts[axis].map((s) => `+${s}`).join("");
3699
+ return isVideoKeyframes(v) ? `${axis}='${Math.round(axis === "x" ? project.width / 2 : project.height / 2)}+(${expr(v, onTimeline, 0, "position")})-${axis === "x" ? "w" : "h"}/2${extra}'` : `${axis}='${Math.round(c)}-${axis === "x" ? "w" : "h"}/2${extra}'`;
3700
+ };
3701
+ graph.push(`${source.startsWith("[") ? source : `${source},`}${chain.join(",")}[l${next}]`);
3702
+ if (clip.blend && clip.blend !== "normal") {
3703
+ graph.push(`color=c=black@0:s=${W}x${H}:r=${fps}:d=${sec(length)},format=yuva420p[t${next}]`);
3704
+ const clear = scope.within.length > 0;
3705
+ graph.push(`[t${next}][l${next}]overlay=${at("x", cx)}:${at("y", cy)}:eof_action=pass:${enable},format=gbrap,split${clear ? `=3` : ""}[f${next}][g${next}]${clear ? `[h${next}]` : ""}`);
3706
+ graph.push(`[${cur2}]format=gbrap,split${clear ? `=3` : ""}[c${next}][d${next}]${clear ? `[e${next}]` : ""}`);
3707
+ const layerFirst = clip.blend === "color-dodge" || clip.blend === "color-burn";
3708
+ graph.push(`${layerFirst ? `[f${next}][c${next}]` : `[c${next}][f${next}]`}blend=all_mode=${FFMPEG_BLEND[clip.blend]}[b${next}]`);
3709
+ graph.push(`[g${next}]alphaextract[m${next}]`);
3710
+ if (clear) {
3711
+ graph.push(`[e${next}]alphaextract[ea${next}]`);
3712
+ graph.push(`[b${next}][ea${next}]alphamerge[bb${next}]`);
3713
+ graph.push(`[h${next}]format=gbrp[ho${next}]`);
3714
+ graph.push(`[ho${next}][bb${next}]overlay=format=gbrp[bm${next}]`);
3715
+ graph.push(`[bm${next}][m${next}]alphamerge[x${next}]`);
3716
+ } else graph.push(`[b${next}][m${next}]alphamerge[x${next}]`);
3717
+ graph.push(`[d${next}][x${next}]overlay=format=gbrp,format=yuva420p[${next}]`);
3718
+ blended.add(clip.blend);
3719
+ } else graph.push(`[${cur2}][l${next}]overlay=${at("x", cx)}:${at("y", cy)}:eof_action=pass:${enable}[${next}]`);
3720
+ cur2 = next;
3721
+ const before = clip.transition ? videoClipBefore(track, clip) : null;
3722
+ if (before && clip.transition.type === "dip") {
3723
+ const d = clip.transition.duration;
3724
+ const dip = `d${k}`;
3725
+ graph.push(`color=c=${color(_nullishCoalesce(clip.transition.color, () => ( "#000000")))}:s=${W}x${H}:r=${fps}:d=${sec(d)},format=yuva420p,fade=t=in:st=0:d=${sec(d / 2)}:alpha=1,fade=t=out:st=${sec(d / 2)}:d=${sec(d / 2)}:alpha=1,setpts=PTS+${sec(clip.start - d / 2)}/TB[l${dip}]`);
3726
+ graph.push(`[${cur2}][l${dip}]overlay=eof_action=pass:enable='between(t,${sec(clip.start - d / 2)},${sec(clip.start + d / 2)})'[${dip}]`);
3727
+ cur2 = dip;
3728
+ }
3519
3729
  }
3520
3730
  }
3521
- }
3731
+ return { video: cur2, audio: sounds };
3732
+ };
3733
+ const top = compose(project.tracks, measure ? null : "base", D, { within: [], picture: !measure, sound: true });
3734
+ const audio = top.audio;
3735
+ const cur = top.video;
3522
3736
  if (!measure) graph.push(`[${cur}]format=yuv420p[vout]`);
3523
3737
  if (measure && !audio.length) throw new Error("Nothing on the timeline makes a sound, so there is no loudness to measure.");
3524
3738
  const loudness = options.loudness || measure ? `,${loudnormFilter(options.loudness, measure)}` : "";
@@ -3526,7 +3740,7 @@ function buildFfmpeg(project, options, measure) {
3526
3740
  if (options.loudness && !audio.length) notes.push("Nothing makes a sound, so there was no loudness to normalise.");
3527
3741
  else if (options.loudness && !options.loudness.measured)
3528
3742
  notes.push("Loudness is normalised in one pass, which adjusts the level as it goes; for a fixed gain that keeps the dynamics, run toVideoLoudnessMeasure first and pass its result as loudness.measured.");
3529
- if (project.tracks.some((t) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access', _82 => _82.get, 'call', _83 => _83(c.asset), 'optionalAccess', _84 => _84.kind]) === "video" && _optionalChain([assets, 'access', _85 => _85.get, 'call', _86 => _86(c.asset), 'optionalAccess', _87 => _87.hasAudio]) === void 0)))
3743
+ if (allVideoTracks(project).some(({ track: t }) => t.clips.some((c) => c.type === "media" && _optionalChain([assets, 'access', _89 => _89.get, 'call', _90 => _90(c.asset), 'optionalAccess', _91 => _91.kind]) === "video" && _optionalChain([assets, 'access', _92 => _92.get, 'call', _93 => _93(c.asset), 'optionalAccess', _94 => _94.hasAudio]) === void 0)))
3530
3744
  notes.push('Video assets are assumed to have sound; set "hasAudio": false on any that do not, or ffmpeg will stop at a missing audio stream.');
3531
3745
  if (animated.size)
3532
3746
  notes.push(`Keyframed ${[...animated].sort().join(", ")} ${animated.size > 1 ? "are" : "is"} rendered as straight lines between keys, with eased segments sampled every two frames${animated.has("scale") ? "; animated scale needs ffmpeg 5 or later (scale with eval=frame)" : ""}.`);
@@ -3563,12 +3777,12 @@ function buildFfmpeg(project, options, measure) {
3563
3777
  // src/video-editor/proxy.ts
3564
3778
  var VIDEO_PROXY_SCALES = [0.5, 0.25];
3565
3779
  function videoAssetProxy(asset, scale) {
3566
- if (!_optionalChain([asset, 'optionalAccess', _88 => _88.proxies, 'optionalAccess', _89 => _89.length]) || !(scale > 0) || scale >= 1) return null;
3780
+ if (!_optionalChain([asset, 'optionalAccess', _95 => _95.proxies, 'optionalAccess', _96 => _96.length]) || !(scale > 0) || scale >= 1) return null;
3567
3781
  let best = null;
3568
3782
  for (const p of asset.proxies) if (p.scale >= scale - 1e-9 && p.scale < 1 && (!best || p.scale < best.scale)) best = p;
3569
3783
  return best;
3570
3784
  }
3571
- var videoPreviewSrc = (asset, scale, fallback = _nullishCoalesce(_optionalChain([asset, 'optionalAccess', _90 => _90.src]), () => ( ""))) => _nullishCoalesce(_optionalChain([videoAssetProxy, 'call', _91 => _91(asset, scale), 'optionalAccess', _92 => _92.src]), () => ( fallback));
3785
+ var videoPreviewSrc = (asset, scale, fallback = _nullishCoalesce(_optionalChain([asset, 'optionalAccess', _97 => _97.src]), () => ( ""))) => _nullishCoalesce(_optionalChain([videoAssetProxy, 'call', _98 => _98(asset, scale), 'optionalAccess', _99 => _99.src]), () => ( fallback));
3572
3786
  function resolveVideoPreviewScale(scale, shown) {
3573
3787
  if (scale === void 0 || scale === "auto") return Math.min(1, Math.max(0, shown));
3574
3788
  if (scale === "full") return 1;
@@ -3627,7 +3841,7 @@ function videoProxyPlan(project, options = {}) {
3627
3841
  if (!a) throw new Error(`There is no asset "${id}"; the assets are ${project.assets.map((x) => `"${x.id}"`).join(", ") || "none"}.`);
3628
3842
  if (a.kind !== "video") throw new Error(`Asset "${id}" is ${a.kind === "image" ? "an image" : "audio"}; only video assets have proxies.`);
3629
3843
  }
3630
- return project.assets.filter((a) => a.kind === "video" && (!wanted || wanted.has(a.id)) && !_optionalChain([a, 'access', _93 => _93.proxies, 'optionalAccess', _94 => _94.some, 'call', _95 => _95((p) => Math.abs(p.scale - scale) < 1e-9)])).map((a) => videoProxyFfmpeg(a, { ...options, scale }));
3844
+ return project.assets.filter((a) => a.kind === "video" && (!wanted || wanted.has(a.id)) && !_optionalChain([a, 'access', _100 => _100.proxies, 'optionalAccess', _101 => _101.some, 'call', _102 => _102((p) => Math.abs(p.scale - scale) < 1e-9)])).map((a) => videoProxyFfmpeg(a, { ...options, scale }));
3631
3845
  }
3632
3846
  var VIDEO_PREVIEW_SCALE_OPTIONS = [
3633
3847
  { value: "auto", label: "Auto", scale: "auto" },
@@ -3635,7 +3849,7 @@ var VIDEO_PREVIEW_SCALE_OPTIONS = [
3635
3849
  ...VIDEO_PROXY_SCALES.map((s) => ({ value: String(s), label: s === 0.5 ? "\xBD" : s === 0.25 ? "\xBC" : `${s * 100}%`, scale: s }))
3636
3850
  ];
3637
3851
  var parseVideoPreviewScale = (value) => value === "full" ? "full" : Number(value) > 0 && Number(value) <= 1 ? Number(value) : "auto";
3638
- var videoProjectHasProxies = (project) => project.assets.some((a) => !!_optionalChain([a, 'access', _96 => _96.proxies, 'optionalAccess', _97 => _97.length]));
3852
+ var videoProjectHasProxies = (project) => project.assets.some((a) => !!_optionalChain([a, 'access', _103 => _103.proxies, 'optionalAccess', _104 => _104.length]));
3639
3853
 
3640
3854
  // src/video-editor/frame-cache.ts
3641
3855
  var videoSourceFrame = (sourceTime, fps) => Math.max(0, Math.floor(sourceTime * fps / 1e3 + 1e-6));
@@ -3643,7 +3857,7 @@ var videoSourceFrameTime = (frame, fps) => (frame + 0.5) * 1e3 / fps;
3643
3857
  var videoFrameCacheKey = (src, frame) => `${frame}@${src}`;
3644
3858
  function videoFrameRequestsAt(project, time, srcFor = (l) => l.src) {
3645
3859
  const out = [];
3646
- for (const layer of videoFrameAt(project, time).layers) {
3860
+ for (const layer of videoFrameLeafLayers(videoFrameAt(project, time).layers)) {
3647
3861
  if (layer.type !== "media" || layer.assetKind !== "video") continue;
3648
3862
  const src = srcFor(layer);
3649
3863
  if (!src) continue;
@@ -3679,7 +3893,7 @@ function videoCacheEvictions(entries, options) {
3679
3893
  return d >= 0 ? d : -d * behind;
3680
3894
  };
3681
3895
  const out = [];
3682
- for (const e of entries.filter((x) => !_optionalChain([options, 'access', _98 => _98.protect, 'optionalAccess', _99 => _99.has, 'call', _100 => _100(x.key)])).sort((a, b) => cost(b) - cost(a))) {
3896
+ for (const e of entries.filter((x) => !_optionalChain([options, 'access', _105 => _105.protect, 'optionalAccess', _106 => _106.has, 'call', _107 => _107(x.key)])).sort((a, b) => cost(b) - cost(a))) {
3683
3897
  if (total <= options.maxBytes) break;
3684
3898
  out.push(e.key);
3685
3899
  total -= e.bytes;
@@ -3720,7 +3934,7 @@ var VideoFrameCache = (_class5 = class {
3720
3934
  const entry = this.entries.get(videoFrameCacheKey(src, videoSourceFrame(sourceTime, fps)));
3721
3935
  if (entry) this.hits++;
3722
3936
  else this.misses++;
3723
- return _optionalChain([entry, 'optionalAccess', _101 => _101.frame, 'access', _102 => _102.image]);
3937
+ return _optionalChain([entry, 'optionalAccess', _108 => _108.frame, 'access', _109 => _109.image]);
3724
3938
  }
3725
3939
  /** Whether a frame is cached, without counting it. */
3726
3940
  has(src, sourceTime, fps) {
@@ -3728,7 +3942,7 @@ var VideoFrameCache = (_class5 = class {
3728
3942
  }
3729
3943
  /** Keeps a decoded frame (replacing any with the same key), then evicts to the budget. */
3730
3944
  put(request, frame) {
3731
- if (this.destroyed) return void _optionalChain([frame, 'access', _103 => _103.image, 'access', _104 => _104.close, 'optionalCall', _105 => _105()]);
3945
+ if (this.destroyed) return void _optionalChain([frame, 'access', _110 => _110.image, 'access', _111 => _111.close, 'optionalCall', _112 => _112()]);
3732
3946
  const old = this.entries.get(request.key);
3733
3947
  if (old) this.drop(old);
3734
3948
  const bytes = Math.max(1, frame.width * frame.height * 4);
@@ -3827,7 +4041,7 @@ var VideoFrameCache = (_class5 = class {
3827
4041
  (frame) => {
3828
4042
  this.inFlight.delete(r.key);
3829
4043
  if (generation === this.generation || this.bytes + frame.width * frame.height * 4 <= this.maxBytes) this.put(r, frame);
3830
- else _optionalChain([frame, 'access', _106 => _106.image, 'access', _107 => _107.close, 'optionalCall', _108 => _108()]);
4044
+ else _optionalChain([frame, 'access', _113 => _113.image, 'access', _114 => _114.close, 'optionalCall', _115 => _115()]);
3831
4045
  },
3832
4046
  () => this.inFlight.delete(r.key)
3833
4047
  ).finally(() => this.pump());
@@ -3840,7 +4054,7 @@ var VideoFrameCache = (_class5 = class {
3840
4054
  drop(e) {
3841
4055
  this.entries.delete(e.key);
3842
4056
  this.bytes -= e.bytes;
3843
- _optionalChain([e, 'access', _109 => _109.frame, 'access', _110 => _110.image, 'access', _111 => _111.close, 'optionalCall', _112 => _112()]);
4057
+ _optionalChain([e, 'access', _116 => _116.frame, 'access', _117 => _117.image, 'access', _118 => _118.close, 'optionalCall', _119 => _119()]);
3844
4058
  }
3845
4059
  emit() {
3846
4060
  for (const l of [...this.listeners]) l();
@@ -3888,11 +4102,11 @@ function createVideoElementDecoder(options = {}) {
3888
4102
 
3889
4103
  // src/video-editor/preview.ts
3890
4104
  var VideoPreviewEngine = (_class6 = class {
3891
- constructor(source, canvas2, options = {}) {;_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);_class6.prototype.__init38.call(this);_class6.prototype.__init39.call(this);
4105
+ constructor(source, canvas2, options = {}) {;_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);_class6.prototype.__init38.call(this);_class6.prototype.__init39.call(this);_class6.prototype.__init40.call(this);
3892
4106
  this.source = source;
3893
4107
  this.options = options;
3894
4108
  this.canvas = canvas2;
3895
- this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access', _117 => _117.getContext, 'optionalCall', _118 => _118("2d")]), () => ( null));
4109
+ this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access', _124 => _124.getContext, 'optionalCall', _125 => _125("2d")]), () => ( null));
3896
4110
  this.unsubscribe = source.subscribe(() => this.update());
3897
4111
  this.lutCache = options.luts !== void 0 ? options.luts : _nullishCoalesce(source.luts, () => ( null));
3898
4112
  this.unsubscribeLuts = this.lutCache ? this.lutCache.subscribe(() => this.redraw()) : () => {
@@ -3928,11 +4142,11 @@ var VideoPreviewEngine = (_class6 = class {
3928
4142
  __init34() {this.playing = false}
3929
4143
  __init35() {this.fps = 30}
3930
4144
 
3931
-
4145
+ __init36() {this.scratchCanvases = /* @__PURE__ */ new Map()}
3932
4146
  /** The canvas's pixels and the frame they show, or `null` when its context cannot be read. */
3933
4147
  readPixels() {
3934
4148
  const ctx = this.ctx;
3935
- if (!_optionalChain([ctx, 'optionalAccess', _119 => _119.getImageData]) || !this.drawn) return null;
4149
+ if (!_optionalChain([ctx, 'optionalAccess', _126 => _126.getImageData]) || !this.drawn) return null;
3936
4150
  return { ...this.drawn, pixels: ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) };
3937
4151
  }
3938
4152
  createMedia(kind) {
@@ -3970,8 +4184,8 @@ var VideoPreviewEngine = (_class6 = class {
3970
4184
  entry.media.pause();
3971
4185
  entry.media.removeEventListener("seeked", entry.onReady);
3972
4186
  entry.media.removeEventListener("loadeddata", entry.onReady);
3973
- _optionalChain([entry, 'access', _120 => _120.media, 'access', _121 => _121.removeAttribute, 'optionalCall', _122 => _122("src")]);
3974
- _optionalChain([entry, 'access', _123 => _123.media, 'access', _124 => _124.load, 'optionalCall', _125 => _125()]);
4187
+ _optionalChain([entry, 'access', _127 => _127.media, 'access', _128 => _128.removeAttribute, 'optionalCall', _129 => _129("src")]);
4188
+ _optionalChain([entry, 'access', _130 => _130.media, 'access', _131 => _131.load, 'optionalCall', _132 => _132()]);
3975
4189
  this.media.delete(entry.clip);
3976
4190
  }
3977
4191
  /** Brings the media elements in step with the editor and draws the frame. */
@@ -3985,7 +4199,7 @@ var VideoPreviewEngine = (_class6 = class {
3985
4199
  this.srcFor = (layer) => videoPreviewSrc(assets.get(_nullishCoalesce(layer.asset, () => ( ""))), scale, layer.src);
3986
4200
  this.playing = playing;
3987
4201
  this.fps = project.fps;
3988
- _optionalChain([this, 'access', _126 => _126.frameCache, 'optionalAccess', _127 => _127.update, 'call', _128 => _128(project, time, { playing, srcFor: this.srcFor })]);
4202
+ _optionalChain([this, 'access', _133 => _133.frameCache, 'optionalAccess', _134 => _134.update, 'call', _135 => _135(project, time, { playing, srcFor: this.srcFor })]);
3989
4203
  const tolerance = (_nullishCoalesce(this.options.tolerance, () => ( 200))) / 1e3;
3990
4204
  const active = /* @__PURE__ */ new Set();
3991
4205
  const sync = (clip, src, kind, sourceTime, speed, gain) => {
@@ -4009,10 +4223,10 @@ var VideoPreviewEngine = (_class6 = class {
4009
4223
  }
4010
4224
  };
4011
4225
  const sounding = new Map(frame.audio.map((a) => [a.clip, a]));
4012
- for (const layer of frame.layers) {
4226
+ for (const layer of videoFrameLeafLayers(frame.layers)) {
4013
4227
  if (layer.type !== "media" || layer.assetKind !== "video" || !layer.src) continue;
4014
4228
  const sound = sounding.get(layer.clip);
4015
- sync(layer.clip, this.srcFor(layer), "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess', _129 => _129.speed]), () => ( 1)), sound ? sound.gain : null);
4229
+ sync(layer.clip, this.srcFor(layer), "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess', _136 => _136.speed]), () => ( 1)), sound ? sound.gain : null);
4016
4230
  sounding.delete(layer.clip);
4017
4231
  }
4018
4232
  for (const a of sounding.values()) sync(a.clip, a.src, "audio", a.sourceTime, a.speed, a.gain);
@@ -4027,7 +4241,7 @@ var VideoPreviewEngine = (_class6 = class {
4027
4241
  if (!this.destroyed) this.draw(videoFrameAt(this.source.getState().project, this.source.getState().time));
4028
4242
  });
4029
4243
  }
4030
- __init36() {this.sourceFor = (layer) => {
4244
+ __init37() {this.sourceFor = (layer) => {
4031
4245
  if (layer.assetKind === "image" && layer.src) {
4032
4246
  const img = this.image(layer.src);
4033
4247
  return img.complete ? img : null;
@@ -4035,7 +4249,7 @@ var VideoPreviewEngine = (_class6 = class {
4035
4249
  const entry = this.media.get(layer.clip);
4036
4250
  const ready = !!entry && entry.media.readyState >= 2;
4037
4251
  const cache = this.frameCache;
4038
- const src = _nullishCoalesce(_optionalChain([entry, 'optionalAccess', _130 => _130.src]), () => ( this.srcFor(layer)));
4252
+ const src = _nullishCoalesce(_optionalChain([entry, 'optionalAccess', _137 => _137.src]), () => ( this.srcFor(layer)));
4039
4253
  const sourceTime = _nullishCoalesce(layer.sourceTime, () => ( 0));
4040
4254
  if (cache && src && (!this.playing || !ready || entry.media.seeking)) {
4041
4255
  const hit = cache.get(src, sourceTime, this.fps);
@@ -4068,32 +4282,34 @@ var VideoPreviewEngine = (_class6 = class {
4068
4282
  };
4069
4283
  drawVideoFrame(this.ctx, frame, sourceFor, resolution, { defineSvgFilter: this.defineSvgFilter, lutFor: this.lutFor, scratch: this.scratch });
4070
4284
  this.drawn = { time: frame.time, complete: this.complete };
4071
- _optionalChain([this, 'access', _131 => _131.options, 'access', _132 => _132.onFrame, 'optionalCall', _133 => _133(frame)]);
4072
- _optionalChain([this, 'access', _134 => _134.pixelsTarget, 'optionalAccess', _135 => _135.drawn, 'call', _136 => _136(frame.time, this.complete)]);
4285
+ _optionalChain([this, 'access', _138 => _138.options, 'access', _139 => _139.onFrame, 'optionalCall', _140 => _140(frame)]);
4286
+ _optionalChain([this, 'access', _141 => _141.pixelsTarget, 'optionalAccess', _142 => _142.drawn, 'call', _143 => _143(frame.time, this.complete)]);
4073
4287
  }
4074
4288
  /** A grade's LUT once loaded; while it loads (or if it fails) the frame is drawn without it and counts as incomplete. */
4075
- __init37() {this.lutFor = (src) => {
4076
- const lut = _optionalChain([this, 'access', _137 => _137.lutCache, 'optionalAccess', _138 => _138.get, 'call', _139 => _139(src)]);
4289
+ __init38() {this.lutFor = (src) => {
4290
+ const lut = _optionalChain([this, 'access', _144 => _144.lutCache, 'optionalAccess', _145 => _145.get, 'call', _146 => _146(src)]);
4077
4291
  if (!lut) this.complete = false;
4078
4292
  return lut;
4079
4293
  }}
4080
- __init38() {this.scratch = (width, height) => {
4081
- if (this.scratchCanvas === void 0) {
4082
- if (this.options.createScratch) this.scratchCanvas = this.options.createScratch(width, height);
4294
+ __init39() {this.scratch = (width, height, slot) => {
4295
+ if (!this.scratchCanvases.has(slot)) {
4296
+ let made = null;
4297
+ if (this.options.createScratch) made = this.options.createScratch(width, height, slot);
4083
4298
  else if (typeof document !== "undefined") {
4084
4299
  const canvas2 = document.createElement("canvas");
4085
4300
  const ctx = canvas2.getContext("2d", { willReadFrequently: true });
4086
- this.scratchCanvas = ctx ? { canvas: canvas2, ctx } : null;
4087
- } else this.scratchCanvas = null;
4301
+ made = ctx ? { canvas: canvas2, ctx } : null;
4302
+ }
4303
+ this.scratchCanvases.set(slot, made);
4088
4304
  }
4089
- const s = this.scratchCanvas;
4305
+ const s = this.scratchCanvases.get(slot);
4090
4306
  if (!s) return null;
4091
4307
  const c = s.canvas;
4092
4308
  if (c.width !== width) c.width = width;
4093
4309
  if (c.height !== height) c.height = height;
4094
4310
  return s;
4095
4311
  }}
4096
- __init39() {this.defineSvgFilter = (id, markup) => {
4312
+ __init40() {this.defineSvgFilter = (id, markup) => {
4097
4313
  if (this.options.defineSvgFilter === null) return;
4098
4314
  if (this.options.defineSvgFilter) return this.options.defineSvgFilter(id, markup);
4099
4315
  if (this.filters.has(id) || typeof document === "undefined") return;
@@ -4115,7 +4331,7 @@ var VideoPreviewEngine = (_class6 = class {
4115
4331
  this.detachPixels();
4116
4332
  this.unsubscribeLuts();
4117
4333
  this.unsubscribeFrames();
4118
- _optionalChain([this, 'access', _140 => _140.filterHost, 'optionalAccess', _141 => _141.remove, 'call', _142 => _142()]);
4334
+ _optionalChain([this, 'access', _147 => _147.filterHost, 'optionalAccess', _148 => _148.remove, 'call', _149 => _149()]);
4119
4335
  this.filterHost = null;
4120
4336
  this.filters.clear();
4121
4337
  this.unsubscribe();
@@ -4131,24 +4347,24 @@ var VideoEditorController = (_class7 = class {
4131
4347
  /** Sound levels per asset, loaded on demand for waveforms and agents. */
4132
4348
 
4133
4349
  /** The pixels the preview last drew, for scopes and `get_video_scopes` (a `VideoPreviewEngine` fills it). */
4134
- __init40() {this.previewPixels = new VideoPreviewPixels()}
4350
+ __init41() {this.previewPixels = new VideoPreviewPixels()}
4135
4351
  /** Grades' LUTs, loaded on demand for the preview, SVG frames and agents. */
4136
4352
 
4137
4353
  /** The host's frame cache, if it gave one (the preview fills and reads it). */
4138
4354
 
4139
4355
 
4140
4356
 
4141
- __init41() {this.selection = []}
4357
+ __init42() {this.selection = []}
4142
4358
 
4143
4359
 
4144
- __init42() {this.error = null}
4145
- __init43() {this.revision = 0}
4146
- __init44() {this.applying = false}
4360
+ __init43() {this.error = null}
4361
+ __init44() {this.revision = 0}
4362
+ __init45() {this.applying = false}
4147
4363
 
4148
- __init45() {this.listeners = /* @__PURE__ */ new Set()}
4149
- __init46() {this.unsubscribers = []}
4364
+ __init46() {this.listeners = /* @__PURE__ */ new Set()}
4365
+ __init47() {this.unsubscribers = []}
4150
4366
 
4151
- constructor(options = {}) {;_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);
4367
+ constructor(options = {}) {;_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);
4152
4368
  this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
4153
4369
  this.audioPeaks = options.audioPeaks instanceof VideoAudioPeaksCache ? options.audioPeaks : new VideoAudioPeaksCache(options.audioPeaks);
4154
4370
  this.luts = options.luts instanceof VideoLutCache ? options.luts : new VideoLutCache(options.luts);
@@ -4176,8 +4392,8 @@ var VideoEditorController = (_class7 = class {
4176
4392
  this.player.pause();
4177
4393
  this.unsubscribers.splice(0).forEach((u) => u());
4178
4394
  }
4179
- __init47() {this.getState = () => this.state}
4180
- __init48() {this.subscribe = (listener) => {
4395
+ __init48() {this.getState = () => this.state}
4396
+ __init49() {this.subscribe = (listener) => {
4181
4397
  this.connect();
4182
4398
  this.listeners.add(listener);
4183
4399
  return () => {
@@ -4234,7 +4450,7 @@ var VideoEditorController = (_class7 = class {
4234
4450
  this.applying = false;
4235
4451
  }
4236
4452
  this.notify();
4237
- _optionalChain([this, 'access', _143 => _143.onChange, 'optionalCall', _144 => _144(project)]);
4453
+ _optionalChain([this, 'access', _150 => _150.onChange, 'optionalCall', _151 => _151(project)]);
4238
4454
  }
4239
4455
  commit(label, next) {
4240
4456
  const before = this.project;
@@ -4309,7 +4525,7 @@ var VideoEditorController = (_class7 = class {
4309
4525
  }
4310
4526
  /** Selects clips (unknown ids are dropped). `add` toggles them into the current selection. */
4311
4527
  select(clips, options = {}) {
4312
- const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess', _145 => _145.filter, 'call', _146 => _146((id) => findVideoClip(this.project, id))]), () => ( []));
4528
+ const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess', _152 => _152.filter, 'call', _153 => _153((id) => findVideoClip(this.project, id))]), () => ( []));
4313
4529
  let next;
4314
4530
  if (options.add) {
4315
4531
  next = [...this.selection];
@@ -4457,15 +4673,17 @@ var VIDEO_FADE_CURVE_LABELS = {
4457
4673
  };
4458
4674
  function videoClipTint(clip, asset) {
4459
4675
  if (clip.type === "text") return "#a855f7";
4676
+ if (clip.type === "sequence") return "#0d9488";
4460
4677
  if (clip.type === "color") return clip.color;
4461
4678
  if (!asset) return "#ef4444";
4462
4679
  return asset.kind === "audio" ? "#10b981" : asset.kind === "image" ? "#f59e0b" : "#3b82f6";
4463
4680
  }
4464
- function videoClipLabel(clip, asset) {
4681
+ function videoClipLabel(clip, asset, sequence) {
4465
4682
  if (clip.name) return clip.name;
4466
4683
  if (clip.type === "text") return clip.text.split("\n")[0];
4467
4684
  if (clip.type === "color") return clip.color;
4468
- const base = _nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _147 => _147.name]), () => ( _optionalChain([asset, 'optionalAccess', _148 => _148.src, 'access', _149 => _149.replace, 'call', _150 => _150(/^.*[\\/]/, "")]))), () => ( clip.asset));
4685
+ if (clip.type === "sequence") return `Compound: ${_nullishCoalesce(_optionalChain([sequence, 'optionalAccess', _154 => _154.name]), () => ( clip.sequence))}`;
4686
+ const base = _nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _155 => _155.name]), () => ( _optionalChain([asset, 'optionalAccess', _156 => _156.src, 'access', _157 => _157.replace, 'call', _158 => _158(/^.*[\\/]/, "")]))), () => ( clip.asset));
4469
4687
  const speed = videoClipSpeed(clip);
4470
4688
  return speed !== 1 ? `${base} (${Math.round(speed * 100)}%)` : base;
4471
4689
  }
@@ -4509,7 +4727,7 @@ function videoAssetDropEdit(project, assetId, options = {}) {
4509
4727
  const asset = videoAssetMap(project).get(assetId);
4510
4728
  if (!asset) return null;
4511
4729
  const kind = asset.kind === "audio" ? "audio" : "video";
4512
- const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access', _151 => _151.tracks, 'access', _152 => _152.find, 'call', _153 => _153((t) => t.kind === kind && !t.locked), 'optionalAccess', _154 => _154.id])));
4730
+ const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access', _159 => _159.tracks, 'access', _160 => _160.find, 'call', _161 => _161((t) => t.kind === kind && !t.locked), 'optionalAccess', _162 => _162.id])));
4513
4731
  if (!track) return null;
4514
4732
  return { op: "add_clip", track, clip: { type: "media", asset: asset.id, ...options.start !== void 0 ? { start: options.start } : {} }, ...options.ripple ? { ripple: true } : {}, ...options.mode && options.mode !== "refuse" ? { mode: options.mode } : {} };
4515
4733
  }
@@ -4558,15 +4776,15 @@ var KEY_LABELS = {
4558
4776
  scale: { label: "Scale", step: 0.05 },
4559
4777
  rotation: { label: "Rotation", step: 1 }
4560
4778
  };
4561
- var animatableOf = (clip, property) => VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([clip, 'access', _155 => _155.transform, 'optionalAccess', _156 => _156[property]]) : property === "volume" ? clip.type === "media" ? clip.volume : void 0 : clip.opacity;
4779
+ var animatableOf = (clip, property) => VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([clip, 'access', _163 => _163.transform, 'optionalAccess', _164 => _164[property]]) : property === "volume" ? clip.type === "media" || clip.type === "sequence" ? clip.volume : void 0 : clip.opacity;
4562
4780
  var playheadInClip = (clip, time) => Math.round(Math.min(clip.duration, Math.max(0, time - clip.start)));
4563
4781
  function videoKeyframeRows(project, clipId, time) {
4564
4782
  const at = findVideoClip(project, clipId);
4565
4783
  if (!at) return [];
4566
4784
  const { clip } = at;
4567
4785
  const asset = clip.type === "media" ? videoAssetMap(project).get(clip.asset) : void 0;
4568
- const visual = !(_optionalChain([asset, 'optionalAccess', _157 => _157.kind]) === "audio" || at.track.kind === "audio");
4569
- const audible = clip.type === "media" && videoAssetHasSound(asset);
4786
+ const visual = !(_optionalChain([asset, 'optionalAccess', _165 => _165.kind]) === "audio" || at.track.kind === "audio");
4787
+ const audible = clip.type === "media" && videoAssetHasSound(asset) || clip.type === "sequence";
4570
4788
  const properties = [...visual ? ["opacity"] : [], ...audible ? ["volume"] : [], ...visual ? VIDEO_TRANSFORM_PROPERTIES : []];
4571
4789
  const clipTime = playheadInClip(clip, time);
4572
4790
  return properties.map((property) => {
@@ -4799,6 +5017,34 @@ var videoAssetOut = (a) => {
4799
5017
  const { transcript, ...rest } = a;
4800
5018
  return transcript ? { ...rest, transcriptWords: transcript.length } : rest;
4801
5019
  };
5020
+ var describeVideoTracks = (tracks) => tracks.map((t, index) => ({
5021
+ id: t.id,
5022
+ kind: t.kind,
5023
+ index,
5024
+ ...t.name ? { name: t.name } : {},
5025
+ ...t.muted ? { muted: true } : {},
5026
+ ...t.hidden ? { hidden: true } : {},
5027
+ ...t.locked ? { locked: true } : {},
5028
+ ...t.syncLocked === false ? { syncLocked: false } : {},
5029
+ ...t.volume !== void 0 ? { volume: t.volume } : {},
5030
+ clips: sortedVideoClips(t).map((c) => ({ ...c, end: videoClipEnd(c), ...c.type === "media" || c.type === "sequence" ? { source: videoClipSourceRange(c) } : {} }))
5031
+ }));
5032
+ var videoSequenceUsers = (project, id) => allVideoTracks(project).flatMap(
5033
+ ({ sequence, track }) => track.clips.flatMap((c) => c.type === "sequence" && c.sequence === id ? [{ clip: c.id, ...sequence ? { inSequence: sequence } : {}, track: track.id, start: c.start, end: videoClipEnd(c), source: videoClipSourceRange(c) }] : [])
5034
+ );
5035
+ function describeVideoSequence(project, sequence, tracks = true) {
5036
+ const cycle = findVideoSequenceCycle(project, sequence.id);
5037
+ return {
5038
+ id: sequence.id,
5039
+ ...sequence.name ? { name: sequence.name } : {},
5040
+ duration: videoSequenceDuration(sequence),
5041
+ clipCount: sequence.tracks.reduce((n2, t) => n2 + t.clips.length, 0),
5042
+ nests: videoSequenceChildren(sequence),
5043
+ usedBy: videoSequenceUsers(project, sequence.id),
5044
+ ...cycle ? { cycle } : {},
5045
+ ...tracks ? { tracks: describeVideoTracks(sequence.tracks) } : { trackCount: sequence.tracks.length }
5046
+ };
5047
+ }
4802
5048
  function describeVideoProject(project) {
4803
5049
  return {
4804
5050
  ...project.name ? { name: project.name } : {},
@@ -4808,18 +5054,8 @@ function describeVideoProject(project) {
4808
5054
  duration: videoProjectDuration(project),
4809
5055
  ...project.background ? { background: project.background } : {},
4810
5056
  assets: project.assets.map(videoAssetOut),
4811
- tracks: project.tracks.map((t, index) => ({
4812
- id: t.id,
4813
- kind: t.kind,
4814
- index,
4815
- ...t.name ? { name: t.name } : {},
4816
- ...t.muted ? { muted: true } : {},
4817
- ...t.hidden ? { hidden: true } : {},
4818
- ...t.locked ? { locked: true } : {},
4819
- ...t.syncLocked === false ? { syncLocked: false } : {},
4820
- ...t.volume !== void 0 ? { volume: t.volume } : {},
4821
- clips: sortedVideoClips(t).map((c) => ({ ...c, end: videoClipEnd(c), ...c.type === "media" ? { source: videoClipSourceRange(c) } : {} }))
4822
- })),
5057
+ tracks: describeVideoTracks(project.tracks),
5058
+ ..._optionalChain([project, 'access', _166 => _166.sequences, 'optionalAccess', _167 => _167.length]) ? { sequences: project.sequences.map((q) => describeVideoSequence(project, q, false)) } : {},
4823
5059
  markers: _nullishCoalesce(project.markers, () => ( []))
4824
5060
  };
4825
5061
  }
@@ -4841,25 +5077,25 @@ function createVideoEditorToolProvider(controller, options = {}) {
4841
5077
  const clipOut = (p, id) => {
4842
5078
  for (const t of p.tracks) {
4843
5079
  const c = t.clips.find((x) => x.id === id);
4844
- if (c) return { track: t.id, ...c, end: videoClipEnd(c), ...c.type === "media" ? { source: videoClipSourceRange(c) } : {} };
5080
+ if (c) return { track: t.id, ...c, end: videoClipEnd(c), ...c.type === "media" || c.type === "sequence" ? { source: videoClipSourceRange(c) } : {} };
4845
5081
  }
4846
5082
  return null;
4847
5083
  };
4848
5084
  const placed = (o) => ({
4849
- ..._optionalChain([o, 'access', _158 => _158.created, 'optionalAccess', _159 => _159.length]) ? { created: o.created } : {},
4850
- ..._optionalChain([o, 'access', _160 => _160.trimmed, 'optionalAccess', _161 => _161.length]) ? { trimmed: o.trimmed } : {},
4851
- ..._optionalChain([o, 'access', _162 => _162.removed, 'optionalAccess', _163 => _163.length]) ? { removed: o.removed } : {},
5085
+ ..._optionalChain([o, 'access', _168 => _168.created, 'optionalAccess', _169 => _169.length]) ? { created: o.created } : {},
5086
+ ..._optionalChain([o, 'access', _170 => _170.trimmed, 'optionalAccess', _171 => _171.length]) ? { trimmed: o.trimmed } : {},
5087
+ ..._optionalChain([o, 'access', _172 => _172.removed, 'optionalAccess', _173 => _173.length]) ? { removed: o.removed } : {},
4852
5088
  ...o.rippled ? { rippled: o.rippled } : {}
4853
5089
  });
4854
5090
  const keysOut = (p, clip, property) => {
4855
5091
  const c = clipOut(p, clip);
4856
- const value = VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess', _164 => _164.transform, 'optionalAccess', _165 => _165[property]]) : _optionalChain([c, 'optionalAccess', _166 => _166[property]]);
5092
+ const value = VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess', _174 => _174.transform, 'optionalAccess', _175 => _175[property]]) : _optionalChain([c, 'optionalAccess', _176 => _176[property]]);
4857
5093
  return typeof value === "object" ? { keys: value.keys } : { keys: [], value: _nullishCoalesce(value, () => ( null)) };
4858
5094
  };
4859
5095
  const getProject = {
4860
5096
  name: "get_video_project",
4861
5097
  title: "Read the video project",
4862
- description: "Returns the whole project: frame size, fps, length (ms), assets (id, kind, src, duration), tracks top to bottom (an earlier video track draws over a later one) with each clip in time order (id, type, start, duration, end, source in/out for media, fades, transition) and markers, plus the playhead, selection, issues and whether it can be edited.",
5098
+ description: `Returns the whole project: frame size, fps, length (ms), assets (id, kind, src, duration), tracks top to bottom (an earlier video track draws over a later one) with each clip in time order (id, type, start, duration, end, source in/out for media and compound clips, fades, transition), a summary of each nested sequence (id, name, length, clip count, the sequences it nests, the compound clips that play it) and markers, plus the playhead, selection, issues and whether it can be edited. Compound clips (type "sequence") play a sequence's tracks as one picture and sound; list_video_sequences reads what is inside them.`,
4863
5099
  inputSchema: object({}),
4864
5100
  annotations: { title: "Read the video project", ...READ },
4865
5101
  handler: () => {
@@ -4871,7 +5107,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
4871
5107
  const getFrame = {
4872
5108
  name: "get_video_frame",
4873
5109
  title: "What plays at a moment",
4874
- description: "Returns what plays at a time without changing anything: the pictures back to front (clip, track, asset, source time in ms, opacity, placement rect, rotation, any wipe or iris) and the sounds (clip, source time, gain after volumes, fades and transitions).",
5110
+ description: `Returns what plays at a time without changing anything: the pictures back to front (clip, track, asset, source time in ms, opacity, placement rect, rotation, any wipe or iris) and the sounds (clip, source time, gain after volumes, fades and transitions). A compound clip is one picture with its sequence, the moment of the sequence it shows (sourceTime) and the sequence's own pictures as "layers"; its sounds are listed with clip paths such as "compound/inner".`,
4875
5111
  inputSchema: object({ time: MS("Time from the start (default: the playhead)") }),
4876
5112
  annotations: { title: "What plays at a moment", ...READ },
4877
5113
  handler: ({ time }) => {
@@ -4879,6 +5115,23 @@ function createVideoEditorToolProvider(controller, options = {}) {
4879
5115
  return { ...frame, timecode: formatVideoTimecode(frame.time, project().fps) };
4880
5116
  }
4881
5117
  };
5118
+ const listSequences = {
5119
+ name: "list_video_sequences",
5120
+ title: "List nested sequences",
5121
+ description: `Lists the project's nested sequences, the tracks that compound clips (type "sequence") play as one picture and sound, without changing anything. For each: id, name, length (ms), the sequences it nests, the compound clips that play it (clip id, the track and sequence they sit on, timeline start and end, and the stretch of the sequence they play as source in / out) and its tracks with their clips, in the same shape as get_video_project. A sequence that contains itself lists the loop as "cycle". Clips inside sequences can be read here but not edited yet; edit the compound clip instead (move, trim, its opacity, blend, grade, volume).`,
5122
+ inputSchema: object({ sequence: { type: "string", minLength: 1, description: "Only this sequence id (default: every sequence)." } }),
5123
+ annotations: { title: "List nested sequences", ...READ },
5124
+ handler: ({ sequence }) => {
5125
+ const p = project();
5126
+ const all = _nullishCoalesce(p.sequences, () => ( []));
5127
+ if (sequence !== void 0) {
5128
+ const found = all.find((q) => q.id === sequence);
5129
+ if (!found) throw new Error(`There is no sequence "${sequence}"; ${all.length ? `the sequences are ${all.map((q) => `"${q.id}"`).join(", ")}` : "the project has no sequences"}.`);
5130
+ return { sequences: [describeVideoSequence(p, found)] };
5131
+ }
5132
+ return { sequences: all.map((q) => describeVideoSequence(p, q)) };
5133
+ }
5134
+ };
4882
5135
  const findGaps = {
4883
5136
  name: "find_video_gaps",
4884
5137
  title: "Find gaps",
@@ -4914,7 +5167,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
4914
5167
  annotations: { title: "Render a frame", ...READ },
4915
5168
  handler: async ({ time, times, columns, format = "svg" }) => {
4916
5169
  const p = project();
4917
- const lutSrcs = [...new Set(p.tracks.flatMap((t) => t.clips.map((c) => _optionalChain([c, 'access', _167 => _167.grade, 'optionalAccess', _168 => _168.lut, 'optionalAccess', _169 => _169.src])).filter((x) => !!x)))];
5170
+ const lutSrcs = [...new Set(p.tracks.flatMap((t) => t.clips.map((c) => _optionalChain([c, 'access', _177 => _177.grade, 'optionalAccess', _178 => _178.lut, 'optionalAccess', _179 => _179.src])).filter((x) => !!x)))];
4918
5171
  const missing = (await Promise.all(lutSrcs.map((src) => controller.luts.load(src).then(() => null, (e) => e.message)))).filter((m) => !!m);
4919
5172
  const svgOptions = { lutFor: (src) => controller.luts.peek(src), ...columns ? { columns } : {} };
4920
5173
  const svg = times ? videoContactSheetSvg(p, times, svgOptions) : videoProjectSvg(p, _nullishCoalesce(time, () => ( controller.getState().time)), svgOptions);
@@ -5129,7 +5382,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5129
5382
  const updateClip = {
5130
5383
  name: "update_video_clip",
5131
5384
  title: "Change a clip",
5132
- description: `Changes a clip's properties (one undo step): opacity, transform, fit, blend mode, colour grade, entrance and exit animations, fades, name; for media volume, mute, the sound's fade curves, speed (keeps the stretch of source, so the length changes) or asset; for titles text and style; for colours the colour. Fields in "remove" go back to their defaults. Use move / trim for timing. Returns the clip.`,
5385
+ description: `Changes a clip's properties (one undo step): opacity, transform, fit, blend mode, colour grade, entrance and exit animations, fades, name; for media volume, mute, the sound's fade curves, speed (keeps the stretch of source, so the length changes) or asset; for compound clips in point, volume, mute and fade curves (no speed); for titles text and style; for colours the colour. Fields in "remove" go back to their defaults. Use move / trim for timing. Returns the clip.`,
5133
5386
  inputSchema: object(
5134
5387
  {
5135
5388
  clip: CLIP_ID,
@@ -5142,7 +5395,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5142
5395
  handler: ({ clip, set, remove }) => {
5143
5396
  const current = clipOut(project(), clip);
5144
5397
  if (current && set) {
5145
- const allowed = /* @__PURE__ */ new Set(["name", "opacity", "transform", "fit", "blend", "grade", "animateIn", "animateOut", "fadeIn", "fadeOut", ...current.type === "media" ? ["in", "speed", "volume", "muted", "asset", "fadeInCurve", "fadeOutCurve"] : current.type === "text" ? Object.keys(TEXT_FIELDS) : ["color"]]);
5398
+ const allowed = /* @__PURE__ */ new Set(["name", "opacity", "transform", "fit", "blend", "grade", "animateIn", "animateOut", "fadeIn", "fadeOut", ...current.type === "media" ? ["in", "speed", "volume", "muted", "asset", "fadeInCurve", "fadeOutCurve"] : current.type === "sequence" ? ["in", "volume", "muted", "fadeInCurve", "fadeOutCurve"] : current.type === "text" ? Object.keys(TEXT_FIELDS) : ["color"]]);
5146
5399
  const wrong = Object.keys(set).filter((k) => !allowed.has(k));
5147
5400
  if (wrong.length) throw new Error(`${wrong.map((k) => `"${k}"`).join(", ")} cannot be set on a ${current.type} clip; it takes ${[...allowed].join(", ")}.`);
5148
5401
  }
@@ -5155,7 +5408,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5155
5408
  const at = findVideoClip(p, id);
5156
5409
  if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
5157
5410
  const { clip } = at;
5158
- if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access', _170 => _170.assets, 'access', _171 => _171.find, 'call', _172 => _172((a) => a.id === clip.asset), 'optionalAccess', _173 => _173.kind]) === "audio")
5411
+ if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access', _180 => _180.assets, 'access', _181 => _181.find, 'call', _182 => _182((a) => a.id === clip.asset), 'optionalAccess', _183 => _183.kind]) === "audio")
5159
5412
  throw new Error(`Clip "${id}" is sound only; grades change pictures. Pass clips on video tracks.`);
5160
5413
  }
5161
5414
  };
@@ -5178,7 +5431,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5178
5431
  const grade = videoGradeLook(findVideoClip(project(), clip).clip.grade, preset, _nullishCoalesce(amount, () => ( 1)));
5179
5432
  return grade ? { op: "update_clip", clip, set: { grade } } : { op: "update_clip", clip, remove: ["grade"] };
5180
5433
  });
5181
- return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _174 => _174(r.project, id), 'optionalAccess', _175 => _175.clip, 'access', _176 => _176.grade]), () => ( null)) })) }));
5434
+ return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _184 => _184(r.project, id), 'optionalAccess', _185 => _185.clip, 'access', _186 => _186.grade]), () => ( null)) })) }));
5182
5435
  }
5183
5436
  };
5184
5437
  const copyGrade = {
@@ -5214,7 +5467,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5214
5467
  const lut = await controller.luts.load(src);
5215
5468
  const label = _nullishCoalesce(name, () => ( lut.title));
5216
5469
  const setting = { src, ...strength !== void 0 && strength !== 1 ? { strength } : {}, ...label ? { name: label } : {} };
5217
- return apply(videoLutEdits(project(), clips, setting), "Apply LUT", (r) => ({ lut: { src, ...lut.title ? { title: lut.title } : {}, size: lut.size }, clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _177 => _177(r.project, id), 'optionalAccess', _178 => _178.clip, 'access', _179 => _179.grade]), () => ( null)) })) }));
5470
+ return apply(videoLutEdits(project(), clips, setting), "Apply LUT", (r) => ({ lut: { src, ...lut.title ? { title: lut.title } : {}, size: lut.size }, clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _187 => _187(r.project, id), 'optionalAccess', _188 => _188.clip, 'access', _189 => _189.grade]), () => ( null)) })) }));
5218
5471
  }
5219
5472
  };
5220
5473
  const removeLut = {
@@ -5226,9 +5479,9 @@ function createVideoEditorToolProvider(controller, options = {}) {
5226
5479
  handler: ({ clips }) => {
5227
5480
  gradeTargets(clips);
5228
5481
  const p = project();
5229
- const withLut = clips.filter((id) => _optionalChain([findVideoClip, 'call', _180 => _180(p, id), 'access', _181 => _181.clip, 'access', _182 => _182.grade, 'optionalAccess', _183 => _183.lut]));
5482
+ const withLut = clips.filter((id) => _optionalChain([findVideoClip, 'call', _190 => _190(p, id), 'access', _191 => _191.clip, 'access', _192 => _192.grade, 'optionalAccess', _193 => _193.lut]));
5230
5483
  if (!withLut.length) throw new Error(`None of ${clips.map((c) => `"${c}"`).join(", ")} has a LUT; get_video_project shows each clip's grade.`);
5231
- return apply(videoLutEdits(p, withLut, null), "Remove LUT", (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _184 => _184(r.project, id), 'optionalAccess', _185 => _185.clip, 'access', _186 => _186.grade]), () => ( null)) })) }));
5484
+ return apply(videoLutEdits(p, withLut, null), "Remove LUT", (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _194 => _194(r.project, id), 'optionalAccess', _195 => _195.clip, 'access', _196 => _196.grade]), () => ( null)) })) }));
5232
5485
  }
5233
5486
  };
5234
5487
  const PROXY_SCALE = { type: "number", exclusiveMinimum: 0, exclusiveMaximum: 1, description: "Proxy size as a fraction of the original: 0.5 (half width and height, the default) or 0.25 for very large footage." };
@@ -5286,7 +5539,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5286
5539
  controller.setPreviewScale(scale);
5287
5540
  const s = controller.getState().previewScale;
5288
5541
  const fixed = s === "full" ? 1 : typeof s === "number" ? s : null;
5289
- const plays = fixed === null ? void 0 : project().assets.filter((a) => a.kind === "video").map((a) => ({ asset: a.id, src: _nullishCoalesce(_optionalChain([videoAssetProxy, 'call', _187 => _187(a, fixed), 'optionalAccess', _188 => _188.src]), () => ( a.src)), proxy: !!videoAssetProxy(a, fixed) }));
5542
+ const plays = fixed === null ? void 0 : project().assets.filter((a) => a.kind === "video").map((a) => ({ asset: a.id, src: _nullishCoalesce(_optionalChain([videoAssetProxy, 'call', _197 => _197(a, fixed), 'optionalAccess', _198 => _198.src]), () => ( a.src)), proxy: !!videoAssetProxy(a, fixed) }));
5290
5543
  return { previewScale: s, ...plays ? { plays } : { note: "auto picks the smallest proxy at least as large as the preview canvas shows, so it depends on the preview's size." } };
5291
5544
  }
5292
5545
  };
@@ -5355,7 +5608,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5355
5608
  handler: ({ track, from, to, limit }) => {
5356
5609
  const all = timelineWords(track).filter((w) => w.end > (_nullishCoalesce(from, () => ( 0))) && w.start < (_nullishCoalesce(to, () => ( Infinity))));
5357
5610
  const words = all.slice(0, _nullishCoalesce(limit, () => ( 1e3)));
5358
- return { track: _nullishCoalesce(_optionalChain([words, 'access', _189 => _189[0], 'optionalAccess', _190 => _190.track]), () => ( videoTranscriptTrack(project(), track))), text: words.map((w) => w.word).join(" "), words: words.map((w) => ({ word: w.word, start: round(w.start), end: round(w.end), clip: w.clip })), more: all.length > words.length };
5611
+ return { track: _nullishCoalesce(_optionalChain([words, 'access', _199 => _199[0], 'optionalAccess', _200 => _200.track]), () => ( videoTranscriptTrack(project(), track))), text: words.map((w) => w.word).join(" "), words: words.map((w) => ({ word: w.word, start: round(w.start), end: round(w.end), clip: w.clip })), more: all.length > words.length };
5359
5612
  }
5360
5613
  };
5361
5614
  const findText = {
@@ -5416,8 +5669,9 @@ function createVideoEditorToolProvider(controller, options = {}) {
5416
5669
  const at = findVideoClip(project(), id);
5417
5670
  if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
5418
5671
  const { clip } = at;
5672
+ if (clip.type === "sequence") throw new Error(`Clip "${id}" is a compound clip, which plays its sequence at normal speed; change the speed of the clips inside the sequence instead.`);
5419
5673
  if (clip.type !== "media") throw new Error(`Clip "${id}" is a ${clip.type === "text" ? "title" : "colour"}, so it has no source to change the speed of; use a video or audio clip.`);
5420
- if (_optionalChain([project, 'call', _191 => _191(), 'access', _192 => _192.assets, 'access', _193 => _193.find, 'call', _194 => _194((a) => a.id === clip.asset), 'optionalAccess', _195 => _195.kind]) === "image") throw new Error(`Clip "${id}" is a still, so it has no motion to change the speed of.`);
5674
+ if (_optionalChain([project, 'call', _201 => _201(), 'access', _202 => _202.assets, 'access', _203 => _203.find, 'call', _204 => _204((a) => a.id === clip.asset), 'optionalAccess', _205 => _205.kind]) === "image") throw new Error(`Clip "${id}" is a still, so it has no motion to change the speed of.`);
5421
5675
  return clip;
5422
5676
  };
5423
5677
  const rampOut = (r, id) => {
@@ -5651,6 +5905,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5651
5905
  const found = findVideoClip(p, args.clip);
5652
5906
  if (!found) throw new Error(`There is no clip "${args.clip}"; get_video_project lists the clips.`);
5653
5907
  const c = found.clip;
5908
+ if (c.type === "sequence") throw new Error(`Clip "${c.id}" is a compound clip; its sound is the mix of its sequence "${c.sequence}", so read the levels of the media in it (list_video_sequences lists its clips and assets).`);
5654
5909
  if (c.type !== "media") throw new Error(`Clip "${c.id}" is a ${c.type} clip and has no sound; pass a media clip.`);
5655
5910
  asset = p.assets.find((a) => a.id === c.asset);
5656
5911
  const speed = _nullishCoalesce(c.speed, () => ( 1));
@@ -5745,7 +6000,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
5745
6000
  return { time: t, timecode: formatVideoTimecode(t, p.fps), width: pixels.width, height: pixels.height, ...summarizeVideoScopes(pixels, { bins, columns }) };
5746
6001
  }
5747
6002
  };
5748
- const reads = [getProject, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, previewCache, planProxies, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek, setPreviewScale];
6003
+ const reads = [getProject, listSequences, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, previewCache, planProxies, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek, setPreviewScale];
5749
6004
  const writes = [setSettings, addAsset, updateAsset, removeAsset, setProxies, addTrack, updateTrack, removeTrack, moveTrack, addClip, moveClip, trimClip, splitClip, deleteClips, updateClip, setTransition, setSpeedRamp, addFreeze, removeRamp, setTranscript, removeRanges, applyGradePreset, copyGrade, setLut, removeLut, setKeyframes, removeKeyframes, slipClip, rollEdit, slideClip, liftRange, extractRange, splitAt, closeGaps, addMarker, removeMarker, editVideo, undo, redo];
5750
6005
  return {
5751
6006
  id: _nullishCoalesce(options.id, () => ( "video")),
@@ -5986,5 +6241,14 @@ function createVideoEditorToolProvider(controller, options = {}) {
5986
6241
 
5987
6242
 
5988
6243
 
5989
- exports.VIDEO_BLEND_MODES = VIDEO_BLEND_MODES; exports.VIDEO_BLEND_MODE_LABELS = VIDEO_BLEND_MODE_LABELS; exports.VIDEO_CLIP_ANIMATION_DURATION = VIDEO_CLIP_ANIMATION_DURATION; exports.VIDEO_CLIP_ANIMATION_EFFECTS = VIDEO_CLIP_ANIMATION_EFFECTS; exports.VIDEO_CLIP_ANIMATION_LABELS = VIDEO_CLIP_ANIMATION_LABELS; exports.VIDEO_CLIP_TYPES = VIDEO_CLIP_TYPES; exports.VIDEO_DEFAULT_FILLERS = VIDEO_DEFAULT_FILLERS; exports.VIDEO_DEFAULT_STILL_DURATION = VIDEO_DEFAULT_STILL_DURATION; exports.VIDEO_DEFAULT_TITLE_DURATION = VIDEO_DEFAULT_TITLE_DURATION; exports.VIDEO_EASE_NAMES = VIDEO_EASE_NAMES; exports.VIDEO_EDIT_OPS = VIDEO_EDIT_OPS; exports.VIDEO_FADE_CURVES = VIDEO_FADE_CURVES; exports.VIDEO_FADE_CURVE_LABELS = VIDEO_FADE_CURVE_LABELS; exports.VIDEO_FITS = VIDEO_FITS; exports.VIDEO_GRADE_CONTROLS = VIDEO_GRADE_CONTROLS; exports.VIDEO_GRADE_PRESETS = VIDEO_GRADE_PRESETS; exports.VIDEO_GRADE_PRESET_NAMES = VIDEO_GRADE_PRESET_NAMES; exports.VIDEO_ISSUE_CODES = VIDEO_ISSUE_CODES; exports.VIDEO_KEYFRAME_LIMITS = VIDEO_KEYFRAME_LIMITS; exports.VIDEO_KEYFRAME_PROPERTIES = VIDEO_KEYFRAME_PROPERTIES; exports.VIDEO_LUT_MAX_SIZE = VIDEO_LUT_MAX_SIZE; exports.VIDEO_PEAK_BLOCK = VIDEO_PEAK_BLOCK; exports.VIDEO_PLACEMENT_MODES = VIDEO_PLACEMENT_MODES; exports.VIDEO_PREVIEW_SCALE_OPTIONS = VIDEO_PREVIEW_SCALE_OPTIONS; exports.VIDEO_PROXY_SCALES = VIDEO_PROXY_SCALES; exports.VIDEO_SCOPE_KINDS = VIDEO_SCOPE_KINDS; exports.VIDEO_TIME_EPSILON = VIDEO_TIME_EPSILON; exports.VIDEO_TRANSFORM_PROPERTIES = VIDEO_TRANSFORM_PROPERTIES; exports.VIDEO_TRANSITION_LABELS = VIDEO_TRANSITION_LABELS; exports.VIDEO_TRANSITION_TYPES = VIDEO_TRANSITION_TYPES; exports.VIDEO_VECTORSCOPE_TARGETS = VIDEO_VECTORSCOPE_TARGETS; exports.VIDEO_WIPE_DIRECTIONS = VIDEO_WIPE_DIRECTIONS; exports.VideoAudioPeaksCache = VideoAudioPeaksCache; exports.VideoEditorController = VideoEditorController; exports.VideoFrameCache = VideoFrameCache; exports.VideoLutCache = VideoLutCache; exports.VideoPreviewEngine = VideoPreviewEngine; exports.VideoPreviewPixels = VideoPreviewPixels; exports.allVideoClipIds = allVideoClipIds; exports.applyVideoEdits = applyVideoEdits; exports.applyVideoGrade = applyVideoGrade; exports.applyVideoLut = applyVideoLut; exports.applyVideoLutToPixels = applyVideoLutToPixels; exports.buildVideoAudioPeaks = buildVideoAudioPeaks; exports.checkVideoProject = checkVideoProject; exports.checkVideoTimeRemap = checkVideoTimeRemap; exports.createVideoEditorToolProvider = createVideoEditorToolProvider; exports.createVideoElementDecoder = createVideoElementDecoder; exports.createVideoProject = createVideoProject; exports.decodeVideoAudioPeaks = decodeVideoAudioPeaks; exports.describeVideoProject = describeVideoProject; exports.drawVideoFrame = drawVideoFrame; exports.ffmpegFilterValue = ffmpegFilterValue; exports.findVideoAudioSilences = findVideoAudioSilences; exports.findVideoClip = findVideoClip; exports.findVideoFillers = findVideoFillers; exports.findVideoGaps = findVideoGaps; exports.findVideoTranscriptPauses = findVideoTranscriptPauses; exports.findVideoTranscriptText = findVideoTranscriptText; exports.formatVideoSeconds = formatVideoSeconds; exports.formatVideoTimecode = formatVideoTimecode; exports.formatVideoTimecodeFrames = formatVideoTimecodeFrames; exports.isVideoDropFrameRate = isVideoDropFrameRate; exports.isVideoKeyframes = isVideoKeyframes; exports.mergeVideoRanges = mergeVideoRanges; exports.nextFreeVideoSlot = nextFreeVideoSlot; exports.normalizeVideoGrade = normalizeVideoGrade; exports.normalizeVideoGradeLut = normalizeVideoGradeLut; exports.normalizeVideoWord = normalizeVideoWord; exports.parseVideoAnimatable = parseVideoAnimatable; exports.parseVideoAsset = parseVideoAsset; exports.parseVideoAssetProxies = parseVideoAssetProxies; exports.parseVideoClip = parseVideoClip; exports.parseVideoClipAnimation = parseVideoClipAnimation; exports.parseVideoCubeLut = parseVideoCubeLut; exports.parseVideoGrade = parseVideoGrade; exports.parseVideoGradeLut = parseVideoGradeLut; exports.parseVideoKeyframes = parseVideoKeyframes; exports.parseVideoLoudnessMeasurement = parseVideoLoudnessMeasurement; exports.parseVideoMarker = parseVideoMarker; exports.parseVideoPreviewScale = parseVideoPreviewScale; exports.parseVideoProject = parseVideoProject; exports.parseVideoTimeRemap = parseVideoTimeRemap; exports.parseVideoTrack = parseVideoTrack; exports.parseVideoTranscript = parseVideoTranscript; exports.parseVideoTranscriptWords = parseVideoTranscriptWords; exports.parseVideoTransform = parseVideoTransform; exports.parseVideoTransition = parseVideoTransition; exports.placeVideoRect = placeVideoRect; exports.planVideoClipDrag = planVideoClipDrag; exports.planVideoPrefetch = planVideoPrefetch; exports.probeVideoMedia = probeVideoMedia; exports.readVideoScopesPanel = readVideoScopesPanel; exports.removeVideoKey = removeVideoKey; exports.resolveVideoPreviewScale = resolveVideoPreviewScale; exports.retimeVideoAnimatable = retimeVideoAnimatable; exports.setVideoKey = setVideoKey; exports.snapToVideoFrame = snapToVideoFrame; exports.snapVideoTime = snapVideoTime; exports.sortVideoKeys = sortVideoKeys; exports.sortedVideoClips = sortedVideoClips; exports.splitVideoAnimatable = splitVideoAnimatable; exports.summarizeVideoScopes = summarizeVideoScopes; exports.toVideoEdl = toVideoEdl; exports.toVideoFfmpeg = toVideoFfmpeg; exports.toVideoLoudnessMeasure = toVideoLoudnessMeasure; exports.videoAssetDropEdit = videoAssetDropEdit; exports.videoAssetHasSound = videoAssetHasSound; exports.videoAssetKindOf = videoAssetKindOf; exports.videoAssetMap = videoAssetMap; exports.videoAssetProxy = videoAssetProxy; exports.videoAudioLevels = videoAudioLevels; exports.videoAudioPeakRange = videoAudioPeakRange; exports.videoAudioPeakSpans = videoAudioPeakSpans; exports.videoAudioPeaksDuration = videoAudioPeaksDuration; exports.videoCacheEvictions = videoCacheEvictions; exports.videoChroma = videoChroma; exports.videoClipAfter = videoClipAfter; exports.videoClipAnimated = videoClipAnimated; exports.videoClipAnimationProperties = videoClipAnimationProperties; exports.videoClipAnimationSpans = videoClipAnimationSpans; exports.videoClipBefore = videoClipBefore; exports.videoClipEnd = videoClipEnd; exports.videoClipHandles = videoClipHandles; exports.videoClipKeyframes = videoClipKeyframes; exports.videoClipLabel = videoClipLabel; exports.videoClipOverhang = videoClipOverhang; exports.videoClipShowsWaveform = videoClipShowsWaveform; exports.videoClipSoundGain = videoClipSoundGain; exports.videoClipSourceRange = videoClipSourceRange; exports.videoClipSourceTime = videoClipSourceTime; exports.videoClipSpeed = videoClipSpeed; exports.videoClipSpeedAt = videoClipSpeedAt; exports.videoClipTint = videoClipTint; exports.videoClipWaveform = videoClipWaveform; exports.videoClipWaveformPath = videoClipWaveformPath; exports.videoClipsAt = videoClipsAt; exports.videoContactSheetSvg = videoContactSheetSvg; exports.videoDragLabel = videoDragLabel; exports.videoDropTracks = videoDropTracks; exports.videoEaseBezier = videoEaseBezier; exports.videoEaseProgress = videoEaseProgress; exports.videoExposureNotes = videoExposureNotes; exports.videoExposureStats = videoExposureStats; exports.videoFadeCurveGain = videoFadeCurveGain; exports.videoFrameAt = videoFrameAt; exports.videoFrameCacheKey = videoFrameCacheKey; exports.videoFrameDuration = videoFrameDuration; exports.videoFrameRequestsAt = videoFrameRequestsAt; exports.videoFrameToSvg = videoFrameToSvg; exports.videoFrames = videoFrames; exports.videoFreezeFrameEdit = videoFreezeFrameEdit; exports.videoGradeCssFilter = videoGradeCssFilter; exports.videoGradeFfmpegFilters = videoGradeFfmpegFilters; exports.videoGradeGainsFilterId = videoGradeGainsFilterId; exports.videoGradeLook = videoGradeLook; exports.videoGradePreset = videoGradePreset; exports.videoGradeStepMatrix = videoGradeStepMatrix; exports.videoGradeStepSvgValues = videoGradeStepSvgValues; exports.videoGradeSteps = videoGradeSteps; exports.videoGradeSvgDefs = videoGradeSvgDefs; exports.videoGradeSvgFilter = videoGradeSvgFilter; exports.videoHistogram = videoHistogram; exports.videoHistogramImage = videoHistogramImage; exports.videoHourFrames = videoHourFrames; exports.videoIrisRadius = videoIrisRadius; exports.videoIssueKey = videoIssueKey; exports.videoKeyframeBreakpoints = videoKeyframeBreakpoints; exports.videoKeyframeFfmpegExpr = videoKeyframeFfmpegExpr; exports.videoKeyframeRows = videoKeyframeRows; exports.videoKeyframeToggleEdit = videoKeyframeToggleEdit; exports.videoKeyframeValueEdit = videoKeyframeValueEdit; exports.videoKeysOf = videoKeysOf; exports.videoLuma = videoLuma; exports.videoLumaWaveform = videoLumaWaveform; exports.videoLutEdits = videoLutEdits; exports.videoLutFromFunction = videoLutFromFunction; exports.videoLutSvgApproximation = videoLutSvgApproximation; exports.videoLutSvgPrimitives = videoLutSvgPrimitives; exports.videoLutToCube = videoLutToCube; exports.videoOverlap = videoOverlap; exports.videoPeakDb = videoPeakDb; exports.videoPreviewSrc = videoPreviewSrc; exports.videoProjectDuration = videoProjectDuration; exports.videoProjectHasProxies = videoProjectHasProxies; exports.videoProjectSvg = videoProjectSvg; exports.videoProxyFfmpeg = videoProxyFfmpeg; exports.videoProxyName = videoProxyName; exports.videoProxyPlan = videoProxyPlan; exports.videoRemoveRangesEdits = videoRemoveRangesEdits; exports.videoRulerTicks = videoRulerTicks; exports.videoScopeImage = videoScopeImage; exports.videoShellQuote = videoShellQuote; exports.videoSlowMotionRamp = videoSlowMotionRamp; exports.videoSnapPoints = videoSnapPoints; exports.videoSourceFrame = videoSourceFrame; exports.videoSourceFrameTime = videoSourceFrameTime; exports.videoSpeedRampEdit = videoSpeedRampEdit; exports.videoSpeedRampKeys = videoSpeedRampKeys; exports.videoTextStyle = videoTextStyle; exports.videoTimeRemapBreakpoints = videoTimeRemapBreakpoints; exports.videoTimeRemapSpan = videoTimeRemapSpan; exports.videoTimecodeFrames = videoTimecodeFrames; exports.videoTimelineTranscript = videoTimelineTranscript; exports.videoTitleFrame = videoTitleFrame; exports.videoTrackSyncLocked = videoTrackSyncLocked; exports.videoTranscriptEdit = videoTranscriptEdit; exports.videoTranscriptTrack = videoTranscriptTrack; exports.videoTransformAt = videoTransformAt; exports.videoTransitionAudioCurve = videoTransitionAudioCurve; exports.videoTransitionEase = videoTransitionEase; exports.videoTransitionHasDirection = videoTransitionHasDirection; exports.videoValueAt = videoValueAt; exports.videoVectorscope = videoVectorscope; exports.videoVectorscopeAngle = videoVectorscopeAngle; exports.videoVectorscopeImage = videoVectorscopeImage; exports.videoVectorscopePoint = videoVectorscopePoint; exports.videoWaveformImage = videoWaveformImage; exports.videoWaveformPath = videoWaveformPath; exports.videoWipeRect = videoWipeRect; exports.videoWordAt = videoWordAt; exports.videoWordsRange = videoWordsRange;
6244
+
6245
+
6246
+
6247
+
6248
+
6249
+
6250
+
6251
+
6252
+
6253
+ exports.VIDEO_BLEND_MODES = VIDEO_BLEND_MODES; exports.VIDEO_BLEND_MODE_LABELS = VIDEO_BLEND_MODE_LABELS; exports.VIDEO_CLIP_ANIMATION_DURATION = VIDEO_CLIP_ANIMATION_DURATION; exports.VIDEO_CLIP_ANIMATION_EFFECTS = VIDEO_CLIP_ANIMATION_EFFECTS; exports.VIDEO_CLIP_ANIMATION_LABELS = VIDEO_CLIP_ANIMATION_LABELS; exports.VIDEO_CLIP_TYPES = VIDEO_CLIP_TYPES; exports.VIDEO_DEFAULT_FILLERS = VIDEO_DEFAULT_FILLERS; exports.VIDEO_DEFAULT_STILL_DURATION = VIDEO_DEFAULT_STILL_DURATION; exports.VIDEO_DEFAULT_TITLE_DURATION = VIDEO_DEFAULT_TITLE_DURATION; exports.VIDEO_EASE_NAMES = VIDEO_EASE_NAMES; exports.VIDEO_EDIT_OPS = VIDEO_EDIT_OPS; exports.VIDEO_FADE_CURVES = VIDEO_FADE_CURVES; exports.VIDEO_FADE_CURVE_LABELS = VIDEO_FADE_CURVE_LABELS; exports.VIDEO_FITS = VIDEO_FITS; exports.VIDEO_GRADE_CONTROLS = VIDEO_GRADE_CONTROLS; exports.VIDEO_GRADE_PRESETS = VIDEO_GRADE_PRESETS; exports.VIDEO_GRADE_PRESET_NAMES = VIDEO_GRADE_PRESET_NAMES; exports.VIDEO_ISSUE_CODES = VIDEO_ISSUE_CODES; exports.VIDEO_KEYFRAME_LIMITS = VIDEO_KEYFRAME_LIMITS; exports.VIDEO_KEYFRAME_PROPERTIES = VIDEO_KEYFRAME_PROPERTIES; exports.VIDEO_LUT_MAX_SIZE = VIDEO_LUT_MAX_SIZE; exports.VIDEO_PEAK_BLOCK = VIDEO_PEAK_BLOCK; exports.VIDEO_PLACEMENT_MODES = VIDEO_PLACEMENT_MODES; exports.VIDEO_PREVIEW_SCALE_OPTIONS = VIDEO_PREVIEW_SCALE_OPTIONS; exports.VIDEO_PROXY_SCALES = VIDEO_PROXY_SCALES; exports.VIDEO_SCOPE_KINDS = VIDEO_SCOPE_KINDS; exports.VIDEO_TIME_EPSILON = VIDEO_TIME_EPSILON; exports.VIDEO_TRANSFORM_PROPERTIES = VIDEO_TRANSFORM_PROPERTIES; exports.VIDEO_TRANSITION_LABELS = VIDEO_TRANSITION_LABELS; exports.VIDEO_TRANSITION_TYPES = VIDEO_TRANSITION_TYPES; exports.VIDEO_VECTORSCOPE_TARGETS = VIDEO_VECTORSCOPE_TARGETS; exports.VIDEO_WIPE_DIRECTIONS = VIDEO_WIPE_DIRECTIONS; exports.VideoAudioPeaksCache = VideoAudioPeaksCache; exports.VideoEditorController = VideoEditorController; exports.VideoFrameCache = VideoFrameCache; exports.VideoLutCache = VideoLutCache; exports.VideoPreviewEngine = VideoPreviewEngine; exports.VideoPreviewPixels = VideoPreviewPixels; exports.allVideoClipIds = allVideoClipIds; exports.allVideoTracks = allVideoTracks; exports.applyVideoEdits = applyVideoEdits; exports.applyVideoGrade = applyVideoGrade; exports.applyVideoLut = applyVideoLut; exports.applyVideoLutToPixels = applyVideoLutToPixels; exports.buildVideoAudioPeaks = buildVideoAudioPeaks; exports.checkVideoProject = checkVideoProject; exports.checkVideoTimeRemap = checkVideoTimeRemap; exports.createVideoEditorToolProvider = createVideoEditorToolProvider; exports.createVideoElementDecoder = createVideoElementDecoder; exports.createVideoProject = createVideoProject; exports.decodeVideoAudioPeaks = decodeVideoAudioPeaks; exports.describeVideoProject = describeVideoProject; exports.describeVideoSequence = describeVideoSequence; exports.drawVideoFrame = drawVideoFrame; exports.ffmpegFilterValue = ffmpegFilterValue; exports.findVideoAudioSilences = findVideoAudioSilences; exports.findVideoClip = findVideoClip; exports.findVideoFillers = findVideoFillers; exports.findVideoGaps = findVideoGaps; exports.findVideoSequenceClip = findVideoSequenceClip; exports.findVideoSequenceCycle = findVideoSequenceCycle; exports.findVideoTranscriptPauses = findVideoTranscriptPauses; exports.findVideoTranscriptText = findVideoTranscriptText; exports.formatVideoSeconds = formatVideoSeconds; exports.formatVideoTimecode = formatVideoTimecode; exports.formatVideoTimecodeFrames = formatVideoTimecodeFrames; exports.isVideoDropFrameRate = isVideoDropFrameRate; exports.isVideoKeyframes = isVideoKeyframes; exports.mergeVideoRanges = mergeVideoRanges; exports.nextFreeVideoSlot = nextFreeVideoSlot; exports.normalizeVideoGrade = normalizeVideoGrade; exports.normalizeVideoGradeLut = normalizeVideoGradeLut; exports.normalizeVideoWord = normalizeVideoWord; exports.parseVideoAnimatable = parseVideoAnimatable; exports.parseVideoAsset = parseVideoAsset; exports.parseVideoAssetProxies = parseVideoAssetProxies; exports.parseVideoClip = parseVideoClip; exports.parseVideoClipAnimation = parseVideoClipAnimation; exports.parseVideoCubeLut = parseVideoCubeLut; exports.parseVideoGrade = parseVideoGrade; exports.parseVideoGradeLut = parseVideoGradeLut; exports.parseVideoKeyframes = parseVideoKeyframes; exports.parseVideoLoudnessMeasurement = parseVideoLoudnessMeasurement; exports.parseVideoMarker = parseVideoMarker; exports.parseVideoPreviewScale = parseVideoPreviewScale; exports.parseVideoProject = parseVideoProject; exports.parseVideoSequence = parseVideoSequence; exports.parseVideoTimeRemap = parseVideoTimeRemap; exports.parseVideoTrack = parseVideoTrack; exports.parseVideoTranscript = parseVideoTranscript; exports.parseVideoTranscriptWords = parseVideoTranscriptWords; exports.parseVideoTransform = parseVideoTransform; exports.parseVideoTransition = parseVideoTransition; exports.placeVideoRect = placeVideoRect; exports.planVideoClipDrag = planVideoClipDrag; exports.planVideoPrefetch = planVideoPrefetch; exports.probeVideoMedia = probeVideoMedia; exports.readVideoScopesPanel = readVideoScopesPanel; exports.removeVideoKey = removeVideoKey; exports.resolveVideoPreviewScale = resolveVideoPreviewScale; exports.retimeVideoAnimatable = retimeVideoAnimatable; exports.setVideoKey = setVideoKey; exports.snapToVideoFrame = snapToVideoFrame; exports.snapVideoTime = snapVideoTime; exports.sortVideoKeys = sortVideoKeys; exports.sortedVideoClips = sortedVideoClips; exports.splitVideoAnimatable = splitVideoAnimatable; exports.summarizeVideoScopes = summarizeVideoScopes; exports.toVideoEdl = toVideoEdl; exports.toVideoFfmpeg = toVideoFfmpeg; exports.toVideoLoudnessMeasure = toVideoLoudnessMeasure; exports.videoAssetDropEdit = videoAssetDropEdit; exports.videoAssetHasSound = videoAssetHasSound; exports.videoAssetKindOf = videoAssetKindOf; exports.videoAssetMap = videoAssetMap; exports.videoAssetProxy = videoAssetProxy; exports.videoAudioLevels = videoAudioLevels; exports.videoAudioPeakRange = videoAudioPeakRange; exports.videoAudioPeakSpans = videoAudioPeakSpans; exports.videoAudioPeaksDuration = videoAudioPeaksDuration; exports.videoCacheEvictions = videoCacheEvictions; exports.videoChroma = videoChroma; exports.videoClipAfter = videoClipAfter; exports.videoClipAnimated = videoClipAnimated; exports.videoClipAnimationProperties = videoClipAnimationProperties; exports.videoClipAnimationSpans = videoClipAnimationSpans; exports.videoClipBefore = videoClipBefore; exports.videoClipEnd = videoClipEnd; exports.videoClipHandles = videoClipHandles; exports.videoClipKeyframes = videoClipKeyframes; exports.videoClipLabel = videoClipLabel; exports.videoClipOverhang = videoClipOverhang; exports.videoClipShowsWaveform = videoClipShowsWaveform; exports.videoClipSoundGain = videoClipSoundGain; exports.videoClipSourceRange = videoClipSourceRange; exports.videoClipSourceTime = videoClipSourceTime; exports.videoClipSpeed = videoClipSpeed; exports.videoClipSpeedAt = videoClipSpeedAt; exports.videoClipTint = videoClipTint; exports.videoClipWaveform = videoClipWaveform; exports.videoClipWaveformPath = videoClipWaveformPath; exports.videoClipsAt = videoClipsAt; exports.videoContactSheetSvg = videoContactSheetSvg; exports.videoDragLabel = videoDragLabel; exports.videoDropTracks = videoDropTracks; exports.videoEaseBezier = videoEaseBezier; exports.videoEaseProgress = videoEaseProgress; exports.videoExposureNotes = videoExposureNotes; exports.videoExposureStats = videoExposureStats; exports.videoFadeCurveGain = videoFadeCurveGain; exports.videoFrameAt = videoFrameAt; exports.videoFrameCacheKey = videoFrameCacheKey; exports.videoFrameDuration = videoFrameDuration; exports.videoFrameLeafLayers = videoFrameLeafLayers; exports.videoFrameRequestsAt = videoFrameRequestsAt; exports.videoFrameToSvg = videoFrameToSvg; exports.videoFrames = videoFrames; exports.videoFreezeFrameEdit = videoFreezeFrameEdit; exports.videoGradeCssFilter = videoGradeCssFilter; exports.videoGradeFfmpegFilters = videoGradeFfmpegFilters; exports.videoGradeGainsFilterId = videoGradeGainsFilterId; exports.videoGradeLook = videoGradeLook; exports.videoGradePreset = videoGradePreset; exports.videoGradeStepMatrix = videoGradeStepMatrix; exports.videoGradeStepSvgValues = videoGradeStepSvgValues; exports.videoGradeSteps = videoGradeSteps; exports.videoGradeSvgDefs = videoGradeSvgDefs; exports.videoGradeSvgFilter = videoGradeSvgFilter; exports.videoHistogram = videoHistogram; exports.videoHistogramImage = videoHistogramImage; exports.videoHourFrames = videoHourFrames; exports.videoIrisRadius = videoIrisRadius; exports.videoIssueKey = videoIssueKey; exports.videoKeyframeBreakpoints = videoKeyframeBreakpoints; exports.videoKeyframeFfmpegExpr = videoKeyframeFfmpegExpr; exports.videoKeyframeRows = videoKeyframeRows; exports.videoKeyframeToggleEdit = videoKeyframeToggleEdit; exports.videoKeyframeValueEdit = videoKeyframeValueEdit; exports.videoKeysOf = videoKeysOf; exports.videoLuma = videoLuma; exports.videoLumaWaveform = videoLumaWaveform; exports.videoLutEdits = videoLutEdits; exports.videoLutFromFunction = videoLutFromFunction; exports.videoLutSvgApproximation = videoLutSvgApproximation; exports.videoLutSvgPrimitives = videoLutSvgPrimitives; exports.videoLutToCube = videoLutToCube; exports.videoOverlap = videoOverlap; exports.videoPeakDb = videoPeakDb; exports.videoPreviewSrc = videoPreviewSrc; exports.videoProjectDuration = videoProjectDuration; exports.videoProjectHasProxies = videoProjectHasProxies; exports.videoProjectSvg = videoProjectSvg; exports.videoProxyFfmpeg = videoProxyFfmpeg; exports.videoProxyName = videoProxyName; exports.videoProxyPlan = videoProxyPlan; exports.videoRemoveRangesEdits = videoRemoveRangesEdits; exports.videoRulerTicks = videoRulerTicks; exports.videoScopeImage = videoScopeImage; exports.videoSequenceChildren = videoSequenceChildren; exports.videoSequenceDuration = videoSequenceDuration; exports.videoSequenceMap = videoSequenceMap; exports.videoShellQuote = videoShellQuote; exports.videoSlowMotionRamp = videoSlowMotionRamp; exports.videoSnapPoints = videoSnapPoints; exports.videoSourceFrame = videoSourceFrame; exports.videoSourceFrameTime = videoSourceFrameTime; exports.videoSpeedRampEdit = videoSpeedRampEdit; exports.videoSpeedRampKeys = videoSpeedRampKeys; exports.videoTextStyle = videoTextStyle; exports.videoTimeRemapBreakpoints = videoTimeRemapBreakpoints; exports.videoTimeRemapSpan = videoTimeRemapSpan; exports.videoTimecodeFrames = videoTimecodeFrames; exports.videoTimelineTranscript = videoTimelineTranscript; exports.videoTitleFrame = videoTitleFrame; exports.videoTrackSyncLocked = videoTrackSyncLocked; exports.videoTranscriptEdit = videoTranscriptEdit; exports.videoTranscriptTrack = videoTranscriptTrack; exports.videoTransformAt = videoTransformAt; exports.videoTransitionAudioCurve = videoTransitionAudioCurve; exports.videoTransitionEase = videoTransitionEase; exports.videoTransitionHasDirection = videoTransitionHasDirection; exports.videoValueAt = videoValueAt; exports.videoVectorscope = videoVectorscope; exports.videoVectorscopeAngle = videoVectorscopeAngle; exports.videoVectorscopeImage = videoVectorscopeImage; exports.videoVectorscopePoint = videoVectorscopePoint; exports.videoWaveformImage = videoWaveformImage; exports.videoWaveformPath = videoWaveformPath; exports.videoWipeRect = videoWipeRect; exports.videoWordAt = videoWordAt; exports.videoWordsRange = videoWordsRange;
5990
6254
  //# sourceMappingURL=index.cjs.map