@twick/video-editor 0.15.27 → 0.15.29
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/components/timeline/timeline-view.d.ts +6 -1
- package/dist/components/track/audio-waveform.d.ts +6 -0
- package/dist/components/track/timeline-media-strip.d.ts +14 -0
- package/dist/components/track/track-base.d.ts +5 -1
- package/dist/components/track/track-element-context-menu.d.ts +16 -0
- package/dist/components/track/track-element.d.ts +6 -0
- package/dist/helpers/types.d.ts +1 -0
- package/dist/hooks/use-marquee-selection.d.ts +2 -1
- package/dist/hooks/use-timeline-drop.d.ts +2 -1
- package/dist/index.js +1369 -180
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1370 -181
- package/dist/index.mjs.map +1 -1
- package/dist/video-editor.css +67 -2
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -3,8 +3,9 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
5
5
|
import { useLivePlayerContext, PLAYER_STATE, LivePlayer } from "@twick/live-player";
|
|
6
|
-
import { ImageElement as ImageElement$1, AudioElement, VideoElement as VideoElement$1, useTimelineContext, TIMELINE_ACTION, ElementDeserializer, getCurrentElements, CaptionElement as CaptionElement$1, TIMELINE_ELEMENT_TYPE, getDecimalNumber, resolveIds, TrackElement, TRACK_TYPES,
|
|
6
|
+
import { ImageElement as ImageElement$1, AudioElement, VideoElement as VideoElement$1, useTimelineContext, TIMELINE_ACTION, ElementDeserializer, getCurrentElements, CaptionElement as CaptionElement$1, TIMELINE_ELEMENT_TYPE, canSplitElement, getDecimalNumber, resolveIds, TrackElement, TRACK_TYPES, Track, resolveId, getElementIdsInRange, ValidationError, VALIDATION_ERROR_CODE, formatTimeWithFrames } from "@twick/timeline";
|
|
7
7
|
import React, { useState, useRef, useCallback, useEffect, useMemo, forwardRef, createElement, createContext, useContext, useId, useLayoutEffect, useInsertionEffect, Fragment, Component } from "react";
|
|
8
|
+
import { createPortal } from "react-dom";
|
|
8
9
|
function t(t2, e3, s2) {
|
|
9
10
|
return (e3 = function(t3) {
|
|
10
11
|
var e4 = function(t4, e5) {
|
|
@@ -5943,6 +5944,9 @@ function ea() {
|
|
|
5943
5944
|
function sa() {
|
|
5944
5945
|
return !ta && (!(arguments.length > 0 && void 0 !== arguments[0]) || arguments[0]) && (ta = ea()), ta;
|
|
5945
5946
|
}
|
|
5947
|
+
function ia(t2) {
|
|
5948
|
+
ta = t2;
|
|
5949
|
+
}
|
|
5946
5950
|
const ra = ["filters", "resizeFilter", "src", "crossOrigin", "type"], na = ["cropX", "cropY"];
|
|
5947
5951
|
class oa extends Li {
|
|
5948
5952
|
static getDefaults() {
|
|
@@ -6470,13 +6474,7 @@ function Za(e3, s2) {
|
|
|
6470
6474
|
return tt.setClass(r2, e3), r2;
|
|
6471
6475
|
}
|
|
6472
6476
|
t(Qa, "type", "ColorMatrix"), t(Qa, "defaults", Ja), t(Qa, "uniformLocations", ["uColorMatrix", "uConstants"]), tt.setClass(Qa);
|
|
6473
|
-
Za("Brownie", [0.5997, 0.34553, -0.27082, 0, 0.186, -0.0377, 0.86095, 0.15059, 0, -0.1449, 0.24113, -0.07441, 0.44972, 0, -0.02965, 0, 0, 0, 1, 0]);
|
|
6474
|
-
Za("Vintage", [0.62793, 0.32021, -0.03965, 0, 0.03784, 0.02578, 0.64411, 0.03259, 0, 0.02926, 0.0466, -0.08512, 0.52416, 0, 0.02023, 0, 0, 0, 1, 0]);
|
|
6475
|
-
Za("Kodachrome", [1.12855, -0.39673, -0.03992, 0, 0.24991, -0.16404, 1.08352, -0.05498, 0, 0.09698, -0.16786, -0.56034, 1.60148, 0, 0.13972, 0, 0, 0, 1, 0]);
|
|
6476
|
-
Za("Technicolor", [1.91252, -0.85453, -0.09155, 0, 0.04624, -0.30878, 1.76589, -0.10601, 0, -0.27589, -0.2311, -0.75018, 1.84759, 0, 0.12137, 0, 0, 0, 1, 0]);
|
|
6477
|
-
Za("Polaroid", [1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 1.483, 0, 0, 0, 0, 0, 1, 0]);
|
|
6478
|
-
Za("Sepia", [0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]);
|
|
6479
|
-
Za("BlackWhite", [1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 0, 0, 0, 1, 0]);
|
|
6477
|
+
const $a = Za("Brownie", [0.5997, 0.34553, -0.27082, 0, 0.186, -0.0377, 0.86095, 0.15059, 0, -0.1449, 0.24113, -0.07441, 0.44972, 0, -0.02965, 0, 0, 0, 1, 0]), th = Za("Vintage", [0.62793, 0.32021, -0.03965, 0, 0.03784, 0.02578, 0.64411, 0.03259, 0, 0.02926, 0.0466, -0.08512, 0.52416, 0, 0.02023, 0, 0, 0, 1, 0]), eh = Za("Kodachrome", [1.12855, -0.39673, -0.03992, 0, 0.24991, -0.16404, 1.08352, -0.05498, 0, 0.09698, -0.16786, -0.56034, 1.60148, 0, 0.13972, 0, 0, 0, 1, 0]), sh = Za("Technicolor", [1.91252, -0.85453, -0.09155, 0, 0.04624, -0.30878, 1.76589, -0.10601, 0, -0.27589, -0.2311, -0.75018, 1.84759, 0, 0.12137, 0, 0, 0, 1, 0]), ih = Za("Polaroid", [1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 1.483, 0, 0, 0, 0, 0, 1, 0]), rh = Za("Sepia", [0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]), nh = Za("BlackWhite", [1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 0, 0, 0, 1, 0]);
|
|
6480
6478
|
class oh extends Va {
|
|
6481
6479
|
constructor() {
|
|
6482
6480
|
let t2 = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
|
|
@@ -6839,6 +6837,7 @@ class bh extends Va {
|
|
|
6839
6837
|
}
|
|
6840
6838
|
}
|
|
6841
6839
|
t(bh, "type", "Vibrance"), t(bh, "defaults", { vibrance: 0 }), t(bh, "uniformLocations", ["uVibrance"]), tt.setClass(bh);
|
|
6840
|
+
var Sh = Object.freeze({ __proto__: null, BaseFilter: Va, BlackWhite: nh, BlendColor: Ga, BlendImage: Ua, Blur: qa, Brightness: Ka, Brownie: $a, ColorMatrix: Qa, Composed: oh, Contrast: ah, Convolute: ch, Gamma: uh, Grayscale: gh, HueRotation: ph, Invert: mh, Kodachrome: eh, Noise: vh$1, Pixelate: yh, Polaroid: ih, RemoveColor: _h, Resize: xh, Saturation: Ch, Sepia: rh, Technicolor: sh, Vibrance: bh, Vintage: th });
|
|
6842
6841
|
var __defProp2 = Object.defineProperty;
|
|
6843
6842
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6844
6843
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
|
|
@@ -6899,6 +6898,8 @@ const ELEMENT_TYPES = {
|
|
|
6899
6898
|
RECT: "rect",
|
|
6900
6899
|
/** Circle element type */
|
|
6901
6900
|
CIRCLE: "circle",
|
|
6901
|
+
/** Emoji sticker element type */
|
|
6902
|
+
EMOJI: "emoji",
|
|
6902
6903
|
/** Arrow annotation element type */
|
|
6903
6904
|
ARROW: "arrow",
|
|
6904
6905
|
/** Line annotation / shape element type */
|
|
@@ -7147,7 +7148,25 @@ const rotateControl = new ai({
|
|
|
7147
7148
|
/** Whether to show connection line */
|
|
7148
7149
|
withConnection: true
|
|
7149
7150
|
});
|
|
7150
|
-
|
|
7151
|
+
const COLOR_FILTERS = {
|
|
7152
|
+
SATURATED: "saturated",
|
|
7153
|
+
BRIGHT: "bright",
|
|
7154
|
+
VIBRANT: "vibrant",
|
|
7155
|
+
RETRO: "retro",
|
|
7156
|
+
BLACK_WHITE: "blackWhite",
|
|
7157
|
+
SEPIA: "sepia",
|
|
7158
|
+
COOL: "cool",
|
|
7159
|
+
WARM: "warm",
|
|
7160
|
+
CINEMATIC: "cinematic",
|
|
7161
|
+
SOFT_GLOW: "softGlow",
|
|
7162
|
+
MOODY: "moody",
|
|
7163
|
+
DREAMY: "dreamy",
|
|
7164
|
+
INVERTED: "inverted",
|
|
7165
|
+
VINTAGE: "vintage",
|
|
7166
|
+
DRAMATIC: "dramatic",
|
|
7167
|
+
FADED: "faded"
|
|
7168
|
+
};
|
|
7169
|
+
let LRUCache$1 = class LRUCache {
|
|
7151
7170
|
constructor(maxSize = 100) {
|
|
7152
7171
|
if (maxSize <= 0) {
|
|
7153
7172
|
throw new Error("maxSize must be greater than 0");
|
|
@@ -7207,10 +7226,10 @@ class LRUCache {
|
|
|
7207
7226
|
get size() {
|
|
7208
7227
|
return this.cache.size;
|
|
7209
7228
|
}
|
|
7210
|
-
}
|
|
7211
|
-
class VideoFrameExtractor {
|
|
7229
|
+
};
|
|
7230
|
+
let VideoFrameExtractor$1 = class VideoFrameExtractor {
|
|
7212
7231
|
constructor(options = {}) {
|
|
7213
|
-
this.frameCache = new LRUCache(
|
|
7232
|
+
this.frameCache = new LRUCache$1(
|
|
7214
7233
|
options.maxCacheSize ?? 50
|
|
7215
7234
|
);
|
|
7216
7235
|
this.videoElements = /* @__PURE__ */ new Map();
|
|
@@ -7476,16 +7495,16 @@ class VideoFrameExtractor {
|
|
|
7476
7495
|
this.videoElements.clear();
|
|
7477
7496
|
this.frameCache.clear();
|
|
7478
7497
|
}
|
|
7479
|
-
}
|
|
7480
|
-
let defaultExtractor = null;
|
|
7481
|
-
function getDefaultVideoFrameExtractor(options) {
|
|
7482
|
-
if (!defaultExtractor) {
|
|
7483
|
-
defaultExtractor = new VideoFrameExtractor(options);
|
|
7498
|
+
};
|
|
7499
|
+
let defaultExtractor$1 = null;
|
|
7500
|
+
function getDefaultVideoFrameExtractor$1(options) {
|
|
7501
|
+
if (!defaultExtractor$1) {
|
|
7502
|
+
defaultExtractor$1 = new VideoFrameExtractor$1(options);
|
|
7484
7503
|
}
|
|
7485
|
-
return defaultExtractor;
|
|
7504
|
+
return defaultExtractor$1;
|
|
7486
7505
|
}
|
|
7487
|
-
async function getThumbnailCached(videoUrl, seekTime = 0.1, playbackRate) {
|
|
7488
|
-
const extractor = getDefaultVideoFrameExtractor(
|
|
7506
|
+
async function getThumbnailCached$1(videoUrl, seekTime = 0.1, playbackRate) {
|
|
7507
|
+
const extractor = getDefaultVideoFrameExtractor$1(
|
|
7489
7508
|
void 0
|
|
7490
7509
|
);
|
|
7491
7510
|
return extractor.getFrame(videoUrl, seekTime);
|
|
@@ -7530,6 +7549,275 @@ const getObjectFitSize = (objectFit, elementSize, containerSize) => {
|
|
|
7530
7549
|
};
|
|
7531
7550
|
}
|
|
7532
7551
|
};
|
|
7552
|
+
const {
|
|
7553
|
+
Blur,
|
|
7554
|
+
Brightness,
|
|
7555
|
+
ColorMatrix,
|
|
7556
|
+
Contrast,
|
|
7557
|
+
Grayscale,
|
|
7558
|
+
HueRotation,
|
|
7559
|
+
Invert,
|
|
7560
|
+
Saturation,
|
|
7561
|
+
Sepia
|
|
7562
|
+
} = Sh;
|
|
7563
|
+
let canvas2dFilterBackendInstalled = false;
|
|
7564
|
+
function ensureCanvas2dImageFilterBackend() {
|
|
7565
|
+
if (canvas2dFilterBackendInstalled) return;
|
|
7566
|
+
canvas2dFilterBackendInstalled = true;
|
|
7567
|
+
ia(new Zo());
|
|
7568
|
+
}
|
|
7569
|
+
function getSourceBitmapSize(img) {
|
|
7570
|
+
const el = img.getElement();
|
|
7571
|
+
if (!el) return null;
|
|
7572
|
+
const w2 = "naturalWidth" in el && el.naturalWidth > 0 ? el.naturalWidth : el.width;
|
|
7573
|
+
const h2 = "naturalHeight" in el && el.naturalHeight > 0 ? el.naturalHeight : el.height;
|
|
7574
|
+
if (!w2 || !h2) return null;
|
|
7575
|
+
return { w: w2, h: h2 };
|
|
7576
|
+
}
|
|
7577
|
+
function applyFiltersSafe(img) {
|
|
7578
|
+
ensureCanvas2dImageFilterBackend();
|
|
7579
|
+
img.applyFilters();
|
|
7580
|
+
}
|
|
7581
|
+
const bright = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.48));
|
|
7582
|
+
const contr = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.55));
|
|
7583
|
+
const sat = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.72));
|
|
7584
|
+
const IDENTITY = [
|
|
7585
|
+
1,
|
|
7586
|
+
0,
|
|
7587
|
+
0,
|
|
7588
|
+
0,
|
|
7589
|
+
0,
|
|
7590
|
+
0,
|
|
7591
|
+
1,
|
|
7592
|
+
0,
|
|
7593
|
+
0,
|
|
7594
|
+
0,
|
|
7595
|
+
0,
|
|
7596
|
+
0,
|
|
7597
|
+
1,
|
|
7598
|
+
0,
|
|
7599
|
+
0,
|
|
7600
|
+
0,
|
|
7601
|
+
0,
|
|
7602
|
+
0,
|
|
7603
|
+
1,
|
|
7604
|
+
0
|
|
7605
|
+
];
|
|
7606
|
+
const SEPIA_MATRIX = [
|
|
7607
|
+
0.393,
|
|
7608
|
+
0.769,
|
|
7609
|
+
0.189,
|
|
7610
|
+
0,
|
|
7611
|
+
0,
|
|
7612
|
+
0.349,
|
|
7613
|
+
0.686,
|
|
7614
|
+
0.168,
|
|
7615
|
+
0,
|
|
7616
|
+
0,
|
|
7617
|
+
0.272,
|
|
7618
|
+
0.534,
|
|
7619
|
+
0.131,
|
|
7620
|
+
0,
|
|
7621
|
+
0,
|
|
7622
|
+
0,
|
|
7623
|
+
0,
|
|
7624
|
+
0,
|
|
7625
|
+
1,
|
|
7626
|
+
0
|
|
7627
|
+
];
|
|
7628
|
+
function sepiaMix(strength) {
|
|
7629
|
+
const t2 = Math.min(1, Math.max(0, strength));
|
|
7630
|
+
const m2 = IDENTITY.map((v2, i2) => v2 + (SEPIA_MATRIX[i2] - v2) * t2);
|
|
7631
|
+
return new ColorMatrix({ matrix: m2, colorsOnly: true });
|
|
7632
|
+
}
|
|
7633
|
+
const twickBlurToFabric = (v2) => Math.min(0.22, Math.max(0, v2 * 0.045));
|
|
7634
|
+
function buildFilters(filterType) {
|
|
7635
|
+
switch (filterType) {
|
|
7636
|
+
case COLOR_FILTERS.SATURATED:
|
|
7637
|
+
return {
|
|
7638
|
+
filters: [
|
|
7639
|
+
new Saturation({ saturation: sat(1.4) }),
|
|
7640
|
+
new Contrast({ contrast: contr(1.1) })
|
|
7641
|
+
],
|
|
7642
|
+
opacityFactor: 1
|
|
7643
|
+
};
|
|
7644
|
+
case COLOR_FILTERS.BRIGHT:
|
|
7645
|
+
return {
|
|
7646
|
+
filters: [
|
|
7647
|
+
new Brightness({ brightness: bright(1.3) }),
|
|
7648
|
+
new Contrast({ contrast: contr(1.05) })
|
|
7649
|
+
],
|
|
7650
|
+
opacityFactor: 1
|
|
7651
|
+
};
|
|
7652
|
+
case COLOR_FILTERS.VIBRANT:
|
|
7653
|
+
return {
|
|
7654
|
+
filters: [
|
|
7655
|
+
new Saturation({ saturation: sat(1.6) }),
|
|
7656
|
+
new Brightness({ brightness: bright(1.15) }),
|
|
7657
|
+
new Contrast({ contrast: contr(1.1) })
|
|
7658
|
+
],
|
|
7659
|
+
opacityFactor: 1
|
|
7660
|
+
};
|
|
7661
|
+
case COLOR_FILTERS.RETRO:
|
|
7662
|
+
return {
|
|
7663
|
+
filters: [
|
|
7664
|
+
sepiaMix(0.8),
|
|
7665
|
+
new Contrast({ contrast: contr(1.3) }),
|
|
7666
|
+
new Brightness({ brightness: bright(0.85) }),
|
|
7667
|
+
new Saturation({ saturation: sat(0.8) })
|
|
7668
|
+
],
|
|
7669
|
+
opacityFactor: 1
|
|
7670
|
+
};
|
|
7671
|
+
case COLOR_FILTERS.BLACK_WHITE:
|
|
7672
|
+
return {
|
|
7673
|
+
filters: [
|
|
7674
|
+
new Grayscale(),
|
|
7675
|
+
new Contrast({ contrast: contr(1.25) }),
|
|
7676
|
+
new Brightness({ brightness: bright(1.05) })
|
|
7677
|
+
],
|
|
7678
|
+
opacityFactor: 1
|
|
7679
|
+
};
|
|
7680
|
+
case COLOR_FILTERS.SEPIA:
|
|
7681
|
+
return {
|
|
7682
|
+
filters: [
|
|
7683
|
+
new Sepia(),
|
|
7684
|
+
new Contrast({ contrast: contr(1.08) })
|
|
7685
|
+
],
|
|
7686
|
+
opacityFactor: 1
|
|
7687
|
+
};
|
|
7688
|
+
case COLOR_FILTERS.COOL:
|
|
7689
|
+
return {
|
|
7690
|
+
filters: [
|
|
7691
|
+
new HueRotation({ rotation: 15 / 180 }),
|
|
7692
|
+
new Brightness({ brightness: bright(1.1) }),
|
|
7693
|
+
new Saturation({ saturation: sat(1.3) }),
|
|
7694
|
+
new Contrast({ contrast: contr(1.05) })
|
|
7695
|
+
],
|
|
7696
|
+
opacityFactor: 1
|
|
7697
|
+
};
|
|
7698
|
+
case COLOR_FILTERS.WARM:
|
|
7699
|
+
return {
|
|
7700
|
+
filters: [
|
|
7701
|
+
new HueRotation({ rotation: -15 / 180 }),
|
|
7702
|
+
new Brightness({ brightness: bright(1.15) }),
|
|
7703
|
+
new Saturation({ saturation: sat(1.3) }),
|
|
7704
|
+
new Contrast({ contrast: contr(1.05) })
|
|
7705
|
+
],
|
|
7706
|
+
opacityFactor: 1
|
|
7707
|
+
};
|
|
7708
|
+
case COLOR_FILTERS.CINEMATIC:
|
|
7709
|
+
return {
|
|
7710
|
+
filters: [
|
|
7711
|
+
new Contrast({ contrast: contr(1.4) }),
|
|
7712
|
+
new Brightness({ brightness: bright(0.95) }),
|
|
7713
|
+
new Saturation({ saturation: sat(0.85) }),
|
|
7714
|
+
sepiaMix(0.2)
|
|
7715
|
+
],
|
|
7716
|
+
opacityFactor: 1
|
|
7717
|
+
};
|
|
7718
|
+
case COLOR_FILTERS.SOFT_GLOW:
|
|
7719
|
+
return {
|
|
7720
|
+
filters: [
|
|
7721
|
+
new Brightness({ brightness: bright(1.2) }),
|
|
7722
|
+
new Contrast({ contrast: contr(0.95) }),
|
|
7723
|
+
new Blur({ blur: twickBlurToFabric(1.2) }),
|
|
7724
|
+
new Saturation({ saturation: sat(1.1) })
|
|
7725
|
+
],
|
|
7726
|
+
opacityFactor: 1
|
|
7727
|
+
};
|
|
7728
|
+
case COLOR_FILTERS.MOODY:
|
|
7729
|
+
return {
|
|
7730
|
+
filters: [
|
|
7731
|
+
new Brightness({ brightness: bright(1.05) }),
|
|
7732
|
+
new Contrast({ contrast: contr(1.4) }),
|
|
7733
|
+
new Saturation({ saturation: sat(0.65) }),
|
|
7734
|
+
sepiaMix(0.2)
|
|
7735
|
+
],
|
|
7736
|
+
opacityFactor: 1
|
|
7737
|
+
};
|
|
7738
|
+
case COLOR_FILTERS.DREAMY:
|
|
7739
|
+
return {
|
|
7740
|
+
filters: [
|
|
7741
|
+
new Brightness({ brightness: bright(1.3) }),
|
|
7742
|
+
new Blur({ blur: twickBlurToFabric(2) }),
|
|
7743
|
+
new Saturation({ saturation: sat(1.4) }),
|
|
7744
|
+
new Contrast({ contrast: contr(0.95) })
|
|
7745
|
+
],
|
|
7746
|
+
opacityFactor: 1
|
|
7747
|
+
};
|
|
7748
|
+
case COLOR_FILTERS.INVERTED:
|
|
7749
|
+
return {
|
|
7750
|
+
filters: [
|
|
7751
|
+
new Invert({ invert: true, alpha: false }),
|
|
7752
|
+
new HueRotation({ rotation: 1 })
|
|
7753
|
+
],
|
|
7754
|
+
opacityFactor: 1
|
|
7755
|
+
};
|
|
7756
|
+
case COLOR_FILTERS.VINTAGE:
|
|
7757
|
+
return {
|
|
7758
|
+
filters: [
|
|
7759
|
+
sepiaMix(0.4),
|
|
7760
|
+
new Saturation({ saturation: sat(1.4) }),
|
|
7761
|
+
new Contrast({ contrast: contr(1.2) }),
|
|
7762
|
+
new Brightness({ brightness: bright(1.1) })
|
|
7763
|
+
],
|
|
7764
|
+
opacityFactor: 1
|
|
7765
|
+
};
|
|
7766
|
+
case COLOR_FILTERS.DRAMATIC:
|
|
7767
|
+
return {
|
|
7768
|
+
filters: [
|
|
7769
|
+
new Contrast({ contrast: contr(1.5) }),
|
|
7770
|
+
new Brightness({ brightness: bright(0.9) }),
|
|
7771
|
+
new Saturation({ saturation: sat(1.2) })
|
|
7772
|
+
],
|
|
7773
|
+
opacityFactor: 1
|
|
7774
|
+
};
|
|
7775
|
+
case COLOR_FILTERS.FADED:
|
|
7776
|
+
return {
|
|
7777
|
+
filters: [
|
|
7778
|
+
new Brightness({ brightness: bright(1.2) }),
|
|
7779
|
+
new Saturation({ saturation: sat(0.8) }),
|
|
7780
|
+
new Contrast({ contrast: contr(0.9) })
|
|
7781
|
+
],
|
|
7782
|
+
opacityFactor: 0.9
|
|
7783
|
+
};
|
|
7784
|
+
default:
|
|
7785
|
+
return { filters: [], opacityFactor: 1 };
|
|
7786
|
+
}
|
|
7787
|
+
}
|
|
7788
|
+
function applyFabricMediaColorFilters(img, mediaFilter, elementOpacity) {
|
|
7789
|
+
const key = (mediaFilter == null ? void 0 : mediaFilter.trim()) || "none";
|
|
7790
|
+
if (key === "none") {
|
|
7791
|
+
img.filters = [];
|
|
7792
|
+
img.set("opacity", elementOpacity);
|
|
7793
|
+
applyFiltersSafe(img);
|
|
7794
|
+
return;
|
|
7795
|
+
}
|
|
7796
|
+
const { filters: filterList, opacityFactor } = buildFilters(key);
|
|
7797
|
+
if (filterList.length === 0) {
|
|
7798
|
+
img.filters = [];
|
|
7799
|
+
img.set("opacity", elementOpacity);
|
|
7800
|
+
applyFiltersSafe(img);
|
|
7801
|
+
return;
|
|
7802
|
+
}
|
|
7803
|
+
if (!getSourceBitmapSize(img)) {
|
|
7804
|
+
img.filters = [];
|
|
7805
|
+
img.set("opacity", elementOpacity);
|
|
7806
|
+
return;
|
|
7807
|
+
}
|
|
7808
|
+
img.filters = filterList;
|
|
7809
|
+
img.set("opacity", elementOpacity * opacityFactor);
|
|
7810
|
+
try {
|
|
7811
|
+
applyFiltersSafe(img);
|
|
7812
|
+
} catch {
|
|
7813
|
+
img.filters = [];
|
|
7814
|
+
img.set("opacity", elementOpacity);
|
|
7815
|
+
try {
|
|
7816
|
+
applyFiltersSafe(img);
|
|
7817
|
+
} catch {
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7533
7821
|
const MARGIN = 10;
|
|
7534
7822
|
const addTextElement = ({
|
|
7535
7823
|
element,
|
|
@@ -7620,7 +7908,7 @@ const setImageProps = ({
|
|
|
7620
7908
|
canvasMetadata,
|
|
7621
7909
|
lockAspectRatio = true
|
|
7622
7910
|
}) => {
|
|
7623
|
-
var _a, _b, _c, _d, _e2;
|
|
7911
|
+
var _a, _b, _c, _d, _e2, _f;
|
|
7624
7912
|
const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
|
|
7625
7913
|
const height = (((_b = element.props) == null ? void 0 : _b.height) || 0) * canvasMetadata.scaleY || canvasMetadata.height;
|
|
7626
7914
|
const { x: x2, y: y2 } = convertToCanvasPosition(
|
|
@@ -7634,11 +7922,15 @@ const setImageProps = ({
|
|
|
7634
7922
|
img.set("height", height);
|
|
7635
7923
|
img.set("left", x2);
|
|
7636
7924
|
img.set("top", y2);
|
|
7637
|
-
img.set("opacity", ((_e2 = element.props) == null ? void 0 : _e2.opacity) ?? 1);
|
|
7638
7925
|
img.set("selectable", true);
|
|
7639
7926
|
img.set("hasControls", true);
|
|
7640
7927
|
img.set("touchAction", "all");
|
|
7641
7928
|
img.set("lockUniScaling", lockAspectRatio);
|
|
7929
|
+
applyFabricMediaColorFilters(
|
|
7930
|
+
img,
|
|
7931
|
+
(_e2 = element.props) == null ? void 0 : _e2.mediaFilter,
|
|
7932
|
+
((_f = element.props) == null ? void 0 : _f.opacity) ?? 1
|
|
7933
|
+
);
|
|
7642
7934
|
};
|
|
7643
7935
|
const addCaptionElement = ({
|
|
7644
7936
|
element,
|
|
@@ -7648,48 +7940,52 @@ const addCaptionElement = ({
|
|
|
7648
7940
|
canvasMetadata,
|
|
7649
7941
|
lockAspectRatio = false
|
|
7650
7942
|
}) => {
|
|
7651
|
-
var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2, _p, _q, _r2, _s2, _t2, _u, _v, _w, _x, _y
|
|
7652
|
-
const
|
|
7653
|
-
const
|
|
7943
|
+
var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2, _p, _q, _r2, _s2, _t2, _u, _v, _w, _x, _y;
|
|
7944
|
+
const useTrackDefaults = ((_a = element.props) == null ? void 0 : _a.useTrackDefaults) ?? true;
|
|
7945
|
+
const trackColors = captionProps == null ? void 0 : captionProps.colors;
|
|
7946
|
+
const elementColors = (_b = element.props) == null ? void 0 : _b.colors;
|
|
7947
|
+
const resolvedColors = useTrackDefaults ? trackColors : { ...trackColors ?? {}, ...elementColors ?? {} };
|
|
7948
|
+
const captionTextColor = (resolvedColors == null ? void 0 : resolvedColors.text) ?? ((_c = captionProps == null ? void 0 : captionProps.color) == null ? void 0 : _c.text);
|
|
7654
7949
|
const { x: x2, y: y2 } = convertToCanvasPosition(
|
|
7655
|
-
(
|
|
7656
|
-
(
|
|
7950
|
+
(useTrackDefaults ? captionProps == null ? void 0 : captionProps.x : (_d = element.props) == null ? void 0 : _d.x) ?? (captionProps == null ? void 0 : captionProps.x) ?? 0,
|
|
7951
|
+
(useTrackDefaults ? captionProps == null ? void 0 : captionProps.y : (_e2 = element.props) == null ? void 0 : _e2.y) ?? (captionProps == null ? void 0 : captionProps.y) ?? 0,
|
|
7657
7952
|
canvasMetadata
|
|
7658
7953
|
);
|
|
7659
|
-
let width = ((
|
|
7660
|
-
if ((
|
|
7954
|
+
let width = ((_f = element.props) == null ? void 0 : _f.width) ? element.props.width * canvasMetadata.scaleX : canvasMetadata.width - 2 * MARGIN;
|
|
7955
|
+
if ((_g = element.props) == null ? void 0 : _g.maxWidth) {
|
|
7661
7956
|
width = Math.min(width, element.props.maxWidth * canvasMetadata.scaleX);
|
|
7662
7957
|
}
|
|
7663
|
-
const
|
|
7664
|
-
const resolvedFill = (applyToAll ? captionTextColor : ((_h2 = element.props) == null ? void 0 : _h2.fill) ?? (elementColors == null ? void 0 : elementColors.text) ?? captionTextColor) ?? DEFAULT_CAPTION_PROPS.fill;
|
|
7665
|
-
const trackColors = captionProps == null ? void 0 : captionProps.colors;
|
|
7958
|
+
const resolvedFill = (useTrackDefaults ? void 0 : (_h2 = element.props) == null ? void 0 : _h2.fill) ?? captionTextColor ?? DEFAULT_CAPTION_PROPS.fill;
|
|
7666
7959
|
const trackStroke = trackColors == null ? void 0 : trackColors.outlineColor;
|
|
7667
7960
|
const elementStroke = (elementColors == null ? void 0 : elementColors.outlineColor) ?? ((_i2 = element.props) == null ? void 0 : _i2.stroke);
|
|
7668
|
-
const resolvedStroke = (
|
|
7669
|
-
const
|
|
7961
|
+
const resolvedStroke = (useTrackDefaults ? trackStroke : elementStroke ?? trackStroke) ?? void 0;
|
|
7962
|
+
const trackFont = (captionProps == null ? void 0 : captionProps.font) ?? {};
|
|
7963
|
+
const elementFont = ((_j = element.props) == null ? void 0 : _j.font) ?? {};
|
|
7964
|
+
const resolvedFont = useTrackDefaults ? trackFont : { ...trackFont, ...elementFont };
|
|
7965
|
+
const caption = new Uo(((_k = element.props) == null ? void 0 : _k.text) || element.t || "", {
|
|
7670
7966
|
left: x2,
|
|
7671
7967
|
top: y2,
|
|
7672
7968
|
originX: "center",
|
|
7673
7969
|
originY: "center",
|
|
7674
|
-
angle: ((
|
|
7970
|
+
angle: ((_l = element.props) == null ? void 0 : _l.rotation) || 0,
|
|
7675
7971
|
fontSize: Math.round(
|
|
7676
|
-
((
|
|
7972
|
+
((resolvedFont == null ? void 0 : resolvedFont.size) ?? DEFAULT_CAPTION_PROPS.size) * canvasMetadata.scaleX
|
|
7677
7973
|
),
|
|
7678
|
-
fontFamily: (
|
|
7974
|
+
fontFamily: (resolvedFont == null ? void 0 : resolvedFont.family) ?? DEFAULT_CAPTION_PROPS.family,
|
|
7679
7975
|
fill: resolvedFill,
|
|
7680
|
-
fontWeight: (
|
|
7976
|
+
fontWeight: (resolvedFont == null ? void 0 : resolvedFont.weight) ?? DEFAULT_CAPTION_PROPS.fontWeight,
|
|
7681
7977
|
...resolvedStroke ? { stroke: resolvedStroke } : {},
|
|
7682
|
-
opacity: (
|
|
7978
|
+
opacity: (useTrackDefaults ? void 0 : (_m = element.props) == null ? void 0 : _m.opacity) ?? (captionProps == null ? void 0 : captionProps.opacity) ?? 1,
|
|
7683
7979
|
width,
|
|
7684
7980
|
splitByGrapheme: false,
|
|
7685
|
-
textAlign: ((
|
|
7981
|
+
textAlign: ((_n2 = element.props) == null ? void 0 : _n2.textAlign) ?? "center",
|
|
7686
7982
|
shadow: new Ds({
|
|
7687
|
-
offsetX: (
|
|
7688
|
-
offsetY: (
|
|
7689
|
-
blur: (
|
|
7690
|
-
color: (
|
|
7983
|
+
offsetX: (useTrackDefaults ? void 0 : (_p = (_o2 = element.props) == null ? void 0 : _o2.shadowOffset) == null ? void 0 : _p[0]) ?? ((_q = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _q[0]) ?? ((_r2 = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _r2[0]),
|
|
7984
|
+
offsetY: (useTrackDefaults ? void 0 : (_t2 = (_s2 = element.props) == null ? void 0 : _s2.shadowOffset) == null ? void 0 : _t2[1]) ?? ((_u = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _u[1]) ?? ((_v = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _v[1]),
|
|
7985
|
+
blur: (useTrackDefaults ? void 0 : (_w = element.props) == null ? void 0 : _w.shadowBlur) ?? (captionProps == null ? void 0 : captionProps.shadowBlur) ?? DEFAULT_CAPTION_PROPS.shadowBlur,
|
|
7986
|
+
color: (useTrackDefaults ? void 0 : (_x = element.props) == null ? void 0 : _x.shadowColor) ?? (captionProps == null ? void 0 : captionProps.shadowColor) ?? DEFAULT_CAPTION_PROPS.shadowColor
|
|
7691
7987
|
}),
|
|
7692
|
-
strokeWidth: ((
|
|
7988
|
+
strokeWidth: ((useTrackDefaults ? void 0 : (_y = element.props) == null ? void 0 : _y.lineWidth) ?? (captionProps == null ? void 0 : captionProps.lineWidth) ?? DEFAULT_CAPTION_PROPS.lineWidth) * 0.025
|
|
7693
7989
|
});
|
|
7694
7990
|
caption.set("id", element.id);
|
|
7695
7991
|
caption.set("zIndex", index);
|
|
@@ -7716,7 +8012,7 @@ const addVideoElement = async ({
|
|
|
7716
8012
|
}) => {
|
|
7717
8013
|
var _a;
|
|
7718
8014
|
try {
|
|
7719
|
-
const thumbnailUrl = await getThumbnailCached(
|
|
8015
|
+
const thumbnailUrl = await getThumbnailCached$1(
|
|
7720
8016
|
((_a = element == null ? void 0 : element.props) == null ? void 0 : _a.src) || "",
|
|
7721
8017
|
snapTime
|
|
7722
8018
|
);
|
|
@@ -7744,8 +8040,13 @@ const addImageElement = async ({
|
|
|
7744
8040
|
currentFrameEffect,
|
|
7745
8041
|
lockAspectRatio = true
|
|
7746
8042
|
}) => {
|
|
8043
|
+
var _a, _b;
|
|
7747
8044
|
try {
|
|
7748
|
-
const
|
|
8045
|
+
const rawSrc = imageUrl || element.props.src || "";
|
|
8046
|
+
const mediaFilter = (_b = (_a = element.props) == null ? void 0 : _a.mediaFilter) == null ? void 0 : _b.trim();
|
|
8047
|
+
const useFilter = !!mediaFilter && mediaFilter !== "none";
|
|
8048
|
+
const fromUrlOpts = useFilter && /^https?:\/\//i.test(rawSrc) ? { crossOrigin: "anonymous" } : {};
|
|
8049
|
+
const img = await oa.fromURL(rawSrc, fromUrlOpts);
|
|
7749
8050
|
img.set({
|
|
7750
8051
|
originX: "center",
|
|
7751
8052
|
originY: "center",
|
|
@@ -7782,7 +8083,7 @@ const addMediaGroup = ({
|
|
|
7782
8083
|
currentFrameEffect,
|
|
7783
8084
|
lockAspectRatio = true
|
|
7784
8085
|
}) => {
|
|
7785
|
-
var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2;
|
|
8086
|
+
var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2;
|
|
7786
8087
|
let frameSize;
|
|
7787
8088
|
let angle;
|
|
7788
8089
|
let framePosition;
|
|
@@ -7837,9 +8138,13 @@ const addMediaGroup = ({
|
|
|
7837
8138
|
originX: "center",
|
|
7838
8139
|
originY: "center",
|
|
7839
8140
|
scaleX: newSize.width / img.width,
|
|
7840
|
-
scaleY: newSize.height / img.height
|
|
7841
|
-
opacity: ((_n2 = element.props) == null ? void 0 : _n2.opacity) ?? 1
|
|
8141
|
+
scaleY: newSize.height / img.height
|
|
7842
8142
|
});
|
|
8143
|
+
applyFabricMediaColorFilters(
|
|
8144
|
+
img,
|
|
8145
|
+
(_n2 = element.props) == null ? void 0 : _n2.mediaFilter,
|
|
8146
|
+
((_o2 = element.props) == null ? void 0 : _o2.opacity) ?? 1
|
|
8147
|
+
);
|
|
7843
8148
|
const { x: x2, y: y2 } = convertToCanvasPosition(
|
|
7844
8149
|
(framePosition == null ? void 0 : framePosition.x) || 0,
|
|
7845
8150
|
(framePosition == null ? void 0 : framePosition.y) || 0,
|
|
@@ -8325,7 +8630,8 @@ const CaptionElement = {
|
|
|
8325
8630
|
context.canvasMetadata,
|
|
8326
8631
|
context.videoSize
|
|
8327
8632
|
);
|
|
8328
|
-
|
|
8633
|
+
const useTrackDefaults = ((_a = element.props) == null ? void 0 : _a.useTrackDefaults) ?? true;
|
|
8634
|
+
if (useTrackDefaults) {
|
|
8329
8635
|
return {
|
|
8330
8636
|
element,
|
|
8331
8637
|
operation: CANVAS_OPERATIONS.CAPTION_PROPS_UPDATED,
|
|
@@ -8542,6 +8848,72 @@ const EffectElement = {
|
|
|
8542
8848
|
return;
|
|
8543
8849
|
}
|
|
8544
8850
|
};
|
|
8851
|
+
const EmojiElement = {
|
|
8852
|
+
name: ELEMENT_TYPES.EMOJI,
|
|
8853
|
+
async add(params) {
|
|
8854
|
+
var _a;
|
|
8855
|
+
const { element, index, canvas, canvasMetadata, lockAspectRatio } = params;
|
|
8856
|
+
await addImageElement({
|
|
8857
|
+
element,
|
|
8858
|
+
index,
|
|
8859
|
+
canvas,
|
|
8860
|
+
canvasMetadata,
|
|
8861
|
+
lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio) ?? true
|
|
8862
|
+
});
|
|
8863
|
+
},
|
|
8864
|
+
updateFromFabricObject(object, element, context) {
|
|
8865
|
+
const canvasCenter = getObjectCanvasCenter(object);
|
|
8866
|
+
const { x: x2, y: y2 } = convertToVideoPosition(
|
|
8867
|
+
canvasCenter.x,
|
|
8868
|
+
canvasCenter.y,
|
|
8869
|
+
context.canvasMetadata,
|
|
8870
|
+
context.videoSize
|
|
8871
|
+
);
|
|
8872
|
+
if (object.type === "group") {
|
|
8873
|
+
const scaledW2 = (object.width ?? 0) * (object.scaleX ?? 1);
|
|
8874
|
+
const scaledH2 = (object.height ?? 0) * (object.scaleY ?? 1);
|
|
8875
|
+
const { width: fw, height: fh2 } = convertToVideoDimensions(
|
|
8876
|
+
scaledW2,
|
|
8877
|
+
scaledH2,
|
|
8878
|
+
context.canvasMetadata
|
|
8879
|
+
);
|
|
8880
|
+
const updatedFrameSize = [fw, fh2];
|
|
8881
|
+
const frame2 = element.frame;
|
|
8882
|
+
return {
|
|
8883
|
+
element: {
|
|
8884
|
+
...element,
|
|
8885
|
+
frame: {
|
|
8886
|
+
...frame2,
|
|
8887
|
+
rotation: getObjectCanvasAngle(object),
|
|
8888
|
+
size: updatedFrameSize,
|
|
8889
|
+
x: x2,
|
|
8890
|
+
y: y2
|
|
8891
|
+
}
|
|
8892
|
+
}
|
|
8893
|
+
};
|
|
8894
|
+
}
|
|
8895
|
+
const scaledW = (object.width ?? 0) * (object.scaleX ?? 1);
|
|
8896
|
+
const scaledH = (object.height ?? 0) * (object.scaleY ?? 1);
|
|
8897
|
+
const { width, height } = convertToVideoDimensions(
|
|
8898
|
+
scaledW,
|
|
8899
|
+
scaledH,
|
|
8900
|
+
context.canvasMetadata
|
|
8901
|
+
);
|
|
8902
|
+
return {
|
|
8903
|
+
element: {
|
|
8904
|
+
...element,
|
|
8905
|
+
props: {
|
|
8906
|
+
...element.props,
|
|
8907
|
+
rotation: getObjectCanvasAngle(object),
|
|
8908
|
+
width,
|
|
8909
|
+
height,
|
|
8910
|
+
x: x2,
|
|
8911
|
+
y: y2
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
};
|
|
8915
|
+
}
|
|
8916
|
+
};
|
|
8545
8917
|
class ElementController {
|
|
8546
8918
|
constructor() {
|
|
8547
8919
|
__publicField2(this, "elements", /* @__PURE__ */ new Map());
|
|
@@ -8563,6 +8935,7 @@ function registerElements() {
|
|
|
8563
8935
|
elementController.register(RectElement);
|
|
8564
8936
|
elementController.register(CircleElement);
|
|
8565
8937
|
elementController.register(TextElement);
|
|
8938
|
+
elementController.register(EmojiElement);
|
|
8566
8939
|
elementController.register(CaptionElement);
|
|
8567
8940
|
elementController.register(WatermarkElement);
|
|
8568
8941
|
elementController.register(ArrowElement);
|
|
@@ -9147,6 +9520,8 @@ const DEFAULT_ELEMENT_COLORS = {
|
|
|
9147
9520
|
animation: "#4B9B78",
|
|
9148
9521
|
/** Icon element color - bright orchid */
|
|
9149
9522
|
icon: "#A76CD4",
|
|
9523
|
+
/** Emoji element color - warm amber */
|
|
9524
|
+
emoji: "#F59E0B",
|
|
9150
9525
|
/** Circle element color - deep byzantium */
|
|
9151
9526
|
circle: "#703D8B",
|
|
9152
9527
|
/** Effect element color - cyan accent for global effects */
|
|
@@ -9202,6 +9577,7 @@ function useTimelineDrop({
|
|
|
9202
9577
|
zoomLevel,
|
|
9203
9578
|
labelWidth,
|
|
9204
9579
|
trackHeight,
|
|
9580
|
+
separatorHeight = 0,
|
|
9205
9581
|
/** Width of the track content area (timeline minus labels). Used for accurate time mapping. */
|
|
9206
9582
|
trackContentWidth,
|
|
9207
9583
|
onDrop,
|
|
@@ -9219,7 +9595,9 @@ function useTimelineDrop({
|
|
|
9219
9595
|
const viewportLeft = ((_b = (_a = scrollEl == null ? void 0 : scrollEl.getBoundingClientRect) == null ? void 0 : _a.call(scrollEl)) == null ? void 0 : _b.left) ?? rect.left;
|
|
9220
9596
|
const contentX = clientX - viewportLeft + scrollLeft - labelWidth;
|
|
9221
9597
|
const relY = clientY - rect.top;
|
|
9222
|
-
const
|
|
9598
|
+
const rowHeight = trackHeight + separatorHeight;
|
|
9599
|
+
const yFromFirstTrack = Math.max(0, relY - separatorHeight);
|
|
9600
|
+
const rawTrackIndex = Math.floor(yFromFirstTrack / Math.max(1, rowHeight));
|
|
9223
9601
|
const trackIndex = tracks.length === 0 ? 0 : Math.max(0, Math.min(tracks.length - 1, rawTrackIndex));
|
|
9224
9602
|
const pixelsPerSecond = trackContentWidth != null && trackContentWidth > 0 ? trackContentWidth / duration : 100 * zoomLevel;
|
|
9225
9603
|
const timeSec = Math.max(
|
|
@@ -9234,6 +9612,7 @@ function useTimelineDrop({
|
|
|
9234
9612
|
tracks.length,
|
|
9235
9613
|
labelWidth,
|
|
9236
9614
|
trackHeight,
|
|
9615
|
+
separatorHeight,
|
|
9237
9616
|
zoomLevel,
|
|
9238
9617
|
duration,
|
|
9239
9618
|
trackContentWidth
|
|
@@ -19035,66 +19414,766 @@ const setElementColors = (colors) => {
|
|
|
19035
19414
|
...colors
|
|
19036
19415
|
};
|
|
19037
19416
|
};
|
|
19038
|
-
const
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19417
|
+
const VIEWPORT_MARGIN = 8;
|
|
19418
|
+
function clampMenuPosition(clientX, clientY, width, height) {
|
|
19419
|
+
const vw2 = window.innerWidth;
|
|
19420
|
+
const vh3 = window.innerHeight;
|
|
19421
|
+
const maxLeft = Math.max(VIEWPORT_MARGIN, vw2 - width - VIEWPORT_MARGIN);
|
|
19422
|
+
const maxTop = Math.max(VIEWPORT_MARGIN, vh3 - height - VIEWPORT_MARGIN);
|
|
19423
|
+
return {
|
|
19424
|
+
left: Math.min(Math.max(VIEWPORT_MARGIN, clientX), maxLeft),
|
|
19425
|
+
top: Math.min(Math.max(VIEWPORT_MARGIN, clientY), maxTop)
|
|
19426
|
+
};
|
|
19427
|
+
}
|
|
19428
|
+
const TrackElementContextMenu = ({
|
|
19429
|
+
x: x2,
|
|
19430
|
+
y: y2,
|
|
19431
|
+
canSplit,
|
|
19432
|
+
onSplit,
|
|
19433
|
+
onDelete,
|
|
19434
|
+
onClose
|
|
19051
19435
|
}) => {
|
|
19052
|
-
|
|
19053
|
-
const
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
const
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19436
|
+
const menuRef = useRef(null);
|
|
19437
|
+
const [position, setPosition] = useState(() => ({
|
|
19438
|
+
left: x2,
|
|
19439
|
+
top: y2
|
|
19440
|
+
}));
|
|
19441
|
+
const reposition = useCallback(() => {
|
|
19442
|
+
const el = menuRef.current;
|
|
19443
|
+
if (!el) return;
|
|
19444
|
+
const { width, height } = el.getBoundingClientRect();
|
|
19445
|
+
setPosition(clampMenuPosition(x2, y2, width, height));
|
|
19446
|
+
}, [x2, y2]);
|
|
19447
|
+
useLayoutEffect(() => {
|
|
19448
|
+
reposition();
|
|
19449
|
+
}, [reposition]);
|
|
19061
19450
|
useEffect(() => {
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19451
|
+
window.addEventListener("resize", reposition);
|
|
19452
|
+
return () => window.removeEventListener("resize", reposition);
|
|
19453
|
+
}, [reposition]);
|
|
19454
|
+
useEffect(() => {
|
|
19455
|
+
const handleClickOutside = (e3) => {
|
|
19456
|
+
if (menuRef.current && !menuRef.current.contains(e3.target)) {
|
|
19457
|
+
onClose();
|
|
19458
|
+
}
|
|
19459
|
+
};
|
|
19460
|
+
const handleEscape = (e3) => {
|
|
19461
|
+
if (e3.key === "Escape") onClose();
|
|
19462
|
+
};
|
|
19463
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
19464
|
+
document.addEventListener("keydown", handleEscape);
|
|
19465
|
+
return () => {
|
|
19466
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
19467
|
+
document.removeEventListener("keydown", handleEscape);
|
|
19468
|
+
};
|
|
19469
|
+
}, [onClose]);
|
|
19470
|
+
const wrap = (fn2) => {
|
|
19471
|
+
fn2();
|
|
19472
|
+
onClose();
|
|
19473
|
+
};
|
|
19474
|
+
const menu = /* @__PURE__ */ jsxs(
|
|
19475
|
+
"div",
|
|
19476
|
+
{
|
|
19477
|
+
ref: menuRef,
|
|
19478
|
+
className: "twick-canvas-context-menu",
|
|
19479
|
+
style: { left: position.left, top: position.top },
|
|
19480
|
+
role: "menu",
|
|
19481
|
+
children: [
|
|
19482
|
+
/* @__PURE__ */ jsx(
|
|
19483
|
+
"button",
|
|
19484
|
+
{
|
|
19485
|
+
type: "button",
|
|
19486
|
+
className: "twick-canvas-context-menu-item",
|
|
19487
|
+
onClick: () => wrap(onSplit),
|
|
19488
|
+
disabled: !canSplit,
|
|
19489
|
+
role: "menuitem",
|
|
19490
|
+
style: !canSplit ? { opacity: 0.45, cursor: "not-allowed" } : void 0,
|
|
19491
|
+
children: "Split at playhead"
|
|
19492
|
+
}
|
|
19493
|
+
),
|
|
19494
|
+
/* @__PURE__ */ jsx("div", { className: "twick-canvas-context-menu-separator", role: "separator" }),
|
|
19495
|
+
/* @__PURE__ */ jsx(
|
|
19496
|
+
"button",
|
|
19497
|
+
{
|
|
19498
|
+
type: "button",
|
|
19499
|
+
className: "twick-canvas-context-menu-item twick-canvas-context-menu-item-danger",
|
|
19500
|
+
onClick: () => wrap(onDelete),
|
|
19501
|
+
role: "menuitem",
|
|
19502
|
+
children: "Delete"
|
|
19503
|
+
}
|
|
19504
|
+
)
|
|
19505
|
+
]
|
|
19073
19506
|
}
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19507
|
+
);
|
|
19508
|
+
if (typeof document === "undefined") {
|
|
19509
|
+
return null;
|
|
19510
|
+
}
|
|
19511
|
+
return createPortal(menu, document.body);
|
|
19512
|
+
};
|
|
19513
|
+
const waveformCache = /* @__PURE__ */ new Map();
|
|
19514
|
+
const buildCacheKey = (src, bucketCount) => `${src}::${bucketCount}`;
|
|
19515
|
+
const getSeed = (src) => {
|
|
19516
|
+
let seed = 2166136261;
|
|
19517
|
+
for (let i2 = 0; i2 < src.length; i2 += 1) {
|
|
19518
|
+
seed ^= src.charCodeAt(i2);
|
|
19519
|
+
seed = Math.imul(seed, 16777619);
|
|
19520
|
+
}
|
|
19521
|
+
return seed >>> 0;
|
|
19522
|
+
};
|
|
19523
|
+
const generateFallbackPeaks = (bucketCount, seed) => {
|
|
19524
|
+
const peaks = new Float32Array(bucketCount);
|
|
19525
|
+
let randomState = seed || 123456789;
|
|
19526
|
+
for (let i2 = 0; i2 < bucketCount; i2 += 1) {
|
|
19527
|
+
randomState = 1103515245 * randomState + 12345 >>> 0;
|
|
19528
|
+
const noise = randomState % 1e3 / 1e3;
|
|
19529
|
+
const shape = 0.35 + 0.65 * Math.abs(Math.sin(i2 * 0.21 + noise * 2.7));
|
|
19530
|
+
peaks[i2] = Math.max(0.08, Math.min(1, shape));
|
|
19531
|
+
}
|
|
19532
|
+
return peaks;
|
|
19533
|
+
};
|
|
19534
|
+
const computePeaks = (channelData, bucketCount) => {
|
|
19535
|
+
const peaks = new Float32Array(bucketCount);
|
|
19536
|
+
const step = Math.max(1, Math.floor(channelData.length / bucketCount));
|
|
19537
|
+
for (let i2 = 0; i2 < bucketCount; i2 += 1) {
|
|
19538
|
+
const start = i2 * step;
|
|
19539
|
+
const end = Math.min(channelData.length, start + step);
|
|
19540
|
+
let peak = 0;
|
|
19541
|
+
for (let j2 = start; j2 < end; j2 += 1) {
|
|
19542
|
+
const sample = Math.abs(channelData[j2]);
|
|
19543
|
+
if (sample > peak) {
|
|
19544
|
+
peak = sample;
|
|
19545
|
+
}
|
|
19546
|
+
}
|
|
19547
|
+
peaks[i2] = peak;
|
|
19548
|
+
}
|
|
19549
|
+
return peaks;
|
|
19550
|
+
};
|
|
19551
|
+
const AudioWaveform = ({ src, widthPx, heightPx, label }) => {
|
|
19552
|
+
const canvasRef = useRef(null);
|
|
19553
|
+
const [peaks, setPeaks] = useState(null);
|
|
19554
|
+
const bucketCount = useMemo(
|
|
19555
|
+
() => Math.max(32, Math.min(2048, Math.floor(widthPx / 3))),
|
|
19556
|
+
[widthPx]
|
|
19557
|
+
);
|
|
19558
|
+
useEffect(() => {
|
|
19559
|
+
let isCancelled = false;
|
|
19560
|
+
const controller = new AbortController();
|
|
19561
|
+
const loadWaveform = async () => {
|
|
19562
|
+
if (!src) {
|
|
19563
|
+
setPeaks(generateFallbackPeaks(bucketCount, 1));
|
|
19564
|
+
return;
|
|
19565
|
+
}
|
|
19566
|
+
const cacheKey = buildCacheKey(src, bucketCount);
|
|
19567
|
+
const cached = waveformCache.get(cacheKey);
|
|
19568
|
+
if (cached) {
|
|
19569
|
+
setPeaks(cached);
|
|
19570
|
+
return;
|
|
19571
|
+
}
|
|
19572
|
+
try {
|
|
19573
|
+
const response = await fetch(src, { signal: controller.signal });
|
|
19574
|
+
if (!response.ok) {
|
|
19575
|
+
throw new Error(`Failed to fetch audio (${response.status})`);
|
|
19576
|
+
}
|
|
19577
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
19578
|
+
const audioCtx = new AudioContext();
|
|
19579
|
+
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer.slice(0));
|
|
19580
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
19581
|
+
const computed = computePeaks(channelData, bucketCount);
|
|
19582
|
+
waveformCache.set(cacheKey, computed);
|
|
19583
|
+
if (!isCancelled) {
|
|
19584
|
+
setPeaks(computed);
|
|
19585
|
+
}
|
|
19586
|
+
await audioCtx.close();
|
|
19587
|
+
} catch {
|
|
19588
|
+
if (!isCancelled) {
|
|
19589
|
+
setPeaks(generateFallbackPeaks(bucketCount, getSeed(src)));
|
|
19084
19590
|
}
|
|
19085
19591
|
}
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
|
|
19592
|
+
};
|
|
19593
|
+
loadWaveform();
|
|
19594
|
+
return () => {
|
|
19595
|
+
isCancelled = true;
|
|
19596
|
+
controller.abort();
|
|
19597
|
+
};
|
|
19598
|
+
}, [src, bucketCount]);
|
|
19599
|
+
useEffect(() => {
|
|
19600
|
+
const canvas = canvasRef.current;
|
|
19601
|
+
if (!canvas) {
|
|
19602
|
+
return;
|
|
19603
|
+
}
|
|
19604
|
+
const dpr = window.devicePixelRatio || 1;
|
|
19605
|
+
canvas.width = Math.max(1, Math.floor(widthPx * dpr));
|
|
19606
|
+
canvas.height = Math.max(1, Math.floor(heightPx * dpr));
|
|
19607
|
+
canvas.style.width = `${widthPx}px`;
|
|
19608
|
+
canvas.style.height = `${heightPx}px`;
|
|
19609
|
+
const context = canvas.getContext("2d");
|
|
19610
|
+
if (!context) {
|
|
19611
|
+
return;
|
|
19612
|
+
}
|
|
19613
|
+
context.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
19614
|
+
context.clearRect(0, 0, widthPx, heightPx);
|
|
19615
|
+
const centerY = Math.floor(heightPx / 2) + 0.5;
|
|
19616
|
+
context.strokeStyle = "rgba(255, 255, 255, 0.18)";
|
|
19617
|
+
context.lineWidth = 1;
|
|
19618
|
+
context.beginPath();
|
|
19619
|
+
context.moveTo(0, centerY);
|
|
19620
|
+
context.lineTo(widthPx, centerY);
|
|
19621
|
+
context.stroke();
|
|
19622
|
+
if (!peaks || peaks.length === 0) {
|
|
19623
|
+
return;
|
|
19624
|
+
}
|
|
19625
|
+
const maxHeight = Math.max(6, heightPx - 10);
|
|
19626
|
+
let peakMax = 1e-3;
|
|
19627
|
+
for (let i2 = 0; i2 < peaks.length; i2 += 1) {
|
|
19628
|
+
peakMax = Math.max(peakMax, peaks[i2]);
|
|
19629
|
+
}
|
|
19630
|
+
const gap = 1;
|
|
19631
|
+
const barWidth = Math.max(1, Math.floor(widthPx / peaks.length) - gap);
|
|
19632
|
+
context.fillStyle = "rgba(255, 255, 255, 0.95)";
|
|
19633
|
+
for (let i2 = 0; i2 < peaks.length; i2 += 1) {
|
|
19634
|
+
const normalized = peaks[i2] / peakMax;
|
|
19635
|
+
const waveHeight = Math.max(2, normalized * maxHeight);
|
|
19636
|
+
const x2 = i2 * (barWidth + gap);
|
|
19637
|
+
if (x2 > widthPx) {
|
|
19638
|
+
break;
|
|
19639
|
+
}
|
|
19640
|
+
const y2 = (heightPx - waveHeight) / 2;
|
|
19641
|
+
context.fillRect(x2, y2, barWidth, waveHeight);
|
|
19642
|
+
}
|
|
19643
|
+
}, [widthPx, heightPx, peaks]);
|
|
19644
|
+
return /* @__PURE__ */ jsxs("div", { className: "twick-audio-waveform-root", "aria-label": "Audio waveform", children: [
|
|
19645
|
+
/* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "twick-audio-waveform-canvas" }),
|
|
19646
|
+
label ? /* @__PURE__ */ jsx("span", { className: "twick-audio-waveform-label", children: label }) : null
|
|
19647
|
+
] });
|
|
19648
|
+
};
|
|
19649
|
+
class LRUCache2 {
|
|
19650
|
+
constructor(maxSize = 100) {
|
|
19651
|
+
if (maxSize <= 0) {
|
|
19652
|
+
throw new Error("maxSize must be greater than 0");
|
|
19653
|
+
}
|
|
19654
|
+
this.maxSize = maxSize;
|
|
19655
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
19656
|
+
}
|
|
19657
|
+
/**
|
|
19658
|
+
* Get a value from the cache.
|
|
19659
|
+
* Moves the item to the end (most recently used).
|
|
19660
|
+
*/
|
|
19661
|
+
get(key) {
|
|
19662
|
+
const value = this.cache.get(key);
|
|
19663
|
+
if (value === void 0) {
|
|
19664
|
+
return void 0;
|
|
19665
|
+
}
|
|
19666
|
+
this.cache.delete(key);
|
|
19667
|
+
this.cache.set(key, value);
|
|
19668
|
+
return value;
|
|
19669
|
+
}
|
|
19670
|
+
/**
|
|
19671
|
+
* Set a value in the cache.
|
|
19672
|
+
* If cache is full, removes the least recently used item.
|
|
19673
|
+
*/
|
|
19674
|
+
set(key, value) {
|
|
19675
|
+
if (this.cache.has(key)) {
|
|
19676
|
+
this.cache.delete(key);
|
|
19677
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
19678
|
+
const firstKey = this.cache.keys().next().value;
|
|
19679
|
+
if (firstKey !== void 0) {
|
|
19680
|
+
this.cache.delete(firstKey);
|
|
19681
|
+
}
|
|
19682
|
+
}
|
|
19683
|
+
this.cache.set(key, value);
|
|
19684
|
+
}
|
|
19685
|
+
/**
|
|
19686
|
+
* Check if a key exists in the cache.
|
|
19687
|
+
*/
|
|
19688
|
+
has(key) {
|
|
19689
|
+
return this.cache.has(key);
|
|
19690
|
+
}
|
|
19691
|
+
/**
|
|
19692
|
+
* Delete a key from the cache.
|
|
19693
|
+
*/
|
|
19694
|
+
delete(key) {
|
|
19695
|
+
return this.cache.delete(key);
|
|
19696
|
+
}
|
|
19697
|
+
/**
|
|
19698
|
+
* Clear all entries from the cache.
|
|
19699
|
+
*/
|
|
19700
|
+
clear() {
|
|
19701
|
+
this.cache.clear();
|
|
19702
|
+
}
|
|
19703
|
+
/**
|
|
19704
|
+
* Get the current size of the cache.
|
|
19705
|
+
*/
|
|
19706
|
+
get size() {
|
|
19707
|
+
return this.cache.size;
|
|
19708
|
+
}
|
|
19709
|
+
}
|
|
19710
|
+
class VideoFrameExtractor2 {
|
|
19711
|
+
constructor(options = {}) {
|
|
19712
|
+
this.frameCache = new LRUCache2(
|
|
19713
|
+
options.maxCacheSize ?? 50
|
|
19714
|
+
);
|
|
19715
|
+
this.videoElements = /* @__PURE__ */ new Map();
|
|
19716
|
+
this.maxVideoElements = options.maxVideoElements ?? 5;
|
|
19717
|
+
this.loadTimeout = options.loadTimeout ?? 15e3;
|
|
19718
|
+
this.jpegQuality = options.jpegQuality ?? 0.8;
|
|
19719
|
+
this.playbackRate = options.playbackRate ?? 1;
|
|
19720
|
+
}
|
|
19721
|
+
/**
|
|
19722
|
+
* Get a frame thumbnail from a video at a specific time.
|
|
19723
|
+
* Uses caching and reuses video elements for optimal performance.
|
|
19724
|
+
* Uses 0.1s instead of 0 when seekTime is 0, since frames at t=0 are often blank.
|
|
19725
|
+
*
|
|
19726
|
+
* @param videoUrl - The URL of the video
|
|
19727
|
+
* @param seekTime - The time in seconds to extract the frame (0 is treated as 0.1)
|
|
19728
|
+
* @returns Promise resolving to a thumbnail image URL (data URL or blob URL)
|
|
19729
|
+
*/
|
|
19730
|
+
async getFrame(videoUrl, seekTime = 0.1) {
|
|
19731
|
+
const effectiveSeekTime = seekTime === 0 ? 0.1 : seekTime;
|
|
19732
|
+
const cacheKey = this.getCacheKey(videoUrl, effectiveSeekTime);
|
|
19733
|
+
const cached = this.frameCache.get(cacheKey);
|
|
19734
|
+
if (cached) {
|
|
19735
|
+
return cached;
|
|
19736
|
+
}
|
|
19737
|
+
const videoState = await this.getVideoElement(videoUrl);
|
|
19738
|
+
const thumbnail = await this.extractFrame(videoState.video, effectiveSeekTime);
|
|
19739
|
+
this.frameCache.set(cacheKey, thumbnail);
|
|
19740
|
+
return thumbnail;
|
|
19741
|
+
}
|
|
19742
|
+
/**
|
|
19743
|
+
* Get or create a video element for the given URL.
|
|
19744
|
+
* Reuses existing elements and manages cleanup.
|
|
19745
|
+
*/
|
|
19746
|
+
async getVideoElement(videoUrl) {
|
|
19747
|
+
let videoState = this.videoElements.get(videoUrl);
|
|
19748
|
+
if (videoState && videoState.isReady) {
|
|
19749
|
+
videoState.lastUsed = Date.now();
|
|
19750
|
+
return videoState;
|
|
19751
|
+
}
|
|
19752
|
+
if (videoState && videoState.isLoading && videoState.loadPromise) {
|
|
19753
|
+
await videoState.loadPromise;
|
|
19754
|
+
if (videoState.isReady) {
|
|
19755
|
+
videoState.lastUsed = Date.now();
|
|
19756
|
+
return videoState;
|
|
19757
|
+
}
|
|
19758
|
+
}
|
|
19759
|
+
if (this.videoElements.size >= this.maxVideoElements) {
|
|
19760
|
+
this.cleanupOldVideoElements();
|
|
19761
|
+
}
|
|
19762
|
+
videoState = await this.createVideoElement(videoUrl);
|
|
19763
|
+
this.videoElements.set(videoUrl, videoState);
|
|
19764
|
+
videoState.lastUsed = Date.now();
|
|
19765
|
+
return videoState;
|
|
19766
|
+
}
|
|
19767
|
+
/**
|
|
19768
|
+
* Create and initialize a new video element.
|
|
19769
|
+
*/
|
|
19770
|
+
async createVideoElement(videoUrl) {
|
|
19771
|
+
const video = document.createElement("video");
|
|
19772
|
+
video.crossOrigin = "anonymous";
|
|
19773
|
+
video.muted = true;
|
|
19774
|
+
video.playsInline = true;
|
|
19775
|
+
video.autoplay = false;
|
|
19776
|
+
video.preload = "auto";
|
|
19777
|
+
video.playbackRate = this.playbackRate;
|
|
19778
|
+
video.style.position = "absolute";
|
|
19779
|
+
video.style.left = "-9999px";
|
|
19780
|
+
video.style.top = "-9999px";
|
|
19781
|
+
video.style.width = "1px";
|
|
19782
|
+
video.style.height = "1px";
|
|
19783
|
+
video.style.opacity = "0";
|
|
19784
|
+
video.style.pointerEvents = "none";
|
|
19785
|
+
video.style.zIndex = "-1";
|
|
19786
|
+
const state = {
|
|
19787
|
+
video,
|
|
19788
|
+
isReady: false,
|
|
19789
|
+
isLoading: true,
|
|
19790
|
+
loadPromise: null,
|
|
19791
|
+
lastUsed: Date.now()
|
|
19792
|
+
};
|
|
19793
|
+
state.loadPromise = new Promise((resolve, reject) => {
|
|
19794
|
+
let timeoutId;
|
|
19795
|
+
const cleanup = () => {
|
|
19796
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
19797
|
+
};
|
|
19798
|
+
const handleError = () => {
|
|
19799
|
+
var _a;
|
|
19800
|
+
cleanup();
|
|
19801
|
+
state.isLoading = false;
|
|
19802
|
+
reject(new Error(`Failed to load video: ${((_a = video.error) == null ? void 0 : _a.message) || "Unknown error"}`));
|
|
19803
|
+
};
|
|
19804
|
+
const handleLoadedMetadata = () => {
|
|
19805
|
+
cleanup();
|
|
19806
|
+
state.isReady = true;
|
|
19807
|
+
state.isLoading = false;
|
|
19808
|
+
resolve();
|
|
19809
|
+
};
|
|
19810
|
+
video.addEventListener("error", handleError, { once: true });
|
|
19811
|
+
video.addEventListener("loadedmetadata", handleLoadedMetadata, { once: true });
|
|
19812
|
+
timeoutId = window.setTimeout(() => {
|
|
19813
|
+
cleanup();
|
|
19814
|
+
state.isLoading = false;
|
|
19815
|
+
reject(new Error("Video loading timed out"));
|
|
19816
|
+
}, this.loadTimeout);
|
|
19817
|
+
video.src = videoUrl;
|
|
19818
|
+
document.body.appendChild(video);
|
|
19819
|
+
});
|
|
19820
|
+
try {
|
|
19821
|
+
await state.loadPromise;
|
|
19822
|
+
} catch (error) {
|
|
19823
|
+
if (video.parentNode) {
|
|
19824
|
+
video.remove();
|
|
19825
|
+
}
|
|
19826
|
+
throw error;
|
|
19827
|
+
}
|
|
19828
|
+
return state;
|
|
19829
|
+
}
|
|
19830
|
+
/**
|
|
19831
|
+
* Extract a frame from a video at the specified time.
|
|
19832
|
+
*/
|
|
19833
|
+
async extractFrame(video, seekTime) {
|
|
19834
|
+
return new Promise((resolve, reject) => {
|
|
19835
|
+
video.pause();
|
|
19836
|
+
const timeThreshold = 0.1;
|
|
19837
|
+
if (Math.abs(video.currentTime - seekTime) < timeThreshold) {
|
|
19838
|
+
try {
|
|
19839
|
+
const canvas = document.createElement("canvas");
|
|
19840
|
+
const width = video.videoWidth || 640;
|
|
19841
|
+
const height = video.videoHeight || 360;
|
|
19842
|
+
canvas.width = width;
|
|
19843
|
+
canvas.height = height;
|
|
19844
|
+
const ctx = canvas.getContext("2d");
|
|
19845
|
+
if (!ctx) {
|
|
19846
|
+
reject(new Error("Failed to get canvas context"));
|
|
19847
|
+
return;
|
|
19848
|
+
}
|
|
19849
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
19850
|
+
try {
|
|
19851
|
+
const dataUrl = canvas.toDataURL("image/jpeg", this.jpegQuality);
|
|
19852
|
+
resolve(dataUrl);
|
|
19853
|
+
} catch {
|
|
19854
|
+
canvas.toBlob(
|
|
19855
|
+
(blob) => {
|
|
19856
|
+
if (!blob) {
|
|
19857
|
+
reject(new Error("Failed to create Blob"));
|
|
19858
|
+
return;
|
|
19859
|
+
}
|
|
19860
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
19861
|
+
resolve(blobUrl);
|
|
19862
|
+
},
|
|
19863
|
+
"image/jpeg",
|
|
19864
|
+
this.jpegQuality
|
|
19865
|
+
);
|
|
19866
|
+
}
|
|
19867
|
+
return;
|
|
19868
|
+
} catch (err) {
|
|
19869
|
+
reject(new Error(`Error creating thumbnail: ${err}`));
|
|
19870
|
+
return;
|
|
19871
|
+
}
|
|
19872
|
+
}
|
|
19873
|
+
const handleSeeked = () => {
|
|
19874
|
+
try {
|
|
19875
|
+
const canvas = document.createElement("canvas");
|
|
19876
|
+
const width = video.videoWidth || 640;
|
|
19877
|
+
const height = video.videoHeight || 360;
|
|
19878
|
+
canvas.width = width;
|
|
19879
|
+
canvas.height = height;
|
|
19880
|
+
const ctx = canvas.getContext("2d");
|
|
19881
|
+
if (!ctx) {
|
|
19882
|
+
reject(new Error("Failed to get canvas context"));
|
|
19883
|
+
return;
|
|
19884
|
+
}
|
|
19885
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
19886
|
+
try {
|
|
19887
|
+
const dataUrl = canvas.toDataURL("image/jpeg", this.jpegQuality);
|
|
19888
|
+
resolve(dataUrl);
|
|
19889
|
+
} catch {
|
|
19890
|
+
canvas.toBlob(
|
|
19891
|
+
(blob) => {
|
|
19892
|
+
if (!blob) {
|
|
19893
|
+
reject(new Error("Failed to create Blob"));
|
|
19894
|
+
return;
|
|
19895
|
+
}
|
|
19896
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
19897
|
+
resolve(blobUrl);
|
|
19898
|
+
},
|
|
19899
|
+
"image/jpeg",
|
|
19900
|
+
this.jpegQuality
|
|
19901
|
+
);
|
|
19902
|
+
}
|
|
19903
|
+
} catch (err) {
|
|
19904
|
+
reject(new Error(`Error creating thumbnail: ${err}`));
|
|
19905
|
+
}
|
|
19906
|
+
};
|
|
19907
|
+
video.addEventListener("seeked", handleSeeked, { once: true });
|
|
19908
|
+
const playPromise = video.play();
|
|
19909
|
+
if (playPromise !== void 0) {
|
|
19910
|
+
playPromise.then(() => {
|
|
19911
|
+
video.currentTime = seekTime;
|
|
19912
|
+
}).catch(() => {
|
|
19913
|
+
video.currentTime = seekTime;
|
|
19914
|
+
});
|
|
19915
|
+
} else {
|
|
19916
|
+
video.currentTime = seekTime;
|
|
19917
|
+
}
|
|
19918
|
+
});
|
|
19919
|
+
}
|
|
19920
|
+
/**
|
|
19921
|
+
* Generate cache key for a video URL and seek time.
|
|
19922
|
+
*/
|
|
19923
|
+
getCacheKey(videoUrl, seekTime) {
|
|
19924
|
+
const roundedTime = Math.round(seekTime * 100) / 100;
|
|
19925
|
+
return `${videoUrl}:${roundedTime}`;
|
|
19926
|
+
}
|
|
19927
|
+
/**
|
|
19928
|
+
* Cleanup least recently used video elements.
|
|
19929
|
+
*/
|
|
19930
|
+
cleanupOldVideoElements() {
|
|
19931
|
+
if (this.videoElements.size < this.maxVideoElements) {
|
|
19932
|
+
return;
|
|
19933
|
+
}
|
|
19934
|
+
const entries = Array.from(this.videoElements.entries());
|
|
19935
|
+
entries.sort((a2, b2) => a2[1].lastUsed - b2[1].lastUsed);
|
|
19936
|
+
const toRemove = entries.slice(0, entries.length - this.maxVideoElements + 1);
|
|
19937
|
+
for (const [url, state] of toRemove) {
|
|
19938
|
+
if (state.video.parentNode) {
|
|
19939
|
+
state.video.remove();
|
|
19940
|
+
}
|
|
19941
|
+
this.videoElements.delete(url);
|
|
19942
|
+
}
|
|
19943
|
+
}
|
|
19944
|
+
/**
|
|
19945
|
+
* Clear the frame cache.
|
|
19946
|
+
*/
|
|
19947
|
+
clearCache() {
|
|
19948
|
+
this.frameCache.clear();
|
|
19949
|
+
}
|
|
19950
|
+
/**
|
|
19951
|
+
* Remove a specific video element and clear its cached frames.
|
|
19952
|
+
*/
|
|
19953
|
+
removeVideo(videoUrl) {
|
|
19954
|
+
const state = this.videoElements.get(videoUrl);
|
|
19955
|
+
if (state) {
|
|
19956
|
+
if (state.video.parentNode) {
|
|
19957
|
+
state.video.remove();
|
|
19958
|
+
}
|
|
19959
|
+
this.videoElements.delete(videoUrl);
|
|
19960
|
+
}
|
|
19961
|
+
this.frameCache.clear();
|
|
19962
|
+
}
|
|
19963
|
+
/**
|
|
19964
|
+
* Dispose of all video elements and clear caches.
|
|
19965
|
+
* Removes all video elements from the DOM and clears both the frame cache
|
|
19966
|
+
* and video element cache. Call this when the extractor is no longer needed
|
|
19967
|
+
* to prevent memory leaks.
|
|
19968
|
+
*/
|
|
19969
|
+
dispose() {
|
|
19970
|
+
for (const state of this.videoElements.values()) {
|
|
19971
|
+
if (state.video.parentNode) {
|
|
19972
|
+
state.video.remove();
|
|
19973
|
+
}
|
|
19974
|
+
}
|
|
19975
|
+
this.videoElements.clear();
|
|
19976
|
+
this.frameCache.clear();
|
|
19977
|
+
}
|
|
19978
|
+
}
|
|
19979
|
+
let defaultExtractor = null;
|
|
19980
|
+
function getDefaultVideoFrameExtractor(options) {
|
|
19981
|
+
if (!defaultExtractor) {
|
|
19982
|
+
defaultExtractor = new VideoFrameExtractor2(options);
|
|
19983
|
+
}
|
|
19984
|
+
return defaultExtractor;
|
|
19985
|
+
}
|
|
19986
|
+
async function getThumbnailCached(videoUrl, seekTime = 0.1, playbackRate) {
|
|
19987
|
+
const extractor = getDefaultVideoFrameExtractor(
|
|
19988
|
+
void 0
|
|
19989
|
+
);
|
|
19990
|
+
return extractor.getFrame(videoUrl, seekTime);
|
|
19991
|
+
}
|
|
19992
|
+
const MAX_THUMBNAILS_PER_CLIP = 24;
|
|
19993
|
+
const MIN_THUMBNAIL_WIDTH = 40;
|
|
19994
|
+
const MAX_THUMBNAIL_WIDTH = 96;
|
|
19995
|
+
const videoThumbCache = /* @__PURE__ */ new Map();
|
|
19996
|
+
const inFlightVideoThumbs = /* @__PURE__ */ new Map();
|
|
19997
|
+
const getThumbCacheKey = (src, seekTime) => `${src}:${Math.round(seekTime * 100) / 100}`;
|
|
19998
|
+
const getCachedVideoThumbnail = async (src, seekTime) => {
|
|
19999
|
+
const key = getThumbCacheKey(src, seekTime);
|
|
20000
|
+
const cached = videoThumbCache.get(key);
|
|
20001
|
+
if (cached) {
|
|
20002
|
+
return cached;
|
|
20003
|
+
}
|
|
20004
|
+
const inFlight = inFlightVideoThumbs.get(key);
|
|
20005
|
+
if (inFlight) {
|
|
20006
|
+
return inFlight;
|
|
20007
|
+
}
|
|
20008
|
+
const request = getThumbnailCached(src, seekTime).then((thumb) => {
|
|
20009
|
+
videoThumbCache.set(key, thumb);
|
|
20010
|
+
inFlightVideoThumbs.delete(key);
|
|
20011
|
+
return thumb;
|
|
20012
|
+
}).catch(() => {
|
|
20013
|
+
inFlightVideoThumbs.delete(key);
|
|
20014
|
+
throw new Error("Thumbnail extraction failed");
|
|
20015
|
+
});
|
|
20016
|
+
inFlightVideoThumbs.set(key, request);
|
|
20017
|
+
return request;
|
|
20018
|
+
};
|
|
20019
|
+
const runWithConcurrencyLimit = async (tasks, concurrency) => {
|
|
20020
|
+
const active = /* @__PURE__ */ new Set();
|
|
20021
|
+
for (const task of tasks) {
|
|
20022
|
+
const promise = task();
|
|
20023
|
+
active.add(promise);
|
|
20024
|
+
promise.finally(() => active.delete(promise));
|
|
20025
|
+
if (active.size >= concurrency) {
|
|
20026
|
+
await Promise.race(active);
|
|
20027
|
+
}
|
|
20028
|
+
}
|
|
20029
|
+
await Promise.all(active);
|
|
20030
|
+
};
|
|
20031
|
+
const getThumbnailCount = (widthPx, heightPx) => {
|
|
20032
|
+
const targetThumbWidth = Math.max(
|
|
20033
|
+
MIN_THUMBNAIL_WIDTH,
|
|
20034
|
+
Math.min(MAX_THUMBNAIL_WIDTH, Math.round(heightPx * 1.5))
|
|
20035
|
+
);
|
|
20036
|
+
return Math.max(
|
|
20037
|
+
1,
|
|
20038
|
+
Math.min(MAX_THUMBNAILS_PER_CLIP, Math.ceil(widthPx / targetThumbWidth))
|
|
20039
|
+
);
|
|
20040
|
+
};
|
|
20041
|
+
const ImageTimelineStrip = ({ src, widthPx, heightPx }) => {
|
|
20042
|
+
const count = useMemo(() => getThumbnailCount(widthPx, heightPx), [widthPx, heightPx]);
|
|
20043
|
+
return /* @__PURE__ */ jsx("div", { className: "twick-media-strip twick-media-strip-image", "aria-hidden": "true", children: Array.from({ length: count }, (_2, index) => /* @__PURE__ */ jsx(
|
|
20044
|
+
"img",
|
|
20045
|
+
{
|
|
20046
|
+
src,
|
|
20047
|
+
className: "twick-media-strip-thumb",
|
|
20048
|
+
alt: "",
|
|
20049
|
+
draggable: false
|
|
20050
|
+
},
|
|
20051
|
+
`${src}-${index}`
|
|
20052
|
+
)) });
|
|
20053
|
+
};
|
|
20054
|
+
const VideoTimelineStrip = ({
|
|
20055
|
+
src,
|
|
20056
|
+
widthPx,
|
|
20057
|
+
heightPx,
|
|
20058
|
+
durationSec,
|
|
20059
|
+
mediaOffsetSec = 0,
|
|
20060
|
+
playbackRate = 1,
|
|
20061
|
+
mediaDurationSec
|
|
20062
|
+
}) => {
|
|
20063
|
+
const count = useMemo(() => getThumbnailCount(widthPx, heightPx), [widthPx, heightPx]);
|
|
20064
|
+
const [thumbs, setThumbs] = useState({});
|
|
20065
|
+
const slots = useMemo(() => {
|
|
20066
|
+
const timelineDuration = Math.max(1e-3, durationSec);
|
|
20067
|
+
return Array.from({ length: count }, (_2, index) => {
|
|
20068
|
+
const progress2 = count === 1 ? 0 : index / (count - 1);
|
|
20069
|
+
const timelineTime = progress2 * timelineDuration;
|
|
20070
|
+
let mediaTime = Math.max(0, mediaOffsetSec + timelineTime * playbackRate);
|
|
20071
|
+
if (typeof mediaDurationSec === "number" && mediaDurationSec > 0) {
|
|
20072
|
+
mediaTime = Math.min(mediaTime, Math.max(0, mediaDurationSec - 0.05));
|
|
20073
|
+
}
|
|
20074
|
+
return mediaTime;
|
|
20075
|
+
});
|
|
20076
|
+
}, [count, durationSec, mediaOffsetSec, playbackRate, mediaDurationSec]);
|
|
20077
|
+
useEffect(() => {
|
|
20078
|
+
let cancelled = false;
|
|
20079
|
+
const loadThumbs = async () => {
|
|
20080
|
+
const nextThumbs = {};
|
|
20081
|
+
const tasks = slots.map((seekTime, index) => async () => {
|
|
20082
|
+
try {
|
|
20083
|
+
const thumb = await getCachedVideoThumbnail(src, seekTime);
|
|
20084
|
+
nextThumbs[index] = thumb;
|
|
20085
|
+
} catch {
|
|
20086
|
+
}
|
|
20087
|
+
});
|
|
20088
|
+
await runWithConcurrencyLimit(tasks, 3);
|
|
20089
|
+
if (!cancelled) {
|
|
20090
|
+
setThumbs(nextThumbs);
|
|
20091
|
+
}
|
|
20092
|
+
};
|
|
20093
|
+
loadThumbs();
|
|
20094
|
+
return () => {
|
|
20095
|
+
cancelled = true;
|
|
20096
|
+
};
|
|
20097
|
+
}, [src, slots]);
|
|
20098
|
+
return /* @__PURE__ */ jsx("div", { className: "twick-media-strip twick-media-strip-video", "aria-hidden": "true", children: slots.map((_2, index) => /* @__PURE__ */ jsx(
|
|
20099
|
+
"img",
|
|
20100
|
+
{
|
|
20101
|
+
src: thumbs[index],
|
|
20102
|
+
className: "twick-media-strip-thumb",
|
|
20103
|
+
alt: "",
|
|
20104
|
+
draggable: false
|
|
20105
|
+
},
|
|
20106
|
+
`${src}-${index}`
|
|
20107
|
+
)) });
|
|
20108
|
+
};
|
|
20109
|
+
const TrackElementView = ({
|
|
20110
|
+
element,
|
|
20111
|
+
parentWidth,
|
|
20112
|
+
duration,
|
|
20113
|
+
nextStart,
|
|
20114
|
+
prevEnd,
|
|
20115
|
+
selectedItem,
|
|
20116
|
+
selectedIds,
|
|
20117
|
+
onSelection,
|
|
20118
|
+
onDrag,
|
|
20119
|
+
allowOverlap = false,
|
|
20120
|
+
onDragStateChange,
|
|
20121
|
+
elementColors,
|
|
20122
|
+
currentTime = 0,
|
|
20123
|
+
onContextMenuTarget,
|
|
20124
|
+
onDeleteElement,
|
|
20125
|
+
onSplitElement
|
|
20126
|
+
}) => {
|
|
20127
|
+
var _a, _b;
|
|
20128
|
+
const ref = useRef(null);
|
|
20129
|
+
const dragType = useRef(null);
|
|
20130
|
+
const lastPosRef = useRef(null);
|
|
20131
|
+
const [isDragging2, setIsDragging] = useState(false);
|
|
20132
|
+
const [clipMenu, setClipMenu] = useState(
|
|
20133
|
+
null
|
|
20134
|
+
);
|
|
20135
|
+
const [position, setPosition] = useState({
|
|
20136
|
+
start: 0,
|
|
20137
|
+
end: 0
|
|
20138
|
+
});
|
|
20139
|
+
useEffect(() => {
|
|
20140
|
+
setPosition({
|
|
20141
|
+
start: element.getStart(),
|
|
20142
|
+
end: element.getEnd()
|
|
20143
|
+
});
|
|
20144
|
+
}, [element.getStart(), element.getEnd(), parentWidth, duration]);
|
|
20145
|
+
const bind = useDrag(({ delta: [dx] }) => {
|
|
20146
|
+
if (!parentWidth) return;
|
|
20147
|
+
if (dx == 0) return;
|
|
20148
|
+
if (!isDragging2) {
|
|
20149
|
+
setIsDragging(true);
|
|
20150
|
+
onDragStateChange == null ? void 0 : onDragStateChange(true, element);
|
|
20151
|
+
}
|
|
20152
|
+
dragType.current = DRAG_TYPE.MOVE;
|
|
20153
|
+
setPosition((prev) => {
|
|
20154
|
+
const span = prev.end - prev.start;
|
|
20155
|
+
let newStart = prev.start + dx / parentWidth * duration;
|
|
20156
|
+
newStart = Math.max(0, Math.min(newStart, prev.end - MIN_DURATION));
|
|
20157
|
+
if (!allowOverlap) {
|
|
20158
|
+
if (prevEnd !== null && newStart < prevEnd) {
|
|
20159
|
+
newStart = prevEnd;
|
|
20160
|
+
} else if (nextStart !== null && !allowOverlap && newStart + span > nextStart) {
|
|
20161
|
+
newStart = nextStart - span;
|
|
20162
|
+
}
|
|
20163
|
+
}
|
|
20164
|
+
newStart = Math.max(0, Math.min(newStart, duration - span));
|
|
20165
|
+
return {
|
|
20166
|
+
start: newStart,
|
|
20167
|
+
end: newStart + span
|
|
20168
|
+
};
|
|
20169
|
+
});
|
|
20170
|
+
});
|
|
20171
|
+
const bindStartHandle = useDrag(({ delta: [dx], event }) => {
|
|
20172
|
+
if (event) {
|
|
20173
|
+
event.stopPropagation();
|
|
20174
|
+
}
|
|
20175
|
+
if (dx === 0) return;
|
|
20176
|
+
if (isDragging2) {
|
|
19098
20177
|
setIsDragging(false);
|
|
19099
20178
|
onDragStateChange == null ? void 0 : onDragStateChange(false, element);
|
|
19100
20179
|
}
|
|
@@ -19105,6 +20184,7 @@ const TrackElementView = ({
|
|
|
19105
20184
|
if (prevEnd !== null && !allowOverlap && newStart < prevEnd) {
|
|
19106
20185
|
newStart = prevEnd;
|
|
19107
20186
|
}
|
|
20187
|
+
newStart = Math.max(0, Math.min(newStart, prev.end - MIN_DURATION));
|
|
19108
20188
|
return {
|
|
19109
20189
|
start: newStart,
|
|
19110
20190
|
end: prev.end
|
|
@@ -19129,6 +20209,7 @@ const TrackElementView = ({
|
|
|
19129
20209
|
newEnd = nextStart;
|
|
19130
20210
|
}
|
|
19131
20211
|
}
|
|
20212
|
+
newEnd = Math.max(prev.start + MIN_DURATION, Math.min(newEnd, duration));
|
|
19132
20213
|
return {
|
|
19133
20214
|
start: prev.start,
|
|
19134
20215
|
end: newEnd
|
|
@@ -19166,7 +20247,7 @@ const TrackElementView = ({
|
|
|
19166
20247
|
};
|
|
19167
20248
|
const getElementColor = (elementType) => {
|
|
19168
20249
|
const colors = elementColors || ELEMENT_COLORS;
|
|
19169
|
-
const key = elementType === TIMELINE_ELEMENT_TYPE.VIDEO ? "video" : elementType === TIMELINE_ELEMENT_TYPE.AUDIO ? "audio" : elementType === TIMELINE_ELEMENT_TYPE.IMAGE ? "image" : elementType === TIMELINE_ELEMENT_TYPE.TEXT ? "text" : elementType === TIMELINE_ELEMENT_TYPE.CAPTION ? "caption" : elementType === TIMELINE_ELEMENT_TYPE.RECT ? "rect" : elementType === TIMELINE_ELEMENT_TYPE.CIRCLE ? "circle" : elementType === TIMELINE_ELEMENT_TYPE.ICON ? "icon" : elementType === TIMELINE_ELEMENT_TYPE.EFFECT ? "effect" : "element";
|
|
20250
|
+
const key = elementType === TIMELINE_ELEMENT_TYPE.VIDEO ? "video" : elementType === TIMELINE_ELEMENT_TYPE.AUDIO ? "audio" : elementType === TIMELINE_ELEMENT_TYPE.IMAGE ? "image" : elementType === TIMELINE_ELEMENT_TYPE.TEXT ? "text" : elementType === TIMELINE_ELEMENT_TYPE.CAPTION ? "caption" : elementType === TIMELINE_ELEMENT_TYPE.RECT ? "rect" : elementType === TIMELINE_ELEMENT_TYPE.CIRCLE ? "circle" : elementType === TIMELINE_ELEMENT_TYPE.ICON ? "icon" : elementType === TIMELINE_ELEMENT_TYPE.EMOJI ? "emoji" : elementType === TIMELINE_ELEMENT_TYPE.EFFECT ? "effect" : "element";
|
|
19170
20251
|
if (key in colors) {
|
|
19171
20252
|
return colors[key];
|
|
19172
20253
|
}
|
|
@@ -19175,10 +20256,32 @@ const TrackElementView = ({
|
|
|
19175
20256
|
const isSelected = useMemo(() => {
|
|
19176
20257
|
return selectedIds.has(element.getId());
|
|
19177
20258
|
}, [selectedIds, element]);
|
|
20259
|
+
const isAudioElement = element.getType() === TIMELINE_ELEMENT_TYPE.AUDIO;
|
|
20260
|
+
const isVideoElement = element.getType() === TIMELINE_ELEMENT_TYPE.VIDEO;
|
|
20261
|
+
const isImageElement = element.getType() === TIMELINE_ELEMENT_TYPE.IMAGE;
|
|
20262
|
+
const elementLabel = element.getType() === TIMELINE_ELEMENT_TYPE.EFFECT ? ((_b = (_a = element.getProps) == null ? void 0 : _a.call(element)) == null ? void 0 : _b.effectKey) ?? "Effect" : element.getText ? element.getText() : element.getName() || element.getType();
|
|
20263
|
+
const mediaSrc = (isAudioElement || isVideoElement || isImageElement) && element.getSrc ? element.getSrc() : void 0;
|
|
20264
|
+
const mediaOffsetSec = isVideoElement && element.getStartAt ? element.getStartAt() : 0;
|
|
20265
|
+
const playbackRate = isVideoElement && element.getPlaybackRate ? element.getPlaybackRate() : 1;
|
|
20266
|
+
const mediaDurationSec = isVideoElement && element.getMediaDuration ? element.getMediaDuration() : void 0;
|
|
20267
|
+
const elementWidthPx = Math.max(
|
|
20268
|
+
1,
|
|
20269
|
+
(position.end - position.start) / Math.max(duration, MIN_DURATION) * parentWidth
|
|
20270
|
+
);
|
|
19178
20271
|
const hasHandles = (selectedItem == null ? void 0 : selectedItem.getId()) === element.getId();
|
|
20272
|
+
const contextActionsEnabled = Boolean(
|
|
20273
|
+
onDeleteElement && onSplitElement && onContextMenuTarget
|
|
20274
|
+
);
|
|
20275
|
+
const handleContextMenu = (e3) => {
|
|
20276
|
+
if (!contextActionsEnabled) return;
|
|
20277
|
+
e3.preventDefault();
|
|
20278
|
+
e3.stopPropagation();
|
|
20279
|
+
onContextMenuTarget == null ? void 0 : onContextMenuTarget(element);
|
|
20280
|
+
setClipMenu({ x: e3.clientX, y: e3.clientY });
|
|
20281
|
+
};
|
|
19179
20282
|
const motionProps = {
|
|
19180
20283
|
ref,
|
|
19181
|
-
className: `twick-track-element ${isSelected ? "twick-track-element-selected" : "twick-track-element-default"} ${isDragging2 ? "twick-track-element-dragging" : ""}`,
|
|
20284
|
+
className: `twick-track-element ${isSelected ? "twick-track-element-selected" : "twick-track-element-default"} ${isDragging2 ? "twick-track-element-dragging" : ""} ${isAudioElement ? "twick-track-element-audio" : ""}`,
|
|
19182
20285
|
onMouseDown: (e3) => {
|
|
19183
20286
|
if (e3.target === ref.current) {
|
|
19184
20287
|
setLastPos();
|
|
@@ -19196,6 +20299,7 @@ const TrackElementView = ({
|
|
|
19196
20299
|
onSelection(element, e3);
|
|
19197
20300
|
}
|
|
19198
20301
|
},
|
|
20302
|
+
onContextMenu: handleContextMenu,
|
|
19199
20303
|
style: {
|
|
19200
20304
|
backgroundColor: getElementColor(element.getType()),
|
|
19201
20305
|
width: `${(position.end - position.start) / duration * 100}%`,
|
|
@@ -19203,39 +20307,84 @@ const TrackElementView = ({
|
|
|
19203
20307
|
touchAction: "none"
|
|
19204
20308
|
}
|
|
19205
20309
|
};
|
|
19206
|
-
return /* @__PURE__ */
|
|
19207
|
-
|
|
19208
|
-
|
|
20310
|
+
return /* @__PURE__ */ jsxs(motion.div, { ...motionProps, children: [
|
|
20311
|
+
clipMenu && contextActionsEnabled ? /* @__PURE__ */ jsx(
|
|
20312
|
+
TrackElementContextMenu,
|
|
19209
20313
|
{
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
20314
|
+
x: clipMenu.x,
|
|
20315
|
+
y: clipMenu.y,
|
|
20316
|
+
canSplit: canSplitElement(element, currentTime),
|
|
20317
|
+
onSplit: () => onSplitElement == null ? void 0 : onSplitElement(element, currentTime),
|
|
20318
|
+
onDelete: () => onDeleteElement == null ? void 0 : onDeleteElement(element),
|
|
20319
|
+
onClose: () => setClipMenu(null)
|
|
19213
20320
|
}
|
|
19214
20321
|
) : null,
|
|
19215
|
-
/* @__PURE__ */
|
|
19216
|
-
|
|
19217
|
-
"div",
|
|
19218
|
-
{
|
|
19219
|
-
style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
|
|
19220
|
-
...bindEndHandle(),
|
|
19221
|
-
className: "twick-track-element-handle twick-track-element-handle-end"
|
|
19222
|
-
}
|
|
19223
|
-
) : null,
|
|
19224
|
-
element.getFrameEffects ? element.getFrameEffects().map((frameEffect) => {
|
|
19225
|
-
return /* @__PURE__ */ jsx(
|
|
20322
|
+
/* @__PURE__ */ jsxs("div", { style: { touchAction: "none", height: "100%" }, ...bind(), children: [
|
|
20323
|
+
hasHandles ? /* @__PURE__ */ jsx(
|
|
19226
20324
|
"div",
|
|
19227
20325
|
{
|
|
19228
|
-
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
20326
|
+
style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
|
|
20327
|
+
...bindStartHandle(),
|
|
20328
|
+
className: "twick-track-element-handle twick-track-element-handle-start"
|
|
20329
|
+
}
|
|
20330
|
+
) : null,
|
|
20331
|
+
/* @__PURE__ */ jsx(
|
|
20332
|
+
"div",
|
|
20333
|
+
{
|
|
20334
|
+
className: `twick-track-element-content ${isAudioElement ? "twick-track-element-content-audio" : ""}`,
|
|
20335
|
+
children: isAudioElement ? /* @__PURE__ */ jsx(
|
|
20336
|
+
AudioWaveform,
|
|
20337
|
+
{
|
|
20338
|
+
src: mediaSrc,
|
|
20339
|
+
widthPx: elementWidthPx,
|
|
20340
|
+
heightPx: 46,
|
|
20341
|
+
label: elementLabel
|
|
20342
|
+
}
|
|
20343
|
+
) : isVideoElement && mediaSrc ? /* @__PURE__ */ jsx(
|
|
20344
|
+
VideoTimelineStrip,
|
|
20345
|
+
{
|
|
20346
|
+
src: mediaSrc,
|
|
20347
|
+
widthPx: elementWidthPx,
|
|
20348
|
+
heightPx: 46,
|
|
20349
|
+
durationSec: Math.max(0, element.getDuration()),
|
|
20350
|
+
mediaOffsetSec,
|
|
20351
|
+
playbackRate,
|
|
20352
|
+
mediaDurationSec
|
|
20353
|
+
}
|
|
20354
|
+
) : isImageElement && mediaSrc ? /* @__PURE__ */ jsx(
|
|
20355
|
+
ImageTimelineStrip,
|
|
20356
|
+
{
|
|
20357
|
+
src: mediaSrc,
|
|
20358
|
+
widthPx: elementWidthPx,
|
|
20359
|
+
heightPx: 46
|
|
20360
|
+
}
|
|
20361
|
+
) : elementLabel
|
|
20362
|
+
}
|
|
20363
|
+
),
|
|
20364
|
+
hasHandles ? /* @__PURE__ */ jsx(
|
|
20365
|
+
"div",
|
|
20366
|
+
{
|
|
20367
|
+
style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
|
|
20368
|
+
...bindEndHandle(),
|
|
20369
|
+
className: "twick-track-element-handle twick-track-element-handle-end"
|
|
20370
|
+
}
|
|
20371
|
+
) : null,
|
|
20372
|
+
element.getFrameEffects ? element.getFrameEffects().map((frameEffect) => {
|
|
20373
|
+
return /* @__PURE__ */ jsx(
|
|
20374
|
+
"div",
|
|
20375
|
+
{
|
|
20376
|
+
className: "twick-track-element-frame-effect",
|
|
20377
|
+
style: {
|
|
20378
|
+
backgroundColor: getElementColor("frameEffect"),
|
|
20379
|
+
width: `${(frameEffect.e - frameEffect.s) / element.getDuration() * 100}%`,
|
|
20380
|
+
left: `${frameEffect.s / element.getDuration() * 100}%`
|
|
20381
|
+
}
|
|
20382
|
+
},
|
|
20383
|
+
frameEffect.s + frameEffect.e
|
|
20384
|
+
);
|
|
20385
|
+
}) : null
|
|
20386
|
+
] })
|
|
20387
|
+
] });
|
|
19239
20388
|
};
|
|
19240
20389
|
const TrackBase = ({
|
|
19241
20390
|
duration,
|
|
@@ -19248,11 +20397,21 @@ const TrackBase = ({
|
|
|
19248
20397
|
onDrag,
|
|
19249
20398
|
allowOverlap = false,
|
|
19250
20399
|
onDragStateChange,
|
|
19251
|
-
elementColors
|
|
20400
|
+
elementColors,
|
|
20401
|
+
currentTime,
|
|
20402
|
+
onContextMenuTarget,
|
|
20403
|
+
onDeleteElement,
|
|
20404
|
+
onSplitElement
|
|
19252
20405
|
}) => {
|
|
19253
20406
|
const trackRef = useRef(null);
|
|
19254
20407
|
const trackWidthStyle = `${Math.max(100, duration * zoom * 100)}px`;
|
|
19255
|
-
const elements = track.getElements()
|
|
20408
|
+
const elements = [...track.getElements()].sort((a2, b2) => {
|
|
20409
|
+
const byStart = a2.getStart() - b2.getStart();
|
|
20410
|
+
if (byStart !== 0) return byStart;
|
|
20411
|
+
const byEnd = a2.getEnd() - b2.getEnd();
|
|
20412
|
+
if (byEnd !== 0) return byEnd;
|
|
20413
|
+
return a2.getId().localeCompare(b2.getId());
|
|
20414
|
+
});
|
|
19256
20415
|
return /* @__PURE__ */ jsx(
|
|
19257
20416
|
"div",
|
|
19258
20417
|
{
|
|
@@ -19274,6 +20433,10 @@ const TrackBase = ({
|
|
|
19274
20433
|
onDrag,
|
|
19275
20434
|
onDragStateChange,
|
|
19276
20435
|
elementColors,
|
|
20436
|
+
currentTime,
|
|
20437
|
+
onContextMenuTarget,
|
|
20438
|
+
onDeleteElement,
|
|
20439
|
+
onSplitElement,
|
|
19277
20440
|
nextStart: index < elements.length - 1 ? elements[index + 1].getStart() : null,
|
|
19278
20441
|
prevEnd: index > 0 ? elements[index - 1].getEnd() : 0
|
|
19279
20442
|
},
|
|
@@ -19337,6 +20500,7 @@ function useMarqueeSelection({
|
|
|
19337
20500
|
labelWidth,
|
|
19338
20501
|
trackCount,
|
|
19339
20502
|
trackHeight,
|
|
20503
|
+
separatorHeight = 2,
|
|
19340
20504
|
tracks,
|
|
19341
20505
|
containerRef,
|
|
19342
20506
|
onMarqueeSelect,
|
|
@@ -19390,7 +20554,7 @@ function useMarqueeSelection({
|
|
|
19390
20554
|
const bottom = Math.max(currentMarquee.startY, currentMarquee.endY);
|
|
19391
20555
|
const startTime = Math.max(0, (left - labelWidth) / pixelsPerSecond);
|
|
19392
20556
|
const endTime = Math.min(duration, (right - labelWidth) / pixelsPerSecond);
|
|
19393
|
-
const rowHeight = trackHeight +
|
|
20557
|
+
const rowHeight = trackHeight + separatorHeight;
|
|
19394
20558
|
const startTrackIdx = Math.max(0, Math.floor(top / rowHeight));
|
|
19395
20559
|
const endTrackIdx = Math.min(
|
|
19396
20560
|
trackCount - 1,
|
|
@@ -19419,6 +20583,7 @@ function useMarqueeSelection({
|
|
|
19419
20583
|
labelWidth,
|
|
19420
20584
|
trackCount,
|
|
19421
20585
|
trackHeight,
|
|
20586
|
+
separatorHeight,
|
|
19422
20587
|
tracks,
|
|
19423
20588
|
onMarqueeSelect,
|
|
19424
20589
|
onEmptyClick,
|
|
@@ -19555,7 +20720,7 @@ function getTrackOrSeparatorAt(clientY, containerTop, trackHeight) {
|
|
|
19555
20720
|
return { type: "separator", separatorIndex: index + 1 };
|
|
19556
20721
|
}
|
|
19557
20722
|
const LABEL_WIDTH = 40;
|
|
19558
|
-
const TRACK_HEIGHT =
|
|
20723
|
+
const TRACK_HEIGHT = 52;
|
|
19559
20724
|
const SEPARATOR_HEIGHT = 6;
|
|
19560
20725
|
function TimelineView({
|
|
19561
20726
|
zoomLevel,
|
|
@@ -19578,7 +20743,11 @@ function TimelineView({
|
|
|
19578
20743
|
onDropOnTimeline,
|
|
19579
20744
|
videoResolution,
|
|
19580
20745
|
enableDropOnTimeline = true,
|
|
19581
|
-
chapters = []
|
|
20746
|
+
chapters = [],
|
|
20747
|
+
currentTime = 0,
|
|
20748
|
+
onContextMenuTarget,
|
|
20749
|
+
onDeleteElement,
|
|
20750
|
+
onSplitElement
|
|
19582
20751
|
}) {
|
|
19583
20752
|
const containerRef = useRef(null);
|
|
19584
20753
|
const seekContainerRef = useRef(null);
|
|
@@ -19694,6 +20863,7 @@ function TimelineView({
|
|
|
19694
20863
|
labelWidth: LABEL_WIDTH,
|
|
19695
20864
|
trackCount: (tracks == null ? void 0 : tracks.length) ?? 0,
|
|
19696
20865
|
trackHeight: TRACK_HEIGHT,
|
|
20866
|
+
separatorHeight: SEPARATOR_HEIGHT,
|
|
19697
20867
|
tracks: tracks ?? [],
|
|
19698
20868
|
containerRef: timelineContentRef,
|
|
19699
20869
|
onMarqueeSelect,
|
|
@@ -19707,6 +20877,7 @@ function TimelineView({
|
|
|
19707
20877
|
zoomLevel,
|
|
19708
20878
|
labelWidth: LABEL_WIDTH,
|
|
19709
20879
|
trackHeight: TRACK_HEIGHT,
|
|
20880
|
+
separatorHeight: SEPARATOR_HEIGHT,
|
|
19710
20881
|
trackContentWidth: timelineWidth - LABEL_WIDTH,
|
|
19711
20882
|
onDrop: onDropOnTimeline ?? (async () => {
|
|
19712
20883
|
}),
|
|
@@ -19802,7 +20973,7 @@ function TimelineView({
|
|
|
19802
20973
|
style: {
|
|
19803
20974
|
position: "absolute",
|
|
19804
20975
|
left: LABEL_WIDTH + preview.timeSec / duration * (timelineWidth - LABEL_WIDTH),
|
|
19805
|
-
top: preview.trackIndex * TRACK_HEIGHT + 2,
|
|
20976
|
+
top: SEPARATOR_HEIGHT + preview.trackIndex * (TRACK_HEIGHT + SEPARATOR_HEIGHT) + 2,
|
|
19806
20977
|
width: preview.widthPct / 100 * (timelineWidth - LABEL_WIDTH),
|
|
19807
20978
|
height: TRACK_HEIGHT - 4
|
|
19808
20979
|
}
|
|
@@ -19847,7 +21018,11 @@ function TimelineView({
|
|
|
19847
21018
|
onDragStateChange: (isDragging2, el) => {
|
|
19848
21019
|
setDraggingElementId(isDragging2 && el ? el.getId() : null);
|
|
19849
21020
|
},
|
|
19850
|
-
elementColors
|
|
21021
|
+
elementColors,
|
|
21022
|
+
currentTime,
|
|
21023
|
+
onContextMenuTarget,
|
|
21024
|
+
onDeleteElement,
|
|
21025
|
+
onSplitElement
|
|
19851
21026
|
}
|
|
19852
21027
|
)
|
|
19853
21028
|
] }),
|
|
@@ -19906,7 +21081,8 @@ const useTimelineManager = () => {
|
|
|
19906
21081
|
if (dragType === DRAG_TYPE.START) {
|
|
19907
21082
|
if (element instanceof VideoElement$1 || element instanceof AudioElement) {
|
|
19908
21083
|
const elementProps = element.getProps();
|
|
19909
|
-
const
|
|
21084
|
+
const playbackRate = (elementProps == null ? void 0 : elementProps.playbackRate) || 1;
|
|
21085
|
+
const delta = (updates.start - element.getStart()) * playbackRate;
|
|
19910
21086
|
if (element instanceof AudioElement) {
|
|
19911
21087
|
element.setStartAt(element.getStartAt() + delta);
|
|
19912
21088
|
} else {
|
|
@@ -20019,6 +21195,38 @@ const useTimelineManager = () => {
|
|
|
20019
21195
|
totalDuration
|
|
20020
21196
|
};
|
|
20021
21197
|
};
|
|
21198
|
+
const useTimelineControl = () => {
|
|
21199
|
+
const { editor, setSelectedItem, selectedIds } = useTimelineContext();
|
|
21200
|
+
const deleteItem = (item) => {
|
|
21201
|
+
var _a;
|
|
21202
|
+
const tracks = ((_a = editor.getTimelineData()) == null ? void 0 : _a.tracks) ?? [];
|
|
21203
|
+
const toDelete = item !== void 0 ? [item] : resolveIds(selectedIds, tracks);
|
|
21204
|
+
for (const el of toDelete) {
|
|
21205
|
+
if (el instanceof Track) {
|
|
21206
|
+
editor.removeTrack(el);
|
|
21207
|
+
} else if (el instanceof TrackElement) {
|
|
21208
|
+
editor.removeElement(el);
|
|
21209
|
+
}
|
|
21210
|
+
}
|
|
21211
|
+
setSelectedItem(null);
|
|
21212
|
+
};
|
|
21213
|
+
const splitElement = (element, currentTime) => {
|
|
21214
|
+
if (!canSplitElement(element, currentTime)) return;
|
|
21215
|
+
void editor.splitElement(element, currentTime);
|
|
21216
|
+
};
|
|
21217
|
+
const handleUndo = () => {
|
|
21218
|
+
editor.undo();
|
|
21219
|
+
};
|
|
21220
|
+
const handleRedo = () => {
|
|
21221
|
+
editor.redo();
|
|
21222
|
+
};
|
|
21223
|
+
return {
|
|
21224
|
+
splitElement,
|
|
21225
|
+
deleteItem,
|
|
21226
|
+
handleUndo,
|
|
21227
|
+
handleRedo
|
|
21228
|
+
};
|
|
21229
|
+
};
|
|
20022
21230
|
function useTimelineSelection() {
|
|
20023
21231
|
const { editor, selectedIds, setSelection, setSelectedItem } = useTimelineContext();
|
|
20024
21232
|
const handleItemSelect = useCallback(
|
|
@@ -20099,8 +21307,9 @@ const TimelineManager = ({
|
|
|
20099
21307
|
elementColors
|
|
20100
21308
|
}) => {
|
|
20101
21309
|
var _a, _b;
|
|
20102
|
-
const { playerState } = useLivePlayerContext();
|
|
21310
|
+
const { playerState, currentTime } = useLivePlayerContext();
|
|
20103
21311
|
const { followPlayheadEnabled, editor, videoResolution, setSelectedItem } = useTimelineContext();
|
|
21312
|
+
const { deleteItem, splitElement } = useTimelineControl();
|
|
20104
21313
|
const {
|
|
20105
21314
|
timelineData,
|
|
20106
21315
|
totalDuration,
|
|
@@ -20121,6 +21330,12 @@ const TimelineManager = ({
|
|
|
20121
21330
|
setPlayheadState(state);
|
|
20122
21331
|
}, []);
|
|
20123
21332
|
const isPlayheadActive = followPlayheadEnabled && playerState === PLAYER_STATE.PLAYING || playheadState.isDragging;
|
|
21333
|
+
const handleContextMenuTarget = useCallback(
|
|
21334
|
+
(element) => {
|
|
21335
|
+
setSelectedItem(element);
|
|
21336
|
+
},
|
|
21337
|
+
[setSelectedItem]
|
|
21338
|
+
);
|
|
20124
21339
|
const handleDropOnTimeline = useCallback(
|
|
20125
21340
|
async (params) => {
|
|
20126
21341
|
const { track, timeSec, type, url } = params;
|
|
@@ -20168,6 +21383,10 @@ const TimelineManager = ({
|
|
|
20168
21383
|
onEmptyClick: handleEmptyClick,
|
|
20169
21384
|
onMarqueeSelect: handleMarqueeSelect,
|
|
20170
21385
|
elementColors,
|
|
21386
|
+
currentTime,
|
|
21387
|
+
onContextMenuTarget: handleContextMenuTarget,
|
|
21388
|
+
onDeleteElement: (el) => deleteItem(el),
|
|
21389
|
+
onSplitElement: (el, t2) => splitElement(el, t2),
|
|
20171
21390
|
playheadPositionPx: playheadState.positionPx,
|
|
20172
21391
|
isPlayheadActive,
|
|
20173
21392
|
chapters: ((_a = timelineData == null ? void 0 : timelineData.metadata) == null ? void 0 : _a.chapters) ?? [],
|
|
@@ -20251,11 +21470,12 @@ const PlayerControls = ({
|
|
|
20251
21470
|
}
|
|
20252
21471
|
}, [selectedIds.size, onDelete]);
|
|
20253
21472
|
const hasSelection = selectedIds.size > 0;
|
|
21473
|
+
const canSplitSelected = selectedItem instanceof TrackElement ? canSplitElement(selectedItem, currentTime) : false;
|
|
20254
21474
|
const handleSplit = useCallback(() => {
|
|
20255
|
-
if (selectedItem instanceof TrackElement && onSplit) {
|
|
21475
|
+
if (selectedItem instanceof TrackElement && onSplit && canSplitSelected) {
|
|
20256
21476
|
onSplit(selectedItem, currentTime);
|
|
20257
21477
|
}
|
|
20258
|
-
}, [selectedItem, onSplit, currentTime]);
|
|
21478
|
+
}, [selectedItem, onSplit, currentTime, canSplitSelected]);
|
|
20259
21479
|
const handleZoomIn = useCallback(() => {
|
|
20260
21480
|
if (setZoomLevel && zoomLevel < MAX_ZOOM) {
|
|
20261
21481
|
setZoomLevel(zoomLevel + ZOOM_STEP);
|
|
@@ -20282,9 +21502,9 @@ const PlayerControls = ({
|
|
|
20282
21502
|
"button",
|
|
20283
21503
|
{
|
|
20284
21504
|
onClick: handleSplit,
|
|
20285
|
-
disabled: !
|
|
21505
|
+
disabled: !canSplitSelected,
|
|
20286
21506
|
title: "Split",
|
|
20287
|
-
className: `control-btn split-btn ${!
|
|
21507
|
+
className: `control-btn split-btn ${!canSplitSelected ? "btn-disabled" : ""}`,
|
|
20288
21508
|
children: /* @__PURE__ */ jsx(Scissors, { className: "icon-md" })
|
|
20289
21509
|
}
|
|
20290
21510
|
),
|
|
@@ -20398,37 +21618,6 @@ const usePlayerControl = () => {
|
|
|
20398
21618
|
togglePlayback
|
|
20399
21619
|
};
|
|
20400
21620
|
};
|
|
20401
|
-
const useTimelineControl = () => {
|
|
20402
|
-
const { editor, setSelectedItem, selectedIds } = useTimelineContext();
|
|
20403
|
-
const deleteItem = (item) => {
|
|
20404
|
-
var _a;
|
|
20405
|
-
const tracks = ((_a = editor.getTimelineData()) == null ? void 0 : _a.tracks) ?? [];
|
|
20406
|
-
const toDelete = item !== void 0 ? [item] : resolveIds(selectedIds, tracks);
|
|
20407
|
-
for (const el of toDelete) {
|
|
20408
|
-
if (el instanceof Track) {
|
|
20409
|
-
editor.removeTrack(el);
|
|
20410
|
-
} else if (el instanceof TrackElement) {
|
|
20411
|
-
editor.removeElement(el);
|
|
20412
|
-
}
|
|
20413
|
-
}
|
|
20414
|
-
setSelectedItem(null);
|
|
20415
|
-
};
|
|
20416
|
-
const splitElement = (element, currentTime) => {
|
|
20417
|
-
editor.splitElement(element, currentTime);
|
|
20418
|
-
};
|
|
20419
|
-
const handleUndo = () => {
|
|
20420
|
-
editor.undo();
|
|
20421
|
-
};
|
|
20422
|
-
const handleRedo = () => {
|
|
20423
|
-
editor.redo();
|
|
20424
|
-
};
|
|
20425
|
-
return {
|
|
20426
|
-
splitElement,
|
|
20427
|
-
deleteItem,
|
|
20428
|
-
handleUndo,
|
|
20429
|
-
handleRedo
|
|
20430
|
-
};
|
|
20431
|
-
};
|
|
20432
21621
|
function shouldIgnoreKeydown() {
|
|
20433
21622
|
const active = document.activeElement;
|
|
20434
21623
|
if (!active) return false;
|