@twick/studio 0.15.27 → 0.15.28

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/index.js CHANGED
@@ -8,6 +8,7 @@ const react = require("react");
8
8
  const timeline = require("@twick/timeline");
9
9
  const VideoEditor = require("@twick/video-editor");
10
10
  const livePlayer = require("@twick/live-player");
11
+ const mediaUtils = require("@twick/media-utils");
11
12
  /**
12
13
  * @license lucide-react v0.511.0 - ISC
13
14
  *
@@ -113,103 +114,103 @@ const createLucideIcon = (iconName, iconNode) => {
113
114
  * This source code is licensed under the ISC license.
114
115
  * See the LICENSE file in the root directory of this source tree.
115
116
  */
116
- const __iconNode$A = [
117
+ const __iconNode$C = [
117
118
  ["path", { d: "M17 12H7", key: "16if0g" }],
118
119
  ["path", { d: "M19 18H5", key: "18s9l3" }],
119
120
  ["path", { d: "M21 6H3", key: "1jwq7v" }]
120
121
  ];
121
- const AlignCenter = createLucideIcon("align-center", __iconNode$A);
122
+ const AlignCenter = createLucideIcon("align-center", __iconNode$C);
122
123
  /**
123
124
  * @license lucide-react v0.511.0 - ISC
124
125
  *
125
126
  * This source code is licensed under the ISC license.
126
127
  * See the LICENSE file in the root directory of this source tree.
127
128
  */
128
- const __iconNode$z = [
129
+ const __iconNode$B = [
129
130
  ["path", { d: "M15 12H3", key: "6jk70r" }],
130
131
  ["path", { d: "M17 18H3", key: "1amg6g" }],
131
132
  ["path", { d: "M21 6H3", key: "1jwq7v" }]
132
133
  ];
133
- const AlignLeft = createLucideIcon("align-left", __iconNode$z);
134
+ const AlignLeft = createLucideIcon("align-left", __iconNode$B);
134
135
  /**
135
136
  * @license lucide-react v0.511.0 - ISC
136
137
  *
137
138
  * This source code is licensed under the ISC license.
138
139
  * See the LICENSE file in the root directory of this source tree.
139
140
  */
140
- const __iconNode$y = [
141
+ const __iconNode$A = [
141
142
  ["path", { d: "M21 12H9", key: "dn1m92" }],
142
143
  ["path", { d: "M21 18H7", key: "1ygte8" }],
143
144
  ["path", { d: "M21 6H3", key: "1jwq7v" }]
144
145
  ];
145
- const AlignRight = createLucideIcon("align-right", __iconNode$y);
146
+ const AlignRight = createLucideIcon("align-right", __iconNode$A);
146
147
  /**
147
148
  * @license lucide-react v0.511.0 - ISC
148
149
  *
149
150
  * This source code is licensed under the ISC license.
150
151
  * See the LICENSE file in the root directory of this source tree.
151
152
  */
152
- const __iconNode$x = [
153
+ const __iconNode$z = [
153
154
  [
154
155
  "path",
155
156
  { d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
156
157
  ]
157
158
  ];
158
- const Bold = createLucideIcon("bold", __iconNode$x);
159
+ const Bold = createLucideIcon("bold", __iconNode$z);
159
160
  /**
160
161
  * @license lucide-react v0.511.0 - ISC
161
162
  *
162
163
  * This source code is licensed under the ISC license.
163
164
  * See the LICENSE file in the root directory of this source tree.
164
165
  */
165
- const __iconNode$w = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
166
- const ChevronDown = createLucideIcon("chevron-down", __iconNode$w);
166
+ const __iconNode$y = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
167
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$y);
167
168
  /**
168
169
  * @license lucide-react v0.511.0 - ISC
169
170
  *
170
171
  * This source code is licensed under the ISC license.
171
172
  * See the LICENSE file in the root directory of this source tree.
172
173
  */
173
- const __iconNode$v = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
174
- const ChevronRight = createLucideIcon("chevron-right", __iconNode$v);
174
+ const __iconNode$x = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
175
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$x);
175
176
  /**
176
177
  * @license lucide-react v0.511.0 - ISC
177
178
  *
178
179
  * This source code is licensed under the ISC license.
179
180
  * See the LICENSE file in the root directory of this source tree.
180
181
  */
181
- const __iconNode$u = [
182
+ const __iconNode$w = [
182
183
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
183
184
  ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
184
185
  ];
185
- const CircleCheck = createLucideIcon("circle-check", __iconNode$u);
186
+ const CircleCheck = createLucideIcon("circle-check", __iconNode$w);
186
187
  /**
187
188
  * @license lucide-react v0.511.0 - ISC
188
189
  *
189
190
  * This source code is licensed under the ISC license.
190
191
  * See the LICENSE file in the root directory of this source tree.
191
192
  */
192
- const __iconNode$t = [
193
+ const __iconNode$v = [
193
194
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
194
195
  ["path", { d: "m15 9-6 6", key: "1uzhvr" }],
195
196
  ["path", { d: "m9 9 6 6", key: "z0biqf" }]
196
197
  ];
197
- const CircleX = createLucideIcon("circle-x", __iconNode$t);
198
+ const CircleX = createLucideIcon("circle-x", __iconNode$v);
198
199
  /**
199
200
  * @license lucide-react v0.511.0 - ISC
200
201
  *
201
202
  * This source code is licensed under the ISC license.
202
203
  * See the LICENSE file in the root directory of this source tree.
203
204
  */
204
- const __iconNode$s = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
205
- const Circle = createLucideIcon("circle", __iconNode$s);
205
+ const __iconNode$u = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
206
+ const Circle = createLucideIcon("circle", __iconNode$u);
206
207
  /**
207
208
  * @license lucide-react v0.511.0 - ISC
208
209
  *
209
210
  * This source code is licensed under the ISC license.
210
211
  * See the LICENSE file in the root directory of this source tree.
211
212
  */
212
- const __iconNode$r = [
213
+ const __iconNode$t = [
213
214
  [
214
215
  "path",
215
216
  { d: "M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z", key: "1tn4o7" }
@@ -218,102 +219,118 @@ const __iconNode$r = [
218
219
  ["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
219
220
  ["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
220
221
  ];
221
- const Clapperboard = createLucideIcon("clapperboard", __iconNode$r);
222
+ const Clapperboard = createLucideIcon("clapperboard", __iconNode$t);
222
223
  /**
223
224
  * @license lucide-react v0.511.0 - ISC
224
225
  *
225
226
  * This source code is licensed under the ISC license.
226
227
  * See the LICENSE file in the root directory of this source tree.
227
228
  */
228
- const __iconNode$q = [
229
+ const __iconNode$s = [
229
230
  ["path", { d: "M12 15V3", key: "m9g1x1" }],
230
231
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
231
232
  ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
232
233
  ];
233
- const Download = createLucideIcon("download", __iconNode$q);
234
+ const Download = createLucideIcon("download", __iconNode$s);
234
235
  /**
235
236
  * @license lucide-react v0.511.0 - ISC
236
237
  *
237
238
  * This source code is licensed under the ISC license.
238
239
  * See the LICENSE file in the root directory of this source tree.
239
240
  */
240
- const __iconNode$p = [
241
+ const __iconNode$r = [
241
242
  ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
242
243
  ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
243
244
  ];
244
- const File = createLucideIcon("file", __iconNode$p);
245
+ const File = createLucideIcon("file", __iconNode$r);
245
246
  /**
246
247
  * @license lucide-react v0.511.0 - ISC
247
248
  *
248
249
  * This source code is licensed under the ISC license.
249
250
  * See the LICENSE file in the root directory of this source tree.
250
251
  */
251
- const __iconNode$o = [
252
+ const __iconNode$q = [
253
+ [
254
+ "path",
255
+ {
256
+ d: "M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z",
257
+ key: "sc7q7i"
258
+ }
259
+ ]
260
+ ];
261
+ const Funnel = createLucideIcon("funnel", __iconNode$q);
262
+ /**
263
+ * @license lucide-react v0.511.0 - ISC
264
+ *
265
+ * This source code is licensed under the ISC license.
266
+ * See the LICENSE file in the root directory of this source tree.
267
+ */
268
+ const __iconNode$p = [
252
269
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
253
270
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
254
271
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
255
272
  ];
256
- const Image = createLucideIcon("image", __iconNode$o);
273
+ const Image$1 = createLucideIcon("image", __iconNode$p);
257
274
  /**
258
275
  * @license lucide-react v0.511.0 - ISC
259
276
  *
260
277
  * This source code is licensed under the ISC license.
261
278
  * See the LICENSE file in the root directory of this source tree.
262
279
  */
263
- const __iconNode$n = [
280
+ const __iconNode$o = [
264
281
  ["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
265
282
  ["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
266
283
  ["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
267
284
  ];
268
- const Italic = createLucideIcon("italic", __iconNode$n);
285
+ const Italic = createLucideIcon("italic", __iconNode$o);
269
286
  /**
270
287
  * @license lucide-react v0.511.0 - ISC
271
288
  *
272
289
  * This source code is licensed under the ISC license.
273
290
  * See the LICENSE file in the root directory of this source tree.
274
291
  */
275
- const __iconNode$m = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
276
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$m);
292
+ const __iconNode$n = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
293
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$n);
277
294
  /**
278
295
  * @license lucide-react v0.511.0 - ISC
279
296
  *
280
297
  * This source code is licensed under the ISC license.
281
298
  * See the LICENSE file in the root directory of this source tree.
282
299
  */
283
- const __iconNode$l = [
300
+ const __iconNode$m = [
284
301
  ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
285
302
  ];
286
- const MessageSquare = createLucideIcon("message-square", __iconNode$l);
303
+ const MessageSquare = createLucideIcon("message-square", __iconNode$m);
287
304
  /**
288
305
  * @license lucide-react v0.511.0 - ISC
289
306
  *
290
307
  * This source code is licensed under the ISC license.
291
308
  * See the LICENSE file in the root directory of this source tree.
292
309
  */
293
- const __iconNode$k = [
310
+ const __iconNode$l = [
294
311
  ["circle", { cx: "8", cy: "18", r: "4", key: "1fc0mg" }],
295
312
  ["path", { d: "M12 18V2l7 4", key: "g04rme" }]
296
313
  ];
297
- const Music2 = createLucideIcon("music-2", __iconNode$k);
314
+ const Music2 = createLucideIcon("music-2", __iconNode$l);
298
315
  /**
299
316
  * @license lucide-react v0.511.0 - ISC
300
317
  *
301
318
  * This source code is licensed under the ISC license.
302
319
  * See the LICENSE file in the root directory of this source tree.
303
320
  */
304
- const __iconNode$j = [
321
+ const __iconNode$k = [
305
322
  ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
306
323
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
307
324
  ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
308
325
  ];
309
- const Music = createLucideIcon("music", __iconNode$j);
326
+ const Music = createLucideIcon("music", __iconNode$k);
310
327
  /**
311
328
  * @license lucide-react v0.511.0 - ISC
312
329
  *
313
330
  * This source code is licensed under the ISC license.
314
331
  * See the LICENSE file in the root directory of this source tree.
315
332
  */
316
- const __iconNode$i = [
333
+ const __iconNode$j = [
317
334
  [
318
335
  "path",
319
336
  {
@@ -326,64 +343,64 @@ const __iconNode$i = [
326
343
  ["circle", { cx: "6.5", cy: "12.5", r: ".5", fill: "currentColor", key: "qy21gx" }],
327
344
  ["circle", { cx: "8.5", cy: "7.5", r: ".5", fill: "currentColor", key: "fotxhn" }]
328
345
  ];
329
- const Palette = createLucideIcon("palette", __iconNode$i);
346
+ const Palette = createLucideIcon("palette", __iconNode$j);
330
347
  /**
331
348
  * @license lucide-react v0.511.0 - ISC
332
349
  *
333
350
  * This source code is licensed under the ISC license.
334
351
  * See the LICENSE file in the root directory of this source tree.
335
352
  */
336
- const __iconNode$h = [
353
+ const __iconNode$i = [
337
354
  ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
338
355
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
339
356
  ];
340
- const Pause = createLucideIcon("pause", __iconNode$h);
357
+ const Pause = createLucideIcon("pause", __iconNode$i);
341
358
  /**
342
359
  * @license lucide-react v0.511.0 - ISC
343
360
  *
344
361
  * This source code is licensed under the ISC license.
345
362
  * See the LICENSE file in the root directory of this source tree.
346
363
  */
347
- const __iconNode$g = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
348
- const Play = createLucideIcon("play", __iconNode$g);
364
+ const __iconNode$h = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
365
+ const Play = createLucideIcon("play", __iconNode$h);
349
366
  /**
350
367
  * @license lucide-react v0.511.0 - ISC
351
368
  *
352
369
  * This source code is licensed under the ISC license.
353
370
  * See the LICENSE file in the root directory of this source tree.
354
371
  */
355
- const __iconNode$f = [
372
+ const __iconNode$g = [
356
373
  ["path", { d: "M5 12h14", key: "1ays0h" }],
357
374
  ["path", { d: "M12 5v14", key: "s699le" }]
358
375
  ];
359
- const Plus = createLucideIcon("plus", __iconNode$f);
376
+ const Plus = createLucideIcon("plus", __iconNode$g);
360
377
  /**
361
378
  * @license lucide-react v0.511.0 - ISC
362
379
  *
363
380
  * This source code is licensed under the ISC license.
364
381
  * See the LICENSE file in the root directory of this source tree.
365
382
  */
366
- const __iconNode$e = [
383
+ const __iconNode$f = [
367
384
  ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
368
385
  ];
369
- const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$e);
386
+ const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$f);
370
387
  /**
371
388
  * @license lucide-react v0.511.0 - ISC
372
389
  *
373
390
  * This source code is licensed under the ISC license.
374
391
  * See the LICENSE file in the root directory of this source tree.
375
392
  */
376
- const __iconNode$d = [
393
+ const __iconNode$e = [
377
394
  ["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2", key: "1oxtiu" }]
378
395
  ];
379
- const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$d);
396
+ const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$e);
380
397
  /**
381
398
  * @license lucide-react v0.511.0 - ISC
382
399
  *
383
400
  * This source code is licensed under the ISC license.
384
401
  * See the LICENSE file in the root directory of this source tree.
385
402
  */
386
- const __iconNode$c = [
403
+ const __iconNode$d = [
387
404
  [
388
405
  "path",
389
406
  {
@@ -396,14 +413,14 @@ const __iconNode$c = [
396
413
  ["path", { d: "m8.5 6.5 2-2", key: "vc6u1g" }],
397
414
  ["path", { d: "m17.5 15.5 2-2", key: "wo5hmg" }]
398
415
  ];
399
- const Ruler = createLucideIcon("ruler", __iconNode$c);
416
+ const Ruler = createLucideIcon("ruler", __iconNode$d);
400
417
  /**
401
418
  * @license lucide-react v0.511.0 - ISC
402
419
  *
403
420
  * This source code is licensed under the ISC license.
404
421
  * See the LICENSE file in the root directory of this source tree.
405
422
  */
406
- const __iconNode$b = [
423
+ const __iconNode$c = [
407
424
  [
408
425
  "path",
409
426
  {
@@ -414,32 +431,45 @@ const __iconNode$b = [
414
431
  ["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7", key: "1ydtos" }],
415
432
  ["path", { d: "M7 3v4a1 1 0 0 0 1 1h7", key: "t51u73" }]
416
433
  ];
417
- const Save = createLucideIcon("save", __iconNode$b);
434
+ const Save = createLucideIcon("save", __iconNode$c);
418
435
  /**
419
436
  * @license lucide-react v0.511.0 - ISC
420
437
  *
421
438
  * This source code is licensed under the ISC license.
422
439
  * See the LICENSE file in the root directory of this source tree.
423
440
  */
424
- const __iconNode$a = [
441
+ const __iconNode$b = [
425
442
  ["circle", { cx: "6", cy: "6", r: "3", key: "1lh9wr" }],
426
443
  ["path", { d: "M8.12 8.12 12 12", key: "1alkpv" }],
427
444
  ["path", { d: "M20 4 8.12 15.88", key: "xgtan2" }],
428
445
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
429
446
  ["path", { d: "M14.8 14.8 20 20", key: "ptml3r" }]
430
447
  ];
431
- const Scissors = createLucideIcon("scissors", __iconNode$a);
448
+ const Scissors = createLucideIcon("scissors", __iconNode$b);
432
449
  /**
433
450
  * @license lucide-react v0.511.0 - ISC
434
451
  *
435
452
  * This source code is licensed under the ISC license.
436
453
  * See the LICENSE file in the root directory of this source tree.
437
454
  */
438
- const __iconNode$9 = [
455
+ const __iconNode$a = [
439
456
  ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
440
457
  ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
441
458
  ];
442
- const Search = createLucideIcon("search", __iconNode$9);
459
+ const Search = createLucideIcon("search", __iconNode$a);
460
+ /**
461
+ * @license lucide-react v0.511.0 - ISC
462
+ *
463
+ * This source code is licensed under the ISC license.
464
+ * See the LICENSE file in the root directory of this source tree.
465
+ */
466
+ const __iconNode$9 = [
467
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
468
+ ["path", { d: "M8 14s1.5 2 4 2 4-2 4-2", key: "1y1vjs" }],
469
+ ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9", key: "yxxnd0" }],
470
+ ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9", key: "1p4y9e" }]
471
+ ];
472
+ const Smile = createLucideIcon("smile", __iconNode$9);
443
473
  /**
444
474
  * @license lucide-react v0.511.0 - ISC
445
475
  *
@@ -591,6 +621,7 @@ const defaultToolCategories = [
591
621
  { id: "image", name: "Image", icon: "Image", description: "Add an image element" },
592
622
  { id: "audio", name: "Audio", icon: "Audio", description: "Add an audio element" },
593
623
  { id: "text", name: "Text", icon: "Type", description: "Add text elements" },
624
+ { id: "emoji", name: "Emoji", icon: "Smile", description: "Add emoji stickers" },
594
625
  { id: "text-style", name: "Text Style", icon: "Type", description: "Apply text style presets" },
595
626
  { id: "effect", name: "Effect", icon: "Wand2", description: "Apply GL video effects" },
596
627
  { id: "shape", name: "Shape", icon: "Square", description: "Add lines, arrows, boxes, and circles" },
@@ -610,7 +641,7 @@ const getIcon = (iconName) => {
610
641
  case "Square":
611
642
  return Square;
612
643
  case "Image":
613
- return Image;
644
+ return Image$1;
614
645
  case "Video":
615
646
  return Video;
616
647
  case "Audio":
@@ -625,6 +656,8 @@ const getIcon = (iconName) => {
625
656
  return WandSparkles;
626
657
  case "File":
627
658
  return File;
659
+ case "Smile":
660
+ return Smile;
628
661
  default:
629
662
  return Plus;
630
663
  }
@@ -2999,6 +3032,155 @@ function TextPanelContainer(props) {
2999
3032
  const textPanelProps = useTextPanel(props);
3000
3033
  return /* @__PURE__ */ jsxRuntime.jsx(TextPanel, { ...textPanelProps });
3001
3034
  }
3035
+ const SUPPORTED_EMOJIS = [
3036
+ { emoji: "😀", category: "Smileys" },
3037
+ { emoji: "😁", category: "Smileys" },
3038
+ { emoji: "😂", category: "Smileys" },
3039
+ { emoji: "🤣", category: "Smileys" },
3040
+ { emoji: "😍", category: "Smileys" },
3041
+ { emoji: "🤩", category: "Smileys" },
3042
+ { emoji: "😎", category: "Smileys" },
3043
+ { emoji: "🥳", category: "Smileys" },
3044
+ { emoji: "🤯", category: "Smileys" },
3045
+ { emoji: "🤔", category: "Smileys" },
3046
+ { emoji: "😅", category: "Smileys" },
3047
+ { emoji: "😮", category: "Smileys" },
3048
+ { emoji: "🥹", category: "Smileys" },
3049
+ { emoji: "🔥", category: "Symbols" },
3050
+ { emoji: "✨", category: "Symbols" },
3051
+ { emoji: "⭐", category: "Symbols" },
3052
+ { emoji: "💥", category: "Symbols" },
3053
+ { emoji: "💯", category: "Symbols" },
3054
+ { emoji: "✅", category: "Symbols" },
3055
+ { emoji: "❌", category: "Symbols" },
3056
+ { emoji: "⚡", category: "Symbols" },
3057
+ { emoji: "❤️", category: "Symbols" },
3058
+ { emoji: "🏆", category: "Objects" },
3059
+ { emoji: "💡", category: "Objects" },
3060
+ { emoji: "📈", category: "Objects" },
3061
+ { emoji: "📣", category: "Objects" },
3062
+ { emoji: "🧠", category: "Objects" },
3063
+ { emoji: "👀", category: "Objects" },
3064
+ { emoji: "🤖", category: "Objects" },
3065
+ { emoji: "👍", category: "Hands" },
3066
+ { emoji: "👏", category: "Hands" },
3067
+ { emoji: "🙌", category: "Hands" },
3068
+ { emoji: "🤝", category: "Hands" },
3069
+ { emoji: "🙏", category: "Hands" },
3070
+ { emoji: "🚀", category: "Media" },
3071
+ { emoji: "🎯", category: "Media" },
3072
+ { emoji: "🎉", category: "Media" },
3073
+ { emoji: "🎬", category: "Media" },
3074
+ { emoji: "🎥", category: "Media" },
3075
+ { emoji: "🎧", category: "Media" }
3076
+ ];
3077
+ const CDN_BASE = "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72";
3078
+ function toCodePointString(emoji) {
3079
+ return Array.from(emoji).map((char) => {
3080
+ var _a;
3081
+ return (_a = char.codePointAt(0)) == null ? void 0 : _a.toString(16);
3082
+ }).filter(Boolean).join("-");
3083
+ }
3084
+ const EMOJI_CATEGORIES = [
3085
+ "All",
3086
+ "Smileys",
3087
+ "Symbols",
3088
+ "Objects",
3089
+ "Hands",
3090
+ "Media"
3091
+ ];
3092
+ const EMOJI_CATALOG = SUPPORTED_EMOJIS.map(
3093
+ ({ emoji, category }) => ({
3094
+ emoji,
3095
+ category,
3096
+ label: emoji,
3097
+ imageUrl: `${CDN_BASE}/${toCodePointString(emoji)}.png`
3098
+ })
3099
+ );
3100
+ function EmojiPanel({ items, onItemSelect }) {
3101
+ const [searchQuery, setSearchQuery] = react.useState("");
3102
+ const [activeCategory, setActiveCategory] = react.useState("All");
3103
+ const filteredItems = react.useMemo(() => {
3104
+ const query = searchQuery.trim().toLowerCase();
3105
+ return items.filter((item) => {
3106
+ const categoryMatch = activeCategory === "All" ? true : item.category === activeCategory;
3107
+ if (!categoryMatch) return false;
3108
+ if (!query) return true;
3109
+ return item.emoji.includes(query) || item.label.toLowerCase().includes(query) || item.category.toLowerCase().includes(query);
3110
+ });
3111
+ }, [items, searchQuery, activeCategory]);
3112
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3113
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Emoji Stickers" }),
3114
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
3115
+ "input",
3116
+ {
3117
+ type: "text",
3118
+ placeholder: "Search emoji...",
3119
+ value: searchQuery,
3120
+ onChange: (e) => setSearchQuery(e.target.value),
3121
+ className: "input-dark"
3122
+ }
3123
+ ) }),
3124
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "emoji-categories", children: EMOJI_CATEGORIES.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
3125
+ "button",
3126
+ {
3127
+ type: "button",
3128
+ className: `emoji-category-chip ${activeCategory === category ? "emoji-category-chip-active" : ""}`,
3129
+ onClick: () => setActiveCategory(category),
3130
+ children: category
3131
+ },
3132
+ category
3133
+ )) }),
3134
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "emoji-grid", children: filteredItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
3135
+ "button",
3136
+ {
3137
+ type: "button",
3138
+ className: "emoji-item",
3139
+ title: item.label,
3140
+ onClick: () => onItemSelect(item),
3141
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3142
+ "img",
3143
+ {
3144
+ src: item.imageUrl,
3145
+ alt: item.label,
3146
+ className: "emoji-item-image",
3147
+ loading: "lazy"
3148
+ }
3149
+ )
3150
+ },
3151
+ `${item.emoji}-${item.imageUrl}`
3152
+ )) }),
3153
+ filteredItems.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state-content", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No emoji found" }) }) })
3154
+ ] });
3155
+ }
3156
+ function EmojiPanelContainer({ addElement, videoResolution }) {
3157
+ const emojiFallbackFont = "Poppins, Noto Color Emoji, Apple Color Emoji, Segoe UI Emoji, sans-serif";
3158
+ const canLoadEmojiImage = async (url) => new Promise((resolve) => {
3159
+ let settled = false;
3160
+ const done = (value) => {
3161
+ if (settled) return;
3162
+ settled = true;
3163
+ resolve(value);
3164
+ };
3165
+ const img = new Image();
3166
+ img.onload = () => done(true);
3167
+ img.onerror = () => done(false);
3168
+ img.src = url;
3169
+ window.setTimeout(() => done(false), 3e3);
3170
+ });
3171
+ const handleSelection = async (item) => {
3172
+ if (!addElement) return;
3173
+ const canUseImage = await canLoadEmojiImage(item.imageUrl);
3174
+ if (canUseImage) {
3175
+ const element = new timeline.EmojiElement(item.emoji, item.imageUrl, videoResolution).setName(`Emoji ${item.emoji}`).setEnd(5);
3176
+ await addElement(element);
3177
+ return;
3178
+ }
3179
+ const fallbackText = new timeline.TextElement(item.emoji).setName(`Emoji ${item.emoji} (fallback)`).setFontFamily(emojiFallbackFont).setFontSize(Math.round(videoResolution.height * 0.12)).setFill("#FFFFFF").setLineWidth(0).setEnd(5);
3180
+ await addElement(fallbackText);
3181
+ };
3182
+ return /* @__PURE__ */ jsxRuntime.jsx(EmojiPanel, { items: EMOJI_CATALOG, onItemSelect: handleSelection });
3183
+ }
3002
3184
  const TEXT_STYLE_PRESETS = [
3003
3185
  // Utility / captions
3004
3186
  {
@@ -4724,7 +4906,15 @@ function CaptionsPanel({
4724
4906
  children: [
4725
4907
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "captions-panel-item-header", children: [
4726
4908
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "captions-panel-time captions-panel-time-start", children: formatTime(caption.s) }),
4727
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "captions-panel-time captions-panel-time-end", children: formatTime(caption.e) })
4909
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "captions-panel-time captions-panel-time-end", children: formatTime(caption.e) }),
4910
+ caption.isCustom ? /* @__PURE__ */ jsxRuntime.jsx(
4911
+ "span",
4912
+ {
4913
+ className: "captions-panel-custom",
4914
+ title: "This caption overrides track defaults",
4915
+ children: "Custom"
4916
+ }
4917
+ ) : null
4728
4918
  ] }),
4729
4919
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "captions-panel-item-body", children: [
4730
4920
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4893,11 +5083,15 @@ const useCaptionsPanel = () => {
4893
5083
  }
4894
5084
  captionsTrack.current = editorCaptionsTrack;
4895
5085
  setCaptions(
4896
- editorCaptionsTrack.getElements().map((element) => ({
4897
- s: element.getStart(),
4898
- e: element.getEnd(),
4899
- t: element.getText()
4900
- }))
5086
+ editorCaptionsTrack.getElements().map((element) => {
5087
+ var _a;
5088
+ return {
5089
+ s: element.getStart(),
5090
+ e: element.getEnd(),
5091
+ t: element.getText(),
5092
+ isCustom: ((_a = element.getProps()) == null ? void 0 : _a.useTrackDefaults) === false
5093
+ };
5094
+ })
4901
5095
  );
4902
5096
  };
4903
5097
  react.useEffect(() => {
@@ -4911,14 +5105,13 @@ const useCaptionsPanel = () => {
4911
5105
  capStyle: timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT,
4912
5106
  ...CAPTION_PROPS[timeline.CAPTION_STYLE.WORD_BG_HIGHLIGHT],
4913
5107
  x: 0,
4914
- y: 200,
4915
- applyToAll: true
5108
+ y: 200
4916
5109
  };
4917
5110
  (_a = captionsTrack.current) == null ? void 0 : _a.setProps(props);
4918
5111
  }
4919
5112
  };
4920
5113
  const addCaption = () => {
4921
- const newCaption = { s: 0, e: 0, t: "New Caption" };
5114
+ const newCaption = { s: 0, e: 0, t: "New Caption", isCustom: false };
4922
5115
  if (captions.length > 0) {
4923
5116
  newCaption.s = captions[captions.length - 1].e;
4924
5117
  }
@@ -5811,6 +6004,16 @@ const ElementPanelContainer = ({
5811
6004
  updateElement
5812
6005
  }
5813
6006
  );
6007
+ case "emoji":
6008
+ return /* @__PURE__ */ jsxRuntime.jsx(
6009
+ EmojiPanelContainer,
6010
+ {
6011
+ selectedElement,
6012
+ videoResolution,
6013
+ addElement: addNewElement,
6014
+ updateElement
6015
+ }
6016
+ );
5814
6017
  case "text-style":
5815
6018
  return /* @__PURE__ */ jsxRuntime.jsx(
5816
6019
  TextStylePanelContainer,
@@ -6496,8 +6699,10 @@ const DEFAULT_COLOR_META = {
6496
6699
  const CAPTION_FONTS = Object.values(VideoEditor.AVAILABLE_TEXT_FONTS);
6497
6700
  function CaptionPropPanel({
6498
6701
  selectedElement,
6499
- updateElement
6702
+ updateElement,
6703
+ setApplyPropsToAllCaption
6500
6704
  }) {
6705
+ var _a;
6501
6706
  const { editor, changeLog } = timeline.useTimelineContext();
6502
6707
  const captionRef = react.useRef(null);
6503
6708
  const [capStyle, setCapStyle] = react.useState(
@@ -6515,16 +6720,14 @@ function CaptionPropPanel({
6515
6720
  const [useOutline, setUseOutline] = react.useState(true);
6516
6721
  const track = selectedElement instanceof timeline.CaptionElement ? editor.getTrackById(selectedElement.getTrackId()) : null;
6517
6722
  const trackProps = (track == null ? void 0 : track.getProps()) ?? {};
6518
- const applyToAll = (trackProps == null ? void 0 : trackProps.applyToAll) ?? false;
6519
- const handleUpdateCaption = (updates) => {
6520
- const captionElement = selectedElement;
6521
- if (!captionElement) return;
6522
- const nextFontSize = updates.fontSize ?? fontSize;
6523
- const geometry = timeline.computeCaptionGeometry(nextFontSize, updates.style ?? (capStyle == null ? void 0 : capStyle.value) ?? "");
6524
- const highlightEnabled = updates.useHighlightOverride ?? useHighlight;
6525
- const outlineEnabled = updates.useOutlineOverride ?? useOutline;
6526
- const rawNextColors = updates.colors ?? colors;
6527
- let effectiveColors = { ...rawNextColors };
6723
+ const elementProps = ((_a = selectedElement == null ? void 0 : selectedElement.getProps) == null ? void 0 : _a.call(selectedElement)) ?? {};
6724
+ const useTrackDefaults = (elementProps == null ? void 0 : elementProps.useTrackDefaults) ?? true;
6725
+ const getEffectiveColors = ({
6726
+ nextColors,
6727
+ highlightEnabled,
6728
+ outlineEnabled
6729
+ }) => {
6730
+ let effectiveColors = { ...nextColors };
6528
6731
  if (!highlightEnabled) {
6529
6732
  const { highlight, ...rest } = effectiveColors;
6530
6733
  effectiveColors = rest;
@@ -6533,26 +6736,73 @@ function CaptionPropPanel({
6533
6736
  const { outlineColor, ...rest } = effectiveColors;
6534
6737
  effectiveColors = rest;
6535
6738
  }
6536
- if (applyToAll && track) {
6739
+ return effectiveColors;
6740
+ };
6741
+ const handleUseTrackDefaultsChange = (enabled) => {
6742
+ const captionElement = selectedElement;
6743
+ if (!captionElement) return;
6744
+ const prev = captionElement.getProps() ?? {};
6745
+ const next = { ...prev, useTrackDefaults: enabled };
6746
+ if (enabled) {
6747
+ const keysToClear = [
6748
+ "capStyle",
6749
+ "x",
6750
+ "y",
6751
+ "width",
6752
+ "maxWidth",
6753
+ "textAlign",
6754
+ "rotation",
6755
+ "opacity",
6756
+ "colors",
6757
+ "font",
6758
+ "lineWidth",
6759
+ "rectProps",
6760
+ "shadowColor",
6761
+ "shadowBlur",
6762
+ "shadowOffset",
6763
+ "fill",
6764
+ "stroke"
6765
+ ];
6766
+ for (const k of keysToClear) {
6767
+ delete next[k];
6768
+ }
6769
+ }
6770
+ captionElement.setProps(next);
6771
+ updateElement == null ? void 0 : updateElement(captionElement);
6772
+ setApplyPropsToAllCaption == null ? void 0 : setApplyPropsToAllCaption(enabled);
6773
+ };
6774
+ const handleUpdateCaption = (updates) => {
6775
+ const captionElement = selectedElement;
6776
+ if (!captionElement) return;
6777
+ const nextFontSize = updates.fontSize ?? fontSize;
6778
+ const geometry = timeline.computeCaptionGeometry(nextFontSize, updates.style ?? (capStyle == null ? void 0 : capStyle.value) ?? "");
6779
+ const highlightEnabled = updates.useHighlightOverride ?? useHighlight;
6780
+ const outlineEnabled = updates.useOutlineOverride ?? useOutline;
6781
+ const rawNextColors = updates.colors ?? colors;
6782
+ const effectiveColors = getEffectiveColors({
6783
+ nextColors: rawNextColors,
6784
+ highlightEnabled,
6785
+ outlineEnabled
6786
+ });
6787
+ if (useTrackDefaults && track) {
6537
6788
  const nextFont = {
6538
6789
  size: nextFontSize,
6539
6790
  family: updates.fontFamily ?? fontFamily
6540
6791
  };
6541
6792
  const nextColors = effectiveColors;
6542
6793
  const nextCapStyle = updates.style ?? (capStyle == null ? void 0 : capStyle.value);
6543
- track.setProps({
6544
- ...trackProps,
6794
+ editor.updateTrackProps(track.getId(), {
6545
6795
  capStyle: nextCapStyle,
6546
6796
  font: { ...(trackProps == null ? void 0 : trackProps.font) ?? {}, ...nextFont },
6547
6797
  colors: nextColors,
6548
6798
  lineWidth: geometry.lineWidth,
6549
6799
  rectProps: geometry.rectProps
6550
6800
  });
6551
- editor.refresh();
6552
6801
  } else {
6553
- const elementProps = captionElement.getProps() ?? {};
6802
+ const elementProps2 = captionElement.getProps() ?? {};
6554
6803
  captionElement.setProps({
6555
- ...elementProps,
6804
+ ...elementProps2,
6805
+ useTrackDefaults: false,
6556
6806
  capStyle: updates.style ?? (capStyle == null ? void 0 : capStyle.value),
6557
6807
  font: {
6558
6808
  size: nextFontSize,
@@ -6565,20 +6815,29 @@ function CaptionPropPanel({
6565
6815
  }
6566
6816
  };
6567
6817
  react.useEffect(() => {
6568
- var _a, _b;
6569
6818
  const captionElement = selectedElement;
6570
6819
  if (captionElement) {
6571
6820
  if (captionRef.current) {
6572
6821
  captionRef.current.value = captionElement == null ? void 0 : captionElement.getText();
6573
6822
  }
6574
- const props = applyToAll ? trackProps : captionElement.getProps() ?? {};
6575
- const _capStyle = props == null ? void 0 : props.capStyle;
6823
+ const elementProps2 = captionElement.getProps() ?? {};
6824
+ const elementUseTrackDefaults = (elementProps2 == null ? void 0 : elementProps2.useTrackDefaults) ?? true;
6825
+ const resolvedCapStyle = elementUseTrackDefaults ? trackProps == null ? void 0 : trackProps.capStyle : (elementProps2 == null ? void 0 : elementProps2.capStyle) ?? (trackProps == null ? void 0 : trackProps.capStyle);
6826
+ const resolvedFont = elementUseTrackDefaults ? trackProps == null ? void 0 : trackProps.font : {
6827
+ ...(trackProps == null ? void 0 : trackProps.font) ?? {},
6828
+ ...(elementProps2 == null ? void 0 : elementProps2.font) ?? {}
6829
+ };
6830
+ const resolvedColors = elementUseTrackDefaults ? trackProps == null ? void 0 : trackProps.colors : {
6831
+ ...(trackProps == null ? void 0 : trackProps.colors) ?? {},
6832
+ ...(elementProps2 == null ? void 0 : elementProps2.colors) ?? {}
6833
+ };
6834
+ const _capStyle = resolvedCapStyle;
6576
6835
  if (_capStyle && _capStyle in timeline.CAPTION_STYLE_OPTIONS) {
6577
6836
  setCapStyle(timeline.CAPTION_STYLE_OPTIONS[_capStyle]);
6578
6837
  }
6579
- setFontSize(((_a = props == null ? void 0 : props.font) == null ? void 0 : _a.size) ?? CAPTION_FONT.size);
6580
- setFontFamily(((_b = props == null ? void 0 : props.font) == null ? void 0 : _b.family) ?? CAPTION_FONT.family);
6581
- const c = props == null ? void 0 : props.colors;
6838
+ setFontSize((resolvedFont == null ? void 0 : resolvedFont.size) ?? CAPTION_FONT.size);
6839
+ setFontFamily((resolvedFont == null ? void 0 : resolvedFont.family) ?? CAPTION_FONT.family);
6840
+ const c = resolvedColors;
6582
6841
  setColors({
6583
6842
  text: (c == null ? void 0 : c.text) ?? CAPTION_COLOR.text,
6584
6843
  highlight: (c == null ? void 0 : c.highlight) ?? CAPTION_COLOR.highlight,
@@ -6588,7 +6847,7 @@ function CaptionPropPanel({
6588
6847
  setUseHighlight((c == null ? void 0 : c.highlight) != null);
6589
6848
  setUseOutline((c == null ? void 0 : c.outlineColor) != null);
6590
6849
  }
6591
- }, [selectedElement, applyToAll, changeLog]);
6850
+ }, [selectedElement, track, changeLog]);
6592
6851
  if (!(selectedElement instanceof timeline.CaptionElement)) {
6593
6852
  return null;
6594
6853
  }
@@ -6642,6 +6901,18 @@ function CaptionPropPanel({
6642
6901
  ] }, key);
6643
6902
  };
6644
6903
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
6904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "checkbox-control", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "checkbox-label", children: [
6905
+ /* @__PURE__ */ jsxRuntime.jsx(
6906
+ "input",
6907
+ {
6908
+ type: "checkbox",
6909
+ checked: useTrackDefaults,
6910
+ onChange: (e) => handleUseTrackDefaultsChange(e.target.checked),
6911
+ className: "checkbox-purple"
6912
+ }
6913
+ ),
6914
+ "Use track defaults"
6915
+ ] }) }) }),
6645
6916
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-section", children: [
6646
6917
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "label-dark", children: "Caption Style" }),
6647
6918
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -6845,92 +7116,82 @@ function PlaybackPropsPanel({
6845
7116
  )
6846
7117
  ] });
6847
7118
  }
6848
- const hasAudio = async (src) => {
6849
- if (!src) return false;
6850
- const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
6851
- if (!isSafeUrl) return false;
6852
- try {
6853
- const audioBuffer = await fetchAndDecodeAudio(src);
6854
- if (audioBuffer.duration === 0 || audioBuffer.length === 0) {
6855
- return false;
6856
- }
6857
- if (isAudioSilent(audioBuffer)) {
6858
- return false;
6859
- }
6860
- return true;
6861
- } catch (error) {
6862
- return false;
6863
- }
6864
- };
6865
- const fetchAndDecodeAudio = async (src) => {
6866
- const response = await fetch(src);
6867
- if (!response.ok) throw new Error(`Failed to fetch source: ${response.status}`);
6868
- const arrayBuffer = await response.arrayBuffer();
6869
- return decodeAudioData(arrayBuffer);
7119
+ const NONE_VALUE = "none";
7120
+ const FILTER_LABELS = {
7121
+ [NONE_VALUE]: "None",
7122
+ [mediaUtils.COLOR_FILTERS.SATURATED]: "Saturated",
7123
+ [mediaUtils.COLOR_FILTERS.BRIGHT]: "Bright",
7124
+ [mediaUtils.COLOR_FILTERS.VIBRANT]: "Vibrant",
7125
+ [mediaUtils.COLOR_FILTERS.RETRO]: "Retro",
7126
+ [mediaUtils.COLOR_FILTERS.BLACK_WHITE]: "Black & white",
7127
+ [mediaUtils.COLOR_FILTERS.SEPIA]: "Sepia",
7128
+ [mediaUtils.COLOR_FILTERS.COOL]: "Cool",
7129
+ [mediaUtils.COLOR_FILTERS.WARM]: "Warm",
7130
+ [mediaUtils.COLOR_FILTERS.CINEMATIC]: "Cinematic",
7131
+ [mediaUtils.COLOR_FILTERS.SOFT_GLOW]: "Soft glow",
7132
+ [mediaUtils.COLOR_FILTERS.MOODY]: "Moody",
7133
+ [mediaUtils.COLOR_FILTERS.DREAMY]: "Dreamy",
7134
+ [mediaUtils.COLOR_FILTERS.INVERTED]: "Inverted",
7135
+ [mediaUtils.COLOR_FILTERS.VINTAGE]: "Vintage",
7136
+ [mediaUtils.COLOR_FILTERS.DRAMATIC]: "Dramatic",
7137
+ [mediaUtils.COLOR_FILTERS.FADED]: "Faded"
6870
7138
  };
6871
- const decodeAudioData = async (arrayBuffer) => {
6872
- const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
6873
- if (!AudioContextCtor) throw new Error("Web Audio API not supported");
6874
- const audioContext = new AudioContextCtor();
6875
- try {
6876
- return await new Promise((resolve, reject) => {
6877
- audioContext.decodeAudioData(
6878
- arrayBuffer.slice(0),
6879
- (buf) => resolve(buf),
6880
- (err) => reject(err || new Error("Failed to decode audio: no audio track found or unsupported format"))
6881
- );
6882
- });
6883
- } finally {
6884
- audioContext.close();
7139
+ function isMediaFilterElement(el) {
7140
+ return el instanceof timeline.VideoElement || el instanceof timeline.ImageElement;
7141
+ }
7142
+ function ColorFilterPropsPanel({
7143
+ selectedElement,
7144
+ updateElement
7145
+ }) {
7146
+ const mediaEl = isMediaFilterElement(selectedElement) ? selectedElement : null;
7147
+ const options = react.useMemo(() => {
7148
+ const entries = Object.values(mediaUtils.COLOR_FILTERS).map(
7149
+ (value) => ({
7150
+ value,
7151
+ label: FILTER_LABELS[value] ?? value
7152
+ })
7153
+ );
7154
+ return [{ value: NONE_VALUE, label: FILTER_LABELS[NONE_VALUE] }, ...entries];
7155
+ }, []);
7156
+ const elementProps = (mediaEl == null ? void 0 : mediaEl.getProps()) ?? {};
7157
+ const mediaFilter = elementProps.mediaFilter ?? NONE_VALUE;
7158
+ const handleFilterChange = (value) => {
7159
+ if (!mediaEl || !updateElement) return;
7160
+ const allowed = Object.values(mediaUtils.COLOR_FILTERS);
7161
+ const next = value === NONE_VALUE ? NONE_VALUE : allowed.includes(value) ? value : NONE_VALUE;
7162
+ updateElement(
7163
+ mediaEl.setProps({
7164
+ ...elementProps,
7165
+ mediaFilter: next
7166
+ })
7167
+ );
7168
+ };
7169
+ const [isOpen, setIsOpen] = react.useState(false);
7170
+ if (!mediaEl) {
7171
+ return null;
6885
7172
  }
6886
- };
6887
- const isAudioSilent = (buffer, threshold = 1e-3) => {
6888
- for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
6889
- const channelData = buffer.getChannelData(channel);
6890
- for (let i = 0; i < channelData.length; i += 100) {
6891
- if (Math.abs(channelData[i]) > threshold) {
6892
- return false;
7173
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
7174
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Look" }),
7175
+ /* @__PURE__ */ jsxRuntime.jsx(
7176
+ AccordionItem,
7177
+ {
7178
+ title: "Color filter",
7179
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Funnel, { className: "icon-sm" }),
7180
+ isOpen,
7181
+ onToggle: () => setIsOpen((open) => !open),
7182
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-group", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxRuntime.jsx(PropertyRow, { label: "Preset", children: /* @__PURE__ */ jsxRuntime.jsx(
7183
+ "select",
7184
+ {
7185
+ value: options.some((o) => o.value === mediaFilter) ? mediaFilter : NONE_VALUE,
7186
+ onChange: (e) => handleFilterChange(e.target.value),
7187
+ className: "select-dark w-full",
7188
+ children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
7189
+ }
7190
+ ) }) }) })
6893
7191
  }
6894
- }
6895
- }
6896
- return true;
6897
- };
6898
- const loadFile = (accept) => {
6899
- return new Promise((resolve, reject) => {
6900
- try {
6901
- const input = document.createElement("input");
6902
- input.type = "file";
6903
- input.accept = accept;
6904
- input.style.display = "none";
6905
- document.body.appendChild(input);
6906
- const cleanup = () => {
6907
- input.value = "";
6908
- document.body.removeChild(input);
6909
- };
6910
- input.onchange = () => {
6911
- const file = input.files && input.files[0];
6912
- cleanup();
6913
- if (!file) {
6914
- reject(new Error("No file selected"));
6915
- return;
6916
- }
6917
- resolve(file);
6918
- };
6919
- input.click();
6920
- } catch (error) {
6921
- reject(error);
6922
- }
6923
- });
6924
- };
6925
- const saveAsFile = (content, type, name) => {
6926
- const blob = typeof content === "string" ? new Blob([content], { type }) : content;
6927
- const url = URL.createObjectURL(blob);
6928
- const a = document.createElement("a");
6929
- a.href = url;
6930
- a.download = name;
6931
- a.click();
6932
- URL.revokeObjectURL(url);
6933
- };
7192
+ )
7193
+ ] });
7194
+ }
6934
7195
  function GenerateCaptionsPanel({
6935
7196
  selectedElement,
6936
7197
  addCaptionsToTimeline,
@@ -7034,7 +7295,7 @@ function GenerateCaptionsPanel({
7034
7295
  const videoUrl = videoElement.getSrc();
7035
7296
  if (videoUrl) {
7036
7297
  try {
7037
- const hasAudioTrack = await hasAudio(videoUrl);
7298
+ const hasAudioTrack = await mediaUtils.hasAudio(videoUrl);
7038
7299
  setContainsAudio(hasAudioTrack);
7039
7300
  } catch (error) {
7040
7301
  console.error("Error checking audio:", error);
@@ -7503,6 +7764,7 @@ function PropertiesPanelContainer({
7503
7764
  selectedElement && !(selectedElement instanceof timeline.CaptionElement) && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: (() => {
7504
7765
  const isText = selectedElement instanceof timeline.TextElement;
7505
7766
  const isVideo = selectedElement instanceof timeline.VideoElement;
7767
+ const isImage = selectedElement instanceof timeline.ImageElement;
7506
7768
  const isAudio = selectedElement instanceof timeline.AudioElement;
7507
7769
  const isAnnotation = selectedElement instanceof timeline.ArrowElement || selectedElement instanceof timeline.LineElement || selectedElement instanceof timeline.RectElement || selectedElement instanceof timeline.CircleElement;
7508
7770
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -7534,6 +7796,13 @@ function PropertiesPanelContainer({
7534
7796
  updateElement
7535
7797
  }
7536
7798
  ),
7799
+ (isVideo || isImage) && /* @__PURE__ */ jsxRuntime.jsx(
7800
+ ColorFilterPropsPanel,
7801
+ {
7802
+ selectedElement,
7803
+ updateElement
7804
+ }
7805
+ ),
7537
7806
  isText && /* @__PURE__ */ jsxRuntime.jsx(
7538
7807
  TextEffects,
7539
7808
  {
@@ -7581,7 +7850,7 @@ const useStudioOperation = (studioConfig) => {
7581
7850
  if (studioConfig == null ? void 0 : studioConfig.loadProject) {
7582
7851
  project = await studioConfig.loadProject();
7583
7852
  } else {
7584
- const file = await loadFile("application/json");
7853
+ const file = await mediaUtils.loadFile("application/json");
7585
7854
  const text = await file.text();
7586
7855
  setProjectName(file.name);
7587
7856
  project = JSON.parse(text);
@@ -7601,7 +7870,7 @@ const useStudioOperation = (studioConfig) => {
7601
7870
  if ((studioConfig == null ? void 0 : studioConfig.saveProject) && present) {
7602
7871
  await studioConfig.saveProject(present, fileName);
7603
7872
  } else {
7604
- const file = await saveAsFile(
7873
+ const file = await mediaUtils.saveAsFile(
7605
7874
  JSON.stringify(present),
7606
7875
  "application/json",
7607
7876
  fileName
@@ -7631,19 +7900,19 @@ const useStudioOperation = (studioConfig) => {
7631
7900
  const languages = timeline.getCaptionLanguages(present);
7632
7901
  if (languages.length <= 1) {
7633
7902
  const content = format === "srt" ? timeline.exportCaptionsAsSRT(present, languages[0]) : timeline.exportCaptionsAsVTT(present, languages[0]);
7634
- await saveAsFile(content, "text/plain", `${baseName}.${format}`);
7903
+ await mediaUtils.saveAsFile(content, "text/plain", `${baseName}.${format}`);
7635
7904
  return;
7636
7905
  }
7637
7906
  for (const language of languages) {
7638
7907
  const content = format === "srt" ? timeline.exportCaptionsAsSRT(present, language) : timeline.exportCaptionsAsVTT(present, language);
7639
- await saveAsFile(content, "text/plain", `${baseName}.${language}.${format}`);
7908
+ await mediaUtils.saveAsFile(content, "text/plain", `${baseName}.${language}.${format}`);
7640
7909
  }
7641
7910
  };
7642
7911
  const onExportChapters = async (format) => {
7643
7912
  if (!present) return;
7644
7913
  const content = format === "youtube" ? timeline.exportChaptersAsYouTube(present) : timeline.exportChaptersAsJSON(present);
7645
7914
  const fileName = `${(projectName || "chapters").replace(/\.json$/i, "")}.${format === "youtube" ? "txt" : "json"}`;
7646
- await saveAsFile(content, "text/plain", fileName);
7915
+ await mediaUtils.saveAsFile(content, "text/plain", fileName);
7647
7916
  };
7648
7917
  const addCaptionsToTimeline = (captions) => {
7649
7918
  var _a;
@@ -8148,6 +8417,10 @@ Object.defineProperty(exports, "ElementValidator", {
8148
8417
  enumerable: true,
8149
8418
  get: () => timeline.ElementValidator
8150
8419
  });
8420
+ Object.defineProperty(exports, "EmojiElement", {
8421
+ enumerable: true,
8422
+ get: () => timeline.EmojiElement
8423
+ });
8151
8424
  Object.defineProperty(exports, "INITIAL_TIMELINE_DATA", {
8152
8425
  enumerable: true,
8153
8426
  get: () => timeline.INITIAL_TIMELINE_DATA
@@ -8312,6 +8585,7 @@ exports.DEFAULT_PROJECT_TEMPLATES = DEFAULT_PROJECT_TEMPLATES;
8312
8585
  exports.DEFAULT_STUDIO_CONFIG = DEFAULT_STUDIO_CONFIG;
8313
8586
  exports.DEMO_STUDIO_CONFIG = DEMO_STUDIO_CONFIG;
8314
8587
  exports.EDU_STUDIO_CONFIG = EDU_STUDIO_CONFIG;
8588
+ exports.EmojiPanel = EmojiPanel;
8315
8589
  exports.ImagePanel = ImagePanel;
8316
8590
  exports.MARKETING_STUDIO_CONFIG = MARKETING_STUDIO_CONFIG;
8317
8591
  exports.RecordPanel = RecordPanel;