@pixerate/editor 0.90.0 → 0.91.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.
- package/dist/video-editor/index.cjs +561 -69
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +285 -1
- package/dist/video-editor/index.d.ts +285 -1
- package/dist/video-editor/index.js +506 -14
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-video-editor/SKILL.md +13 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7;
|
|
2
2
|
|
|
3
3
|
var _chunkWAJQKWQUcjs = require('../chunk-WAJQKWQU.cjs');
|
|
4
4
|
|
|
@@ -852,12 +852,33 @@ function parseVideoAsset(v, path = "asset") {
|
|
|
852
852
|
height: num(v.height, `${path}.height`, { optional: true, positive: true }),
|
|
853
853
|
hasAudio: bool(v.hasAudio, `${path}.hasAudio`),
|
|
854
854
|
poster: str(v.poster, `${path}.poster`, true),
|
|
855
|
-
transcript: parseVideoTranscriptWords(v.transcript, `${path}.transcript`)
|
|
855
|
+
transcript: parseVideoTranscriptWords(v.transcript, `${path}.transcript`),
|
|
856
|
+
proxies: parseVideoAssetProxies(v.proxies, `${path}.proxies`)
|
|
856
857
|
});
|
|
858
|
+
if (kind !== "video" && asset.proxies) fail(`${path}.proxies`, "are only for video assets");
|
|
857
859
|
if (kind === "image" && asset.transcript) fail(`${path}.transcript`, "is only for video and audio assets");
|
|
858
860
|
if (kind === "image" && asset.duration !== void 0) fail(`${path}.duration`, "is only for video and audio assets");
|
|
859
861
|
return asset;
|
|
860
862
|
}
|
|
863
|
+
function parseVideoAssetProxies(v, path) {
|
|
864
|
+
if (v === void 0) return void 0;
|
|
865
|
+
if (!Array.isArray(v)) return fail(path, "must be a list of { scale, src }");
|
|
866
|
+
const out = v.map((p, i) => {
|
|
867
|
+
const at = `${path}[${i}]`;
|
|
868
|
+
if (!isObject(p)) return fail(at, "must be an object { scale, src }");
|
|
869
|
+
const unknown = Object.keys(p).find((k) => !["scale", "src", "width", "height"].includes(k));
|
|
870
|
+
if (unknown) fail(`${at}.${unknown}`, "is not a proxy setting; use scale, src, width, height");
|
|
871
|
+
const scale = num(p.scale, `${at}.scale`, { positive: true });
|
|
872
|
+
if (scale >= 1) fail(`${at}.scale`, "must be less than 1 (0.5 is half size)");
|
|
873
|
+
return compact({ scale, src: str(p.src, `${at}.src`), width: num(p.width, `${at}.width`, { optional: true, positive: true }), height: num(p.height, `${at}.height`, { optional: true, positive: true }) });
|
|
874
|
+
});
|
|
875
|
+
const scales = /* @__PURE__ */ new Set();
|
|
876
|
+
for (const [i, p] of out.entries()) {
|
|
877
|
+
if (scales.has(p.scale)) fail(`${path}[${i}].scale`, `repeats ${p.scale}; give each proxy its own scale`);
|
|
878
|
+
scales.add(p.scale);
|
|
879
|
+
}
|
|
880
|
+
return out.length ? out : void 0;
|
|
881
|
+
}
|
|
861
882
|
function parseVideoClip(v, path = "clip") {
|
|
862
883
|
if (!isObject(v)) return fail(path, "must be an object");
|
|
863
884
|
const type = oneOf(_nullishCoalesce(v.type, () => ( "media")), VIDEO_CLIP_TYPES, `${path}.type`, false);
|
|
@@ -3219,7 +3240,7 @@ var color = (c) => {
|
|
|
3219
3240
|
const h = hex[1].length === 3 ? [...hex[1]].map((x) => x + x).join("") : hex[1];
|
|
3220
3241
|
return `0x${h.slice(0, 6)}${h.length === 8 ? `@${(parseInt(h.slice(6), 16) / 255).toFixed(3)}` : ""}`;
|
|
3221
3242
|
};
|
|
3222
|
-
var
|
|
3243
|
+
var videoShellQuote = (s) => /^[\w@%+=:,./-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
|
|
3223
3244
|
function atempo(speed) {
|
|
3224
3245
|
const out = [];
|
|
3225
3246
|
let s = speed;
|
|
@@ -3515,7 +3536,7 @@ function buildFfmpeg(project, options, measure) {
|
|
|
3515
3536
|
const filterComplex = graph.join(";");
|
|
3516
3537
|
if (measure) {
|
|
3517
3538
|
const args2 = ["-hide_banner", "-nostats", ...inputs.flat(), "-filter_complex", filterComplex, "-map", "[aout]", "-f", "null", "-"];
|
|
3518
|
-
return { args: args2, command: ["ffmpeg", ...args2].map(
|
|
3539
|
+
return { args: args2, command: ["ffmpeg", ...args2].map(videoShellQuote).join(" "), filterComplex, duration: D, notes };
|
|
3519
3540
|
}
|
|
3520
3541
|
const args = [
|
|
3521
3542
|
"-y",
|
|
@@ -3536,20 +3557,349 @@ function buildFfmpeg(project, options, measure) {
|
|
|
3536
3557
|
sec(D),
|
|
3537
3558
|
_nullishCoalesce(options.output, () => ( "output.mp4"))
|
|
3538
3559
|
];
|
|
3539
|
-
return { args, command: ["ffmpeg", ...args].map(
|
|
3560
|
+
return { args, command: ["ffmpeg", ...args].map(videoShellQuote).join(" "), filterComplex, duration: D, notes };
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
// src/video-editor/proxy.ts
|
|
3564
|
+
var VIDEO_PROXY_SCALES = [0.5, 0.25];
|
|
3565
|
+
function videoAssetProxy(asset, scale) {
|
|
3566
|
+
if (!_optionalChain([asset, 'optionalAccess', _88 => _88.proxies, 'optionalAccess', _89 => _89.length]) || !(scale > 0) || scale >= 1) return null;
|
|
3567
|
+
let best = null;
|
|
3568
|
+
for (const p of asset.proxies) if (p.scale >= scale - 1e-9 && p.scale < 1 && (!best || p.scale < best.scale)) best = p;
|
|
3569
|
+
return best;
|
|
3570
|
+
}
|
|
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));
|
|
3572
|
+
function resolveVideoPreviewScale(scale, shown) {
|
|
3573
|
+
if (scale === void 0 || scale === "auto") return Math.min(1, Math.max(0, shown));
|
|
3574
|
+
if (scale === "full") return 1;
|
|
3575
|
+
return Math.min(1, Math.max(0, scale));
|
|
3576
|
+
}
|
|
3577
|
+
function videoProxyName(src, scale = 0.5) {
|
|
3578
|
+
const clean = src.replace(/[?#].*$/, "");
|
|
3579
|
+
const base = clean.replace(/\.[^./\\]+$/, "");
|
|
3580
|
+
return `${base}.proxy-${Math.round(scale * 100)}.mp4`;
|
|
3581
|
+
}
|
|
3582
|
+
function videoProxyFfmpeg(asset, options = {}) {
|
|
3583
|
+
if (asset.kind !== "video") throw new Error(`Asset "${asset.id}" is ${asset.kind === "image" ? "an image" : "audio"}; only video assets have proxies.`);
|
|
3584
|
+
const scale = _nullishCoalesce(options.scale, () => ( 0.5));
|
|
3585
|
+
if (!(scale > 0 && scale < 1)) throw new Error(`A proxy's scale must be between 0 and 1 (not including either), not ${scale}; 0.5 is half size.`);
|
|
3586
|
+
const gop = Math.max(1, Math.round(_nullishCoalesce(options.gop, () => ( 12))));
|
|
3587
|
+
const output = _nullishCoalesce(options.output, () => ( videoProxyName(asset.src, scale)));
|
|
3588
|
+
const src = options.resolve ? options.resolve(asset) : asset.src;
|
|
3589
|
+
const size = `scale=trunc(iw*${scale}/2)*2:trunc(ih*${scale}/2)*2`;
|
|
3590
|
+
const args = [
|
|
3591
|
+
"-y",
|
|
3592
|
+
"-i",
|
|
3593
|
+
src,
|
|
3594
|
+
"-vf",
|
|
3595
|
+
size,
|
|
3596
|
+
"-c:v",
|
|
3597
|
+
"libx264",
|
|
3598
|
+
"-preset",
|
|
3599
|
+
"veryfast",
|
|
3600
|
+
"-tune",
|
|
3601
|
+
"fastdecode",
|
|
3602
|
+
"-crf",
|
|
3603
|
+
String(_nullishCoalesce(options.crf, () => ( 28))),
|
|
3604
|
+
"-g",
|
|
3605
|
+
String(gop),
|
|
3606
|
+
"-keyint_min",
|
|
3607
|
+
String(gop),
|
|
3608
|
+
"-sc_threshold",
|
|
3609
|
+
"0",
|
|
3610
|
+
"-bf",
|
|
3611
|
+
"0",
|
|
3612
|
+
"-pix_fmt",
|
|
3613
|
+
"yuv420p",
|
|
3614
|
+
...asset.hasAudio === false ? ["-an"] : ["-c:a", "aac", "-b:a", "128k"],
|
|
3615
|
+
"-movflags",
|
|
3616
|
+
"+faststart",
|
|
3617
|
+
output
|
|
3618
|
+
];
|
|
3619
|
+
const proxy = { scale, src: output, ...asset.width && asset.height ? { width: Math.trunc(asset.width * scale / 2) * 2, height: Math.trunc(asset.height * scale / 2) * 2 } : {} };
|
|
3620
|
+
return { asset: asset.id, args, command: ["ffmpeg", ...args].map(videoShellQuote).join(" "), proxy };
|
|
3621
|
+
}
|
|
3622
|
+
function videoProxyPlan(project, options = {}) {
|
|
3623
|
+
const scale = _nullishCoalesce(options.scale, () => ( 0.5));
|
|
3624
|
+
const wanted = options.assets ? new Set(options.assets) : null;
|
|
3625
|
+
if (options.assets) for (const id of options.assets) {
|
|
3626
|
+
const a = project.assets.find((x) => x.id === id);
|
|
3627
|
+
if (!a) throw new Error(`There is no asset "${id}"; the assets are ${project.assets.map((x) => `"${x.id}"`).join(", ") || "none"}.`);
|
|
3628
|
+
if (a.kind !== "video") throw new Error(`Asset "${id}" is ${a.kind === "image" ? "an image" : "audio"}; only video assets have proxies.`);
|
|
3629
|
+
}
|
|
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 }));
|
|
3631
|
+
}
|
|
3632
|
+
var VIDEO_PREVIEW_SCALE_OPTIONS = [
|
|
3633
|
+
{ value: "auto", label: "Auto", scale: "auto" },
|
|
3634
|
+
{ value: "full", label: "Full", scale: "full" },
|
|
3635
|
+
...VIDEO_PROXY_SCALES.map((s) => ({ value: String(s), label: s === 0.5 ? "\xBD" : s === 0.25 ? "\xBC" : `${s * 100}%`, scale: s }))
|
|
3636
|
+
];
|
|
3637
|
+
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]));
|
|
3639
|
+
|
|
3640
|
+
// src/video-editor/frame-cache.ts
|
|
3641
|
+
var videoSourceFrame = (sourceTime, fps) => Math.max(0, Math.floor(sourceTime * fps / 1e3 + 1e-6));
|
|
3642
|
+
var videoSourceFrameTime = (frame, fps) => (frame + 0.5) * 1e3 / fps;
|
|
3643
|
+
var videoFrameCacheKey = (src, frame) => `${frame}@${src}`;
|
|
3644
|
+
function videoFrameRequestsAt(project, time, srcFor = (l) => l.src) {
|
|
3645
|
+
const out = [];
|
|
3646
|
+
for (const layer of videoFrameAt(project, time).layers) {
|
|
3647
|
+
if (layer.type !== "media" || layer.assetKind !== "video") continue;
|
|
3648
|
+
const src = srcFor(layer);
|
|
3649
|
+
if (!src) continue;
|
|
3650
|
+
const frame = videoSourceFrame(_nullishCoalesce(layer.sourceTime, () => ( 0)), project.fps);
|
|
3651
|
+
out.push({ key: videoFrameCacheKey(src, frame), src, frame, sourceTime: videoSourceFrameTime(frame, project.fps), time });
|
|
3652
|
+
}
|
|
3653
|
+
return out;
|
|
3654
|
+
}
|
|
3655
|
+
function planVideoPrefetch(project, time, options = {}) {
|
|
3656
|
+
const window2 = _nullishCoalesce(options.window, () => ( 2e3));
|
|
3657
|
+
const dir = _nullishCoalesce(options.direction, () => ( 1));
|
|
3658
|
+
const step = 1e3 / project.fps;
|
|
3659
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3660
|
+
const out = [];
|
|
3661
|
+
for (let i = 0; i * step <= window2 + 1e-6; i++) {
|
|
3662
|
+
const t = time + dir * i * step;
|
|
3663
|
+
if (t < 0) break;
|
|
3664
|
+
for (const r of videoFrameRequestsAt(project, t, options.srcFor)) {
|
|
3665
|
+
if (seen.has(r.key)) continue;
|
|
3666
|
+
seen.add(r.key);
|
|
3667
|
+
out.push(r);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return out;
|
|
3671
|
+
}
|
|
3672
|
+
function videoCacheEvictions(entries, options) {
|
|
3673
|
+
let total = entries.reduce((n2, e) => n2 + e.bytes, 0);
|
|
3674
|
+
if (total <= options.maxBytes) return [];
|
|
3675
|
+
const dir = _nullishCoalesce(options.direction, () => ( 1));
|
|
3676
|
+
const behind = _nullishCoalesce(options.behind, () => ( 2));
|
|
3677
|
+
const cost = (e) => {
|
|
3678
|
+
const d = (e.time - options.playhead) * dir;
|
|
3679
|
+
return d >= 0 ? d : -d * behind;
|
|
3680
|
+
};
|
|
3681
|
+
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))) {
|
|
3683
|
+
if (total <= options.maxBytes) break;
|
|
3684
|
+
out.push(e.key);
|
|
3685
|
+
total -= e.bytes;
|
|
3686
|
+
}
|
|
3687
|
+
return out;
|
|
3688
|
+
}
|
|
3689
|
+
var defaultCapture = async (source) => {
|
|
3690
|
+
if (typeof createImageBitmap !== "function") return null;
|
|
3691
|
+
const image = await createImageBitmap(source);
|
|
3692
|
+
return { image, width: image.width, height: image.height };
|
|
3693
|
+
};
|
|
3694
|
+
var VideoFrameCache = (_class5 = class {
|
|
3695
|
+
constructor(options = {}) {;_class5.prototype.__init13.call(this);_class5.prototype.__init14.call(this);_class5.prototype.__init15.call(this);_class5.prototype.__init16.call(this);_class5.prototype.__init17.call(this);_class5.prototype.__init18.call(this);_class5.prototype.__init19.call(this);_class5.prototype.__init20.call(this);_class5.prototype.__init21.call(this);_class5.prototype.__init22.call(this);_class5.prototype.__init23.call(this);_class5.prototype.__init24.call(this);
|
|
3696
|
+
this.options = options;
|
|
3697
|
+
this.maxBytes = _nullishCoalesce(options.maxBytes, () => ( 512 * 1024 * 1024));
|
|
3698
|
+
this.prefetch = _nullishCoalesce(options.prefetch, () => ( 2e3));
|
|
3699
|
+
this.decode = options.decode !== void 0 ? options.decode : typeof document !== "undefined" ? createVideoElementDecoder() : null;
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
__init13() {this.entries = /* @__PURE__ */ new Map()}
|
|
3703
|
+
__init14() {this.inFlight = /* @__PURE__ */ new Set()}
|
|
3704
|
+
__init15() {this.queue = []}
|
|
3705
|
+
__init16() {this.protect = /* @__PURE__ */ new Set()}
|
|
3706
|
+
__init17() {this.listeners = /* @__PURE__ */ new Set()}
|
|
3707
|
+
__init18() {this.playhead = 0}
|
|
3708
|
+
__init19() {this.direction = 1}
|
|
3709
|
+
__init20() {this.hits = 0}
|
|
3710
|
+
__init21() {this.misses = 0}
|
|
3711
|
+
__init22() {this.bytes = 0}
|
|
3712
|
+
__init23() {this.generation = 0}
|
|
3713
|
+
__init24() {this.destroyed = false}
|
|
3714
|
+
|
|
3715
|
+
|
|
3716
|
+
|
|
3717
|
+
|
|
3718
|
+
/** The cached frame of `src` showing at a source time (ms), on the project's `fps` grid. Counts a hit or a miss. */
|
|
3719
|
+
get(src, sourceTime, fps) {
|
|
3720
|
+
const entry = this.entries.get(videoFrameCacheKey(src, videoSourceFrame(sourceTime, fps)));
|
|
3721
|
+
if (entry) this.hits++;
|
|
3722
|
+
else this.misses++;
|
|
3723
|
+
return _optionalChain([entry, 'optionalAccess', _101 => _101.frame, 'access', _102 => _102.image]);
|
|
3724
|
+
}
|
|
3725
|
+
/** Whether a frame is cached, without counting it. */
|
|
3726
|
+
has(src, sourceTime, fps) {
|
|
3727
|
+
return this.entries.has(videoFrameCacheKey(src, videoSourceFrame(sourceTime, fps)));
|
|
3728
|
+
}
|
|
3729
|
+
/** Keeps a decoded frame (replacing any with the same key), then evicts to the budget. */
|
|
3730
|
+
put(request, frame) {
|
|
3731
|
+
if (this.destroyed) return void _optionalChain([frame, 'access', _103 => _103.image, 'access', _104 => _104.close, 'optionalCall', _105 => _105()]);
|
|
3732
|
+
const old = this.entries.get(request.key);
|
|
3733
|
+
if (old) this.drop(old);
|
|
3734
|
+
const bytes = Math.max(1, frame.width * frame.height * 4);
|
|
3735
|
+
this.entries.set(request.key, { key: request.key, time: request.time, bytes, frame });
|
|
3736
|
+
this.bytes += bytes;
|
|
3737
|
+
this.evict();
|
|
3738
|
+
this.emit();
|
|
3739
|
+
}
|
|
3740
|
+
/** Keeps what a ready media element shows now as the frame of `src` at `sourceTime` (async; does nothing if it is cached). */
|
|
3741
|
+
capture(src, sourceTime, fps, time, source) {
|
|
3742
|
+
const frame = videoSourceFrame(sourceTime, fps);
|
|
3743
|
+
const key = videoFrameCacheKey(src, frame);
|
|
3744
|
+
if (this.entries.has(key) || this.inFlight.has(key)) return;
|
|
3745
|
+
this.inFlight.add(key);
|
|
3746
|
+
void (_nullishCoalesce(this.options.capture, () => ( defaultCapture)))(source).then((f) => {
|
|
3747
|
+
if (f) this.put({ key, time }, f);
|
|
3748
|
+
}).catch(() => {
|
|
3749
|
+
}).finally(() => this.inFlight.delete(key));
|
|
3750
|
+
}
|
|
3751
|
+
/**
|
|
3752
|
+
* Follows the playhead: protects the prefetch window, evicts to the budget, and queues the
|
|
3753
|
+
* window's missing frames for decoding (nearest first). `srcFor` gives the file a layer plays.
|
|
3754
|
+
*/
|
|
3755
|
+
update(project, time, options = {}) {
|
|
3756
|
+
if (this.destroyed) return;
|
|
3757
|
+
this.direction = _nullishCoalesce(options.direction, () => ( (time < this.playhead && !options.playing ? -1 : 1)));
|
|
3758
|
+
this.playhead = time;
|
|
3759
|
+
this.srcFor = options.srcFor;
|
|
3760
|
+
const plan = this.prefetch > 0 ? planVideoPrefetch(project, time, { window: this.prefetch, direction: this.direction, srcFor: options.srcFor }) : videoFrameRequestsAt(project, time, options.srcFor);
|
|
3761
|
+
this.protect = new Set(plan.map((r) => r.key));
|
|
3762
|
+
for (const r of plan) {
|
|
3763
|
+
const e = this.entries.get(r.key);
|
|
3764
|
+
if (e) e.time = r.time;
|
|
3765
|
+
}
|
|
3766
|
+
this.evict();
|
|
3767
|
+
this.generation++;
|
|
3768
|
+
this.queue = this.decode ? plan.filter((r) => !this.entries.has(r.key) && !this.inFlight.has(r.key)) : [];
|
|
3769
|
+
this.pump();
|
|
3770
|
+
}
|
|
3771
|
+
/**
|
|
3772
|
+
* The stretches of the timeline whose video frames are all cached, between `from` and `to` (ms),
|
|
3773
|
+
* for the files the preview last played (its `srcFor`, e.g. proxies) unless `srcFor` is given.
|
|
3774
|
+
* `step` (ms, at least a frame) samples more coarsely.
|
|
3775
|
+
*/
|
|
3776
|
+
cachedRanges(project, options = {}) {
|
|
3777
|
+
const step = Math.max(1e3 / project.fps, _nullishCoalesce(options.step, () => ( 0)));
|
|
3778
|
+
const from = Math.max(0, _nullishCoalesce(options.from, () => ( 0)));
|
|
3779
|
+
const to = _nullishCoalesce(options.to, () => ( Math.max(...project.tracks.flatMap((t) => t.clips.map((c) => c.start + c.duration)), 0)));
|
|
3780
|
+
const out = [];
|
|
3781
|
+
for (let t = from; t < to - 1e-6; t += step) {
|
|
3782
|
+
const requests = videoFrameRequestsAt(project, t, _nullishCoalesce(options.srcFor, () => ( this.srcFor)));
|
|
3783
|
+
const cached = requests.length > 0 && requests.every((r) => this.entries.has(r.key));
|
|
3784
|
+
const last = out[out.length - 1];
|
|
3785
|
+
if (!cached) continue;
|
|
3786
|
+
const end = Math.min(to, t + step);
|
|
3787
|
+
if (last && Math.abs(last.end - t) < 1e-6) last.end = end;
|
|
3788
|
+
else out.push({ start: t, end });
|
|
3789
|
+
}
|
|
3790
|
+
return out.map((r) => ({ start: Math.round(r.start * 10) / 10, end: Math.round(r.end * 10) / 10 }));
|
|
3791
|
+
}
|
|
3792
|
+
stats() {
|
|
3793
|
+
return { frames: this.entries.size, bytes: this.bytes, maxBytes: this.maxBytes, hits: this.hits, misses: this.misses, pending: this.inFlight.size + this.queue.length, prefetch: this.prefetch };
|
|
3794
|
+
}
|
|
3795
|
+
/** Called when frames arrive or leave. */
|
|
3796
|
+
subscribe(listener) {
|
|
3797
|
+
this.listeners.add(listener);
|
|
3798
|
+
return () => {
|
|
3799
|
+
this.listeners.delete(listener);
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
/** Drops every frame (closing them) and the queue. */
|
|
3803
|
+
clear() {
|
|
3804
|
+
for (const e of [...this.entries.values()]) this.drop(e);
|
|
3805
|
+
this.queue = [];
|
|
3806
|
+
this.generation++;
|
|
3807
|
+
this.emit();
|
|
3808
|
+
}
|
|
3809
|
+
/** Clears and stops decoding for good. */
|
|
3810
|
+
destroy() {
|
|
3811
|
+
this.clear();
|
|
3812
|
+
this.destroyed = true;
|
|
3813
|
+
this.listeners.clear();
|
|
3814
|
+
}
|
|
3815
|
+
pump() {
|
|
3816
|
+
const limit = Math.max(1, _nullishCoalesce(this.options.concurrency, () => ( 2)));
|
|
3817
|
+
while (this.decode && this.inFlight.size < limit && this.queue.length) {
|
|
3818
|
+
const r = this.queue.shift();
|
|
3819
|
+
if (this.entries.has(r.key) || this.inFlight.has(r.key)) continue;
|
|
3820
|
+
if (this.bytes >= this.maxBytes && [...this.entries.keys()].every((k) => this.protect.has(k))) {
|
|
3821
|
+
this.queue = [];
|
|
3822
|
+
break;
|
|
3823
|
+
}
|
|
3824
|
+
const generation = this.generation;
|
|
3825
|
+
this.inFlight.add(r.key);
|
|
3826
|
+
this.decode(r.src, r.sourceTime).then(
|
|
3827
|
+
(frame) => {
|
|
3828
|
+
this.inFlight.delete(r.key);
|
|
3829
|
+
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()]);
|
|
3831
|
+
},
|
|
3832
|
+
() => this.inFlight.delete(r.key)
|
|
3833
|
+
).finally(() => this.pump());
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
evict() {
|
|
3837
|
+
const drop = videoCacheEvictions([...this.entries.values()], { playhead: this.playhead, maxBytes: this.maxBytes, direction: this.direction, behind: this.options.behind, protect: this.protect });
|
|
3838
|
+
for (const key of drop) this.drop(this.entries.get(key));
|
|
3839
|
+
}
|
|
3840
|
+
drop(e) {
|
|
3841
|
+
this.entries.delete(e.key);
|
|
3842
|
+
this.bytes -= e.bytes;
|
|
3843
|
+
_optionalChain([e, 'access', _109 => _109.frame, 'access', _110 => _110.image, 'access', _111 => _111.close, 'optionalCall', _112 => _112()]);
|
|
3844
|
+
}
|
|
3845
|
+
emit() {
|
|
3846
|
+
for (const l of [...this.listeners]) l();
|
|
3847
|
+
}
|
|
3848
|
+
}, _class5);
|
|
3849
|
+
function createVideoElementDecoder(options = {}) {
|
|
3850
|
+
const videos = /* @__PURE__ */ new Map();
|
|
3851
|
+
const make = _nullishCoalesce(options.createVideo, () => ( (() => {
|
|
3852
|
+
const el = document.createElement("video");
|
|
3853
|
+
el.muted = true;
|
|
3854
|
+
el.preload = "auto";
|
|
3855
|
+
el.crossOrigin = "anonymous";
|
|
3856
|
+
el.playsInline = true;
|
|
3857
|
+
return el;
|
|
3858
|
+
})));
|
|
3859
|
+
const timeout = _nullishCoalesce(options.timeout, () => ( 5e3));
|
|
3860
|
+
const once = (el, type) => new Promise((resolve, reject) => {
|
|
3861
|
+
const timer = setTimeout(() => reject(new Error(`The video did not ${type} within ${timeout} ms.`)), timeout);
|
|
3862
|
+
el.addEventListener(type, () => (clearTimeout(timer), resolve()), { once: true });
|
|
3863
|
+
el.addEventListener("error", () => (clearTimeout(timer), reject(new Error("The video could not be loaded."))), { once: true });
|
|
3864
|
+
});
|
|
3865
|
+
return (src, sourceTime) => {
|
|
3866
|
+
let v = videos.get(src);
|
|
3867
|
+
if (!v) {
|
|
3868
|
+
const el2 = make();
|
|
3869
|
+
el2.src = src;
|
|
3870
|
+
v = { el: el2, chain: el2.readyState >= 1 ? Promise.resolve() : once(el2, "loadedmetadata") };
|
|
3871
|
+
videos.set(src, v);
|
|
3872
|
+
}
|
|
3873
|
+
const { el } = v;
|
|
3874
|
+
const next = v.chain.then(async () => {
|
|
3875
|
+
const presented = "requestVideoFrameCallback" in el ? new Promise((r) => el.requestVideoFrameCallback(() => r())) : null;
|
|
3876
|
+
const seeked = once(el, "seeked");
|
|
3877
|
+
el.currentTime = sourceTime / 1e3;
|
|
3878
|
+
await seeked;
|
|
3879
|
+
if (presented) await Promise.race([presented, new Promise((r) => setTimeout(r, 100))]);
|
|
3880
|
+
const image = await createImageBitmap(el);
|
|
3881
|
+
return { image, width: image.width, height: image.height };
|
|
3882
|
+
});
|
|
3883
|
+
v.chain = next.catch(() => {
|
|
3884
|
+
});
|
|
3885
|
+
return next;
|
|
3886
|
+
};
|
|
3540
3887
|
}
|
|
3541
3888
|
|
|
3542
3889
|
// src/video-editor/preview.ts
|
|
3543
|
-
var VideoPreviewEngine = (
|
|
3544
|
-
constructor(source, canvas2, options = {}) {;
|
|
3890
|
+
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);
|
|
3545
3892
|
this.source = source;
|
|
3546
3893
|
this.options = options;
|
|
3547
3894
|
this.canvas = canvas2;
|
|
3548
|
-
this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access',
|
|
3895
|
+
this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access', _117 => _117.getContext, 'optionalCall', _118 => _118("2d")]), () => ( null));
|
|
3549
3896
|
this.unsubscribe = source.subscribe(() => this.update());
|
|
3550
3897
|
this.lutCache = options.luts !== void 0 ? options.luts : _nullishCoalesce(source.luts, () => ( null));
|
|
3551
3898
|
this.unsubscribeLuts = this.lutCache ? this.lutCache.subscribe(() => this.redraw()) : () => {
|
|
3552
3899
|
};
|
|
3900
|
+
this.frameCache = options.frameCache !== void 0 ? options.frameCache : _nullishCoalesce(source.frameCache, () => ( null));
|
|
3901
|
+
this.unsubscribeFrames = this.frameCache ? this.frameCache.subscribe(() => this.redraw()) : () => {
|
|
3902
|
+
};
|
|
3553
3903
|
const pixels = options.pixels !== void 0 ? options.pixels : source.previewPixels;
|
|
3554
3904
|
this.detachPixels = pixels ? pixels.attach(() => this.readPixels()) : () => {
|
|
3555
3905
|
};
|
|
@@ -3560,24 +3910,29 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3560
3910
|
|
|
3561
3911
|
|
|
3562
3912
|
|
|
3563
|
-
|
|
3564
|
-
|
|
3913
|
+
__init25() {this.media = /* @__PURE__ */ new Map()}
|
|
3914
|
+
__init26() {this.images = /* @__PURE__ */ new Map()}
|
|
3915
|
+
|
|
3916
|
+
__init27() {this.scheduled = false}
|
|
3917
|
+
__init28() {this.destroyed = false}
|
|
3918
|
+
__init29() {this.filterHost = null}
|
|
3919
|
+
__init30() {this.filters = /* @__PURE__ */ new Set()}
|
|
3920
|
+
|
|
3565
3921
|
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
__init17() {this.filterHost = null}
|
|
3569
|
-
__init18() {this.filters = /* @__PURE__ */ new Set()}
|
|
3922
|
+
__init31() {this.drawn = null}
|
|
3923
|
+
__init32() {this.complete = true}
|
|
3570
3924
|
|
|
3571
3925
|
|
|
3572
|
-
__init19() {this.drawn = null}
|
|
3573
|
-
__init20() {this.complete = true}
|
|
3574
3926
|
|
|
3927
|
+
__init33() {this.srcFor = (layer) => layer.src}
|
|
3928
|
+
__init34() {this.playing = false}
|
|
3929
|
+
__init35() {this.fps = 30}
|
|
3575
3930
|
|
|
3576
3931
|
|
|
3577
3932
|
/** The canvas's pixels and the frame they show, or `null` when its context cannot be read. */
|
|
3578
3933
|
readPixels() {
|
|
3579
3934
|
const ctx = this.ctx;
|
|
3580
|
-
if (!_optionalChain([ctx, 'optionalAccess',
|
|
3935
|
+
if (!_optionalChain([ctx, 'optionalAccess', _119 => _119.getImageData]) || !this.drawn) return null;
|
|
3581
3936
|
return { ...this.drawn, pixels: ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) };
|
|
3582
3937
|
}
|
|
3583
3938
|
createMedia(kind) {
|
|
@@ -3615,15 +3970,22 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3615
3970
|
entry.media.pause();
|
|
3616
3971
|
entry.media.removeEventListener("seeked", entry.onReady);
|
|
3617
3972
|
entry.media.removeEventListener("loadeddata", entry.onReady);
|
|
3618
|
-
_optionalChain([entry, 'access',
|
|
3619
|
-
_optionalChain([entry, 'access',
|
|
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()]);
|
|
3620
3975
|
this.media.delete(entry.clip);
|
|
3621
3976
|
}
|
|
3622
3977
|
/** Brings the media elements in step with the editor and draws the frame. */
|
|
3623
3978
|
update() {
|
|
3624
3979
|
if (this.destroyed) return;
|
|
3625
|
-
const
|
|
3980
|
+
const state = this.source.getState();
|
|
3981
|
+
const { project, time, playing } = state;
|
|
3626
3982
|
const frame = videoFrameAt(project, time);
|
|
3983
|
+
const scale = resolveVideoPreviewScale(_nullishCoalesce(this.options.previewScale, () => ( state.previewScale)), this.resolutionFor(project.width));
|
|
3984
|
+
const assets = new Map(project.assets.map((a) => [a.id, a]));
|
|
3985
|
+
this.srcFor = (layer) => videoPreviewSrc(assets.get(_nullishCoalesce(layer.asset, () => ( ""))), scale, layer.src);
|
|
3986
|
+
this.playing = playing;
|
|
3987
|
+
this.fps = project.fps;
|
|
3988
|
+
_optionalChain([this, 'access', _126 => _126.frameCache, 'optionalAccess', _127 => _127.update, 'call', _128 => _128(project, time, { playing, srcFor: this.srcFor })]);
|
|
3627
3989
|
const tolerance = (_nullishCoalesce(this.options.tolerance, () => ( 200))) / 1e3;
|
|
3628
3990
|
const active = /* @__PURE__ */ new Set();
|
|
3629
3991
|
const sync = (clip, src, kind, sourceTime, speed, gain) => {
|
|
@@ -3650,7 +4012,7 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3650
4012
|
for (const layer of frame.layers) {
|
|
3651
4013
|
if (layer.type !== "media" || layer.assetKind !== "video" || !layer.src) continue;
|
|
3652
4014
|
const sound = sounding.get(layer.clip);
|
|
3653
|
-
sync(layer.clip, layer
|
|
4015
|
+
sync(layer.clip, this.srcFor(layer), "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess', _129 => _129.speed]), () => ( 1)), sound ? sound.gain : null);
|
|
3654
4016
|
sounding.delete(layer.clip);
|
|
3655
4017
|
}
|
|
3656
4018
|
for (const a of sounding.values()) sync(a.clip, a.src, "audio", a.sourceTime, a.speed, a.gain);
|
|
@@ -3665,21 +4027,35 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3665
4027
|
if (!this.destroyed) this.draw(videoFrameAt(this.source.getState().project, this.source.getState().time));
|
|
3666
4028
|
});
|
|
3667
4029
|
}
|
|
3668
|
-
|
|
4030
|
+
__init36() {this.sourceFor = (layer) => {
|
|
3669
4031
|
if (layer.assetKind === "image" && layer.src) {
|
|
3670
4032
|
const img = this.image(layer.src);
|
|
3671
4033
|
return img.complete ? img : null;
|
|
3672
4034
|
}
|
|
3673
4035
|
const entry = this.media.get(layer.clip);
|
|
3674
|
-
|
|
4036
|
+
const ready = !!entry && entry.media.readyState >= 2;
|
|
4037
|
+
const cache = this.frameCache;
|
|
4038
|
+
const src = _nullishCoalesce(_optionalChain([entry, 'optionalAccess', _130 => _130.src]), () => ( this.srcFor(layer)));
|
|
4039
|
+
const sourceTime = _nullishCoalesce(layer.sourceTime, () => ( 0));
|
|
4040
|
+
if (cache && src && (!this.playing || !ready || entry.media.seeking)) {
|
|
4041
|
+
const hit = cache.get(src, sourceTime, this.fps);
|
|
4042
|
+
if (hit) return hit;
|
|
4043
|
+
}
|
|
4044
|
+
if (!ready) return null;
|
|
3675
4045
|
if (entry.media.seeking) this.complete = false;
|
|
4046
|
+
else if (cache && !this.playing) cache.capture(entry.src, sourceTime, this.fps, this.source.getState().time, entry.media);
|
|
3676
4047
|
return entry.media;
|
|
3677
4048
|
}}
|
|
4049
|
+
/** Canvas pixels per frame pixel: the option, else the displayed width at the device pixel ratio (at most 1). */
|
|
4050
|
+
resolutionFor(frameWidth) {
|
|
4051
|
+
if (this.options.resolution !== void 0) return this.options.resolution;
|
|
4052
|
+
const shown = this.canvas.clientWidth || frameWidth;
|
|
4053
|
+
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
4054
|
+
return Math.min(1, shown * dpr / frameWidth);
|
|
4055
|
+
}
|
|
3678
4056
|
draw(frame) {
|
|
3679
4057
|
if (!this.ctx) return;
|
|
3680
|
-
const
|
|
3681
|
-
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
3682
|
-
const resolution = _nullishCoalesce(this.options.resolution, () => ( Math.min(1, shown * dpr / frame.width)));
|
|
4058
|
+
const resolution = this.resolutionFor(frame.width);
|
|
3683
4059
|
const w = Math.max(1, Math.round(frame.width * resolution));
|
|
3684
4060
|
const h = Math.max(1, Math.round(frame.height * resolution));
|
|
3685
4061
|
if (this.canvas.width !== w) this.canvas.width = w;
|
|
@@ -3692,16 +4068,16 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3692
4068
|
};
|
|
3693
4069
|
drawVideoFrame(this.ctx, frame, sourceFor, resolution, { defineSvgFilter: this.defineSvgFilter, lutFor: this.lutFor, scratch: this.scratch });
|
|
3694
4070
|
this.drawn = { time: frame.time, complete: this.complete };
|
|
3695
|
-
_optionalChain([this, 'access',
|
|
3696
|
-
_optionalChain([this, 'access',
|
|
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)]);
|
|
3697
4073
|
}
|
|
3698
4074
|
/** A grade's LUT once loaded; while it loads (or if it fails) the frame is drawn without it and counts as incomplete. */
|
|
3699
|
-
|
|
3700
|
-
const lut = _optionalChain([this, 'access',
|
|
4075
|
+
__init37() {this.lutFor = (src) => {
|
|
4076
|
+
const lut = _optionalChain([this, 'access', _137 => _137.lutCache, 'optionalAccess', _138 => _138.get, 'call', _139 => _139(src)]);
|
|
3701
4077
|
if (!lut) this.complete = false;
|
|
3702
4078
|
return lut;
|
|
3703
4079
|
}}
|
|
3704
|
-
|
|
4080
|
+
__init38() {this.scratch = (width, height) => {
|
|
3705
4081
|
if (this.scratchCanvas === void 0) {
|
|
3706
4082
|
if (this.options.createScratch) this.scratchCanvas = this.options.createScratch(width, height);
|
|
3707
4083
|
else if (typeof document !== "undefined") {
|
|
@@ -3717,7 +4093,7 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3717
4093
|
if (c.height !== height) c.height = height;
|
|
3718
4094
|
return s;
|
|
3719
4095
|
}}
|
|
3720
|
-
|
|
4096
|
+
__init39() {this.defineSvgFilter = (id, markup) => {
|
|
3721
4097
|
if (this.options.defineSvgFilter === null) return;
|
|
3722
4098
|
if (this.options.defineSvgFilter) return this.options.defineSvgFilter(id, markup);
|
|
3723
4099
|
if (this.filters.has(id) || typeof document === "undefined") return;
|
|
@@ -3738,43 +4114,49 @@ var VideoPreviewEngine = (_class5 = class {
|
|
|
3738
4114
|
this.destroyed = true;
|
|
3739
4115
|
this.detachPixels();
|
|
3740
4116
|
this.unsubscribeLuts();
|
|
3741
|
-
|
|
4117
|
+
this.unsubscribeFrames();
|
|
4118
|
+
_optionalChain([this, 'access', _140 => _140.filterHost, 'optionalAccess', _141 => _141.remove, 'call', _142 => _142()]);
|
|
3742
4119
|
this.filterHost = null;
|
|
3743
4120
|
this.filters.clear();
|
|
3744
4121
|
this.unsubscribe();
|
|
3745
4122
|
for (const entry of [...this.media.values()]) this.release(entry);
|
|
3746
4123
|
this.images.clear();
|
|
3747
4124
|
}
|
|
3748
|
-
},
|
|
4125
|
+
}, _class6);
|
|
3749
4126
|
|
|
3750
4127
|
// src/video-editor/controller.ts
|
|
3751
|
-
var VideoEditorController = (
|
|
4128
|
+
var VideoEditorController = (_class7 = class {
|
|
3752
4129
|
|
|
3753
4130
|
|
|
3754
4131
|
/** Sound levels per asset, loaded on demand for waveforms and agents. */
|
|
3755
4132
|
|
|
3756
4133
|
/** The pixels the preview last drew, for scopes and `get_video_scopes` (a `VideoPreviewEngine` fills it). */
|
|
3757
|
-
|
|
4134
|
+
__init40() {this.previewPixels = new VideoPreviewPixels()}
|
|
3758
4135
|
/** Grades' LUTs, loaded on demand for the preview, SVG frames and agents. */
|
|
3759
4136
|
|
|
4137
|
+
/** The host's frame cache, if it gave one (the preview fills and reads it). */
|
|
4138
|
+
|
|
3760
4139
|
|
|
3761
4140
|
|
|
3762
|
-
|
|
4141
|
+
__init41() {this.selection = []}
|
|
3763
4142
|
|
|
3764
|
-
__init27() {this.error = null}
|
|
3765
|
-
__init28() {this.revision = 0}
|
|
3766
|
-
__init29() {this.applying = false}
|
|
3767
4143
|
|
|
3768
|
-
|
|
3769
|
-
|
|
4144
|
+
__init42() {this.error = null}
|
|
4145
|
+
__init43() {this.revision = 0}
|
|
4146
|
+
__init44() {this.applying = false}
|
|
3770
4147
|
|
|
3771
|
-
|
|
4148
|
+
__init45() {this.listeners = /* @__PURE__ */ new Set()}
|
|
4149
|
+
__init46() {this.unsubscribers = []}
|
|
4150
|
+
|
|
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);
|
|
3772
4152
|
this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
|
|
3773
4153
|
this.audioPeaks = options.audioPeaks instanceof VideoAudioPeaksCache ? options.audioPeaks : new VideoAudioPeaksCache(options.audioPeaks);
|
|
3774
4154
|
this.luts = options.luts instanceof VideoLutCache ? options.luts : new VideoLutCache(options.luts);
|
|
4155
|
+
this.frameCache = _nullishCoalesce(options.frameCache, () => ( null));
|
|
3775
4156
|
this.project = options.project ? parseVideoProject(options.project) : createVideoProject();
|
|
3776
4157
|
this.issues = checkVideoProject(this.project);
|
|
3777
4158
|
this.readOnly = _nullishCoalesce(options.readOnly, () => ( false));
|
|
4159
|
+
this.previewScale = checkPreviewScale(_nullishCoalesce(options.previewScale, () => ( "auto")));
|
|
3778
4160
|
this.onChange = options.onChange;
|
|
3779
4161
|
this.player = new (0, _chunk2FGRTYJNcjs.AnimationPlayer)({ clock: options.clock, duration: () => videoProjectDuration(this.project), loop: "none" });
|
|
3780
4162
|
this.state = this.computeState();
|
|
@@ -3794,8 +4176,8 @@ var VideoEditorController = (_class6 = class {
|
|
|
3794
4176
|
this.player.pause();
|
|
3795
4177
|
this.unsubscribers.splice(0).forEach((u) => u());
|
|
3796
4178
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
4179
|
+
__init47() {this.getState = () => this.state}
|
|
4180
|
+
__init48() {this.subscribe = (listener) => {
|
|
3799
4181
|
this.connect();
|
|
3800
4182
|
this.listeners.add(listener);
|
|
3801
4183
|
return () => {
|
|
@@ -3824,6 +4206,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3824
4206
|
canRedo: h.canRedo,
|
|
3825
4207
|
undoLabel: h.undoName,
|
|
3826
4208
|
redoLabel: h.redoName,
|
|
4209
|
+
previewScale: this.previewScale,
|
|
3827
4210
|
revision: this.revision
|
|
3828
4211
|
};
|
|
3829
4212
|
}
|
|
@@ -3851,7 +4234,7 @@ var VideoEditorController = (_class6 = class {
|
|
|
3851
4234
|
this.applying = false;
|
|
3852
4235
|
}
|
|
3853
4236
|
this.notify();
|
|
3854
|
-
_optionalChain([this, 'access',
|
|
4237
|
+
_optionalChain([this, 'access', _143 => _143.onChange, 'optionalCall', _144 => _144(project)]);
|
|
3855
4238
|
}
|
|
3856
4239
|
commit(label, next) {
|
|
3857
4240
|
const before = this.project;
|
|
@@ -3913,9 +4296,20 @@ var VideoEditorController = (_class6 = class {
|
|
|
3913
4296
|
this.readOnly = readOnly;
|
|
3914
4297
|
this.notify();
|
|
3915
4298
|
}
|
|
4299
|
+
/**
|
|
4300
|
+
* Sets how large a picture the preview plays: `full` (the originals), a fraction such as 0.5
|
|
4301
|
+
* (the smallest proxy at least that large, else the original) or `auto` (as large as the canvas
|
|
4302
|
+
* shows). A view setting, not part of the project: no undo step.
|
|
4303
|
+
*/
|
|
4304
|
+
setPreviewScale(scale) {
|
|
4305
|
+
const next = checkPreviewScale(scale);
|
|
4306
|
+
if (next === this.previewScale) return;
|
|
4307
|
+
this.previewScale = next;
|
|
4308
|
+
this.notify();
|
|
4309
|
+
}
|
|
3916
4310
|
/** Selects clips (unknown ids are dropped). `add` toggles them into the current selection. */
|
|
3917
4311
|
select(clips, options = {}) {
|
|
3918
|
-
const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess',
|
|
4312
|
+
const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess', _145 => _145.filter, 'call', _146 => _146((id) => findVideoClip(this.project, id))]), () => ( []));
|
|
3919
4313
|
let next;
|
|
3920
4314
|
if (options.add) {
|
|
3921
4315
|
next = [...this.selection];
|
|
@@ -3996,7 +4390,12 @@ var VideoEditorController = (_class6 = class {
|
|
|
3996
4390
|
toEdl(options = {}) {
|
|
3997
4391
|
return toVideoEdl(this.project, options);
|
|
3998
4392
|
}
|
|
3999
|
-
},
|
|
4393
|
+
}, _class7);
|
|
4394
|
+
function checkPreviewScale(scale) {
|
|
4395
|
+
if (scale === "auto" || scale === "full") return scale;
|
|
4396
|
+
if (typeof scale !== "number" || !(scale > 0 && scale <= 1)) throw new Error(`A preview scale is "auto", "full" or a fraction above 0 up to 1 (0.5 is half size), not ${JSON.stringify(scale)}.`);
|
|
4397
|
+
return scale === 1 ? "full" : scale;
|
|
4398
|
+
}
|
|
4000
4399
|
|
|
4001
4400
|
// src/video-editor/ui.ts
|
|
4002
4401
|
var STEPS = [40, 100, 200, 500, 1e3, 2e3, 5e3, 1e4, 15e3, 3e4, 6e4, 12e4, 3e5, 6e5];
|
|
@@ -4066,7 +4465,7 @@ function videoClipLabel(clip, asset) {
|
|
|
4066
4465
|
if (clip.name) return clip.name;
|
|
4067
4466
|
if (clip.type === "text") return clip.text.split("\n")[0];
|
|
4068
4467
|
if (clip.type === "color") return clip.color;
|
|
4069
|
-
const base = _nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess',
|
|
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));
|
|
4070
4469
|
const speed = videoClipSpeed(clip);
|
|
4071
4470
|
return speed !== 1 ? `${base} (${Math.round(speed * 100)}%)` : base;
|
|
4072
4471
|
}
|
|
@@ -4110,7 +4509,7 @@ function videoAssetDropEdit(project, assetId, options = {}) {
|
|
|
4110
4509
|
const asset = videoAssetMap(project).get(assetId);
|
|
4111
4510
|
if (!asset) return null;
|
|
4112
4511
|
const kind = asset.kind === "audio" ? "audio" : "video";
|
|
4113
|
-
const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access',
|
|
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])));
|
|
4114
4513
|
if (!track) return null;
|
|
4115
4514
|
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 } : {} };
|
|
4116
4515
|
}
|
|
@@ -4159,14 +4558,14 @@ var KEY_LABELS = {
|
|
|
4159
4558
|
scale: { label: "Scale", step: 0.05 },
|
|
4160
4559
|
rotation: { label: "Rotation", step: 1 }
|
|
4161
4560
|
};
|
|
4162
|
-
var animatableOf = (clip, property) => VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([clip, 'access',
|
|
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;
|
|
4163
4562
|
var playheadInClip = (clip, time) => Math.round(Math.min(clip.duration, Math.max(0, time - clip.start)));
|
|
4164
4563
|
function videoKeyframeRows(project, clipId, time) {
|
|
4165
4564
|
const at = findVideoClip(project, clipId);
|
|
4166
4565
|
if (!at) return [];
|
|
4167
4566
|
const { clip } = at;
|
|
4168
4567
|
const asset = clip.type === "media" ? videoAssetMap(project).get(clip.asset) : void 0;
|
|
4169
|
-
const visual = !(_optionalChain([asset, 'optionalAccess',
|
|
4568
|
+
const visual = !(_optionalChain([asset, 'optionalAccess', _157 => _157.kind]) === "audio" || at.track.kind === "audio");
|
|
4170
4569
|
const audible = clip.type === "media" && videoAssetHasSound(asset);
|
|
4171
4570
|
const properties = [...visual ? ["opacity"] : [], ...audible ? ["volume"] : [], ...visual ? VIDEO_TRANSFORM_PROPERTIES : []];
|
|
4172
4571
|
const clipTime = playheadInClip(clip, time);
|
|
@@ -4447,14 +4846,14 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4447
4846
|
return null;
|
|
4448
4847
|
};
|
|
4449
4848
|
const placed = (o) => ({
|
|
4450
|
-
..._optionalChain([o, 'access',
|
|
4451
|
-
..._optionalChain([o, 'access',
|
|
4452
|
-
..._optionalChain([o, 'access',
|
|
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 } : {},
|
|
4453
4852
|
...o.rippled ? { rippled: o.rippled } : {}
|
|
4454
4853
|
});
|
|
4455
4854
|
const keysOut = (p, clip, property) => {
|
|
4456
4855
|
const c = clipOut(p, clip);
|
|
4457
|
-
const value = VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess',
|
|
4856
|
+
const value = VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess', _164 => _164.transform, 'optionalAccess', _165 => _165[property]]) : _optionalChain([c, 'optionalAccess', _166 => _166[property]]);
|
|
4458
4857
|
return typeof value === "object" ? { keys: value.keys } : { keys: [], value: _nullishCoalesce(value, () => ( null)) };
|
|
4459
4858
|
};
|
|
4460
4859
|
const getProject = {
|
|
@@ -4466,7 +4865,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4466
4865
|
handler: () => {
|
|
4467
4866
|
const state = controller.getState();
|
|
4468
4867
|
const reason = readOnlyReason();
|
|
4469
|
-
return { ...describeVideoProject(state.project), playhead: state.time, selection: state.selection, issues: issueSummary(state.issues), editable: !reason, ...reason ? { readOnlyReason: reason } : {} };
|
|
4868
|
+
return { ...describeVideoProject(state.project), playhead: state.time, previewScale: state.previewScale, selection: state.selection, issues: issueSummary(state.issues), editable: !reason, ...reason ? { readOnlyReason: reason } : {} };
|
|
4470
4869
|
}
|
|
4471
4870
|
};
|
|
4472
4871
|
const getFrame = {
|
|
@@ -4515,7 +4914,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4515
4914
|
annotations: { title: "Render a frame", ...READ },
|
|
4516
4915
|
handler: async ({ time, times, columns, format = "svg" }) => {
|
|
4517
4916
|
const p = project();
|
|
4518
|
-
const lutSrcs = [...new Set(p.tracks.flatMap((t) => t.clips.map((c) => _optionalChain([c, 'access',
|
|
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)))];
|
|
4519
4918
|
const missing = (await Promise.all(lutSrcs.map((src) => controller.luts.load(src).then(() => null, (e) => e.message)))).filter((m) => !!m);
|
|
4520
4919
|
const svgOptions = { lutFor: (src) => controller.luts.peek(src), ...columns ? { columns } : {} };
|
|
4521
4920
|
const svg = times ? videoContactSheetSvg(p, times, svgOptions) : videoProjectSvg(p, _nullishCoalesce(time, () => ( controller.getState().time)), svgOptions);
|
|
@@ -4634,7 +5033,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4634
5033
|
name: "update_video_asset",
|
|
4635
5034
|
title: "Change media details",
|
|
4636
5035
|
description: "Changes an asset's src, name, duration, size, sound or poster (e.g. relinking moved media). Clips that now run past its end are refused. Returns the asset.",
|
|
4637
|
-
inputSchema: object({ asset: ASSET_ID, set: object(ASSET_FIELDS, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["name", "duration", "width", "height", "hasAudio", "poster", "transcript"] }, description: "Fields to clear (transcript removes its words)." } }, ["asset"]),
|
|
5036
|
+
inputSchema: object({ asset: ASSET_ID, set: object(ASSET_FIELDS, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["name", "duration", "width", "height", "hasAudio", "poster", "transcript", "proxies"] }, description: "Fields to clear (transcript removes its words, proxies its proxies)." } }, ["asset"]),
|
|
4638
5037
|
annotations: { title: "Change media details", ...WRITE_IDEMPOTENT },
|
|
4639
5038
|
handler: ({ asset, set, remove }) => apply({ op: "update_asset", asset, set, remove }, "Change media", (r) => ({ asset: videoAssetOut(r.project.assets.find((a) => a.id === asset)) }))
|
|
4640
5039
|
};
|
|
@@ -4756,7 +5155,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4756
5155
|
const at = findVideoClip(p, id);
|
|
4757
5156
|
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
4758
5157
|
const { clip } = at;
|
|
4759
|
-
if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access',
|
|
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")
|
|
4760
5159
|
throw new Error(`Clip "${id}" is sound only; grades change pictures. Pass clips on video tracks.`);
|
|
4761
5160
|
}
|
|
4762
5161
|
};
|
|
@@ -4779,7 +5178,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4779
5178
|
const grade = videoGradeLook(findVideoClip(project(), clip).clip.grade, preset, _nullishCoalesce(amount, () => ( 1)));
|
|
4780
5179
|
return grade ? { op: "update_clip", clip, set: { grade } } : { op: "update_clip", clip, remove: ["grade"] };
|
|
4781
5180
|
});
|
|
4782
|
-
return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call',
|
|
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)) })) }));
|
|
4783
5182
|
}
|
|
4784
5183
|
};
|
|
4785
5184
|
const copyGrade = {
|
|
@@ -4815,7 +5214,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4815
5214
|
const lut = await controller.luts.load(src);
|
|
4816
5215
|
const label = _nullishCoalesce(name, () => ( lut.title));
|
|
4817
5216
|
const setting = { src, ...strength !== void 0 && strength !== 1 ? { strength } : {}, ...label ? { name: label } : {} };
|
|
4818
|
-
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',
|
|
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)) })) }));
|
|
4819
5218
|
}
|
|
4820
5219
|
};
|
|
4821
5220
|
const removeLut = {
|
|
@@ -4827,9 +5226,82 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4827
5226
|
handler: ({ clips }) => {
|
|
4828
5227
|
gradeTargets(clips);
|
|
4829
5228
|
const p = project();
|
|
4830
|
-
const withLut = clips.filter((id) => _optionalChain([findVideoClip, 'call',
|
|
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]));
|
|
4831
5230
|
if (!withLut.length) throw new Error(`None of ${clips.map((c) => `"${c}"`).join(", ")} has a LUT; get_video_project shows each clip's grade.`);
|
|
4832
|
-
return apply(videoLutEdits(p, withLut, null), "Remove LUT", (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call',
|
|
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)) })) }));
|
|
5232
|
+
}
|
|
5233
|
+
};
|
|
5234
|
+
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." };
|
|
5235
|
+
const planProxies = {
|
|
5236
|
+
name: "plan_video_proxies",
|
|
5237
|
+
title: "Plan proxy files",
|
|
5238
|
+
description: 'Returns the ffmpeg commands that make smaller proxy copies of video assets for smooth preview playback, without changing anything: one per video asset (or each of "assets") that has no proxy at that scale yet, each with { asset, args, command, proxy }. Proxies are scaled, H.264 with a keyframe every few frames so scrubbing is quick, and keep the sound. After the host runs a command, attach its "proxy" with set_video_proxies. The export always uses the originals.',
|
|
5239
|
+
inputSchema: object({
|
|
5240
|
+
scale: PROXY_SCALE,
|
|
5241
|
+
assets: { type: "array", items: ASSET_ID, minItems: 1, uniqueItems: true, description: "Only these video assets (default: every video asset without a proxy at this scale)." },
|
|
5242
|
+
gop: { type: "integer", minimum: 1, maximum: 300, description: "Frames between keyframes (default 12): fewer makes seeking cheaper and files larger." },
|
|
5243
|
+
crf: { type: "integer", minimum: 0, maximum: 51, description: "x264 quality, lower is better (default 28: proxies are for editing)." }
|
|
5244
|
+
}),
|
|
5245
|
+
annotations: { title: "Plan proxy files", ...READ },
|
|
5246
|
+
handler: ({ scale, assets, gop, crf }) => {
|
|
5247
|
+
const commands = videoProxyPlan(project(), { scale, assets, gop, crf });
|
|
5248
|
+
return { scale: _nullishCoalesce(scale, () => ( 0.5)), commands, ...commands.length ? {} : { note: "Every video asset already has a proxy at this scale." } };
|
|
5249
|
+
}
|
|
5250
|
+
};
|
|
5251
|
+
const setProxies = {
|
|
5252
|
+
name: "set_video_proxies",
|
|
5253
|
+
title: "Attach proxies",
|
|
5254
|
+
description: "Sets a video asset's proxies, the smaller copies the preview plays instead of the original (one undo step); an empty list removes them. Each proxy must match the original's length and timing (plan_video_proxies makes such files). The export, EDL and source times always use the original. Returns the asset.",
|
|
5255
|
+
inputSchema: object(
|
|
5256
|
+
{
|
|
5257
|
+
asset: { ...ASSET_ID, description: "A video asset id from get_video_project." },
|
|
5258
|
+
proxies: {
|
|
5259
|
+
type: "array",
|
|
5260
|
+
maxItems: 8,
|
|
5261
|
+
description: "Its proxies, one per scale (empty removes them all).",
|
|
5262
|
+
items: object({ scale: PROXY_SCALE, src: { type: "string", minLength: 1, description: "Where the preview loads the proxy from (URL or path)." }, width: { type: "number", exclusiveMinimum: 0, description: "Its width in pixels." }, height: { type: "number", exclusiveMinimum: 0, description: "Its height in pixels." } }, ["scale", "src"], "One proxy.")
|
|
5263
|
+
}
|
|
5264
|
+
},
|
|
5265
|
+
["asset", "proxies"]
|
|
5266
|
+
),
|
|
5267
|
+
annotations: { title: "Attach proxies", ...WRITE_IDEMPOTENT },
|
|
5268
|
+
handler: ({ asset, proxies }) => {
|
|
5269
|
+
const a = project().assets.find((x) => x.id === asset);
|
|
5270
|
+
if (!a) throw new Error(`There is no asset "${asset}"; the assets are ${project().assets.map((x) => `"${x.id}"`).join(", ") || "none"}.`);
|
|
5271
|
+
if (a.kind !== "video") throw new Error(`Asset "${asset}" is ${a.kind === "image" ? "an image" : "audio"}; only video assets have proxies.`);
|
|
5272
|
+
const edit = proxies.length ? { op: "update_asset", asset, set: { proxies } } : { op: "update_asset", asset, remove: ["proxies"] };
|
|
5273
|
+
return apply(edit, proxies.length ? "Attach proxies" : "Remove proxies", (r) => ({ asset: videoAssetOut(r.project.assets.find((x) => x.id === asset)) }));
|
|
5274
|
+
}
|
|
5275
|
+
};
|
|
5276
|
+
const setPreviewScale = {
|
|
5277
|
+
name: "set_video_preview_scale",
|
|
5278
|
+
title: "Set the preview size",
|
|
5279
|
+
description: 'Sets how large a picture the preview plays, without changing the project (no undo step): "full" plays the originals, a fraction (0.5, 0.25) plays the smallest proxy at least that large (or the original where there is none), "auto" (the default) picks by the size the preview is shown at. Use a proxy scale when playback stutters on large footage. Returns the setting and, for each video asset, what the preview plays at a fixed scale.',
|
|
5280
|
+
inputSchema: object(
|
|
5281
|
+
{ scale: { anyOf: [{ type: "string", enum: ["auto", "full"] }, { type: "number", exclusiveMinimum: 0, maximum: 1 }], description: '"auto", "full", or a fraction above 0 up to 1 (0.5 is half size).' } },
|
|
5282
|
+
["scale"]
|
|
5283
|
+
),
|
|
5284
|
+
annotations: { title: "Set the preview size", ...WRITE_IDEMPOTENT },
|
|
5285
|
+
handler: ({ scale }) => {
|
|
5286
|
+
controller.setPreviewScale(scale);
|
|
5287
|
+
const s = controller.getState().previewScale;
|
|
5288
|
+
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) }));
|
|
5290
|
+
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
|
+
}
|
|
5292
|
+
};
|
|
5293
|
+
const previewCache = {
|
|
5294
|
+
name: "get_video_preview_cache",
|
|
5295
|
+
title: "Read the preview's frame cache",
|
|
5296
|
+
description: `Reports the preview's cache of decoded frames, without changing anything: whether the host has one, how many frames and bytes it holds against its budget, hits and misses, frames still decoding, how far ahead it prefetches (ms), and the stretches of the timeline between "from" and "to" whose frames are all cached (these play and scrub without waiting). Use it to tell whether stuttering is the cache filling, and before asking the person to review a passage. With no cache, says so.`,
|
|
5297
|
+
inputSchema: object({ from: MS("Start of the stretch to report cached ranges for (default 0)"), to: MS("End of it (default: the end of the project)") }),
|
|
5298
|
+
annotations: { title: "Read the preview's frame cache", ...READ },
|
|
5299
|
+
handler: ({ from, to }) => {
|
|
5300
|
+
const cache = controller.frameCache;
|
|
5301
|
+
if (!cache) return { enabled: false, reason: "The host has not given this editor a frame cache (VideoEditorController frameCache), so the preview decodes each frame as it plays." };
|
|
5302
|
+
if (from !== void 0 && to !== void 0 && !(to > from)) throw new Error(`"to" (${to} ms) must be after "from" (${from} ms).`);
|
|
5303
|
+
const ranges = cache.cachedRanges(project(), { from, to });
|
|
5304
|
+
return { enabled: true, ...cache.stats(), cachedRanges: ranges };
|
|
4833
5305
|
}
|
|
4834
5306
|
};
|
|
4835
5307
|
const TRANSCRIPT_TRACK = { ...TRACK_ID, description: "Track whose words to read (default: the first audio track with a transcribed clip, else the first video track with one)." };
|
|
@@ -4883,7 +5355,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4883
5355
|
handler: ({ track, from, to, limit }) => {
|
|
4884
5356
|
const all = timelineWords(track).filter((w) => w.end > (_nullishCoalesce(from, () => ( 0))) && w.start < (_nullishCoalesce(to, () => ( Infinity))));
|
|
4885
5357
|
const words = all.slice(0, _nullishCoalesce(limit, () => ( 1e3)));
|
|
4886
|
-
return { track: _nullishCoalesce(_optionalChain([words, 'access',
|
|
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 };
|
|
4887
5359
|
}
|
|
4888
5360
|
};
|
|
4889
5361
|
const findText = {
|
|
@@ -4945,7 +5417,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4945
5417
|
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
4946
5418
|
const { clip } = at;
|
|
4947
5419
|
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.`);
|
|
4948
|
-
if (_optionalChain([project, 'call',
|
|
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.`);
|
|
4949
5421
|
return clip;
|
|
4950
5422
|
};
|
|
4951
5423
|
const rampOut = (r, id) => {
|
|
@@ -5273,8 +5745,8 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
5273
5745
|
return { time: t, timecode: formatVideoTimecode(t, p.fps), width: pixels.width, height: pixels.height, ...summarizeVideoScopes(pixels, { bins, columns }) };
|
|
5274
5746
|
}
|
|
5275
5747
|
};
|
|
5276
|
-
const reads = [getProject, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek];
|
|
5277
|
-
const writes = [setSettings, addAsset, updateAsset, removeAsset, 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];
|
|
5748
|
+
const reads = [getProject, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, previewCache, planProxies, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek, setPreviewScale];
|
|
5749
|
+
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];
|
|
5278
5750
|
return {
|
|
5279
5751
|
id: _nullishCoalesce(options.id, () => ( "video")),
|
|
5280
5752
|
namespace: options.namespace,
|
|
@@ -5494,5 +5966,25 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
5494
5966
|
|
|
5495
5967
|
|
|
5496
5968
|
|
|
5497
|
-
|
|
5969
|
+
|
|
5970
|
+
|
|
5971
|
+
|
|
5972
|
+
|
|
5973
|
+
|
|
5974
|
+
|
|
5975
|
+
|
|
5976
|
+
|
|
5977
|
+
|
|
5978
|
+
|
|
5979
|
+
|
|
5980
|
+
|
|
5981
|
+
|
|
5982
|
+
|
|
5983
|
+
|
|
5984
|
+
|
|
5985
|
+
|
|
5986
|
+
|
|
5987
|
+
|
|
5988
|
+
|
|
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;
|
|
5498
5990
|
//# sourceMappingURL=index.cjs.map
|