@pixerate/editor 0.86.0 → 0.87.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 +505 -48
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +243 -1
- package/dist/video-editor/index.d.ts +243 -1
- package/dist/video-editor/index.js +463 -6
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-video-editor/SKILL.md +12 -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;
|
|
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;
|
|
2
2
|
|
|
3
3
|
var _chunkWAJQKWQUcjs = require('../chunk-WAJQKWQU.cjs');
|
|
4
4
|
|
|
@@ -2124,6 +2124,379 @@ var VideoAudioPeaksCache = (_class2 = class {
|
|
|
2124
2124
|
}
|
|
2125
2125
|
}, _class2);
|
|
2126
2126
|
|
|
2127
|
+
// src/video-editor/scopes.ts
|
|
2128
|
+
var VIDEO_SCOPE_KINDS = ["histogram", "waveform", "vectorscope"];
|
|
2129
|
+
var videoLuma = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
2130
|
+
function videoChroma(r, g, b) {
|
|
2131
|
+
const y = videoLuma(r, g, b);
|
|
2132
|
+
return [(b - y) / 1.8556, (r - y) / 1.5748];
|
|
2133
|
+
}
|
|
2134
|
+
function eachPixel(pixels, maxPixels, visit) {
|
|
2135
|
+
const { width, height, data } = pixels;
|
|
2136
|
+
if (!(width > 0 && height > 0) || data.length < width * height * 4) throw new Error(`The pixels are not ${width} \xD7 ${height} RGBA: there are ${data.length} values, and ${width * height * 4} are needed.`);
|
|
2137
|
+
const step = Math.max(1, Math.sqrt(width * height / Math.max(1, _nullishCoalesce(maxPixels, () => ( 262144)))));
|
|
2138
|
+
let count = 0;
|
|
2139
|
+
for (let fy = step / 2; fy < height; fy += step) {
|
|
2140
|
+
const y = Math.floor(fy);
|
|
2141
|
+
for (let fx = step / 2; fx < width; fx += step) {
|
|
2142
|
+
const x = Math.floor(fx);
|
|
2143
|
+
const i = (y * width + x) * 4;
|
|
2144
|
+
visit(data[i], data[i + 1], data[i + 2], x, y);
|
|
2145
|
+
count++;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
return count;
|
|
2149
|
+
}
|
|
2150
|
+
var binOf = (v, bins) => Math.min(bins - 1, Math.floor((v + 1e-6) / 256 * bins));
|
|
2151
|
+
function videoHistogram(pixels, options = {}) {
|
|
2152
|
+
const bins = Math.max(1, Math.min(256, Math.round(_nullishCoalesce(options.bins, () => ( 256)))));
|
|
2153
|
+
const out = { bins, r: Array(bins).fill(0), g: Array(bins).fill(0), b: Array(bins).fill(0), luma: Array(bins).fill(0), total: 0 };
|
|
2154
|
+
out.total = eachPixel(pixels, options.maxPixels, (r, g, b) => {
|
|
2155
|
+
out.r[binOf(r, bins)]++;
|
|
2156
|
+
out.g[binOf(g, bins)]++;
|
|
2157
|
+
out.b[binOf(b, bins)]++;
|
|
2158
|
+
out.luma[binOf(videoLuma(r, g, b), bins)]++;
|
|
2159
|
+
});
|
|
2160
|
+
return out;
|
|
2161
|
+
}
|
|
2162
|
+
function videoLumaWaveform(pixels, options = {}) {
|
|
2163
|
+
const columns = Math.max(1, Math.min(pixels.width, Math.round(_nullishCoalesce(options.columns, () => ( Math.min(pixels.width, 256))))));
|
|
2164
|
+
const levels = Math.max(2, Math.min(256, Math.round(_nullishCoalesce(options.levels, () => ( 128)))));
|
|
2165
|
+
const data = new Uint32Array(columns * levels);
|
|
2166
|
+
let peak = 0;
|
|
2167
|
+
eachPixel(pixels, options.maxPixels, (r, g, b, x) => {
|
|
2168
|
+
const c = Math.min(columns - 1, Math.floor(x / pixels.width * columns));
|
|
2169
|
+
const i = c * levels + binOf(videoLuma(r, g, b), levels);
|
|
2170
|
+
if (++data[i] > peak) peak = data[i];
|
|
2171
|
+
});
|
|
2172
|
+
return { columns, levels, data, peak };
|
|
2173
|
+
}
|
|
2174
|
+
var videoVectorscopePoint = (cb, cr, size) => [(cb + 0.5) * (size - 1), (0.5 - cr) * (size - 1)];
|
|
2175
|
+
function videoVectorscope(pixels, options = {}) {
|
|
2176
|
+
const size = Math.max(16, Math.min(512, Math.round(_nullishCoalesce(options.size, () => ( 128)))));
|
|
2177
|
+
const data = new Uint32Array(size * size);
|
|
2178
|
+
let peak = 0;
|
|
2179
|
+
eachPixel(pixels, options.maxPixels, (r, g, b) => {
|
|
2180
|
+
const [cb, cr] = videoChroma(r / 255, g / 255, b / 255);
|
|
2181
|
+
const [x, y] = videoVectorscopePoint(cb, cr, size);
|
|
2182
|
+
const i = Math.round(y) * size + Math.round(x);
|
|
2183
|
+
if (++data[i] > peak) peak = data[i];
|
|
2184
|
+
});
|
|
2185
|
+
return { size, data, peak };
|
|
2186
|
+
}
|
|
2187
|
+
var round3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
2188
|
+
var hex2 = (v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0");
|
|
2189
|
+
function videoExposureStats(pixels, options = {}) {
|
|
2190
|
+
const low = (_nullishCoalesce(options.shadowLevel, () => ( 0.02))) * 255;
|
|
2191
|
+
const high = (_nullishCoalesce(options.highlightLevel, () => ( 0.98))) * 255;
|
|
2192
|
+
const lumaBins = new Uint32Array(256);
|
|
2193
|
+
let sumY = 0;
|
|
2194
|
+
let sumR = 0;
|
|
2195
|
+
let sumG = 0;
|
|
2196
|
+
let sumB = 0;
|
|
2197
|
+
let sumSat = 0;
|
|
2198
|
+
let maxSat = 0;
|
|
2199
|
+
let shadows = 0;
|
|
2200
|
+
let highlights = 0;
|
|
2201
|
+
let cr = 0;
|
|
2202
|
+
let cg = 0;
|
|
2203
|
+
let cbl = 0;
|
|
2204
|
+
const total = eachPixel(pixels, options.maxPixels, (r, g, b) => {
|
|
2205
|
+
const y = videoLuma(r, g, b);
|
|
2206
|
+
lumaBins[Math.min(255, Math.round(y))]++;
|
|
2207
|
+
sumY += y;
|
|
2208
|
+
sumR += r;
|
|
2209
|
+
sumG += g;
|
|
2210
|
+
sumB += b;
|
|
2211
|
+
const [u, v] = videoChroma(r / 255, g / 255, b / 255);
|
|
2212
|
+
const sat = Math.min(1, Math.hypot(u, v) / 0.5);
|
|
2213
|
+
sumSat += sat;
|
|
2214
|
+
if (sat > maxSat) maxSat = sat;
|
|
2215
|
+
if (y <= low) shadows++;
|
|
2216
|
+
if (y >= high) highlights++;
|
|
2217
|
+
if (r >= high) cr++;
|
|
2218
|
+
if (g >= high) cg++;
|
|
2219
|
+
if (b >= high) cbl++;
|
|
2220
|
+
});
|
|
2221
|
+
const percentile = (p) => {
|
|
2222
|
+
const target = p * total;
|
|
2223
|
+
let seen = 0;
|
|
2224
|
+
for (let i = 0; i < 256; i++) {
|
|
2225
|
+
seen += lumaBins[i];
|
|
2226
|
+
if (seen >= target && seen > 0) return i / 255;
|
|
2227
|
+
}
|
|
2228
|
+
return 1;
|
|
2229
|
+
};
|
|
2230
|
+
let min = 0;
|
|
2231
|
+
while (min < 255 && !lumaBins[min]) min++;
|
|
2232
|
+
let max = 255;
|
|
2233
|
+
while (max > 0 && !lumaBins[max]) max--;
|
|
2234
|
+
return {
|
|
2235
|
+
pixels: total,
|
|
2236
|
+
luma: { mean: round3(sumY / total / 255), median: round3(percentile(0.5)), min: round3(min / 255), max: round3(max / 255), p1: round3(percentile(0.01)), p99: round3(percentile(0.99)) },
|
|
2237
|
+
clipped: { shadows: round3(shadows / total), highlights: round3(highlights / total), r: round3(cr / total), g: round3(cg / total), b: round3(cbl / total) },
|
|
2238
|
+
saturation: { mean: round3(sumSat / total), max: round3(maxSat) },
|
|
2239
|
+
average: `#${hex2(sumR / total)}${hex2(sumG / total)}${hex2(sumB / total)}`
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
function videoExposureNotes(stats) {
|
|
2243
|
+
const notes = [];
|
|
2244
|
+
const pct = (v2) => `${Math.round(v2 * 1e3) / 10}%`;
|
|
2245
|
+
if (stats.clipped.shadows >= 0.05) notes.push(`${pct(stats.clipped.shadows)} of the picture is crushed to black; raise exposure or lower contrast.`);
|
|
2246
|
+
if (stats.clipped.highlights >= 0.02) notes.push(`${pct(stats.clipped.highlights)} of the picture is blown to white; lower exposure.`);
|
|
2247
|
+
else {
|
|
2248
|
+
const channels = ["r", "g", "b"].filter((k) => stats.clipped[k] >= 0.05);
|
|
2249
|
+
if (channels.length) notes.push(`The ${channels.map((k) => ({ r: "red", g: "green", b: "blue" })[k]).join(" and ")} channel${channels.length > 1 ? "s clip" : " clips"} in ${pct(Math.max(...channels.map((k) => stats.clipped[k])))} of the picture; lower exposure or saturation.`);
|
|
2250
|
+
}
|
|
2251
|
+
if (stats.luma.mean < 0.2 && stats.clipped.shadows < 0.05) notes.push(`The picture is dark (mean luma ${stats.luma.mean}); raise exposure if it is not meant to be.`);
|
|
2252
|
+
if (stats.luma.mean > 0.75 && stats.clipped.highlights < 0.02) notes.push(`The picture is bright (mean luma ${stats.luma.mean}); lower exposure if it is not meant to be.`);
|
|
2253
|
+
if (stats.luma.p99 - stats.luma.p1 < 0.3 && stats.pixels > 1) notes.push(`The picture is flat (most of it lies between luma ${stats.luma.p1} and ${stats.luma.p99}); raise contrast.`);
|
|
2254
|
+
const [r, g, b] = [1, 3, 5].map((i) => parseInt(stats.average.slice(i, i + 2), 16) / 255);
|
|
2255
|
+
const [u, v] = videoChroma(r, g, b);
|
|
2256
|
+
if (Math.hypot(u, v) > 0.04 && stats.saturation.mean < 0.35) {
|
|
2257
|
+
const warm = v - u;
|
|
2258
|
+
notes.push(`The picture has a ${Math.abs(warm) > Math.abs(v + u) * 0.5 ? warm > 0 ? "warm (orange)" : "cool (blue)" : v > 0 ? "magenta" : "green"} cast (average ${stats.average}); adjust temperature and tint.`);
|
|
2259
|
+
}
|
|
2260
|
+
return notes;
|
|
2261
|
+
}
|
|
2262
|
+
var videoVectorscopeAngle = (cb, cr) => (Math.atan2(cr, cb) * 180 / Math.PI % 360 + 360) % 360;
|
|
2263
|
+
function summarizeVideoScopes(pixels, options = {}) {
|
|
2264
|
+
const stats = videoExposureStats(pixels, options);
|
|
2265
|
+
const bins = Math.max(2, Math.min(64, Math.round(_nullishCoalesce(options.bins, () => ( 16)))));
|
|
2266
|
+
const h = videoHistogram(pixels, { ...options, bins });
|
|
2267
|
+
const share = (a) => a.map((v) => round3(v / h.total));
|
|
2268
|
+
const columns = Math.max(1, Math.min(32, Math.round(_nullishCoalesce(options.columns, () => ( 8)))));
|
|
2269
|
+
const w = videoLumaWaveform(pixels, { ...options, columns, levels: 256 });
|
|
2270
|
+
const waveform = Array.from({ length: w.columns }, (_, c) => {
|
|
2271
|
+
const counts = w.data.subarray(c * 256, c * 256 + 256);
|
|
2272
|
+
const n2 = counts.reduce((a, b) => a + b, 0);
|
|
2273
|
+
let lo = 0;
|
|
2274
|
+
while (lo < 255 && !counts[lo]) lo++;
|
|
2275
|
+
let hi = 255;
|
|
2276
|
+
while (hi > 0 && !counts[hi]) hi--;
|
|
2277
|
+
let seen = 0;
|
|
2278
|
+
let median = 0;
|
|
2279
|
+
for (let i = 0; i < 256; i++) {
|
|
2280
|
+
seen += counts[i];
|
|
2281
|
+
if (seen >= n2 / 2 && n2) {
|
|
2282
|
+
median = i;
|
|
2283
|
+
break;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
return { start: round3(c / w.columns), end: round3((c + 1) / w.columns), min: round3(lo / 255), median: round3(median / 255), max: round3(hi / 255) };
|
|
2287
|
+
}).filter((_, c) => w.data.subarray(c * 256, c * 256 + 256).some((v) => v > 0));
|
|
2288
|
+
const chroma = [];
|
|
2289
|
+
eachPixel(pixels, Math.min(_nullishCoalesce(options.maxPixels, () => ( 65536)), 65536), (r, g, b) => {
|
|
2290
|
+
const [u, v] = videoChroma(r / 255, g / 255, b / 255);
|
|
2291
|
+
chroma.push([u, v, Math.hypot(u, v)]);
|
|
2292
|
+
});
|
|
2293
|
+
chroma.sort((a, b) => b[2] - a[2]);
|
|
2294
|
+
const top = chroma.slice(0, Math.max(1, Math.ceil(chroma.length / 10)));
|
|
2295
|
+
const mu = top.reduce((s, c) => s + c[0], 0) / top.length;
|
|
2296
|
+
const mv = top.reduce((s, c) => s + c[1], 0) / top.length;
|
|
2297
|
+
const strength = Math.min(1, top.reduce((s, c) => s + c[2], 0) / top.length / 0.5);
|
|
2298
|
+
const coherence = Math.hypot(mu, mv) / Math.max(1e-9, top.reduce((s, c) => s + c[2], 0) / top.length);
|
|
2299
|
+
return {
|
|
2300
|
+
stats,
|
|
2301
|
+
notes: videoExposureNotes(stats),
|
|
2302
|
+
histogram: { bins, luma: share(h.luma), r: share(h.r), g: share(h.g), b: share(h.b) },
|
|
2303
|
+
waveform,
|
|
2304
|
+
vectorscope: { hue: strength < 0.02 ? null : Math.round(videoVectorscopeAngle(mu, mv)), strength: round3(strength), spread: round3(1 - coherence) }
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
var VIDEO_VECTORSCOPE_TARGETS = [
|
|
2308
|
+
["R", [0.75, 0, 0]],
|
|
2309
|
+
["Mg", [0.75, 0, 0.75]],
|
|
2310
|
+
["B", [0, 0, 0.75]],
|
|
2311
|
+
["Cy", [0, 0.75, 0.75]],
|
|
2312
|
+
["G", [0, 0.75, 0]],
|
|
2313
|
+
["Yl", [0.75, 0.75, 0]]
|
|
2314
|
+
].map(([name, rgb]) => {
|
|
2315
|
+
const [cb, cr] = videoChroma(rgb[0], rgb[1], rgb[2]);
|
|
2316
|
+
return { name, rgb: [...rgb], cb, cr };
|
|
2317
|
+
});
|
|
2318
|
+
function canvas(width, height) {
|
|
2319
|
+
const data = new Uint8ClampedArray(width * height * 4);
|
|
2320
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
2321
|
+
data[i] = 12;
|
|
2322
|
+
data[i + 1] = 14;
|
|
2323
|
+
data[i + 2] = 18;
|
|
2324
|
+
data[i + 3] = 255;
|
|
2325
|
+
}
|
|
2326
|
+
return { width, height, data };
|
|
2327
|
+
}
|
|
2328
|
+
function plot(img, x, y, rgb, alpha = 1, add = false) {
|
|
2329
|
+
const xi = Math.round(x);
|
|
2330
|
+
const yi = Math.round(y);
|
|
2331
|
+
if (xi < 0 || yi < 0 || xi >= img.width || yi >= img.height) return;
|
|
2332
|
+
const i = (yi * img.width + xi) * 4;
|
|
2333
|
+
for (let k = 0; k < 3; k++) img.data[i + k] = add ? img.data[i + k] + rgb[k] * alpha : img.data[i + k] * (1 - alpha) + rgb[k] * alpha;
|
|
2334
|
+
}
|
|
2335
|
+
var GRID = [70, 76, 88];
|
|
2336
|
+
var glow = (count, peak) => count ? 0.25 + 0.75 * (Math.log1p(count) / Math.log1p(Math.max(1, peak))) : 0;
|
|
2337
|
+
function videoHistogramImage(h, width = 256, height = 128) {
|
|
2338
|
+
const img = canvas(width, height);
|
|
2339
|
+
for (const q of [0.25, 0.5, 0.75]) for (let y = 0; y < height; y++) plot(img, q * (width - 1), y, GRID);
|
|
2340
|
+
const inner = (a) => a.length > 2 ? a.slice(1, -1) : a;
|
|
2341
|
+
const peak = Math.max(1, ...inner(h.r), ...inner(h.g), ...inner(h.b), ...inner(h.luma));
|
|
2342
|
+
const height01 = (v) => Math.min(1, Math.sqrt(v / peak));
|
|
2343
|
+
for (let x = 0; x < width; x++) {
|
|
2344
|
+
const bin = Math.min(h.bins - 1, Math.floor(x / width * h.bins));
|
|
2345
|
+
const channels = [
|
|
2346
|
+
[h.r, [200, 40, 40]],
|
|
2347
|
+
[h.g, [40, 180, 40]],
|
|
2348
|
+
[h.b, [50, 80, 220]]
|
|
2349
|
+
];
|
|
2350
|
+
for (const [a, rgb] of channels) {
|
|
2351
|
+
const top = height - height01(a[bin]) * height;
|
|
2352
|
+
for (let y = Math.ceil(top); y < height; y++) plot(img, x, y, rgb, 0.8, true);
|
|
2353
|
+
}
|
|
2354
|
+
plot(img, x, height - 1 - height01(h.luma[bin]) * (height - 1), [255, 255, 255]);
|
|
2355
|
+
}
|
|
2356
|
+
return img;
|
|
2357
|
+
}
|
|
2358
|
+
function videoWaveformImage(w, width = 256, height = 128) {
|
|
2359
|
+
const img = canvas(width, height);
|
|
2360
|
+
for (const q of [0, 0.25, 0.5, 0.75, 1]) for (let x = 0; x < width; x++) plot(img, x, (1 - q) * (height - 1), GRID);
|
|
2361
|
+
for (let x = 0; x < width; x++) {
|
|
2362
|
+
const c = Math.min(w.columns - 1, Math.floor(x / width * w.columns));
|
|
2363
|
+
for (let y = 0; y < height; y++) {
|
|
2364
|
+
const level = Math.min(w.levels - 1, Math.floor((height - 1 - y) / height * w.levels));
|
|
2365
|
+
const a = glow(w.data[c * w.levels + level], w.peak);
|
|
2366
|
+
if (a) plot(img, x, y, [120, 255, 140], a);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return img;
|
|
2370
|
+
}
|
|
2371
|
+
function videoVectorscopeImage(v, size = v.size) {
|
|
2372
|
+
const img = canvas(size, size);
|
|
2373
|
+
const c = (size - 1) / 2;
|
|
2374
|
+
for (let a = 0; a < 720; a++) {
|
|
2375
|
+
const t = a / 720 * 2 * Math.PI;
|
|
2376
|
+
plot(img, c + Math.cos(t) * c, c - Math.sin(t) * c, GRID);
|
|
2377
|
+
}
|
|
2378
|
+
const skin = 123 * Math.PI / 180;
|
|
2379
|
+
for (let r = 0; r < c; r++) plot(img, c + Math.cos(skin) * r, c - Math.sin(skin) * r, [140, 110, 90]);
|
|
2380
|
+
for (const t of VIDEO_VECTORSCOPE_TARGETS) {
|
|
2381
|
+
const [x, y] = videoVectorscopePoint(t.cb, t.cr, size);
|
|
2382
|
+
const rgb = t.rgb.map((k) => 80 + k * 200);
|
|
2383
|
+
for (let k = -3; k <= 3; k++) {
|
|
2384
|
+
plot(img, x + k, y - 3, rgb);
|
|
2385
|
+
plot(img, x + k, y + 3, rgb);
|
|
2386
|
+
plot(img, x - 3, y + k, rgb);
|
|
2387
|
+
plot(img, x + 3, y + k, rgb);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
for (let y = 0; y < size; y++)
|
|
2391
|
+
for (let x = 0; x < size; x++) {
|
|
2392
|
+
const sy = Math.min(v.size - 1, Math.floor(y / size * v.size));
|
|
2393
|
+
const sx = Math.min(v.size - 1, Math.floor(x / size * v.size));
|
|
2394
|
+
const a = glow(v.data[sy * v.size + sx], v.peak);
|
|
2395
|
+
if (!a) continue;
|
|
2396
|
+
const cb = sx / (v.size - 1) - 0.5;
|
|
2397
|
+
const cr = 0.5 - sy / (v.size - 1);
|
|
2398
|
+
const r = Math.min(1, Math.max(0, 0.6 + 1.5748 * cr));
|
|
2399
|
+
const g = Math.min(1, Math.max(0, 0.6 - 0.1873 * cb - 0.4681 * cr));
|
|
2400
|
+
const b = Math.min(1, Math.max(0, 0.6 + 1.8556 * cb));
|
|
2401
|
+
plot(img, x, y, [r * 255, g * 255, b * 255], a);
|
|
2402
|
+
}
|
|
2403
|
+
return img;
|
|
2404
|
+
}
|
|
2405
|
+
function videoScopeImage(kind, pixels, width = 256, height = 128, options = {}) {
|
|
2406
|
+
if (kind === "histogram") return videoHistogramImage(videoHistogram(pixels, { ...options, bins: Math.min(256, width) }), width, height);
|
|
2407
|
+
if (kind === "waveform") return videoWaveformImage(videoLumaWaveform(pixels, { ...options, columns: Math.min(pixels.width, width), levels: Math.min(256, height) }), width, height);
|
|
2408
|
+
const size = Math.min(width, height);
|
|
2409
|
+
return videoVectorscopeImage(videoVectorscope(pixels, { ...options, size }), size);
|
|
2410
|
+
}
|
|
2411
|
+
var VideoPreviewPixels = (_class3 = class {constructor() { _class3.prototype.__init6.call(this);_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this); }
|
|
2412
|
+
__init6() {this.reader = null}
|
|
2413
|
+
__init7() {this.drawnAt = null}
|
|
2414
|
+
__init8() {this.listeners = /* @__PURE__ */ new Set()}
|
|
2415
|
+
/** Gives the canvas to read (the latest preview wins). Returns a function that takes it away again. */
|
|
2416
|
+
attach(reader) {
|
|
2417
|
+
this.reader = reader;
|
|
2418
|
+
this.drawnAt = null;
|
|
2419
|
+
return () => {
|
|
2420
|
+
if (this.reader !== reader) return;
|
|
2421
|
+
this.reader = null;
|
|
2422
|
+
this.drawnAt = null;
|
|
2423
|
+
this.emit();
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
/** Whether a preview is attached. */
|
|
2427
|
+
get attached() {
|
|
2428
|
+
return this.reader !== null;
|
|
2429
|
+
}
|
|
2430
|
+
/** The preview drew the frame at `time`; `complete` is false while a picture was missing or stale. */
|
|
2431
|
+
drawn(time, complete) {
|
|
2432
|
+
this.drawnAt = { time, complete };
|
|
2433
|
+
this.emit();
|
|
2434
|
+
}
|
|
2435
|
+
/** The last frame drawn, with its pixels; `null` without a preview. Throws when the canvas cannot be read. */
|
|
2436
|
+
read() {
|
|
2437
|
+
if (!this.reader) return null;
|
|
2438
|
+
try {
|
|
2439
|
+
return this.reader();
|
|
2440
|
+
} catch (e) {
|
|
2441
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
2442
|
+
throw new Error(`The preview's pixels cannot be read (${reason}). Media from another origin must be served with CORS headers (Access-Control-Allow-Origin) for its pixels to be measured.`);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
/** Called after each frame the preview draws. */
|
|
2446
|
+
subscribe(listener) {
|
|
2447
|
+
this.listeners.add(listener);
|
|
2448
|
+
return () => {
|
|
2449
|
+
this.listeners.delete(listener);
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* The frame at `time` once it is complete: now, if the last frame drawn was it, else the next
|
|
2454
|
+
* complete drawing of it. Fails after `timeout` ms (default 5000).
|
|
2455
|
+
*/
|
|
2456
|
+
next(time, timeout = 5e3) {
|
|
2457
|
+
const matches = () => this.drawnAt && this.drawnAt.complete && Math.abs(this.drawnAt.time - time) < 0.5;
|
|
2458
|
+
return new Promise((resolve, reject) => {
|
|
2459
|
+
const done = (fn) => {
|
|
2460
|
+
clearTimeout(timer);
|
|
2461
|
+
off();
|
|
2462
|
+
fn();
|
|
2463
|
+
};
|
|
2464
|
+
const check = () => {
|
|
2465
|
+
if (!this.reader) return done(() => reject(new Error("The preview was closed before it drew the frame.")));
|
|
2466
|
+
if (!matches()) return;
|
|
2467
|
+
try {
|
|
2468
|
+
const snap = this.read();
|
|
2469
|
+
done(() => snap ? resolve(snap) : reject(new Error("The preview has no pixels to read.")));
|
|
2470
|
+
} catch (e) {
|
|
2471
|
+
done(() => reject(e));
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
const off = this.subscribe(check);
|
|
2475
|
+
const timer = setTimeout(() => done(() => reject(new Error(`The preview did not finish drawing ${Math.round(time)} ms within ${timeout} ms; its media may still be loading or may have failed to load. Try again, or check the asset sources.`))), timeout);
|
|
2476
|
+
check();
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
emit() {
|
|
2480
|
+
for (const l of [...this.listeners]) l();
|
|
2481
|
+
}
|
|
2482
|
+
}, _class3);
|
|
2483
|
+
function readVideoScopesPanel(feed, kinds = VIDEO_SCOPE_KINDS, options = {}) {
|
|
2484
|
+
let snap;
|
|
2485
|
+
try {
|
|
2486
|
+
snap = feed.read();
|
|
2487
|
+
} catch (e) {
|
|
2488
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
2489
|
+
}
|
|
2490
|
+
if (!snap) return { ok: false, message: feed.attached ? "The preview has not drawn a frame yet." : "Show the preview to see its scopes." };
|
|
2491
|
+
const width = _nullishCoalesce(options.width, () => ( 256));
|
|
2492
|
+
const height = _nullishCoalesce(options.height, () => ( 128));
|
|
2493
|
+
const opts = { maxPixels: _nullishCoalesce(options.maxPixels, () => ( 65536)) };
|
|
2494
|
+
const images = {};
|
|
2495
|
+
for (const kind of kinds) images[kind] = videoScopeImage(kind, snap.pixels, width, height, opts);
|
|
2496
|
+
const stats = videoExposureStats(snap.pixels, opts);
|
|
2497
|
+
return { ok: true, time: snap.time, complete: snap.complete, images, stats, notes: videoExposureNotes(stats) };
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2127
2500
|
// src/video-editor/transcript.ts
|
|
2128
2501
|
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2129
2502
|
function cueTime(s) {
|
|
@@ -2845,26 +3218,40 @@ function buildFfmpeg(project, options, measure) {
|
|
|
2845
3218
|
}
|
|
2846
3219
|
|
|
2847
3220
|
// src/video-editor/preview.ts
|
|
2848
|
-
var VideoPreviewEngine = (
|
|
2849
|
-
constructor(source,
|
|
3221
|
+
var VideoPreviewEngine = (_class4 = class {
|
|
3222
|
+
constructor(source, canvas2, options = {}) {;_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);_class4.prototype.__init11.call(this);_class4.prototype.__init12.call(this);_class4.prototype.__init13.call(this);_class4.prototype.__init14.call(this);_class4.prototype.__init15.call(this);_class4.prototype.__init16.call(this);_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);
|
|
2850
3223
|
this.source = source;
|
|
2851
3224
|
this.options = options;
|
|
2852
|
-
this.canvas =
|
|
2853
|
-
this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([
|
|
3225
|
+
this.canvas = canvas2;
|
|
3226
|
+
this.ctx = options.context !== void 0 ? options.context : _nullishCoalesce(_optionalChain([canvas2, 'access', _73 => _73.getContext, 'optionalCall', _74 => _74("2d")]), () => ( null));
|
|
2854
3227
|
this.unsubscribe = source.subscribe(() => this.update());
|
|
3228
|
+
const pixels = options.pixels !== void 0 ? options.pixels : source.previewPixels;
|
|
3229
|
+
this.detachPixels = pixels ? pixels.attach(() => this.readPixels()) : () => {
|
|
3230
|
+
};
|
|
3231
|
+
this.pixelsTarget = _nullishCoalesce(pixels, () => ( null));
|
|
2855
3232
|
this.update();
|
|
2856
3233
|
}
|
|
2857
3234
|
|
|
2858
3235
|
|
|
2859
3236
|
|
|
2860
3237
|
|
|
2861
|
-
|
|
2862
|
-
|
|
3238
|
+
__init9() {this.media = /* @__PURE__ */ new Map()}
|
|
3239
|
+
__init10() {this.images = /* @__PURE__ */ new Map()}
|
|
3240
|
+
|
|
3241
|
+
__init11() {this.scheduled = false}
|
|
3242
|
+
__init12() {this.destroyed = false}
|
|
3243
|
+
__init13() {this.filterHost = null}
|
|
3244
|
+
__init14() {this.filters = /* @__PURE__ */ new Set()}
|
|
3245
|
+
|
|
2863
3246
|
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
3247
|
+
__init15() {this.drawn = null}
|
|
3248
|
+
__init16() {this.complete = true}
|
|
3249
|
+
/** The canvas's pixels and the frame they show, or `null` when its context cannot be read. */
|
|
3250
|
+
readPixels() {
|
|
3251
|
+
const ctx = this.ctx;
|
|
3252
|
+
if (!_optionalChain([ctx, 'optionalAccess', _75 => _75.getImageData]) || !this.drawn) return null;
|
|
3253
|
+
return { ...this.drawn, pixels: ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) };
|
|
3254
|
+
}
|
|
2868
3255
|
createMedia(kind) {
|
|
2869
3256
|
if (this.options.createMedia) return this.options.createMedia(kind);
|
|
2870
3257
|
const el = document.createElement(kind);
|
|
@@ -2900,8 +3287,8 @@ var VideoPreviewEngine = (_class3 = class {
|
|
|
2900
3287
|
entry.media.pause();
|
|
2901
3288
|
entry.media.removeEventListener("seeked", entry.onReady);
|
|
2902
3289
|
entry.media.removeEventListener("loadeddata", entry.onReady);
|
|
2903
|
-
_optionalChain([entry, 'access',
|
|
2904
|
-
_optionalChain([entry, 'access',
|
|
3290
|
+
_optionalChain([entry, 'access', _76 => _76.media, 'access', _77 => _77.removeAttribute, 'optionalCall', _78 => _78("src")]);
|
|
3291
|
+
_optionalChain([entry, 'access', _79 => _79.media, 'access', _80 => _80.load, 'optionalCall', _81 => _81()]);
|
|
2905
3292
|
this.media.delete(entry.clip);
|
|
2906
3293
|
}
|
|
2907
3294
|
/** Brings the media elements in step with the editor and draws the frame. */
|
|
@@ -2935,7 +3322,7 @@ var VideoPreviewEngine = (_class3 = class {
|
|
|
2935
3322
|
for (const layer of frame.layers) {
|
|
2936
3323
|
if (layer.type !== "media" || layer.assetKind !== "video" || !layer.src) continue;
|
|
2937
3324
|
const sound = sounding.get(layer.clip);
|
|
2938
|
-
sync(layer.clip, layer.src, "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess',
|
|
3325
|
+
sync(layer.clip, layer.src, "video", _nullishCoalesce(layer.sourceTime, () => ( 0)), _nullishCoalesce(_optionalChain([sound, 'optionalAccess', _82 => _82.speed]), () => ( 1)), sound ? sound.gain : null);
|
|
2939
3326
|
sounding.delete(layer.clip);
|
|
2940
3327
|
}
|
|
2941
3328
|
for (const a of sounding.values()) sync(a.clip, a.src, "audio", a.sourceTime, a.speed, a.gain);
|
|
@@ -2950,13 +3337,15 @@ var VideoPreviewEngine = (_class3 = class {
|
|
|
2950
3337
|
if (!this.destroyed) this.draw(videoFrameAt(this.source.getState().project, this.source.getState().time));
|
|
2951
3338
|
});
|
|
2952
3339
|
}
|
|
2953
|
-
|
|
3340
|
+
__init17() {this.sourceFor = (layer) => {
|
|
2954
3341
|
if (layer.assetKind === "image" && layer.src) {
|
|
2955
3342
|
const img = this.image(layer.src);
|
|
2956
3343
|
return img.complete ? img : null;
|
|
2957
3344
|
}
|
|
2958
3345
|
const entry = this.media.get(layer.clip);
|
|
2959
|
-
|
|
3346
|
+
if (!entry || entry.media.readyState < 2) return null;
|
|
3347
|
+
if (entry.media.seeking) this.complete = false;
|
|
3348
|
+
return entry.media;
|
|
2960
3349
|
}}
|
|
2961
3350
|
draw(frame) {
|
|
2962
3351
|
if (!this.ctx) return;
|
|
@@ -2967,10 +3356,18 @@ var VideoPreviewEngine = (_class3 = class {
|
|
|
2967
3356
|
const h = Math.max(1, Math.round(frame.height * resolution));
|
|
2968
3357
|
if (this.canvas.width !== w) this.canvas.width = w;
|
|
2969
3358
|
if (this.canvas.height !== h) this.canvas.height = h;
|
|
2970
|
-
|
|
2971
|
-
|
|
3359
|
+
this.complete = true;
|
|
3360
|
+
const sourceFor = (layer) => {
|
|
3361
|
+
const source = this.sourceFor(layer);
|
|
3362
|
+
if (!source) this.complete = false;
|
|
3363
|
+
return source;
|
|
3364
|
+
};
|
|
3365
|
+
drawVideoFrame(this.ctx, frame, sourceFor, resolution, { defineSvgFilter: this.defineSvgFilter });
|
|
3366
|
+
this.drawn = { time: frame.time, complete: this.complete };
|
|
3367
|
+
_optionalChain([this, 'access', _83 => _83.options, 'access', _84 => _84.onFrame, 'optionalCall', _85 => _85(frame)]);
|
|
3368
|
+
_optionalChain([this, 'access', _86 => _86.pixelsTarget, 'optionalAccess', _87 => _87.drawn, 'call', _88 => _88(frame.time, this.complete)]);
|
|
2972
3369
|
}
|
|
2973
|
-
|
|
3370
|
+
__init18() {this.defineSvgFilter = (id, markup) => {
|
|
2974
3371
|
if (this.options.defineSvgFilter === null) return;
|
|
2975
3372
|
if (this.options.defineSvgFilter) return this.options.defineSvgFilter(id, markup);
|
|
2976
3373
|
if (this.filters.has(id) || typeof document === "undefined") return;
|
|
@@ -2989,33 +3386,36 @@ var VideoPreviewEngine = (_class3 = class {
|
|
|
2989
3386
|
/** Stops following the editor and releases every media element. */
|
|
2990
3387
|
destroy() {
|
|
2991
3388
|
this.destroyed = true;
|
|
2992
|
-
|
|
3389
|
+
this.detachPixels();
|
|
3390
|
+
_optionalChain([this, 'access', _89 => _89.filterHost, 'optionalAccess', _90 => _90.remove, 'call', _91 => _91()]);
|
|
2993
3391
|
this.filterHost = null;
|
|
2994
3392
|
this.filters.clear();
|
|
2995
3393
|
this.unsubscribe();
|
|
2996
3394
|
for (const entry of [...this.media.values()]) this.release(entry);
|
|
2997
3395
|
this.images.clear();
|
|
2998
3396
|
}
|
|
2999
|
-
},
|
|
3397
|
+
}, _class4);
|
|
3000
3398
|
|
|
3001
3399
|
// src/video-editor/controller.ts
|
|
3002
|
-
var VideoEditorController = (
|
|
3400
|
+
var VideoEditorController = (_class5 = class {
|
|
3003
3401
|
|
|
3004
3402
|
|
|
3005
3403
|
/** Sound levels per asset, loaded on demand for waveforms and agents. */
|
|
3006
3404
|
|
|
3405
|
+
/** The pixels the preview last drew, for scopes and `get_video_scopes` (a `VideoPreviewEngine` fills it). */
|
|
3406
|
+
__init19() {this.previewPixels = new VideoPreviewPixels()}
|
|
3007
3407
|
|
|
3008
3408
|
|
|
3009
|
-
|
|
3409
|
+
__init20() {this.selection = []}
|
|
3010
3410
|
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3411
|
+
__init21() {this.error = null}
|
|
3412
|
+
__init22() {this.revision = 0}
|
|
3413
|
+
__init23() {this.applying = false}
|
|
3014
3414
|
|
|
3015
|
-
|
|
3016
|
-
|
|
3415
|
+
__init24() {this.listeners = /* @__PURE__ */ new Set()}
|
|
3416
|
+
__init25() {this.unsubscribers = []}
|
|
3017
3417
|
|
|
3018
|
-
constructor(options = {}) {;
|
|
3418
|
+
constructor(options = {}) {;_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);_class5.prototype.__init25.call(this);_class5.prototype.__init26.call(this);_class5.prototype.__init27.call(this);
|
|
3019
3419
|
this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
|
|
3020
3420
|
this.audioPeaks = options.audioPeaks instanceof VideoAudioPeaksCache ? options.audioPeaks : new VideoAudioPeaksCache(options.audioPeaks);
|
|
3021
3421
|
this.project = options.project ? parseVideoProject(options.project) : createVideoProject();
|
|
@@ -3040,8 +3440,8 @@ var VideoEditorController = (_class4 = class {
|
|
|
3040
3440
|
this.player.pause();
|
|
3041
3441
|
this.unsubscribers.splice(0).forEach((u) => u());
|
|
3042
3442
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
3443
|
+
__init26() {this.getState = () => this.state}
|
|
3444
|
+
__init27() {this.subscribe = (listener) => {
|
|
3045
3445
|
this.connect();
|
|
3046
3446
|
this.listeners.add(listener);
|
|
3047
3447
|
return () => {
|
|
@@ -3097,7 +3497,7 @@ var VideoEditorController = (_class4 = class {
|
|
|
3097
3497
|
this.applying = false;
|
|
3098
3498
|
}
|
|
3099
3499
|
this.notify();
|
|
3100
|
-
_optionalChain([this, 'access',
|
|
3500
|
+
_optionalChain([this, 'access', _92 => _92.onChange, 'optionalCall', _93 => _93(project)]);
|
|
3101
3501
|
}
|
|
3102
3502
|
commit(label, next) {
|
|
3103
3503
|
const before = this.project;
|
|
@@ -3161,7 +3561,7 @@ var VideoEditorController = (_class4 = class {
|
|
|
3161
3561
|
}
|
|
3162
3562
|
/** Selects clips (unknown ids are dropped). `add` toggles them into the current selection. */
|
|
3163
3563
|
select(clips, options = {}) {
|
|
3164
|
-
const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess',
|
|
3564
|
+
const known = _nullishCoalesce(_optionalChain([clips, 'optionalAccess', _94 => _94.filter, 'call', _95 => _95((id) => findVideoClip(this.project, id))]), () => ( []));
|
|
3165
3565
|
let next;
|
|
3166
3566
|
if (options.add) {
|
|
3167
3567
|
next = [...this.selection];
|
|
@@ -3242,7 +3642,7 @@ var VideoEditorController = (_class4 = class {
|
|
|
3242
3642
|
toEdl(options = {}) {
|
|
3243
3643
|
return toVideoEdl(this.project, options);
|
|
3244
3644
|
}
|
|
3245
|
-
},
|
|
3645
|
+
}, _class5);
|
|
3246
3646
|
|
|
3247
3647
|
// src/video-editor/tools.ts
|
|
3248
3648
|
var round = (ms) => Math.round(ms * 10) / 10;
|
|
@@ -3455,14 +3855,14 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3455
3855
|
return null;
|
|
3456
3856
|
};
|
|
3457
3857
|
const placed = (o) => ({
|
|
3458
|
-
..._optionalChain([o, 'access',
|
|
3459
|
-
..._optionalChain([o, 'access',
|
|
3460
|
-
..._optionalChain([o, 'access',
|
|
3858
|
+
..._optionalChain([o, 'access', _96 => _96.created, 'optionalAccess', _97 => _97.length]) ? { created: o.created } : {},
|
|
3859
|
+
..._optionalChain([o, 'access', _98 => _98.trimmed, 'optionalAccess', _99 => _99.length]) ? { trimmed: o.trimmed } : {},
|
|
3860
|
+
..._optionalChain([o, 'access', _100 => _100.removed, 'optionalAccess', _101 => _101.length]) ? { removed: o.removed } : {},
|
|
3461
3861
|
...o.rippled ? { rippled: o.rippled } : {}
|
|
3462
3862
|
});
|
|
3463
3863
|
const keysOut = (p, clip, property) => {
|
|
3464
3864
|
const c = clipOut(p, clip);
|
|
3465
|
-
const value = VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess',
|
|
3865
|
+
const value = VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([c, 'optionalAccess', _102 => _102.transform, 'optionalAccess', _103 => _103[property]]) : _optionalChain([c, 'optionalAccess', _104 => _104[property]]);
|
|
3466
3866
|
return typeof value === "object" ? { keys: value.keys } : { keys: [], value: _nullishCoalesce(value, () => ( null)) };
|
|
3467
3867
|
};
|
|
3468
3868
|
const getProject = {
|
|
@@ -3760,7 +4160,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3760
4160
|
const at = findVideoClip(p, id);
|
|
3761
4161
|
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
3762
4162
|
const { clip } = at;
|
|
3763
|
-
if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access',
|
|
4163
|
+
if (at.track.kind !== "video" || clip.type === "media" && _optionalChain([p, 'access', _105 => _105.assets, 'access', _106 => _106.find, 'call', _107 => _107((a) => a.id === clip.asset), 'optionalAccess', _108 => _108.kind]) === "audio")
|
|
3764
4164
|
throw new Error(`Clip "${id}" is sound only; grades change pictures. Pass clips on video tracks.`);
|
|
3765
4165
|
}
|
|
3766
4166
|
};
|
|
@@ -3781,7 +4181,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3781
4181
|
gradeTargets(clips);
|
|
3782
4182
|
const grade = videoGradePreset(preset, _nullishCoalesce(amount, () => ( 1)));
|
|
3783
4183
|
const edits = clips.map((clip) => grade ? { op: "update_clip", clip, set: { grade } } : { op: "update_clip", clip, remove: ["grade"] });
|
|
3784
|
-
return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call',
|
|
4184
|
+
return apply(edits, `Apply ${preset} look`, (r) => ({ clips: clips.map((id) => ({ id, grade: _nullishCoalesce(_optionalChain([findVideoClip, 'call', _109 => _109(r.project, id), 'optionalAccess', _110 => _110.clip, 'access', _111 => _111.grade]), () => ( null)) })) }));
|
|
3785
4185
|
}
|
|
3786
4186
|
};
|
|
3787
4187
|
const copyGrade = {
|
|
@@ -3848,7 +4248,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3848
4248
|
handler: ({ track, from, to, limit }) => {
|
|
3849
4249
|
const all = timelineWords(track).filter((w) => w.end > (_nullishCoalesce(from, () => ( 0))) && w.start < (_nullishCoalesce(to, () => ( Infinity))));
|
|
3850
4250
|
const words = all.slice(0, _nullishCoalesce(limit, () => ( 1e3)));
|
|
3851
|
-
return { track: _nullishCoalesce(_optionalChain([words, 'access',
|
|
4251
|
+
return { track: _nullishCoalesce(_optionalChain([words, 'access', _112 => _112[0], 'optionalAccess', _113 => _113.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 };
|
|
3852
4252
|
}
|
|
3853
4253
|
};
|
|
3854
4254
|
const findText = {
|
|
@@ -3910,7 +4310,7 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
3910
4310
|
if (!at) throw new Error(`There is no clip "${id}"; get_video_project lists the clips.`);
|
|
3911
4311
|
const { clip } = at;
|
|
3912
4312
|
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.`);
|
|
3913
|
-
if (_optionalChain([project, 'call',
|
|
4313
|
+
if (_optionalChain([project, 'call', _114 => _114(), 'access', _115 => _115.assets, 'access', _116 => _116.find, 'call', _117 => _117((a) => a.id === clip.asset), 'optionalAccess', _118 => _118.kind]) === "image") throw new Error(`Clip "${id}" is a still, so it has no motion to change the speed of.`);
|
|
3914
4314
|
return clip;
|
|
3915
4315
|
};
|
|
3916
4316
|
const rampOut = (r, id) => {
|
|
@@ -4199,7 +4599,46 @@ function createVideoEditorToolProvider(controller, options = {}) {
|
|
|
4199
4599
|
return { asset: r.asset.id, from: r.from, to: r.to, silences, total: round(silences.reduce((n2, x) => n2 + x.end - x.start, 0)) };
|
|
4200
4600
|
}
|
|
4201
4601
|
};
|
|
4202
|
-
const
|
|
4602
|
+
const pixelsAt = async (time) => {
|
|
4603
|
+
if (options.framePixels) {
|
|
4604
|
+
const px = await options.framePixels(time);
|
|
4605
|
+
if (!px) throw new Error(`The host could not render the frame at ${Math.round(time)} ms; use render_video_frame to look at it instead.`);
|
|
4606
|
+
return px;
|
|
4607
|
+
}
|
|
4608
|
+
const feed = controller.previewPixels;
|
|
4609
|
+
if (!feed.attached) throw new Error("No preview is showing this project, so there are no pixels to measure. The host must show a <VideoPreview> for this editor (or pass framePixels to the tool provider); until then use get_video_frame and render_video_frame.");
|
|
4610
|
+
const state = controller.getState();
|
|
4611
|
+
if (Math.abs(time - state.time) < 0.5) return (await feed.next(state.time, options.framePixelsTimeout)).pixels;
|
|
4612
|
+
const { time: before, playing } = state;
|
|
4613
|
+
if (playing) controller.pause();
|
|
4614
|
+
controller.seek(time);
|
|
4615
|
+
try {
|
|
4616
|
+
return (await feed.next(controller.getState().time, options.framePixelsTimeout)).pixels;
|
|
4617
|
+
} finally {
|
|
4618
|
+
controller.seek(before);
|
|
4619
|
+
if (playing) controller.play();
|
|
4620
|
+
}
|
|
4621
|
+
};
|
|
4622
|
+
const getScopes = {
|
|
4623
|
+
name: "get_video_scopes",
|
|
4624
|
+
title: "Measure a frame with scopes",
|
|
4625
|
+
description: "Measures the rendered picture at a moment as colour scopes would, without changing the project: luma (0 = black, 1 = white) mean, median, min, max and 1st / 99th percentiles; the share of pixels crushed to black or blown to white (and per channel); saturation; the average colour (a cast shows as a tint); a coarse luma and RGB histogram; the luma spread of vertical strips left to right (a waveform); the main hue and strength on a vectorscope; and notes on what to fix. Use it before and after grading (update_video_clip grade, apply_video_grade_preset) to check exposure and colour. Needs the pixels the host draws: fails with a reason when no preview is showing.",
|
|
4626
|
+
inputSchema: object({
|
|
4627
|
+
time: MS("Time from the start to measure (default: the playhead). Another time is shown briefly and the playhead put back"),
|
|
4628
|
+
bins: { type: "integer", minimum: 2, maximum: 64, description: "Histogram steps, darkest first (default 16)." },
|
|
4629
|
+
columns: { type: "integer", minimum: 1, maximum: 32, description: "Waveform strips across the picture, left to right (default 8)." }
|
|
4630
|
+
}),
|
|
4631
|
+
annotations: { title: "Measure a frame with scopes", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
4632
|
+
handler: async ({ time, bins, columns }) => {
|
|
4633
|
+
const p = project();
|
|
4634
|
+
const end = videoProjectDuration(p);
|
|
4635
|
+
const t = _nullishCoalesce(time, () => ( controller.getState().time));
|
|
4636
|
+
if (time !== void 0 && time > end) throw new Error(`Time ${time} ms is past the end of the project (${end} ms).`);
|
|
4637
|
+
const pixels = await pixelsAt(t);
|
|
4638
|
+
return { time: t, timecode: formatVideoTimecode(t, p.fps), width: pixels.width, height: pixels.height, ...summarizeVideoScopes(pixels, { bins, columns }) };
|
|
4639
|
+
}
|
|
4640
|
+
};
|
|
4641
|
+
const reads = [getProject, getFrame, getTranscript, findText, findFillers, findPauses, findGaps, checkProject, renderFrame, getScopes, exportFfmpeg, exportLoudnessMeasure, exportEdl, audioLevels, findSilences, seek];
|
|
4203
4642
|
const writes = [setSettings, addAsset, updateAsset, removeAsset, addTrack, updateTrack, removeTrack, moveTrack, addClip, moveClip, trimClip, splitClip, deleteClips, updateClip, setTransition, setSpeedRamp, addFreeze, removeRamp, setTranscript, removeRanges, applyGradePreset, copyGrade, setKeyframes, removeKeyframes, slipClip, rollEdit, slideClip, liftRange, extractRange, splitAt, closeGaps, addMarker, removeMarker, editVideo, undo, redo];
|
|
4204
4643
|
return {
|
|
4205
4644
|
id: _nullishCoalesce(options.id, () => ( "video")),
|
|
@@ -4286,7 +4725,7 @@ function videoClipLabel(clip, asset) {
|
|
|
4286
4725
|
if (clip.name) return clip.name;
|
|
4287
4726
|
if (clip.type === "text") return clip.text.split("\n")[0];
|
|
4288
4727
|
if (clip.type === "color") return clip.color;
|
|
4289
|
-
const base = _nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess',
|
|
4728
|
+
const base = _nullishCoalesce(_nullishCoalesce(_optionalChain([asset, 'optionalAccess', _119 => _119.name]), () => ( _optionalChain([asset, 'optionalAccess', _120 => _120.src, 'access', _121 => _121.replace, 'call', _122 => _122(/^.*[\\/]/, "")]))), () => ( clip.asset));
|
|
4290
4729
|
const speed = videoClipSpeed(clip);
|
|
4291
4730
|
return speed !== 1 ? `${base} (${Math.round(speed * 100)}%)` : base;
|
|
4292
4731
|
}
|
|
@@ -4330,7 +4769,7 @@ function videoAssetDropEdit(project, assetId, options = {}) {
|
|
|
4330
4769
|
const asset = videoAssetMap(project).get(assetId);
|
|
4331
4770
|
if (!asset) return null;
|
|
4332
4771
|
const kind = asset.kind === "audio" ? "audio" : "video";
|
|
4333
|
-
const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access',
|
|
4772
|
+
const track = _nullishCoalesce(options.track, () => ( _optionalChain([project, 'access', _123 => _123.tracks, 'access', _124 => _124.find, 'call', _125 => _125((t) => t.kind === kind && !t.locked), 'optionalAccess', _126 => _126.id])));
|
|
4334
4773
|
if (!track) return null;
|
|
4335
4774
|
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 } : {} };
|
|
4336
4775
|
}
|
|
@@ -4379,14 +4818,14 @@ var KEY_LABELS = {
|
|
|
4379
4818
|
scale: { label: "Scale", step: 0.05 },
|
|
4380
4819
|
rotation: { label: "Rotation", step: 1 }
|
|
4381
4820
|
};
|
|
4382
|
-
var animatableOf = (clip, property) => VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([clip, 'access',
|
|
4821
|
+
var animatableOf = (clip, property) => VIDEO_TRANSFORM_PROPERTIES.includes(property) ? _optionalChain([clip, 'access', _127 => _127.transform, 'optionalAccess', _128 => _128[property]]) : property === "volume" ? clip.type === "media" ? clip.volume : void 0 : clip.opacity;
|
|
4383
4822
|
var playheadInClip = (clip, time) => Math.round(Math.min(clip.duration, Math.max(0, time - clip.start)));
|
|
4384
4823
|
function videoKeyframeRows(project, clipId, time) {
|
|
4385
4824
|
const at = findVideoClip(project, clipId);
|
|
4386
4825
|
if (!at) return [];
|
|
4387
4826
|
const { clip } = at;
|
|
4388
4827
|
const asset = clip.type === "media" ? videoAssetMap(project).get(clip.asset) : void 0;
|
|
4389
|
-
const visual = !(_optionalChain([asset, 'optionalAccess',
|
|
4828
|
+
const visual = !(_optionalChain([asset, 'optionalAccess', _129 => _129.kind]) === "audio" || at.track.kind === "audio");
|
|
4390
4829
|
const audible = clip.type === "media" && videoAssetHasSound(asset);
|
|
4391
4830
|
const properties = [...visual ? ["opacity"] : [], ...audible ? ["volume"] : [], ...visual ? VIDEO_TRANSFORM_PROPERTIES : []];
|
|
4392
4831
|
const clipTime = playheadInClip(clip, time);
|
|
@@ -4610,5 +5049,23 @@ function videoKeyframeToggleEdit(project, clipId, property, time) {
|
|
|
4610
5049
|
|
|
4611
5050
|
|
|
4612
5051
|
|
|
4613
|
-
|
|
5052
|
+
|
|
5053
|
+
|
|
5054
|
+
|
|
5055
|
+
|
|
5056
|
+
|
|
5057
|
+
|
|
5058
|
+
|
|
5059
|
+
|
|
5060
|
+
|
|
5061
|
+
|
|
5062
|
+
|
|
5063
|
+
|
|
5064
|
+
|
|
5065
|
+
|
|
5066
|
+
|
|
5067
|
+
|
|
5068
|
+
|
|
5069
|
+
|
|
5070
|
+
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_PEAK_BLOCK = VIDEO_PEAK_BLOCK; exports.VIDEO_PLACEMENT_MODES = VIDEO_PLACEMENT_MODES; 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.VideoPreviewEngine = VideoPreviewEngine; exports.VideoPreviewPixels = VideoPreviewPixels; exports.allVideoClipIds = allVideoClipIds; exports.applyVideoEdits = applyVideoEdits; exports.applyVideoGrade = applyVideoGrade; exports.buildVideoAudioPeaks = buildVideoAudioPeaks; exports.checkVideoProject = checkVideoProject; exports.checkVideoTimeRemap = checkVideoTimeRemap; exports.createVideoEditorToolProvider = createVideoEditorToolProvider; 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.normalizeVideoWord = normalizeVideoWord; exports.parseVideoAnimatable = parseVideoAnimatable; exports.parseVideoAsset = parseVideoAsset; exports.parseVideoClip = parseVideoClip; exports.parseVideoClipAnimation = parseVideoClipAnimation; exports.parseVideoGrade = parseVideoGrade; exports.parseVideoKeyframes = parseVideoKeyframes; exports.parseVideoLoudnessMeasurement = parseVideoLoudnessMeasurement; exports.parseVideoMarker = parseVideoMarker; 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.probeVideoMedia = probeVideoMedia; exports.readVideoScopesPanel = readVideoScopesPanel; exports.removeVideoKey = removeVideoKey; 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.videoAudioLevels = videoAudioLevels; exports.videoAudioPeakRange = videoAudioPeakRange; exports.videoAudioPeakSpans = videoAudioPeakSpans; exports.videoAudioPeaksDuration = videoAudioPeaksDuration; 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.videoFrameDuration = videoFrameDuration; exports.videoFrameToSvg = videoFrameToSvg; exports.videoFrames = videoFrames; exports.videoFreezeFrameEdit = videoFreezeFrameEdit; exports.videoGradeCssFilter = videoGradeCssFilter; exports.videoGradeFfmpegFilters = videoGradeFfmpegFilters; exports.videoGradeGainsFilterId = videoGradeGainsFilterId; 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.videoOverlap = videoOverlap; exports.videoPeakDb = videoPeakDb; exports.videoProjectDuration = videoProjectDuration; exports.videoProjectSvg = videoProjectSvg; exports.videoRemoveRangesEdits = videoRemoveRangesEdits; exports.videoRulerTicks = videoRulerTicks; exports.videoScopeImage = videoScopeImage; exports.videoSlowMotionRamp = videoSlowMotionRamp; exports.videoSnapPoints = videoSnapPoints; 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;
|
|
4614
5071
|
//# sourceMappingURL=index.cjs.map
|