@twick/studio 0.15.23 → 0.15.25

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.mjs CHANGED
@@ -3,7 +3,7 @@ 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 { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
5
  import { forwardRef, createElement, useState, useEffect, useRef, useCallback, createContext, useContext, useMemo } from "react";
6
- import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, TIMELINE_ELEMENT_TYPE, EffectElement, CAPTION_STYLE, CaptionElement, TRACK_TYPES, LineElement, ArrowElement, RectElement, CircleElement, ElementTextEffect, ElementAnimation, CAPTION_STYLE_OPTIONS, exportChaptersAsYouTube, exportChaptersAsJSON, getCaptionLanguages, exportCaptionsAsSRT, exportCaptionsAsVTT, PLAYER_STATE } from "@twick/timeline";
6
+ import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, TIMELINE_ELEMENT_TYPE, EffectElement, computeCaptionGeometry, CAPTION_STYLE, CaptionElement, TRACK_TYPES, LineElement, ArrowElement, RectElement, CircleElement, ElementTextEffect, ElementAnimation, CAPTION_STYLE_OPTIONS, exportChaptersAsYouTube, exportChaptersAsJSON, getCaptionLanguages, exportCaptionsAsSRT, exportCaptionsAsVTT, PLAYER_STATE } from "@twick/timeline";
7
7
  import { AudioElement as AudioElement2, CAPTION_COLOR, CAPTION_FONT, CAPTION_STYLE as CAPTION_STYLE2, CAPTION_STYLE_OPTIONS as CAPTION_STYLE_OPTIONS2, CaptionElement as CaptionElement2, CircleElement as CircleElement2, ElementAdder, ElementAnimation as ElementAnimation2, ElementCloner, ElementDeserializer, ElementFrameEffect, ElementRemover, ElementSerializer, ElementSplitter, ElementTextEffect as ElementTextEffect2, ElementUpdater, ElementValidator, INITIAL_TIMELINE_DATA, IconElement, ImageElement as ImageElement2, PROCESS_STATE, RectElement as RectElement2, TIMELINE_ACTION, TIMELINE_ELEMENT_TYPE as TIMELINE_ELEMENT_TYPE2, TextElement as TextElement2, TimelineEditor, TimelineProvider, Track as Track2, TrackElement as TrackElement2, VideoElement as VideoElement2, WORDS_PER_PHRASE, generateShortUuid, getCurrentElements, getTotalDuration, isElementId, isTrackId, useTimelineContext as useTimelineContext2 } from "@twick/timeline";
8
8
  import VideoEditor, { useEditorManager, BrowserMediaManager, TIMELINE_DROP_MEDIA_TYPE, throttle, AVAILABLE_TEXT_FONTS, TEXT_EFFECTS, ANIMATIONS } from "@twick/video-editor";
9
9
  import { ANIMATIONS as ANIMATIONS2, BaseMediaManager, BrowserMediaManager as BrowserMediaManager2, PlayerControls, TEXT_EFFECTS as TEXT_EFFECTS2, TimelineManager, default as default2, animationGifs, getAnimationGif, setElementColors, useEditorManager as useEditorManager2, usePlayerControl, useTimelineControl } from "@twick/video-editor";
@@ -4672,6 +4672,16 @@ function EffectStylePanelContainer({
4672
4672
  }
4673
4673
  );
4674
4674
  }
4675
+ const formatTime = (seconds) => {
4676
+ if (!Number.isFinite(seconds) || seconds < 0) return "0:00.00";
4677
+ const totalMs = Math.round(seconds * 1e3);
4678
+ const totalSeconds = Math.floor(totalMs / 1e3);
4679
+ const minutes = Math.floor(totalSeconds / 60);
4680
+ const secs = totalSeconds % 60;
4681
+ const ms = Math.floor(totalMs % 1e3 / 10);
4682
+ const pad = (n, l = 2) => String(n).padStart(l, "0");
4683
+ return `${minutes}:${pad(secs)}.${pad(ms)}`;
4684
+ };
4675
4685
  function CaptionsPanel({
4676
4686
  captions,
4677
4687
  addCaption,
@@ -4679,54 +4689,102 @@ function CaptionsPanel({
4679
4689
  deleteCaption,
4680
4690
  updateCaption
4681
4691
  }) {
4682
- return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
4683
- /* @__PURE__ */ jsx("h3", { className: "panel-title", children: "Captions" }),
4684
- captions.map((caption, i) => /* @__PURE__ */ jsxs(
4685
- "div",
4686
- {
4687
- className: "panel-section gap-2",
4688
- children: [
4689
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
4690
- "input",
4691
- {
4692
- type: "text",
4693
- placeholder: "Enter caption text",
4694
- value: caption.t,
4695
- onChange: (e) => updateCaption(i, { ...caption, t: e.target.value }),
4696
- className: "input-dark"
4697
- }
4698
- ) }),
4699
- /* @__PURE__ */ jsxs("div", { className: "flex-container justify-between", children: [
4700
- /* @__PURE__ */ jsx(
4701
- "button",
4702
- {
4703
- onClick: () => splitCaption(i),
4704
- className: "btn-ghost",
4705
- title: "Split caption",
4706
- children: /* @__PURE__ */ jsx(Scissors, { className: "icon-sm" })
4707
- }
4708
- ),
4709
- /* @__PURE__ */ jsx(
4710
- "button",
4711
- {
4712
- onClick: () => deleteCaption(i),
4713
- className: "btn-ghost",
4714
- title: "Delete caption",
4715
- children: /* @__PURE__ */ jsx(Trash2, { className: "icon-sm", color: "var(--color-red-500)" })
4716
- }
4717
- )
4718
- ] })
4719
- ]
4720
- },
4721
- i
4722
- )),
4723
- /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("button", { onClick: addCaption, className: "btn-primary w-full", title: "Add caption", children: "Add" }) })
4692
+ return /* @__PURE__ */ jsxs("div", { className: "panel-container captions-panel", children: [
4693
+ /* @__PURE__ */ jsxs("div", { className: "captions-panel-header", children: [
4694
+ /* @__PURE__ */ jsx("h3", { className: "panel-title", children: "Captions" }),
4695
+ /* @__PURE__ */ jsxs("div", { className: "captions-panel-header-meta", children: [
4696
+ captions.length === 0 ? /* @__PURE__ */ jsx("span", { className: "captions-panel-count", children: "No captions yet" }) : null,
4697
+ /* @__PURE__ */ jsx(
4698
+ "button",
4699
+ {
4700
+ onClick: addCaption,
4701
+ className: "btn-primary captions-panel-add-button",
4702
+ title: "Add caption",
4703
+ children: "Add caption"
4704
+ }
4705
+ )
4706
+ ] })
4707
+ ] }),
4708
+ captions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "panel-section captions-panel-empty", children: [
4709
+ /* @__PURE__ */ jsx("p", { className: "captions-panel-empty-title", children: "Start your first caption" }),
4710
+ /* @__PURE__ */ jsx("p", { className: "captions-panel-empty-subtitle", children: "Use the button above to add the first caption block for the active track." }),
4711
+ /* @__PURE__ */ jsx(
4712
+ "button",
4713
+ {
4714
+ onClick: addCaption,
4715
+ className: "btn-primary captions-panel-empty-button",
4716
+ title: "Add first caption",
4717
+ children: "Add caption"
4718
+ }
4719
+ )
4720
+ ] }) : /* @__PURE__ */ jsx("div", { className: "panel-section captions-panel-list", children: captions.map((caption, i) => {
4721
+ return /* @__PURE__ */ jsxs(
4722
+ "div",
4723
+ {
4724
+ className: "captions-panel-item",
4725
+ children: [
4726
+ /* @__PURE__ */ jsxs("div", { className: "captions-panel-item-header", children: [
4727
+ /* @__PURE__ */ jsx("span", { className: "captions-panel-time captions-panel-time-start", children: formatTime(caption.s) }),
4728
+ /* @__PURE__ */ jsx("span", { className: "captions-panel-time captions-panel-time-end", children: formatTime(caption.e) })
4729
+ ] }),
4730
+ /* @__PURE__ */ jsxs("div", { className: "captions-panel-item-body", children: [
4731
+ /* @__PURE__ */ jsx(
4732
+ "textarea",
4733
+ {
4734
+ placeholder: "Enter caption text",
4735
+ value: caption.t,
4736
+ onChange: (e) => updateCaption(i, { ...caption, t: e.target.value }),
4737
+ className: "input-dark captions-panel-textarea"
4738
+ }
4739
+ ),
4740
+ /* @__PURE__ */ jsxs("div", { className: "captions-panel-actions", children: [
4741
+ /* @__PURE__ */ jsx(
4742
+ "button",
4743
+ {
4744
+ onClick: () => splitCaption(i),
4745
+ className: "btn-ghost captions-panel-action-button",
4746
+ title: "Split caption at midpoint",
4747
+ children: /* @__PURE__ */ jsx(Scissors, { className: "icon-sm" })
4748
+ }
4749
+ ),
4750
+ /* @__PURE__ */ jsx(
4751
+ "button",
4752
+ {
4753
+ onClick: () => deleteCaption(i),
4754
+ className: "btn-ghost captions-panel-action-button",
4755
+ title: "Delete caption",
4756
+ children: /* @__PURE__ */ jsx(
4757
+ Trash2,
4758
+ {
4759
+ className: "icon-sm",
4760
+ color: "var(--color-red-500)"
4761
+ }
4762
+ )
4763
+ }
4764
+ )
4765
+ ] })
4766
+ ] })
4767
+ ]
4768
+ },
4769
+ i
4770
+ );
4771
+ }) })
4724
4772
  ] });
4725
4773
  }
4774
+ const HIGHLIGHT_BG_FONT_SIZE = 46;
4775
+ const WORD_BY_WORD_FONT_SIZE = 46;
4776
+ const WORD_BY_WORD_WITH_BG_FONT_SIZE = 46;
4777
+ const OUTLINE_ONLY_FONT_SIZE = 42;
4778
+ const SOFT_BOX_FONT_SIZE = 40;
4779
+ const HIGHLIGHT_BG_GEOMETRY = computeCaptionGeometry(HIGHLIGHT_BG_FONT_SIZE, CAPTION_STYLE.WORD_BG_HIGHLIGHT);
4780
+ const WORD_BY_WORD_GEOMETRY = computeCaptionGeometry(WORD_BY_WORD_FONT_SIZE, CAPTION_STYLE.WORD_BY_WORD);
4781
+ const WORD_BY_WORD_WITH_BG_GEOMETRY = computeCaptionGeometry(WORD_BY_WORD_WITH_BG_FONT_SIZE, CAPTION_STYLE.WORD_BY_WORD_WITH_BG);
4782
+ const OUTLINE_ONLY_GEOMETRY = computeCaptionGeometry(OUTLINE_ONLY_FONT_SIZE, CAPTION_STYLE.OUTLINE_ONLY);
4783
+ const SOFT_BOX_GEOMETRY = computeCaptionGeometry(SOFT_BOX_FONT_SIZE, CAPTION_STYLE.SOFT_BOX);
4726
4784
  const CAPTION_PROPS = {
4727
4785
  [CAPTION_STYLE.WORD_BG_HIGHLIGHT]: {
4728
4786
  font: {
4729
- size: 46,
4787
+ size: HIGHLIGHT_BG_FONT_SIZE,
4730
4788
  weight: 700,
4731
4789
  family: "Bangers"
4732
4790
  },
@@ -4735,15 +4793,16 @@ const CAPTION_PROPS = {
4735
4793
  highlight: "#ff4081",
4736
4794
  bgColor: "#444444"
4737
4795
  },
4738
- lineWidth: 0.35,
4796
+ lineWidth: HIGHLIGHT_BG_GEOMETRY.lineWidth,
4797
+ rectProps: HIGHLIGHT_BG_GEOMETRY.rectProps,
4739
4798
  stroke: "#000000",
4740
4799
  fontWeight: 700,
4741
- shadowOffset: [-3, 3],
4800
+ shadowOffset: [-1, 1],
4742
4801
  shadowColor: "#000000"
4743
4802
  },
4744
4803
  [CAPTION_STYLE.WORD_BY_WORD]: {
4745
4804
  font: {
4746
- size: 46,
4805
+ size: WORD_BY_WORD_FONT_SIZE,
4747
4806
  weight: 700,
4748
4807
  family: "Bangers"
4749
4808
  },
@@ -4752,15 +4811,16 @@ const CAPTION_PROPS = {
4752
4811
  highlight: "#ff4081",
4753
4812
  bgColor: "#444444"
4754
4813
  },
4755
- lineWidth: 0.35,
4814
+ lineWidth: WORD_BY_WORD_GEOMETRY.lineWidth,
4815
+ rectProps: WORD_BY_WORD_GEOMETRY.rectProps,
4756
4816
  stroke: "#000000",
4757
- shadowOffset: [-2, 2],
4817
+ shadowOffset: [-1, 1],
4758
4818
  shadowColor: "#000000",
4759
4819
  shadowBlur: 5
4760
4820
  },
4761
4821
  [CAPTION_STYLE.WORD_BY_WORD_WITH_BG]: {
4762
4822
  font: {
4763
- size: 46,
4823
+ size: WORD_BY_WORD_WITH_BG_FONT_SIZE,
4764
4824
  weight: 700,
4765
4825
  family: "Bangers"
4766
4826
  },
@@ -4769,14 +4829,15 @@ const CAPTION_PROPS = {
4769
4829
  highlight: "#ff4081",
4770
4830
  bgColor: "#444444"
4771
4831
  },
4772
- lineWidth: 0.35,
4773
- shadowOffset: [-2, 2],
4832
+ lineWidth: WORD_BY_WORD_WITH_BG_GEOMETRY.lineWidth,
4833
+ rectProps: WORD_BY_WORD_WITH_BG_GEOMETRY.rectProps,
4834
+ shadowOffset: [-1, 1],
4774
4835
  shadowColor: "#000000",
4775
4836
  shadowBlur: 5
4776
4837
  },
4777
4838
  [CAPTION_STYLE.OUTLINE_ONLY]: {
4778
4839
  font: {
4779
- size: 42,
4840
+ size: OUTLINE_ONLY_FONT_SIZE,
4780
4841
  weight: 600,
4781
4842
  family: "Arial"
4782
4843
  },
@@ -4785,7 +4846,8 @@ const CAPTION_PROPS = {
4785
4846
  highlight: "#ff4081",
4786
4847
  bgColor: "#000000"
4787
4848
  },
4788
- lineWidth: 0.5,
4849
+ lineWidth: OUTLINE_ONLY_GEOMETRY.lineWidth,
4850
+ rectProps: OUTLINE_ONLY_GEOMETRY.rectProps,
4789
4851
  stroke: "#000000",
4790
4852
  fontWeight: 600,
4791
4853
  shadowOffset: [0, 0],
@@ -4794,7 +4856,7 @@ const CAPTION_PROPS = {
4794
4856
  },
4795
4857
  [CAPTION_STYLE.SOFT_BOX]: {
4796
4858
  font: {
4797
- size: 40,
4859
+ size: SOFT_BOX_FONT_SIZE,
4798
4860
  weight: 600,
4799
4861
  family: "Montserrat"
4800
4862
  },
@@ -4803,7 +4865,8 @@ const CAPTION_PROPS = {
4803
4865
  highlight: "#ff4081",
4804
4866
  bgColor: "#333333"
4805
4867
  },
4806
- lineWidth: 0.2,
4868
+ lineWidth: SOFT_BOX_GEOMETRY.lineWidth,
4869
+ rectProps: SOFT_BOX_GEOMETRY.rectProps,
4807
4870
  stroke: "#000000",
4808
4871
  fontWeight: 600,
4809
4872
  shadowOffset: [-1, 1],
@@ -4908,141 +4971,13 @@ function CaptionsPanelContainer() {
4908
4971
  const captionsPanelProps = useCaptionsPanel();
4909
4972
  return /* @__PURE__ */ jsx(CaptionsPanel, { ...captionsPanelProps });
4910
4973
  }
4911
- const FAL_IMAGE_ENDPOINTS = [
4912
- {
4913
- provider: "fal",
4914
- endpointId: "fal-ai/flux-pro/kontext",
4915
- label: "FLUX.1 Kontext [pro]",
4916
- description: "Professional image generation with context-aware editing",
4917
- popularity: 5,
4918
- category: "image",
4919
- inputAsset: ["image"],
4920
- availableDimensions: [
4921
- { width: 1024, height: 1024, label: "1024x1024 (1:1)" },
4922
- { width: 1024, height: 576, label: "1024x576 (16:9)" },
4923
- { width: 576, height: 1024, label: "576x1024 (9:16)" }
4924
- ]
4925
- },
4926
- {
4927
- provider: "fal",
4928
- endpointId: "fal-ai/flux/dev",
4929
- label: "FLUX.1 [dev]",
4930
- description: "High-quality image generation",
4931
- popularity: 5,
4932
- category: "image",
4933
- minSteps: 1,
4934
- maxSteps: 50,
4935
- defaultSteps: 28,
4936
- minGuidanceScale: 1,
4937
- maxGuidanceScale: 20,
4938
- defaultGuidanceScale: 3.5,
4939
- hasSeed: true
4940
- },
4941
- {
4942
- provider: "fal",
4943
- endpointId: "fal-ai/flux/schnell",
4944
- label: "FLUX.1 [schnell]",
4945
- description: "Ultra-fast image generation",
4946
- popularity: 4,
4947
- category: "image",
4948
- defaultSteps: 4,
4949
- availableDimensions: [
4950
- { width: 1024, height: 1024, label: "1024x1024 (1:1)" },
4951
- { width: 1024, height: 576, label: "1024x576 (16:9)" },
4952
- { width: 576, height: 1024, label: "576x1024 (9:16)" }
4953
- ]
4954
- },
4955
- {
4956
- provider: "fal",
4957
- endpointId: "fal-ai/gemini-25-flash-image",
4958
- label: "Gemini 2.5 Flash Image",
4959
- description: "Rapid text-to-image generation",
4960
- popularity: 5,
4961
- category: "image",
4962
- availableDimensions: [
4963
- { width: 1024, height: 1024, label: "1024x1024 (1:1)" },
4964
- { width: 1024, height: 768, label: "1024x768 (4:3)" },
4965
- { width: 768, height: 1024, label: "768x1024 (3:4)" },
4966
- { width: 1024, height: 576, label: "1024x576 (16:9)" },
4967
- { width: 576, height: 1024, label: "576x1024 (9:16)" }
4968
- ]
4969
- },
4970
- {
4971
- provider: "fal",
4972
- endpointId: "fal-ai/ideogram/v3",
4973
- label: "Ideogram V3",
4974
- description: "Advanced text-to-image with superior text rendering",
4975
- popularity: 5,
4976
- category: "image",
4977
- hasSeed: true,
4978
- hasNegativePrompt: true
4979
- }
4980
- ];
4981
- const FAL_VIDEO_ENDPOINTS = [
4982
- {
4983
- provider: "fal",
4984
- endpointId: "fal-ai/veo3",
4985
- label: "Veo 3",
4986
- description: "Google Veo 3 text-to-video",
4987
- popularity: 5,
4988
- category: "video",
4989
- availableDurations: [4, 6, 8],
4990
- defaultDuration: 8,
4991
- availableDimensions: [
4992
- { width: 576, height: 1024, label: "576x1024 (9:16)" },
4993
- { width: 1024, height: 576, label: "1024x576 (16:9)" },
4994
- { width: 1024, height: 1024, label: "1024x1024 (1:1)" }
4995
- ]
4996
- },
4997
- {
4998
- provider: "fal",
4999
- endpointId: "fal-ai/veo3/fast",
5000
- label: "Veo 3 Fast",
5001
- description: "Accelerated Veo 3 text-to-video",
5002
- popularity: 5,
5003
- category: "video",
5004
- availableDurations: [4, 6, 8],
5005
- defaultDuration: 8,
5006
- availableDimensions: [
5007
- { width: 576, height: 1024, label: "576x1024 (9:16)" },
5008
- { width: 1024, height: 576, label: "1024x576 (16:9)" },
5009
- { width: 1024, height: 1024, label: "1024x1024 (1:1)" }
5010
- ]
5011
- },
5012
- {
5013
- provider: "fal",
5014
- endpointId: "fal-ai/veo3/image-to-video",
5015
- label: "Veo 3 Image-to-Video",
5016
- description: "Animate images with Veo 3",
5017
- popularity: 5,
5018
- category: "video",
5019
- inputAsset: ["image"],
5020
- availableDurations: [8],
5021
- defaultDuration: 8
5022
- },
5023
- {
5024
- provider: "fal",
5025
- endpointId: "fal-ai/kling-video/v2.5-turbo/pro/text-to-video",
5026
- label: "Kling 2.5 Turbo Pro",
5027
- description: "Text-to-video with fluid motion",
5028
- popularity: 5,
5029
- category: "video",
5030
- availableDurations: [5, 10],
5031
- defaultDuration: 5,
5032
- availableDimensions: [
5033
- { width: 1024, height: 576, label: "1024x576 (16:9)" },
5034
- { width: 576, height: 1024, label: "576x1024 (9:16)" },
5035
- { width: 1024, height: 1024, label: "1024x1024 (1:1)" }
5036
- ]
5037
- }
5038
- ];
5039
4974
  const DEFAULT_IMAGE_DURATION = 5;
5040
4975
  function GenerateMediaPanelContainer({
5041
4976
  videoResolution,
5042
4977
  addElement,
5043
4978
  studioConfig
5044
4979
  }) {
5045
- var _a;
4980
+ var _a, _b, _c;
5046
4981
  const { getCurrentTime } = useLivePlayerContext();
5047
4982
  const [tab, setTab] = useState("image");
5048
4983
  const [prompt2, setPrompt] = useState("");
@@ -5053,8 +4988,12 @@ function GenerateMediaPanelContainer({
5053
4988
  const imageService = studioConfig == null ? void 0 : studioConfig.imageGenerationService;
5054
4989
  const videoService = studioConfig == null ? void 0 : studioConfig.videoGenerationService;
5055
4990
  const hasAnyService = !!imageService || !!videoService;
5056
- const endpoints = tab === "image" ? FAL_IMAGE_ENDPOINTS : FAL_VIDEO_ENDPOINTS;
5057
- const defaultEndpointId = ((_a = endpoints[0]) == null ? void 0 : _a.endpointId) ?? "";
4991
+ const imageModels = ((_a = imageService == null ? void 0 : imageService.getAvailableModels) == null ? void 0 : _a.call(imageService)) ?? [];
4992
+ const videoModels = ((_b = videoService == null ? void 0 : videoService.getAvailableModels) == null ? void 0 : _b.call(videoService)) ?? [];
4993
+ const endpoints = tab === "image" ? imageModels : videoModels;
4994
+ const defaultEndpointId = ((_c = endpoints[0]) == null ? void 0 : _c.endpointId) ?? "";
4995
+ const selectedEndpoint = endpoints.find((endpoint) => endpoint.endpointId === selectedEndpointId) ?? endpoints[0];
4996
+ const selectedProvider = selectedEndpoint == null ? void 0 : selectedEndpoint.provider;
5058
4997
  useEffect(() => {
5059
4998
  if (!selectedEndpointId && defaultEndpointId) {
5060
4999
  setSelectedEndpointId(defaultEndpointId);
@@ -5116,9 +5055,16 @@ function GenerateMediaPanelContainer({
5116
5055
  setStatus("Starting...");
5117
5056
  try {
5118
5057
  const endpointId = selectedEndpointId || defaultEndpointId;
5058
+ const provider = selectedProvider;
5059
+ if (!endpointId || !provider) {
5060
+ setError("No model is configured for this tab");
5061
+ setIsGenerating(false);
5062
+ setStatus(null);
5063
+ return;
5064
+ }
5119
5065
  if (tab === "image" && imageService) {
5120
5066
  const requestId = await imageService.generateImage({
5121
- provider: "fal",
5067
+ provider,
5122
5068
  endpointId,
5123
5069
  prompt: prompt2.trim()
5124
5070
  });
@@ -5128,7 +5074,7 @@ function GenerateMediaPanelContainer({
5128
5074
  }
5129
5075
  } else if (tab === "video" && videoService) {
5130
5076
  const requestId = await videoService.generateVideo({
5131
- provider: "fal",
5077
+ provider,
5132
5078
  endpointId,
5133
5079
  prompt: prompt2.trim()
5134
5080
  });
@@ -5150,7 +5096,8 @@ function GenerateMediaPanelContainer({
5150
5096
  defaultEndpointId,
5151
5097
  imageService,
5152
5098
  videoService,
5153
- pollStatus
5099
+ pollStatus,
5100
+ selectedProvider
5154
5101
  ]);
5155
5102
  if (!hasAnyService) {
5156
5103
  return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Image and video generation require configuration. Add imageGenerationService and videoGenerationService to StudioConfig." }) });
@@ -6438,6 +6385,116 @@ const CAPTION_COLOR2 = {
6438
6385
  bgColor: "#8C52FF",
6439
6386
  outlineColor: "#000000"
6440
6387
  };
6388
+ const CAPTION_STYLE_COLOR_META = {
6389
+ // Word background highlight - white text on colored pill
6390
+ highlight_bg: {
6391
+ // Text color, and background pill color used in animation.
6392
+ usedColors: ["text", "bgColor"],
6393
+ labels: {
6394
+ text: "Text Color",
6395
+ bgColor: "Highlight Background"
6396
+ }
6397
+ },
6398
+ // Simple word-by-word – text only
6399
+ word_by_word: {
6400
+ // Visualizer uses text as fill + outlineColor for stroke, and highlight for active word.
6401
+ usedColors: ["text", "highlight", "outlineColor"],
6402
+ labels: {
6403
+ text: "Text Color",
6404
+ highlight: "Highlight Color",
6405
+ outlineColor: "Outline Color"
6406
+ }
6407
+ },
6408
+ // Word-by-word with a phrase bar background
6409
+ word_by_word_with_bg: {
6410
+ // Text color (fill), highlight for active word, outlineColor (stroke), bgColor used by phrase rect.
6411
+ usedColors: ["text", "highlight", "bgColor", "outlineColor"],
6412
+ labels: {
6413
+ text: "Text Color",
6414
+ bgColor: "Bar Background",
6415
+ highlight: "Highlight Color",
6416
+ outlineColor: "Outline Color"
6417
+ }
6418
+ },
6419
+ // Classic outlined text
6420
+ outline_only: {
6421
+ // Outline-only style: fill + outline color; highlight not used in animation.
6422
+ usedColors: ["text", "outlineColor"],
6423
+ labels: {
6424
+ text: "Fill Color",
6425
+ outlineColor: "Outline Color"
6426
+ }
6427
+ },
6428
+ // Soft rounded box behind text
6429
+ soft_box: {
6430
+ usedColors: ["text", "bgColor", "highlight", "outlineColor"],
6431
+ labels: {
6432
+ text: "Text Color",
6433
+ highlight: "Highlight Color",
6434
+ bgColor: "Box Background",
6435
+ outlineColor: "Outline Color"
6436
+ }
6437
+ },
6438
+ // Broadcast style lower-third bar
6439
+ lower_third: {
6440
+ // Title text, bar background, highlight color and outline color.
6441
+ usedColors: ["text", "bgColor", "outlineColor"],
6442
+ labels: {
6443
+ text: "Title Text Color",
6444
+ bgColor: "Bar Background",
6445
+ highlight: "Highlight Color",
6446
+ outlineColor: "Outline Color"
6447
+ }
6448
+ },
6449
+ // Typewriter – text only
6450
+ typewriter: {
6451
+ // Text color and outline color (stroke) used by visualizer; highlight not animated.
6452
+ usedColors: ["text", "outlineColor"],
6453
+ labels: {
6454
+ text: "Text Color",
6455
+ outlineColor: "Outline Color"
6456
+ }
6457
+ },
6458
+ // Karaoke – base text plus active word highlight
6459
+ karaoke: {
6460
+ // Base text color, active word highlight color, outline color.
6461
+ usedColors: ["text", "highlight", "outlineColor"],
6462
+ labels: {
6463
+ text: "Text Color",
6464
+ highlight: "Highlight Color",
6465
+ outlineColor: "Outline Color"
6466
+ }
6467
+ },
6468
+ // Karaoke-word – single active word, previous words dimmed
6469
+ "karaoke-word": {
6470
+ // Same color needs as karaoke.
6471
+ usedColors: ["text", "highlight", "outlineColor"],
6472
+ labels: {
6473
+ text: "Text Color",
6474
+ highlight: "Highlight Color",
6475
+ outlineColor: "Outline Color"
6476
+ }
6477
+ },
6478
+ // Pop / scale – text only
6479
+ pop_scale: {
6480
+ // Text color, highlight color for active word, and outline color; no background.
6481
+ usedColors: ["text", "highlight", "outlineColor"],
6482
+ labels: {
6483
+ text: "Text Color",
6484
+ highlight: "Highlight Color",
6485
+ outlineColor: "Outline Color"
6486
+ }
6487
+ }
6488
+ };
6489
+ const DEFAULT_COLOR_META = {
6490
+ usedColors: ["text", "bgColor", "outlineColor"],
6491
+ labels: {
6492
+ text: "Text Color",
6493
+ bgColor: "Background Color",
6494
+ outlineColor: "Outline Color"
6495
+ }
6496
+ };
6497
+ const CAPTION_FONTS = Object.values(AVAILABLE_TEXT_FONTS);
6441
6498
  function CaptionPropPanel({
6442
6499
  selectedElement,
6443
6500
  updateElement
@@ -6455,24 +6512,42 @@ function CaptionPropPanel({
6455
6512
  bgColor: CAPTION_COLOR2.bgColor,
6456
6513
  outlineColor: CAPTION_COLOR2.outlineColor
6457
6514
  });
6515
+ const [useHighlight, setUseHighlight] = useState(true);
6516
+ const [useOutline, setUseOutline] = useState(true);
6458
6517
  const track = selectedElement instanceof CaptionElement ? editor.getTrackById(selectedElement.getTrackId()) : null;
6459
6518
  const trackProps = (track == null ? void 0 : track.getProps()) ?? {};
6460
6519
  const applyToAll = (trackProps == null ? void 0 : trackProps.applyToAll) ?? false;
6461
6520
  const handleUpdateCaption = (updates) => {
6462
6521
  const captionElement = selectedElement;
6463
6522
  if (!captionElement) return;
6523
+ const nextFontSize = updates.fontSize ?? fontSize;
6524
+ const geometry = computeCaptionGeometry(nextFontSize, updates.style ?? (capStyle == null ? void 0 : capStyle.value) ?? "");
6525
+ const highlightEnabled = updates.useHighlightOverride ?? useHighlight;
6526
+ const outlineEnabled = updates.useOutlineOverride ?? useOutline;
6527
+ const rawNextColors = updates.colors ?? colors;
6528
+ let effectiveColors = { ...rawNextColors };
6529
+ if (!highlightEnabled) {
6530
+ const { highlight, ...rest } = effectiveColors;
6531
+ effectiveColors = rest;
6532
+ }
6533
+ if (!outlineEnabled) {
6534
+ const { outlineColor, ...rest } = effectiveColors;
6535
+ effectiveColors = rest;
6536
+ }
6464
6537
  if (applyToAll && track) {
6465
6538
  const nextFont = {
6466
- size: updates.fontSize ?? fontSize,
6539
+ size: nextFontSize,
6467
6540
  family: updates.fontFamily ?? fontFamily
6468
6541
  };
6469
- const nextColors = updates.colors ?? colors;
6542
+ const nextColors = effectiveColors;
6470
6543
  const nextCapStyle = updates.style ?? (capStyle == null ? void 0 : capStyle.value);
6471
6544
  track.setProps({
6472
6545
  ...trackProps,
6473
6546
  capStyle: nextCapStyle,
6474
6547
  font: { ...(trackProps == null ? void 0 : trackProps.font) ?? {}, ...nextFont },
6475
- colors: nextColors
6548
+ colors: nextColors,
6549
+ lineWidth: geometry.lineWidth,
6550
+ rectProps: geometry.rectProps
6476
6551
  });
6477
6552
  editor.refresh();
6478
6553
  } else {
@@ -6481,10 +6556,11 @@ function CaptionPropPanel({
6481
6556
  ...elementProps,
6482
6557
  capStyle: updates.style ?? (capStyle == null ? void 0 : capStyle.value),
6483
6558
  font: {
6484
- size: updates.fontSize ?? fontSize,
6559
+ size: nextFontSize,
6485
6560
  family: updates.fontFamily ?? fontFamily
6486
6561
  },
6487
- colors: updates.colors ?? colors
6562
+ colors: effectiveColors,
6563
+ lineWidth: geometry.lineWidth
6488
6564
  });
6489
6565
  updateElement == null ? void 0 : updateElement(captionElement);
6490
6566
  }
@@ -6510,11 +6586,62 @@ function CaptionPropPanel({
6510
6586
  bgColor: (c == null ? void 0 : c.bgColor) ?? CAPTION_COLOR2.bgColor,
6511
6587
  outlineColor: (c == null ? void 0 : c.outlineColor) ?? CAPTION_COLOR2.outlineColor
6512
6588
  });
6589
+ setUseHighlight((c == null ? void 0 : c.highlight) != null);
6590
+ setUseOutline((c == null ? void 0 : c.outlineColor) != null);
6513
6591
  }
6514
6592
  }, [selectedElement, applyToAll, changeLog]);
6515
6593
  if (!(selectedElement instanceof CaptionElement)) {
6516
6594
  return null;
6517
6595
  }
6596
+ const currentStyleKey = capStyle == null ? void 0 : capStyle.value;
6597
+ const currentColorMeta = currentStyleKey && CAPTION_STYLE_COLOR_META[currentStyleKey] || DEFAULT_COLOR_META;
6598
+ const defaultColorLabels = {
6599
+ text: "Text Color",
6600
+ bgColor: "Background Color",
6601
+ highlight: "Highlight Color",
6602
+ outlineColor: "Outline Color"
6603
+ };
6604
+ const renderColorControl = (key) => {
6605
+ if (key === "highlight" && !useHighlight) {
6606
+ return null;
6607
+ }
6608
+ if (key === "outlineColor" && !useOutline) {
6609
+ return null;
6610
+ }
6611
+ const label = currentColorMeta.labels[key] ?? defaultColorLabels[key];
6612
+ const value = colors[key];
6613
+ const handleChange = (next) => {
6614
+ const nextColors = { ...colors, [key]: next };
6615
+ setColors(nextColors);
6616
+ handleUpdateCaption({ colors: nextColors });
6617
+ };
6618
+ if (value == null) {
6619
+ return null;
6620
+ }
6621
+ return /* @__PURE__ */ jsxs("div", { className: "color-control", children: [
6622
+ /* @__PURE__ */ jsx("label", { className: "label-small", children: label }),
6623
+ /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
6624
+ /* @__PURE__ */ jsx(
6625
+ "input",
6626
+ {
6627
+ type: "color",
6628
+ value,
6629
+ onChange: (e) => handleChange(e.target.value),
6630
+ className: "color-picker"
6631
+ }
6632
+ ),
6633
+ /* @__PURE__ */ jsx(
6634
+ "input",
6635
+ {
6636
+ type: "text",
6637
+ value,
6638
+ onChange: (e) => handleChange(e.target.value),
6639
+ className: "color-text"
6640
+ }
6641
+ )
6642
+ ] })
6643
+ ] }, key);
6644
+ };
6518
6645
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
6519
6646
  /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
6520
6647
  /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Caption Style" }),
@@ -6561,7 +6688,7 @@ function CaptionPropPanel({
6561
6688
  ] }),
6562
6689
  /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
6563
6690
  /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Font" }),
6564
- /* @__PURE__ */ jsxs(
6691
+ /* @__PURE__ */ jsx(
6565
6692
  "select",
6566
6693
  {
6567
6694
  value: fontFamily,
@@ -6571,111 +6698,59 @@ function CaptionPropPanel({
6571
6698
  handleUpdateCaption({ fontFamily: value });
6572
6699
  },
6573
6700
  className: "select-dark w-full",
6574
- children: [
6575
- /* @__PURE__ */ jsx("option", { value: "Bangers", children: "Bangers" }),
6576
- /* @__PURE__ */ jsx("option", { value: "Arial", children: "Arial" }),
6577
- /* @__PURE__ */ jsx("option", { value: "Helvetica", children: "Helvetica" }),
6578
- /* @__PURE__ */ jsx("option", { value: "Times New Roman", children: "Times New Roman" })
6579
- ]
6701
+ children: CAPTION_FONTS.map((font) => /* @__PURE__ */ jsx("option", { value: font, children: font }, font))
6580
6702
  }
6581
6703
  )
6582
6704
  ] }),
6583
6705
  /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
6584
6706
  /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Colors" }),
6585
6707
  /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
6586
- /* @__PURE__ */ jsxs("div", { className: "color-control", children: [
6587
- /* @__PURE__ */ jsx("label", { className: "label-small", children: "Text Color" }),
6588
- /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
6589
- /* @__PURE__ */ jsx(
6590
- "input",
6591
- {
6592
- type: "color",
6593
- value: colors.text,
6594
- onChange: (e) => {
6595
- const newColors = { ...colors, text: e.target.value };
6596
- setColors(newColors);
6597
- handleUpdateCaption({ colors: newColors });
6598
- },
6599
- className: "color-picker"
6600
- }
6601
- ),
6602
- /* @__PURE__ */ jsx(
6603
- "input",
6604
- {
6605
- type: "text",
6606
- value: colors.text,
6607
- onChange: (e) => {
6608
- const newColors = { ...colors, text: e.target.value };
6609
- setColors(newColors);
6610
- handleUpdateCaption({ colors: newColors });
6611
- },
6612
- className: "color-text"
6613
- }
6614
- )
6615
- ] })
6616
- ] }),
6617
- /* @__PURE__ */ jsxs("div", { className: "color-control", children: [
6618
- /* @__PURE__ */ jsx("label", { className: "label-small", children: "Background Color" }),
6619
- /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
6620
- /* @__PURE__ */ jsx(
6621
- "input",
6622
- {
6623
- type: "color",
6624
- value: colors.bgColor,
6625
- onChange: (e) => {
6626
- const newColors = { ...colors, bgColor: e.target.value };
6627
- setColors(newColors);
6628
- handleUpdateCaption({ colors: newColors });
6629
- },
6630
- className: "color-picker"
6631
- }
6632
- ),
6633
- /* @__PURE__ */ jsx(
6634
- "input",
6635
- {
6636
- type: "text",
6637
- value: colors.bgColor,
6638
- onChange: (e) => {
6639
- const newColors = { ...colors, bgColor: e.target.value };
6640
- setColors(newColors);
6641
- handleUpdateCaption({ colors: newColors });
6642
- },
6643
- className: "color-text"
6644
- }
6645
- )
6646
- ] })
6647
- ] }),
6648
- /* @__PURE__ */ jsxs("div", { className: "color-control", children: [
6649
- /* @__PURE__ */ jsx("label", { className: "label-small", children: "Outline Color" }),
6650
- /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
6651
- /* @__PURE__ */ jsx(
6652
- "input",
6653
- {
6654
- type: "color",
6655
- value: colors.outlineColor,
6656
- onChange: (e) => {
6657
- const newColors = { ...colors, outlineColor: e.target.value };
6658
- setColors(newColors);
6659
- handleUpdateCaption({ colors: newColors });
6660
- },
6661
- className: "color-picker"
6662
- }
6663
- ),
6664
- /* @__PURE__ */ jsx(
6665
- "input",
6666
- {
6667
- type: "text",
6668
- value: colors.outlineColor,
6669
- onChange: (e) => {
6670
- const newColors = { ...colors, outlineColor: e.target.value };
6671
- setColors(newColors);
6672
- handleUpdateCaption({ colors: newColors });
6673
- },
6674
- className: "color-text"
6675
- }
6676
- )
6677
- ] })
6678
- ] })
6708
+ currentColorMeta.usedColors.includes("highlight") && /* @__PURE__ */ jsx("div", { className: "checkbox-control", children: /* @__PURE__ */ jsxs("label", { className: "checkbox-label", children: [
6709
+ /* @__PURE__ */ jsx(
6710
+ "input",
6711
+ {
6712
+ type: "checkbox",
6713
+ checked: useHighlight,
6714
+ onChange: (e) => {
6715
+ const enabled = e.target.checked;
6716
+ setUseHighlight(enabled);
6717
+ const nextColors = enabled ? { ...colors, highlight: colors.highlight || CAPTION_COLOR2.highlight } : { ...colors };
6718
+ setColors(nextColors);
6719
+ handleUpdateCaption({
6720
+ colors: nextColors,
6721
+ useHighlightOverride: enabled
6722
+ });
6723
+ },
6724
+ className: "checkbox-purple"
6725
+ }
6726
+ ),
6727
+ "Use Highlight Color"
6728
+ ] }) }),
6729
+ currentColorMeta.usedColors.includes("outlineColor") && /* @__PURE__ */ jsx("div", { className: "checkbox-control", children: /* @__PURE__ */ jsxs("label", { className: "checkbox-label", children: [
6730
+ /* @__PURE__ */ jsx(
6731
+ "input",
6732
+ {
6733
+ type: "checkbox",
6734
+ checked: useOutline,
6735
+ onChange: (e) => {
6736
+ const enabled = e.target.checked;
6737
+ setUseOutline(enabled);
6738
+ const nextColors = enabled ? {
6739
+ ...colors,
6740
+ outlineColor: colors.outlineColor || CAPTION_COLOR2.outlineColor
6741
+ } : { ...colors };
6742
+ setColors(nextColors);
6743
+ handleUpdateCaption({
6744
+ colors: nextColors,
6745
+ useOutlineOverride: enabled
6746
+ });
6747
+ },
6748
+ className: "checkbox-purple"
6749
+ }
6750
+ ),
6751
+ "Use Outline Color"
6752
+ ] }) }),
6753
+ currentColorMeta.usedColors.map((key) => renderColorControl(key))
6679
6754
  ] })
6680
6755
  ] })
6681
6756
  ] });