@twick/studio 0.14.11 → 0.14.13

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
@@ -110,15 +110,49 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
110
110
  * This source code is licensed under the ISC license.
111
111
  * See the LICENSE file in the root directory of this source tree.
112
112
  */
113
- const __iconNode$o = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
114
- const Circle = createLucideIcon("circle", __iconNode$o);
113
+ const __iconNode$s = [
114
+ ["rect", { width: "18", height: "14", x: "3", y: "5", rx: "2", ry: "2", key: "12ruh7" }],
115
+ ["path", { d: "M7 15h4M15 15h2M7 11h2M13 11h4", key: "1ueiar" }]
116
+ ];
117
+ const Captions = createLucideIcon("captions", __iconNode$s);
115
118
  /**
116
119
  * @license lucide-react v0.511.0 - ISC
117
120
  *
118
121
  * This source code is licensed under the ISC license.
119
122
  * See the LICENSE file in the root directory of this source tree.
120
123
  */
121
- const __iconNode$n = [
124
+ const __iconNode$r = [
125
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
126
+ ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
127
+ ];
128
+ const CircleCheck = createLucideIcon("circle-check", __iconNode$r);
129
+ /**
130
+ * @license lucide-react v0.511.0 - ISC
131
+ *
132
+ * This source code is licensed under the ISC license.
133
+ * See the LICENSE file in the root directory of this source tree.
134
+ */
135
+ const __iconNode$q = [
136
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
137
+ ["path", { d: "m15 9-6 6", key: "1uzhvr" }],
138
+ ["path", { d: "m9 9 6 6", key: "z0biqf" }]
139
+ ];
140
+ const CircleX = createLucideIcon("circle-x", __iconNode$q);
141
+ /**
142
+ * @license lucide-react v0.511.0 - ISC
143
+ *
144
+ * This source code is licensed under the ISC license.
145
+ * See the LICENSE file in the root directory of this source tree.
146
+ */
147
+ const __iconNode$p = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
148
+ const Circle = createLucideIcon("circle", __iconNode$p);
149
+ /**
150
+ * @license lucide-react v0.511.0 - ISC
151
+ *
152
+ * This source code is licensed under the ISC license.
153
+ * See the LICENSE file in the root directory of this source tree.
154
+ */
155
+ const __iconNode$o = [
122
156
  [
123
157
  "path",
124
158
  { 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" }
@@ -127,119 +161,119 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
127
161
  ["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
128
162
  ["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
129
163
  ];
130
- const Clapperboard = createLucideIcon("clapperboard", __iconNode$n);
164
+ const Clapperboard = createLucideIcon("clapperboard", __iconNode$o);
131
165
  /**
132
166
  * @license lucide-react v0.511.0 - ISC
133
167
  *
134
168
  * This source code is licensed under the ISC license.
135
169
  * See the LICENSE file in the root directory of this source tree.
136
170
  */
137
- const __iconNode$m = [
171
+ const __iconNode$n = [
138
172
  ["path", { d: "M12 15V3", key: "m9g1x1" }],
139
173
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
140
174
  ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
141
175
  ];
142
- const Download = createLucideIcon("download", __iconNode$m);
176
+ const Download = createLucideIcon("download", __iconNode$n);
143
177
  /**
144
178
  * @license lucide-react v0.511.0 - ISC
145
179
  *
146
180
  * This source code is licensed under the ISC license.
147
181
  * See the LICENSE file in the root directory of this source tree.
148
182
  */
149
- const __iconNode$l = [
183
+ const __iconNode$m = [
150
184
  ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
151
185
  ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
152
186
  ];
153
- const File = createLucideIcon("file", __iconNode$l);
187
+ const File = createLucideIcon("file", __iconNode$m);
154
188
  /**
155
189
  * @license lucide-react v0.511.0 - ISC
156
190
  *
157
191
  * This source code is licensed under the ISC license.
158
192
  * See the LICENSE file in the root directory of this source tree.
159
193
  */
160
- const __iconNode$k = [
194
+ const __iconNode$l = [
161
195
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
162
196
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
163
197
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
164
198
  ];
165
- const Image = createLucideIcon("image", __iconNode$k);
199
+ const Image = createLucideIcon("image", __iconNode$l);
166
200
  /**
167
201
  * @license lucide-react v0.511.0 - ISC
168
202
  *
169
203
  * This source code is licensed under the ISC license.
170
204
  * See the LICENSE file in the root directory of this source tree.
171
205
  */
172
- const __iconNode$j = [
206
+ const __iconNode$k = [
173
207
  ["path", { d: "M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8", key: "18ogeb" }]
174
208
  ];
175
- const Infinity = createLucideIcon("infinity", __iconNode$j);
209
+ const Infinity = createLucideIcon("infinity", __iconNode$k);
176
210
  /**
177
211
  * @license lucide-react v0.511.0 - ISC
178
212
  *
179
213
  * This source code is licensed under the ISC license.
180
214
  * See the LICENSE file in the root directory of this source tree.
181
215
  */
182
- const __iconNode$i = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
183
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$i);
216
+ const __iconNode$j = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
217
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$j);
184
218
  /**
185
219
  * @license lucide-react v0.511.0 - ISC
186
220
  *
187
221
  * This source code is licensed under the ISC license.
188
222
  * See the LICENSE file in the root directory of this source tree.
189
223
  */
190
- const __iconNode$h = [
224
+ const __iconNode$i = [
191
225
  ["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" }]
192
226
  ];
193
- const MessageSquare = createLucideIcon("message-square", __iconNode$h);
227
+ const MessageSquare = createLucideIcon("message-square", __iconNode$i);
194
228
  /**
195
229
  * @license lucide-react v0.511.0 - ISC
196
230
  *
197
231
  * This source code is licensed under the ISC license.
198
232
  * See the LICENSE file in the root directory of this source tree.
199
233
  */
200
- const __iconNode$g = [
234
+ const __iconNode$h = [
201
235
  ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
202
236
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
203
237
  ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
204
238
  ];
205
- const Music = createLucideIcon("music", __iconNode$g);
239
+ const Music = createLucideIcon("music", __iconNode$h);
206
240
  /**
207
241
  * @license lucide-react v0.511.0 - ISC
208
242
  *
209
243
  * This source code is licensed under the ISC license.
210
244
  * See the LICENSE file in the root directory of this source tree.
211
245
  */
212
- const __iconNode$f = [
246
+ const __iconNode$g = [
213
247
  ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
214
248
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
215
249
  ];
216
- const Pause = createLucideIcon("pause", __iconNode$f);
250
+ const Pause = createLucideIcon("pause", __iconNode$g);
217
251
  /**
218
252
  * @license lucide-react v0.511.0 - ISC
219
253
  *
220
254
  * This source code is licensed under the ISC license.
221
255
  * See the LICENSE file in the root directory of this source tree.
222
256
  */
223
- const __iconNode$e = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
224
- const Play = createLucideIcon("play", __iconNode$e);
257
+ const __iconNode$f = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
258
+ const Play = createLucideIcon("play", __iconNode$f);
225
259
  /**
226
260
  * @license lucide-react v0.511.0 - ISC
227
261
  *
228
262
  * This source code is licensed under the ISC license.
229
263
  * See the LICENSE file in the root directory of this source tree.
230
264
  */
231
- const __iconNode$d = [
265
+ const __iconNode$e = [
232
266
  ["path", { d: "M5 12h14", key: "1ays0h" }],
233
267
  ["path", { d: "M12 5v14", key: "s699le" }]
234
268
  ];
235
- const Plus = createLucideIcon("plus", __iconNode$d);
269
+ const Plus = createLucideIcon("plus", __iconNode$e);
236
270
  /**
237
271
  * @license lucide-react v0.511.0 - ISC
238
272
  *
239
273
  * This source code is licensed under the ISC license.
240
274
  * See the LICENSE file in the root directory of this source tree.
241
275
  */
242
- const __iconNode$c = [
276
+ const __iconNode$d = [
243
277
  [
244
278
  "path",
245
279
  {
@@ -250,39 +284,39 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
250
284
  ["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7", key: "1ydtos" }],
251
285
  ["path", { d: "M7 3v4a1 1 0 0 0 1 1h7", key: "t51u73" }]
252
286
  ];
253
- const Save = createLucideIcon("save", __iconNode$c);
287
+ const Save = createLucideIcon("save", __iconNode$d);
254
288
  /**
255
289
  * @license lucide-react v0.511.0 - ISC
256
290
  *
257
291
  * This source code is licensed under the ISC license.
258
292
  * See the LICENSE file in the root directory of this source tree.
259
293
  */
260
- const __iconNode$b = [
294
+ const __iconNode$c = [
261
295
  ["circle", { cx: "6", cy: "6", r: "3", key: "1lh9wr" }],
262
296
  ["path", { d: "M8.12 8.12 12 12", key: "1alkpv" }],
263
297
  ["path", { d: "M20 4 8.12 15.88", key: "xgtan2" }],
264
298
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
265
299
  ["path", { d: "M14.8 14.8 20 20", key: "ptml3r" }]
266
300
  ];
267
- const Scissors = createLucideIcon("scissors", __iconNode$b);
301
+ const Scissors = createLucideIcon("scissors", __iconNode$c);
268
302
  /**
269
303
  * @license lucide-react v0.511.0 - ISC
270
304
  *
271
305
  * This source code is licensed under the ISC license.
272
306
  * See the LICENSE file in the root directory of this source tree.
273
307
  */
274
- const __iconNode$a = [
308
+ const __iconNode$b = [
275
309
  ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
276
310
  ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
277
311
  ];
278
- const Search = createLucideIcon("search", __iconNode$a);
312
+ const Search = createLucideIcon("search", __iconNode$b);
279
313
  /**
280
314
  * @license lucide-react v0.511.0 - ISC
281
315
  *
282
316
  * This source code is licensed under the ISC license.
283
317
  * See the LICENSE file in the root directory of this source tree.
284
318
  */
285
- const __iconNode$9 = [
319
+ const __iconNode$a = [
286
320
  [
287
321
  "path",
288
322
  {
@@ -292,14 +326,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
292
326
  ],
293
327
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
294
328
  ];
295
- const Settings = createLucideIcon("settings", __iconNode$9);
329
+ const Settings = createLucideIcon("settings", __iconNode$a);
296
330
  /**
297
331
  * @license lucide-react v0.511.0 - ISC
298
332
  *
299
333
  * This source code is licensed under the ISC license.
300
334
  * See the LICENSE file in the root directory of this source tree.
301
335
  */
302
- const __iconNode$8 = [
336
+ const __iconNode$9 = [
303
337
  [
304
338
  "path",
305
339
  {
@@ -312,62 +346,62 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
312
346
  ["path", { d: "M4 17v2", key: "vumght" }],
313
347
  ["path", { d: "M5 18H3", key: "zchphs" }]
314
348
  ];
315
- const Sparkles = createLucideIcon("sparkles", __iconNode$8);
349
+ const Sparkles = createLucideIcon("sparkles", __iconNode$9);
316
350
  /**
317
351
  * @license lucide-react v0.511.0 - ISC
318
352
  *
319
353
  * This source code is licensed under the ISC license.
320
354
  * See the LICENSE file in the root directory of this source tree.
321
355
  */
322
- const __iconNode$7 = [
356
+ const __iconNode$8 = [
323
357
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
324
358
  ];
325
- const Square = createLucideIcon("square", __iconNode$7);
359
+ const Square = createLucideIcon("square", __iconNode$8);
326
360
  /**
327
361
  * @license lucide-react v0.511.0 - ISC
328
362
  *
329
363
  * This source code is licensed under the ISC license.
330
364
  * See the LICENSE file in the root directory of this source tree.
331
365
  */
332
- const __iconNode$6 = [
366
+ const __iconNode$7 = [
333
367
  ["path", { d: "M3 6h18", key: "d0wm0j" }],
334
368
  ["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }],
335
369
  ["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }],
336
370
  ["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }],
337
371
  ["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }]
338
372
  ];
339
- const Trash2 = createLucideIcon("trash-2", __iconNode$6);
373
+ const Trash2 = createLucideIcon("trash-2", __iconNode$7);
340
374
  /**
341
375
  * @license lucide-react v0.511.0 - ISC
342
376
  *
343
377
  * This source code is licensed under the ISC license.
344
378
  * See the LICENSE file in the root directory of this source tree.
345
379
  */
346
- const __iconNode$5 = [
380
+ const __iconNode$6 = [
347
381
  ["path", { d: "M12 4v16", key: "1654pz" }],
348
382
  ["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2", key: "e0r10z" }],
349
383
  ["path", { d: "M9 20h6", key: "s66wpe" }]
350
384
  ];
351
- const Type = createLucideIcon("type", __iconNode$5);
385
+ const Type = createLucideIcon("type", __iconNode$6);
352
386
  /**
353
387
  * @license lucide-react v0.511.0 - ISC
354
388
  *
355
389
  * This source code is licensed under the ISC license.
356
390
  * See the LICENSE file in the root directory of this source tree.
357
391
  */
358
- const __iconNode$4 = [
392
+ const __iconNode$5 = [
359
393
  ["path", { d: "M12 3v12", key: "1x0j5s" }],
360
394
  ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
361
395
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
362
396
  ];
363
- const Upload = createLucideIcon("upload", __iconNode$4);
397
+ const Upload = createLucideIcon("upload", __iconNode$5);
364
398
  /**
365
399
  * @license lucide-react v0.511.0 - ISC
366
400
  *
367
401
  * This source code is licensed under the ISC license.
368
402
  * See the LICENSE file in the root directory of this source tree.
369
403
  */
370
- const __iconNode$3 = [
404
+ const __iconNode$4 = [
371
405
  [
372
406
  "path",
373
407
  {
@@ -377,14 +411,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
377
411
  ],
378
412
  ["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", key: "158x01" }]
379
413
  ];
380
- const Video = createLucideIcon("video", __iconNode$3);
414
+ const Video = createLucideIcon("video", __iconNode$4);
381
415
  /**
382
416
  * @license lucide-react v0.511.0 - ISC
383
417
  *
384
418
  * This source code is licensed under the ISC license.
385
419
  * See the LICENSE file in the root directory of this source tree.
386
420
  */
387
- const __iconNode$2 = [
421
+ const __iconNode$3 = [
388
422
  [
389
423
  "path",
390
424
  {
@@ -395,7 +429,25 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
395
429
  ["path", { d: "M16 9a5 5 0 0 1 0 6", key: "1q6k2b" }],
396
430
  ["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728", key: "ijwkga" }]
397
431
  ];
398
- const Volume2 = createLucideIcon("volume-2", __iconNode$2);
432
+ const Volume2 = createLucideIcon("volume-2", __iconNode$3);
433
+ /**
434
+ * @license lucide-react v0.511.0 - ISC
435
+ *
436
+ * This source code is licensed under the ISC license.
437
+ * See the LICENSE file in the root directory of this source tree.
438
+ */
439
+ const __iconNode$2 = [
440
+ [
441
+ "path",
442
+ {
443
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z",
444
+ key: "uqj9uw"
445
+ }
446
+ ],
447
+ ["line", { x1: "22", x2: "16", y1: "9", y2: "15", key: "1ewh16" }],
448
+ ["line", { x1: "16", x2: "22", y1: "9", y2: "15", key: "5ykzw1" }]
449
+ ];
450
+ const VolumeX = createLucideIcon("volume-x", __iconNode$2);
399
451
  /**
400
452
  * @license lucide-react v0.511.0 - ISC
401
453
  *
@@ -2664,6 +2716,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2664
2716
  icon: "MessageSquare",
2665
2717
  description: "Subtitle Style"
2666
2718
  }
2719
+ ],
2720
+ [
2721
+ "generate-subtitles",
2722
+ {
2723
+ id: "generate-subtitles",
2724
+ name: "Generate Subtitles",
2725
+ icon: "Subtitles",
2726
+ description: "Generate Subtitles"
2727
+ }
2667
2728
  ]
2668
2729
  ]);
2669
2730
  const getIcon = (iconName) => {
@@ -2676,6 +2737,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2676
2737
  return Image;
2677
2738
  case "Music":
2678
2739
  return Music;
2740
+ case "Subtitles":
2741
+ return Captions;
2679
2742
  case "MessageSquare":
2680
2743
  return MessageSquare;
2681
2744
  case "Settings":
@@ -2708,6 +2771,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2708
2771
  sections.push(propsCategories.get("animations"));
2709
2772
  sections.push(propsCategories.get("color-effects"));
2710
2773
  sections.push(propsCategories.get("playback-props"));
2774
+ sections.push(propsCategories.get("generate-subtitles"));
2711
2775
  } else if (selectedElement instanceof timeline.AudioElement) {
2712
2776
  sections.push(propsCategories.get("element-props"));
2713
2777
  sections.push(propsCategories.get("playback-props"));
@@ -3145,10 +3209,250 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3145
3209
  ] })
3146
3210
  ] });
3147
3211
  }
3212
+ const hasAudio = async (src) => {
3213
+ if (!src) return false;
3214
+ const isSafeUrl = /^(https?:|blob:|data:)/i.test(src);
3215
+ if (!isSafeUrl) return false;
3216
+ try {
3217
+ const audioBuffer = await fetchAndDecodeAudio(src);
3218
+ if (audioBuffer.duration === 0 || audioBuffer.length === 0) {
3219
+ return false;
3220
+ }
3221
+ if (isAudioSilent(audioBuffer)) {
3222
+ return false;
3223
+ }
3224
+ return true;
3225
+ } catch (error) {
3226
+ return false;
3227
+ }
3228
+ };
3229
+ const fetchAndDecodeAudio = async (src) => {
3230
+ const response = await fetch(src);
3231
+ if (!response.ok) throw new Error(`Failed to fetch source: ${response.status}`);
3232
+ const arrayBuffer = await response.arrayBuffer();
3233
+ return decodeAudioData(arrayBuffer);
3234
+ };
3235
+ const decodeAudioData = async (arrayBuffer) => {
3236
+ const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
3237
+ if (!AudioContextCtor) throw new Error("Web Audio API not supported");
3238
+ const audioContext = new AudioContextCtor();
3239
+ try {
3240
+ return await new Promise((resolve, reject) => {
3241
+ audioContext.decodeAudioData(
3242
+ arrayBuffer.slice(0),
3243
+ (buf) => resolve(buf),
3244
+ (err) => reject(err || new Error("Failed to decode audio: no audio track found or unsupported format"))
3245
+ );
3246
+ });
3247
+ } finally {
3248
+ audioContext.close();
3249
+ }
3250
+ };
3251
+ const isAudioSilent = (buffer, threshold = 1e-3) => {
3252
+ for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
3253
+ const channelData = buffer.getChannelData(channel);
3254
+ for (let i = 0; i < channelData.length; i += 100) {
3255
+ if (Math.abs(channelData[i]) > threshold) {
3256
+ return false;
3257
+ }
3258
+ }
3259
+ }
3260
+ return true;
3261
+ };
3262
+ const loadFile = (accept) => {
3263
+ return new Promise((resolve, reject) => {
3264
+ try {
3265
+ const input = document.createElement("input");
3266
+ input.type = "file";
3267
+ input.accept = accept;
3268
+ input.style.display = "none";
3269
+ document.body.appendChild(input);
3270
+ const cleanup = () => {
3271
+ input.value = "";
3272
+ document.body.removeChild(input);
3273
+ };
3274
+ input.onchange = () => {
3275
+ const file = input.files && input.files[0];
3276
+ cleanup();
3277
+ if (!file) {
3278
+ reject(new Error("No file selected"));
3279
+ return;
3280
+ }
3281
+ resolve(file);
3282
+ };
3283
+ input.click();
3284
+ } catch (error) {
3285
+ reject(error);
3286
+ }
3287
+ });
3288
+ };
3289
+ const saveAsFile = (content, type, name) => {
3290
+ const blob = typeof content === "string" ? new Blob([content], { type }) : content;
3291
+ const url = URL.createObjectURL(blob);
3292
+ const a = document.createElement("a");
3293
+ a.href = url;
3294
+ a.download = name;
3295
+ a.click();
3296
+ URL.revokeObjectURL(url);
3297
+ };
3298
+ function GenerateSubtitlesPanel({
3299
+ selectedElement,
3300
+ addSubtitlesToTimeline,
3301
+ onGenerateSubtitles,
3302
+ getSubtitleStatus
3303
+ }) {
3304
+ const [containsAudio, setContainsAudio] = react.useState(null);
3305
+ const [isLoading, setIsLoading] = react.useState(true);
3306
+ const [isGenerating, setIsGenerating] = react.useState(false);
3307
+ const [pollingStatus, setPollingStatus] = react.useState("idle");
3308
+ const [errorMessage, setErrorMessage] = react.useState(null);
3309
+ const pollingIntervalRef = react.useRef(null);
3310
+ const currentReqIdRef = react.useRef(null);
3311
+ react.useEffect(() => {
3312
+ return () => {
3313
+ if (pollingIntervalRef.current) {
3314
+ clearInterval(pollingIntervalRef.current);
3315
+ }
3316
+ };
3317
+ }, []);
3318
+ const stopPolling = () => {
3319
+ if (pollingIntervalRef.current) {
3320
+ clearInterval(pollingIntervalRef.current);
3321
+ pollingIntervalRef.current = null;
3322
+ }
3323
+ };
3324
+ const startPolling = async (reqId) => {
3325
+ if (!getSubtitleStatus) {
3326
+ return;
3327
+ }
3328
+ setPollingStatus("polling");
3329
+ setIsGenerating(true);
3330
+ setErrorMessage(null);
3331
+ const poll = async () => {
3332
+ try {
3333
+ const response = await getSubtitleStatus(reqId);
3334
+ if (response.status === "completed") {
3335
+ stopPolling();
3336
+ setPollingStatus("success");
3337
+ setIsGenerating(false);
3338
+ addSubtitlesToTimeline(response.subtitles || []);
3339
+ setTimeout(() => {
3340
+ setPollingStatus("idle");
3341
+ }, 3e3);
3342
+ } else if (response.status === "pending") {
3343
+ } else if (response.status === "failed") {
3344
+ stopPolling();
3345
+ setPollingStatus("error");
3346
+ setIsGenerating(false);
3347
+ setErrorMessage(response.error || "Failed to generate subtitles");
3348
+ console.error("Error generating subtitles:", response.error);
3349
+ }
3350
+ } catch (error) {
3351
+ stopPolling();
3352
+ setPollingStatus("error");
3353
+ setIsGenerating(false);
3354
+ setErrorMessage(error instanceof Error ? error.message : "Failed to get subtitle status");
3355
+ console.error("Error polling for subtitles:", error);
3356
+ }
3357
+ };
3358
+ await poll();
3359
+ pollingIntervalRef.current = setInterval(poll, 2e3);
3360
+ };
3361
+ const handleGenerateSubtitles = async () => {
3362
+ if (!(selectedElement instanceof timeline.VideoElement)) {
3363
+ return;
3364
+ }
3365
+ const videoElement = selectedElement;
3366
+ try {
3367
+ const reqId = await onGenerateSubtitles(videoElement);
3368
+ if (!reqId) {
3369
+ setPollingStatus("error");
3370
+ setIsGenerating(false);
3371
+ setErrorMessage("Failed to start subtitle generation");
3372
+ console.error("Error generating subtitles: Failed to start subtitle generation");
3373
+ return;
3374
+ }
3375
+ currentReqIdRef.current = reqId;
3376
+ await startPolling(reqId);
3377
+ } catch (error) {
3378
+ setPollingStatus("error");
3379
+ setIsGenerating(false);
3380
+ setErrorMessage(error instanceof Error ? error.message : "Failed to start subtitle generation");
3381
+ console.error("Error generating subtitles:", error);
3382
+ }
3383
+ };
3384
+ const checkAudio = async () => {
3385
+ setIsLoading(true);
3386
+ if (selectedElement instanceof timeline.VideoElement) {
3387
+ const videoElement = selectedElement;
3388
+ const videoUrl = videoElement.getSrc();
3389
+ if (videoUrl) {
3390
+ try {
3391
+ const hasAudioTrack = await hasAudio(videoUrl);
3392
+ setContainsAudio(hasAudioTrack);
3393
+ } catch (error) {
3394
+ console.error("Error checking audio:", error);
3395
+ setContainsAudio(false);
3396
+ }
3397
+ } else {
3398
+ setContainsAudio(false);
3399
+ }
3400
+ } else {
3401
+ setContainsAudio(false);
3402
+ }
3403
+ setIsLoading(false);
3404
+ };
3405
+ react.useEffect(() => {
3406
+ checkAudio();
3407
+ stopPolling();
3408
+ setPollingStatus("idle");
3409
+ setIsGenerating(false);
3410
+ setErrorMessage(null);
3411
+ }, [selectedElement]);
3412
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "panel-container", children: [
3413
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-title", children: "Generate Subtitles Panel" }),
3414
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
3415
+ /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
3416
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Checking for audio..." })
3417
+ ] }) }) }),
3418
+ !isLoading && containsAudio === false && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
3419
+ /* @__PURE__ */ jsxRuntime.jsx(VolumeX, { className: "empty-state-icon" }),
3420
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "No audio track found in this video" })
3421
+ ] }) }) }),
3422
+ !isLoading && containsAudio === true && pollingStatus === "idle" && !isGenerating && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
3423
+ /* @__PURE__ */ jsxRuntime.jsx(Volume2, { className: "empty-state-icon" }),
3424
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate subtitles" })
3425
+ ] }) }) }),
3426
+ !isLoading && isGenerating && pollingStatus === "polling" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
3427
+ /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
3428
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Generating subtitles... Please wait" })
3429
+ ] }) }) }),
3430
+ !isLoading && pollingStatus === "success" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
3431
+ /* @__PURE__ */ jsxRuntime.jsx(CircleCheck, { className: "empty-state-icon", color: "var(--color-green-500)" }),
3432
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: "Subtitles generated successfully!" })
3433
+ ] }) }) }),
3434
+ !isLoading && pollingStatus === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "empty-state-content", children: [
3435
+ /* @__PURE__ */ jsxRuntime.jsx(CircleX, { className: "empty-state-icon", color: "var(--color-red-500)" }),
3436
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate subtitles" })
3437
+ ] }) }) }),
3438
+ !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsxRuntime.jsx(
3439
+ "button",
3440
+ {
3441
+ onClick: handleGenerateSubtitles,
3442
+ disabled: !containsAudio || isGenerating,
3443
+ className: "btn-primary w-full",
3444
+ children: isGenerating ? "Generating..." : "Generate Subtitles"
3445
+ }
3446
+ ) })
3447
+ ] });
3448
+ }
3148
3449
  function PropertiesPanelContainer({
3149
3450
  selectedProp,
3150
3451
  selectedElement,
3151
- updateElement
3452
+ updateElement,
3453
+ addSubtitlesToTimeline,
3454
+ onGenerateSubtitles,
3455
+ getSubtitleStatus
3152
3456
  }) {
3153
3457
  if (!selectedElement) {
3154
3458
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "properties-title", children: "Select Element to see properties" }) }) });
@@ -3184,45 +3488,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3184
3488
  selectedElement,
3185
3489
  updateElement
3186
3490
  }
3491
+ ),
3492
+ selectedProp === "generate-subtitles" && /* @__PURE__ */ jsxRuntime.jsx(
3493
+ GenerateSubtitlesPanel,
3494
+ {
3495
+ selectedElement,
3496
+ addSubtitlesToTimeline,
3497
+ onGenerateSubtitles,
3498
+ getSubtitleStatus
3499
+ }
3187
3500
  )
3188
3501
  ] });
3189
3502
  }
3190
- const loadFile = (accept) => {
3191
- return new Promise((resolve, reject) => {
3192
- try {
3193
- const input = document.createElement("input");
3194
- input.type = "file";
3195
- input.accept = accept;
3196
- input.style.display = "none";
3197
- document.body.appendChild(input);
3198
- const cleanup = () => {
3199
- input.value = "";
3200
- document.body.removeChild(input);
3201
- };
3202
- input.onchange = () => {
3203
- const file = input.files && input.files[0];
3204
- cleanup();
3205
- if (!file) {
3206
- reject(new Error("No file selected"));
3207
- return;
3208
- }
3209
- resolve(file);
3210
- };
3211
- input.click();
3212
- } catch (error) {
3213
- reject(error);
3214
- }
3215
- });
3216
- };
3217
- const saveAsFile = (content, type, name) => {
3218
- const blob = typeof content === "string" ? new Blob([content], { type }) : content;
3219
- const url = URL.createObjectURL(blob);
3220
- const a = document.createElement("a");
3221
- a.href = url;
3222
- a.download = name;
3223
- a.click();
3224
- URL.revokeObjectURL(url);
3225
- };
3226
3503
  const useStudioOperation = (studioConfig) => {
3227
3504
  const { editor, present } = timeline.useTimelineContext();
3228
3505
  const [projectName, setProjectName] = react.useState("");
@@ -3276,7 +3553,79 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3276
3553
  alert("Export video not supported in demo mode");
3277
3554
  }
3278
3555
  };
3279
- return { onLoadProject, onSaveProject, onExportVideo };
3556
+ const onGenerateSubtitles = async (videoElement) => {
3557
+ if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3558
+ const service = studioConfig.subtitleGenerationService;
3559
+ const reqId = await service.generateSubtitles(videoElement, present);
3560
+ return reqId;
3561
+ }
3562
+ alert("Generate subtitles not supported in demo mode");
3563
+ return null;
3564
+ };
3565
+ const addSubtitlesToTimeline = (subtitles) => {
3566
+ var _a;
3567
+ const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(subtitles);
3568
+ if (updatedProjectJSON) {
3569
+ editor.loadProject(updatedProjectJSON);
3570
+ }
3571
+ };
3572
+ const getSubtitleStatus = async (reqId) => {
3573
+ if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3574
+ const service = studioConfig.subtitleGenerationService;
3575
+ return await service.getRequestStatus(reqId);
3576
+ }
3577
+ return {
3578
+ status: "failed",
3579
+ error: "Subtitle generation service not found"
3580
+ };
3581
+ };
3582
+ return {
3583
+ onLoadProject,
3584
+ onSaveProject,
3585
+ onExportVideo,
3586
+ onGenerateSubtitles,
3587
+ addSubtitlesToTimeline,
3588
+ getSubtitleStatus
3589
+ };
3590
+ };
3591
+ const useGenerateSubtitles = (studioConfig) => {
3592
+ const { editor, present } = timeline.useTimelineContext();
3593
+ const onGenerateSubtitles = async (videoElement) => {
3594
+ if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3595
+ const service = studioConfig.subtitleGenerationService;
3596
+ const reqId = await service.generateSubtitles(
3597
+ videoElement,
3598
+ present
3599
+ );
3600
+ return reqId;
3601
+ }
3602
+ alert("Generate subtitles not supported in demo mode");
3603
+ return null;
3604
+ };
3605
+ const addSubtitlesToTimeline = (subtitles) => {
3606
+ var _a;
3607
+ const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(
3608
+ subtitles
3609
+ );
3610
+ if (updatedProjectJSON) {
3611
+ editor.loadProject(updatedProjectJSON);
3612
+ }
3613
+ };
3614
+ const getSubtitleStatus = async (reqId) => {
3615
+ if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3616
+ const service = studioConfig.subtitleGenerationService;
3617
+ return await service.getRequestStatus(reqId);
3618
+ }
3619
+ return {
3620
+ status: "failed",
3621
+ error: "Subtitle generation service not found"
3622
+ };
3623
+ };
3624
+ return {
3625
+ onGenerateSubtitles,
3626
+ addSubtitlesToTimeline,
3627
+ getSubtitleStatus
3628
+ };
3280
3629
  };
3281
3630
  function TwickStudio({ studioConfig }) {
3282
3631
  var _a;
@@ -3290,7 +3639,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3290
3639
  updateElement
3291
3640
  } = useStudioManager();
3292
3641
  const { videoResolution, setVideoResolution } = timeline.useTimelineContext();
3293
- const { onLoadProject, onSaveProject, onExportVideo } = useStudioOperation(studioConfig);
3642
+ const {
3643
+ onLoadProject,
3644
+ onSaveProject,
3645
+ onExportVideo
3646
+ } = useStudioOperation(studioConfig);
3647
+ const { onGenerateSubtitles, addSubtitlesToTimeline, getSubtitleStatus } = useGenerateSubtitles(studioConfig);
3294
3648
  const twickStudiConfig = react.useMemo(
3295
3649
  () => ({
3296
3650
  canvasMode: true,
@@ -3347,7 +3701,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3347
3701
  {
3348
3702
  selectedProp,
3349
3703
  selectedElement,
3350
- updateElement
3704
+ updateElement,
3705
+ addSubtitlesToTimeline,
3706
+ onGenerateSubtitles,
3707
+ getSubtitleStatus
3351
3708
  }
3352
3709
  ),
3353
3710
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -3362,6 +3719,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3362
3719
  ] }) });
3363
3720
  }
3364
3721
  exports2.AudioPanel = AudioPanel;
3722
+ exports2.CAPTION_PROPS = CAPTION_PROPS;
3365
3723
  exports2.CirclePanel = CirclePanel;
3366
3724
  exports2.IconPanel = IconPanel;
3367
3725
  exports2.ImagePanel = ImagePanel;
@@ -3373,6 +3731,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3373
3731
  exports2.TwickStudio = TwickStudio;
3374
3732
  exports2.VideoPanel = VideoPanel;
3375
3733
  exports2.default = TwickStudio;
3734
+ exports2.useGenerateSubtitles = useGenerateSubtitles;
3376
3735
  exports2.useStudioManager = useStudioManager;
3377
3736
  Object.defineProperties(exports2, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3378
3737
  });