@klikeai/cut-preview 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1163 @@
1
+ // ../src/SmartStudioComposition.tsx
2
+ import { AbsoluteFill as AbsoluteFill9 } from "remotion";
3
+
4
+ // ../src/themes.ts
5
+ var STYLE_PRESETS = [
6
+ "bold_impact",
7
+ "clean_saas",
8
+ "ugc_casual",
9
+ "premium_dark"
10
+ ];
11
+ var DEFAULT_CAPTION_STYLE = {
12
+ fontFamily: "Montserrat",
13
+ fontSize: 64,
14
+ primaryColor: "#FFFFFF",
15
+ highlightColor: "#FFFF00",
16
+ outlineColor: "#000000",
17
+ outlineWidth: 3,
18
+ bold: true,
19
+ italic: false,
20
+ uppercase: false,
21
+ activeScale: 1.15,
22
+ animateIn: true,
23
+ template: "classic",
24
+ position: "bottom"
25
+ };
26
+ var DEFAULT_CAPTION_DISPLAY_MODE = "highlight";
27
+ var DEFAULT_CAPTION_COMBINE_MS = 1200;
28
+ var DEFAULT_OVERLAY_STYLE = {
29
+ fontFamily: "Poppins",
30
+ fontSize: 72,
31
+ color: "#FFFFFF",
32
+ outlineColor: "#000000",
33
+ outlineWidth: 3,
34
+ enterAnimation: "spring-pop",
35
+ bold: true
36
+ };
37
+ var DEFAULT_ENDCARD_STYLE = {
38
+ background: "#101010",
39
+ color: "#FFFFFF",
40
+ fontFamily: "Poppins",
41
+ fontSize: 88,
42
+ animateIn: true
43
+ };
44
+ var DEFAULT_PROGRESS_BAR = {
45
+ enabled: false,
46
+ color: "#FFFFFF",
47
+ heightPx: 10
48
+ };
49
+ var THEMES = {
50
+ // Promoção/varejo/urgência: tipografia pesada, caixa alta, box no token
51
+ // ativo, paleta amarelo-vibrante, progress bar de urgência.
52
+ bold_impact: {
53
+ captions: {
54
+ fontFamily: "Anton",
55
+ uppercase: true,
56
+ template: "box",
57
+ highlightColor: "#FFE600",
58
+ primaryColor: "#FFFFFF",
59
+ outlineWidth: 4,
60
+ activeScale: 1.1
61
+ },
62
+ overlays: {
63
+ fontFamily: "Anton",
64
+ enterAnimation: "zoom",
65
+ color: "#FFE600",
66
+ outlineWidth: 4
67
+ },
68
+ endcard: {
69
+ gradient: { from: "#1B1B1B", to: "#4A0E0E", angleDeg: 160 },
70
+ color: "#FFE600",
71
+ fontFamily: "Anton"
72
+ },
73
+ progressBar: { enabled: true, color: "#FFE600" },
74
+ cutTransition: { type: "slide", durationMs: 250 }
75
+ },
76
+ // Produto digital/B2B: limpo, pill translúcido, azul confiável, fades.
77
+ clean_saas: {
78
+ captions: {
79
+ fontFamily: "Inter",
80
+ template: "pill",
81
+ highlightColor: "#4D9FFF",
82
+ primaryColor: "#FFFFFF",
83
+ outlineWidth: 0,
84
+ activeScale: 1.06
85
+ },
86
+ overlays: {
87
+ fontFamily: "Inter",
88
+ enterAnimation: "slide-up",
89
+ color: "#FFFFFF",
90
+ outlineWidth: 2
91
+ },
92
+ endcard: {
93
+ gradient: { from: "#0F2027", to: "#2C5364", angleDeg: 150 },
94
+ color: "#FFFFFF",
95
+ fontFamily: "Inter"
96
+ },
97
+ progressBar: { enabled: true, color: "#4D9FFF", heightPx: 8 },
98
+ cutTransition: { type: "fade", durationMs: 300 }
99
+ },
100
+ // Creator falando pra câmera: informal, pop forte, cores quentes.
101
+ ugc_casual: {
102
+ captions: {
103
+ fontFamily: "Poppins",
104
+ template: "classic",
105
+ highlightColor: "#7CFF6B",
106
+ activeScale: 1.25
107
+ },
108
+ overlays: {
109
+ fontFamily: "Poppins",
110
+ enterAnimation: "spring-pop"
111
+ },
112
+ endcard: {
113
+ background: "#141414",
114
+ color: "#7CFF6B",
115
+ fontFamily: "Poppins"
116
+ },
117
+ cutTransition: { type: "fade", durationMs: 200 }
118
+ },
119
+ // Marca premium: glow dourado sutil, tipografia condensada, fades lentos.
120
+ premium_dark: {
121
+ captions: {
122
+ fontFamily: "Oswald",
123
+ template: "glow",
124
+ primaryColor: "#F4EBDC",
125
+ highlightColor: "#E8C46B",
126
+ outlineWidth: 0,
127
+ activeScale: 1.08,
128
+ bold: false
129
+ },
130
+ overlays: {
131
+ fontFamily: "Oswald",
132
+ enterAnimation: "fade",
133
+ color: "#E8C46B",
134
+ outlineWidth: 1,
135
+ bold: false
136
+ },
137
+ endcard: {
138
+ gradient: { from: "#0B0B0D", to: "#2A2118", angleDeg: 170 },
139
+ color: "#E8C46B",
140
+ fontFamily: "Oswald"
141
+ },
142
+ cutTransition: { type: "fade", durationMs: 500 }
143
+ }
144
+ };
145
+ var resolveTheme = (preset) => {
146
+ if (preset && STYLE_PRESETS.includes(preset)) {
147
+ return THEMES[preset];
148
+ }
149
+ return null;
150
+ };
151
+ var merge = (...layers) => {
152
+ const out = {};
153
+ for (const layer of layers) {
154
+ if (!layer) continue;
155
+ for (const [k, v] of Object.entries(layer)) {
156
+ if (v !== void 0) out[k] = v;
157
+ }
158
+ }
159
+ return out;
160
+ };
161
+ var resolveCaptionStyle = (theme, explicit) => merge(
162
+ DEFAULT_CAPTION_STYLE,
163
+ theme?.captions,
164
+ explicit
165
+ );
166
+ var resolveCaptionDisplayMode = (theme, explicit) => explicit ?? theme?.captions.displayMode ?? DEFAULT_CAPTION_DISPLAY_MODE;
167
+ var resolveCaptionCombineMs = (theme, explicit) => explicit ?? theme?.captions.combineTokensWithinMs ?? DEFAULT_CAPTION_COMBINE_MS;
168
+ var resolveOverlayItem = (theme, item) => merge(
169
+ DEFAULT_OVERLAY_STYLE,
170
+ theme?.overlays,
171
+ item
172
+ );
173
+ var resolveEndcard = (theme, endcard) => merge(
174
+ DEFAULT_ENDCARD_STYLE,
175
+ theme?.endcard,
176
+ endcard
177
+ );
178
+ var resolveProgressBar = (theme, explicit) => merge(DEFAULT_PROGRESS_BAR, theme?.progressBar, explicit);
179
+ var DEFAULT_CUT_TRANSITION_MS = 300;
180
+ var resolveCutTransition = (theme, explicitType, explicitMs) => {
181
+ const type = explicitType ?? theme?.cutTransition?.type ?? "fade";
182
+ const durationMs = explicitMs ?? theme?.cutTransition?.durationMs ?? DEFAULT_CUT_TRANSITION_MS;
183
+ return { type, durationMs };
184
+ };
185
+
186
+ // ../src/layers/VideoBase.tsx
187
+ import { AbsoluteFill, OffthreadVideo } from "remotion";
188
+ import { jsx } from "react/jsx-runtime";
189
+ var VideoBase = ({ source, fit = "cover" }) => {
190
+ return /* @__PURE__ */ jsx(AbsoluteFill, { children: /* @__PURE__ */ jsx(
191
+ OffthreadVideo,
192
+ {
193
+ src: source.videoPath,
194
+ style: { width: "100%", height: "100%", objectFit: fit }
195
+ }
196
+ ) });
197
+ };
198
+
199
+ // ../src/layers/ReframeContainer.tsx
200
+ import { AbsoluteFill as AbsoluteFill2, OffthreadVideo as OffthreadVideo2 } from "remotion";
201
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
202
+ var ReframeContainer = ({ reframe, source, children }) => {
203
+ if (!reframe.enabled) {
204
+ return /* @__PURE__ */ jsx2(Fragment, { children });
205
+ }
206
+ return /* @__PURE__ */ jsxs(AbsoluteFill2, { children: [
207
+ /* @__PURE__ */ jsx2(AbsoluteFill2, { children: /* @__PURE__ */ jsx2(
208
+ OffthreadVideo2,
209
+ {
210
+ src: source.videoPath,
211
+ muted: true,
212
+ style: {
213
+ width: "100%",
214
+ height: "100%",
215
+ objectFit: "cover",
216
+ filter: "blur(30px)",
217
+ transform: "scale(1.1)"
218
+ }
219
+ }
220
+ ) }),
221
+ /* @__PURE__ */ jsx2(
222
+ AbsoluteFill2,
223
+ {
224
+ style: { alignItems: "center", justifyContent: "center" },
225
+ children
226
+ }
227
+ )
228
+ ] });
229
+ };
230
+
231
+ // ../src/layers/CutsSequencer.tsx
232
+ import {
233
+ AbsoluteFill as AbsoluteFill3,
234
+ interpolate as interpolate2,
235
+ OffthreadVideo as OffthreadVideo3,
236
+ useVideoConfig
237
+ } from "remotion";
238
+ import {
239
+ linearTiming,
240
+ TransitionSeries
241
+ } from "@remotion/transitions";
242
+ import { fade } from "@remotion/transitions/fade";
243
+ import { slide } from "@remotion/transitions/slide";
244
+ import { wipe } from "@remotion/transitions/wipe";
245
+
246
+ // ../src/helpers.ts
247
+ import { interpolate, spring } from "remotion";
248
+ var msToFrames = (ms, fps) => Math.round(ms / 1e3 * fps);
249
+ var REFERENCE_MIN_SIDE = 1080;
250
+ var uiScale = (width, height) => Math.min(width, height) / REFERENCE_MIN_SIDE;
251
+ var springScale = (localFrame, fps, peak = 1.15, durationInFrames = 8) => {
252
+ const s = spring({
253
+ fps,
254
+ frame: localFrame,
255
+ config: { damping: 12, stiffness: 200, mass: 0.6 },
256
+ durationInFrames
257
+ });
258
+ return interpolate(s, [0, 1], [1, peak]);
259
+ };
260
+ var justifyForPosition = (position) => {
261
+ if (position === "top") return "flex-start";
262
+ if (position === "bottom") return "flex-end";
263
+ return "center";
264
+ };
265
+ var GRADE_TARGETS = {
266
+ warm: { sepia: 0.35, saturate: 1.25, brightness: 1.04 },
267
+ cold: { saturate: 1.1, hueDeg: -12, brightness: 1.02 },
268
+ vintage: { sepia: 0.5, contrast: 0.92, saturate: 0.85 },
269
+ bw: { grayscale: 1, contrast: 1.08 },
270
+ high_contrast: { contrast: 1.35, saturate: 1.1 },
271
+ vivid: { saturate: 1.45, contrast: 1.1 }
272
+ };
273
+ var gradeFilter = (preset, intensity) => {
274
+ const t = preset ? GRADE_TARGETS[preset] : void 0;
275
+ if (!t) return "none";
276
+ const k = Math.max(0, Math.min(intensity, 1));
277
+ const lerp = (from, to) => from + (to - from) * k;
278
+ const parts = [];
279
+ if (t.grayscale !== void 0) parts.push(`grayscale(${lerp(0, t.grayscale)})`);
280
+ if (t.sepia !== void 0) parts.push(`sepia(${lerp(0, t.sepia)})`);
281
+ if (t.saturate !== void 0) parts.push(`saturate(${lerp(1, t.saturate)})`);
282
+ if (t.contrast !== void 0) parts.push(`contrast(${lerp(1, t.contrast)})`);
283
+ if (t.brightness !== void 0) parts.push(`brightness(${lerp(1, t.brightness)})`);
284
+ if (t.hueDeg !== void 0) parts.push(`hue-rotate(${lerp(0, t.hueDeg)}deg)`);
285
+ return parts.length ? parts.join(" ") : "none";
286
+ };
287
+ var textOutline = (color, px = 3) => {
288
+ const o = [];
289
+ for (let dx = -px; dx <= px; dx++) {
290
+ for (let dy = -px; dy <= px; dy++) {
291
+ if (dx === 0 && dy === 0) continue;
292
+ o.push(`${dx}px ${dy}px 0 ${color}`);
293
+ }
294
+ }
295
+ return o.join(", ");
296
+ };
297
+
298
+ // ../src/layers/CutsSequencer.tsx
299
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
300
+ var presentationFor = (type) => {
301
+ if (type === "wipe") return wipe();
302
+ if (type === "slide") return slide();
303
+ return fade();
304
+ };
305
+ var SegmentVideo = ({
306
+ source,
307
+ startFromFrames,
308
+ fadeInFrames,
309
+ fadeOutFrames,
310
+ durationInFrames,
311
+ reframe
312
+ }) => {
313
+ const volume = (f) => {
314
+ let v = 1;
315
+ if (fadeInFrames > 0) {
316
+ v *= interpolate2(f, [0, fadeInFrames], [0, 1], {
317
+ extrapolateLeft: "clamp",
318
+ extrapolateRight: "clamp"
319
+ });
320
+ }
321
+ if (fadeOutFrames > 0) {
322
+ v *= interpolate2(
323
+ f,
324
+ [durationInFrames - fadeOutFrames, durationInFrames],
325
+ [1, 0],
326
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
327
+ );
328
+ }
329
+ return v;
330
+ };
331
+ const fg = /* @__PURE__ */ jsx3(
332
+ OffthreadVideo3,
333
+ {
334
+ src: source.videoPath,
335
+ startFrom: startFromFrames,
336
+ volume,
337
+ style: {
338
+ width: "100%",
339
+ height: "100%",
340
+ objectFit: reframe.enabled ? "contain" : "cover"
341
+ }
342
+ }
343
+ );
344
+ if (!reframe.enabled) {
345
+ return /* @__PURE__ */ jsx3(AbsoluteFill3, { children: fg });
346
+ }
347
+ return /* @__PURE__ */ jsxs2(AbsoluteFill3, { children: [
348
+ /* @__PURE__ */ jsx3(AbsoluteFill3, { children: /* @__PURE__ */ jsx3(
349
+ OffthreadVideo3,
350
+ {
351
+ src: source.videoPath,
352
+ startFrom: startFromFrames,
353
+ muted: true,
354
+ style: {
355
+ width: "100%",
356
+ height: "100%",
357
+ objectFit: "cover",
358
+ filter: "blur(30px)",
359
+ transform: "scale(1.1)"
360
+ }
361
+ }
362
+ ) }),
363
+ /* @__PURE__ */ jsx3(AbsoluteFill3, { style: { alignItems: "center", justifyContent: "center" }, children: fg })
364
+ ] });
365
+ };
366
+ var CutsSequencer = ({ source, cuts, reframe, transition }) => {
367
+ const { fps } = useVideoConfig();
368
+ const segments = cuts.segmentsKeepMs.filter(([s, e]) => e > s);
369
+ if (segments.length === 0) return null;
370
+ const minSegMs = Math.min(...segments.map(([s, e]) => e - s));
371
+ const useTransition = transition.type !== "none" && transition.durationMs > 0 && segments.length > 1;
372
+ const transF = useTransition ? Math.max(
373
+ 1,
374
+ msToFrames(Math.min(transition.durationMs, minSegMs / 2), fps)
375
+ ) : 0;
376
+ const children = [];
377
+ segments.forEach(([startMs, endMs], i) => {
378
+ const durationInFrames = Math.max(1, msToFrames(endMs - startMs, fps));
379
+ const fadeInF = i > 0 ? transF : 0;
380
+ const fadeOutF = i < segments.length - 1 ? transF : 0;
381
+ children.push(
382
+ /* @__PURE__ */ jsx3(
383
+ TransitionSeries.Sequence,
384
+ {
385
+ durationInFrames,
386
+ children: /* @__PURE__ */ jsx3(
387
+ SegmentVideo,
388
+ {
389
+ source,
390
+ startFromFrames: msToFrames(startMs, fps),
391
+ fadeInFrames: fadeInF,
392
+ fadeOutFrames: fadeOutF,
393
+ durationInFrames,
394
+ reframe
395
+ }
396
+ )
397
+ },
398
+ `seg-${i}`
399
+ )
400
+ );
401
+ if (useTransition && i < segments.length - 1) {
402
+ children.push(
403
+ /* @__PURE__ */ jsx3(
404
+ TransitionSeries.Transition,
405
+ {
406
+ presentation: presentationFor(transition.type),
407
+ timing: linearTiming({ durationInFrames: transF })
408
+ },
409
+ `trans-${i}`
410
+ )
411
+ );
412
+ }
413
+ });
414
+ return /* @__PURE__ */ jsx3(TransitionSeries, { children });
415
+ };
416
+
417
+ // ../src/layers/ZoomLayer.tsx
418
+ import { AbsoluteFill as AbsoluteFill4, spring as spring2, useCurrentFrame } from "remotion";
419
+ import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
420
+ var momentScaleBoost = (frame, fps, moment) => {
421
+ const startF = msToFrames(moment.startMs, fps);
422
+ const endF = msToFrames(moment.endMs, fps);
423
+ const ramp = Math.max(1, Math.round(Math.min(fps * 0.3, (endF - startF) / 2)));
424
+ const enter = spring2({
425
+ fps,
426
+ frame: frame - startF,
427
+ config: { damping: 14, stiffness: 160, mass: 0.8 },
428
+ durationInFrames: ramp
429
+ });
430
+ const exit = spring2({
431
+ fps,
432
+ frame: frame - (endF - ramp),
433
+ config: { damping: 200 },
434
+ durationInFrames: ramp
435
+ });
436
+ return (moment.factor - 1) * Math.max(0, enter - exit);
437
+ };
438
+ var ZoomLayer = ({ zoom, fps, children }) => {
439
+ const frame = useCurrentFrame();
440
+ if (!zoom.enabled) {
441
+ return /* @__PURE__ */ jsx4(Fragment2, { children });
442
+ }
443
+ const moments = zoom.moments?.length ? zoom.moments : [{ startMs: zoom.startMs, endMs: zoom.endMs, factor: zoom.factor }];
444
+ let scale = 1;
445
+ if (zoom.animated) {
446
+ for (const moment of moments) {
447
+ scale += momentScaleBoost(frame, fps, moment);
448
+ }
449
+ } else {
450
+ for (const moment of moments) {
451
+ const startF = msToFrames(moment.startMs, fps);
452
+ const endF = msToFrames(moment.endMs, fps);
453
+ if (frame >= startF && frame <= endF) {
454
+ scale = moment.factor;
455
+ break;
456
+ }
457
+ }
458
+ }
459
+ return /* @__PURE__ */ jsx4(AbsoluteFill4, { style: { transform: `scale(${scale})` }, children });
460
+ };
461
+
462
+ // ../src/layers/CaptionsLayer.tsx
463
+ import {
464
+ AbsoluteFill as AbsoluteFill5,
465
+ Easing,
466
+ interpolate as interpolate3,
467
+ Sequence,
468
+ useCurrentFrame as useCurrentFrame2,
469
+ useVideoConfig as useVideoConfig2
470
+ } from "remotion";
471
+ import {
472
+ createTikTokStyleCaptions
473
+ } from "@remotion/captions";
474
+
475
+ // ../src/fonts.ts
476
+ import { loadFont as loadAnton } from "@remotion/google-fonts/Anton";
477
+ import { loadFont as loadBebasNeue } from "@remotion/google-fonts/BebasNeue";
478
+ import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
479
+ import { loadFont as loadMontserrat } from "@remotion/google-fonts/Montserrat";
480
+ import { loadFont as loadOswald } from "@remotion/google-fonts/Oswald";
481
+ import { loadFont as loadPoppins } from "@remotion/google-fonts/Poppins";
482
+ import { loadFont as loadRoboto } from "@remotion/google-fonts/Roboto";
483
+ var FONTS = {
484
+ Montserrat: loadMontserrat().fontFamily,
485
+ Inter: loadInter().fontFamily,
486
+ Poppins: loadPoppins().fontFamily,
487
+ Oswald: loadOswald().fontFamily,
488
+ "Bebas Neue": loadBebasNeue().fontFamily,
489
+ BebasNeue: "",
490
+ Anton: loadAnton().fontFamily,
491
+ Roboto: loadRoboto().fontFamily
492
+ };
493
+ FONTS.BebasNeue = FONTS["Bebas Neue"];
494
+ var DEFAULT_CAPTION_FONT = "Montserrat";
495
+ var DEFAULT_OVERLAY_FONT = "Poppins";
496
+ function resolveFontFamily(name) {
497
+ if (name && FONTS[name]) {
498
+ return `${FONTS[name]}, Arial, sans-serif`;
499
+ }
500
+ if (name && name.trim()) {
501
+ return `${name}, Arial, sans-serif`;
502
+ }
503
+ return `${FONTS[DEFAULT_CAPTION_FONT]}, Arial, sans-serif`;
504
+ }
505
+ var AVAILABLE_FONTS = Object.keys(FONTS).filter((k) => FONTS[k]);
506
+
507
+ // ../src/layers/CaptionsLayer.tsx
508
+ import { Fragment as Fragment3, jsx as jsx5 } from "react/jsx-runtime";
509
+ var glowShadow = (color, scale = 1) => `0 0 ${8 * scale}px ${color}, 0 0 ${18 * scale}px ${color}, 0 0 ${36 * scale}px ${color}66`;
510
+ var Page = ({ page, style, displayMode, safeZones }) => {
511
+ const frame = useCurrentFrame2();
512
+ const { fps, width, height } = useVideoConfig2();
513
+ const nowMs = frame / fps * 1e3 + page.startMs;
514
+ const template = style.template;
515
+ const fontWeight = style.bold ? 800 : 500;
516
+ const ui = uiScale(width, height);
517
+ const enter = style.animateIn ? interpolate3(frame, [0, 5], [0, 1], {
518
+ extrapolateLeft: "clamp",
519
+ extrapolateRight: "clamp",
520
+ easing: Easing.out(Easing.cubic)
521
+ }) : 1;
522
+ const textShadow = template === "glow" ? glowShadow(style.highlightColor, ui) : template === "pill" || style.outlineWidth <= 0 ? "none" : textOutline(
523
+ style.outlineColor,
524
+ Math.max(1, Math.round(style.outlineWidth * ui))
525
+ );
526
+ const baseStyle = {
527
+ fontFamily: resolveFontFamily(style.fontFamily),
528
+ fontWeight,
529
+ fontStyle: style.italic ? "italic" : "normal",
530
+ fontSize: style.fontSize * ui,
531
+ color: style.primaryColor,
532
+ textShadow,
533
+ textTransform: style.uppercase ? "uppercase" : "none"
534
+ };
535
+ const renderToken = (tokenText, fromMs, toMs, key) => {
536
+ const active = nowMs >= fromMs && nowMs < toMs;
537
+ if (displayMode === "word_by_word" && !active) return null;
538
+ const canHighlight = displayMode === "highlight" || displayMode === "karaoke" || displayMode === "word_by_word";
539
+ const highlight = canHighlight && active;
540
+ let scale = 1;
541
+ if (highlight && style.activeScale > 1) {
542
+ const tokenStartFrame = msToFrames(fromMs - page.startMs, fps);
543
+ scale = springScale(frame - tokenStartFrame, fps, style.activeScale);
544
+ }
545
+ const boxed = template === "box" && highlight;
546
+ return /* @__PURE__ */ jsx5(
547
+ "span",
548
+ {
549
+ style: {
550
+ color: boxed ? "#111111" : highlight ? style.highlightColor : style.primaryColor,
551
+ display: "inline-block",
552
+ transform: `scale(${scale})`,
553
+ whiteSpace: "pre",
554
+ ...boxed ? {
555
+ backgroundColor: style.highlightColor,
556
+ borderRadius: "0.18em",
557
+ padding: "0 0.12em",
558
+ textShadow: "none"
559
+ } : {},
560
+ ...template === "glow" && highlight ? { textShadow: glowShadow(style.highlightColor, ui) } : {}
561
+ },
562
+ children: tokenText
563
+ },
564
+ key
565
+ );
566
+ };
567
+ const position = style.position;
568
+ return /* @__PURE__ */ jsx5(
569
+ AbsoluteFill5,
570
+ {
571
+ style: {
572
+ justifyContent: position === "top" ? "flex-start" : position === "center" ? "center" : "flex-end",
573
+ alignItems: "center",
574
+ paddingTop: height * safeZones.marginTopPercent,
575
+ paddingBottom: height * safeZones.marginBottomPercent,
576
+ paddingLeft: width * safeZones.marginSidePercent,
577
+ paddingRight: width * safeZones.marginSidePercent
578
+ },
579
+ children: /* @__PURE__ */ jsx5(
580
+ "div",
581
+ {
582
+ style: {
583
+ ...baseStyle,
584
+ textAlign: "center",
585
+ lineHeight: 1.25,
586
+ opacity: enter,
587
+ transform: `scale(${interpolate3(enter, [0, 1], [0.92, 1])})`,
588
+ // Template pill: bloco da página sobre fundo arredondado translúcido.
589
+ ...template === "pill" ? {
590
+ backgroundColor: "rgba(12, 12, 14, 0.72)",
591
+ borderRadius: 22 * ui,
592
+ padding: "0.35em 0.6em"
593
+ } : {}
594
+ },
595
+ children: page.tokens.map((t, i) => renderToken(t.text, t.fromMs, t.toMs, i))
596
+ }
597
+ )
598
+ }
599
+ );
600
+ };
601
+ var CaptionsLayer = ({ captions, style, displayMode, combineTokensWithinMs, safeZones, fps }) => {
602
+ if (!captions.enabled || !captions.tokens || captions.tokens.length === 0) {
603
+ return null;
604
+ }
605
+ const remotionCaptions = captions.tokens.map((t, i) => ({
606
+ text: i === 0 ? t.text : ` ${t.text}`,
607
+ startMs: t.startMs,
608
+ endMs: t.endMs,
609
+ timestampMs: (t.startMs + t.endMs) / 2,
610
+ confidence: t.confidence ?? null
611
+ }));
612
+ const sentenceChunks = [];
613
+ let currentChunk = [];
614
+ for (const caption of remotionCaptions) {
615
+ currentChunk.push(caption);
616
+ if (/[.!?…]["')\]]?$/.test(caption.text.trim())) {
617
+ sentenceChunks.push(currentChunk);
618
+ currentChunk = [];
619
+ }
620
+ }
621
+ if (currentChunk.length > 0) sentenceChunks.push(currentChunk);
622
+ const pages = sentenceChunks.flatMap((chunk) => {
623
+ const normalized = chunk.map(
624
+ (c, i) => i === 0 ? { ...c, text: c.text.trimStart() } : c
625
+ );
626
+ return createTikTokStyleCaptions({
627
+ captions: normalized,
628
+ combineTokensWithinMilliseconds: combineTokensWithinMs
629
+ }).pages;
630
+ });
631
+ return /* @__PURE__ */ jsx5(Fragment3, { children: pages.map((page, i) => {
632
+ const from = msToFrames(page.startMs, fps);
633
+ const durationInFrames = Math.max(1, msToFrames(page.durationMs, fps));
634
+ return /* @__PURE__ */ jsx5(
635
+ Sequence,
636
+ {
637
+ from,
638
+ durationInFrames,
639
+ name: `caption-page-${i}`,
640
+ children: /* @__PURE__ */ jsx5(
641
+ Page,
642
+ {
643
+ page,
644
+ style,
645
+ displayMode,
646
+ safeZones
647
+ }
648
+ )
649
+ },
650
+ i
651
+ );
652
+ }) });
653
+ };
654
+
655
+ // ../src/layers/OverlayLayer.tsx
656
+ import {
657
+ AbsoluteFill as AbsoluteFill6,
658
+ interpolate as interpolate4,
659
+ Sequence as Sequence2,
660
+ spring as spring3,
661
+ useCurrentFrame as useCurrentFrame3,
662
+ useVideoConfig as useVideoConfig3
663
+ } from "remotion";
664
+ import { fitText } from "@remotion/layout-utils";
665
+ import { Fragment as Fragment4, jsx as jsx6 } from "react/jsx-runtime";
666
+ var DEFAULT_FADE_MS = 300;
667
+ var DEFAULT_FONT_SIZE = 72;
668
+ var DEFAULT_COLOR = "#FFFFFF";
669
+ var DEFAULT_OUTLINE = "#000000";
670
+ var MIN_FONT_SIZE = 40;
671
+ var fitFontSize = (text, desired, withinWidth, fontFamily, fontWeight, uppercase, minSize = MIN_FONT_SIZE) => {
672
+ try {
673
+ const oneLine = fitText({
674
+ text,
675
+ withinWidth,
676
+ fontFamily,
677
+ fontWeight,
678
+ textTransform: uppercase ? "uppercase" : void 0
679
+ }).fontSize;
680
+ if (oneLine >= desired * 0.7) return Math.min(desired, oneLine);
681
+ const twoLines = fitText({
682
+ text,
683
+ withinWidth: withinWidth * 1.9,
684
+ fontFamily,
685
+ fontWeight,
686
+ textTransform: uppercase ? "uppercase" : void 0
687
+ }).fontSize;
688
+ return Math.max(minSize, Math.min(desired, twoLines));
689
+ } catch {
690
+ return desired;
691
+ }
692
+ };
693
+ var CAPTION_BAND_PCT = 0.13;
694
+ var OverlayText = ({ item, durationInFrames, fps, safeZones, captionsPosition }) => {
695
+ const frame = useCurrentFrame3();
696
+ const { width, height } = useVideoConfig3();
697
+ const fadeInF = Math.max(1, msToFrames(item.fadeMs ?? DEFAULT_FADE_MS, fps));
698
+ const fadeOutF = Math.max(
699
+ 1,
700
+ msToFrames(item.fadeOutMs ?? item.fadeMs ?? DEFAULT_FADE_MS, fps)
701
+ );
702
+ const enterAnim = item.enterAnimation ?? "spring-pop";
703
+ const enterSpring = spring3({
704
+ fps,
705
+ frame,
706
+ config: { damping: 14, stiffness: 200, mass: 0.7 },
707
+ durationInFrames: fadeInF
708
+ });
709
+ const enterLinear = interpolate4(frame, [0, fadeInF], [0, 1], {
710
+ extrapolateLeft: "clamp",
711
+ extrapolateRight: "clamp"
712
+ });
713
+ const opacity = interpolate4(
714
+ frame,
715
+ [0, fadeInF, Math.max(fadeInF, durationInFrames - fadeOutF), durationInFrames],
716
+ [0, 1, 1, 0],
717
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
718
+ );
719
+ let transform = "none";
720
+ if (enterAnim === "slide-up") {
721
+ transform = `translateY(${interpolate4(enterLinear, [0, 1], [40, 0])}px)`;
722
+ } else if (enterAnim === "slide-down") {
723
+ transform = `translateY(${interpolate4(enterLinear, [0, 1], [-40, 0])}px)`;
724
+ } else if (enterAnim === "zoom") {
725
+ transform = `scale(${interpolate4(enterLinear, [0, 1], [0.6, 1])})`;
726
+ } else if (enterAnim === "spring-pop") {
727
+ transform = `scale(${interpolate4(enterSpring, [0, 1], [0.7, 1])})`;
728
+ }
729
+ const fontFamily = resolveFontFamily(item.fontFamily ?? DEFAULT_OVERLAY_FONT);
730
+ const fontWeight = item.bold === false ? 500 : 700;
731
+ const ui = uiScale(width, height);
732
+ const outlineWidth = Math.max(
733
+ 1,
734
+ Math.round((item.outlineWidth ?? 3) * ui)
735
+ );
736
+ const withinWidth = width * (1 - 2 * safeZones.marginSidePercent);
737
+ const fontSize = fitFontSize(
738
+ item.text,
739
+ (item.fontSize ?? DEFAULT_FONT_SIZE) * ui,
740
+ withinWidth,
741
+ fontFamily,
742
+ fontWeight,
743
+ false,
744
+ MIN_FONT_SIZE * ui
745
+ );
746
+ const overlayPos = item.position ?? "center";
747
+ const bottomClearance = safeZones.marginBottomPercent + (captionsPosition === "bottom" && overlayPos === "bottom" ? CAPTION_BAND_PCT : 0);
748
+ const topClearance = safeZones.marginTopPercent + (captionsPosition === "top" && overlayPos === "top" ? CAPTION_BAND_PCT : 0);
749
+ return /* @__PURE__ */ jsx6(
750
+ AbsoluteFill6,
751
+ {
752
+ style: {
753
+ justifyContent: justifyForPosition(item.position),
754
+ alignItems: "center",
755
+ paddingTop: height * topClearance,
756
+ paddingBottom: height * bottomClearance,
757
+ paddingLeft: width * safeZones.marginSidePercent,
758
+ paddingRight: width * safeZones.marginSidePercent
759
+ },
760
+ children: /* @__PURE__ */ jsx6(
761
+ "div",
762
+ {
763
+ style: {
764
+ opacity,
765
+ transform,
766
+ textAlign: "center",
767
+ fontFamily,
768
+ fontWeight,
769
+ fontStyle: item.italic ? "italic" : "normal",
770
+ fontSize,
771
+ color: item.color ?? DEFAULT_COLOR,
772
+ textShadow: outlineWidth > 0 ? textOutline(item.outlineColor ?? DEFAULT_OUTLINE, outlineWidth) : "none",
773
+ lineHeight: 1.15,
774
+ whiteSpace: "pre-wrap"
775
+ },
776
+ children: item.text
777
+ }
778
+ )
779
+ }
780
+ );
781
+ };
782
+ var OverlayLayer = ({ overlays, safeZones, fps, captionsPosition = null }) => {
783
+ if (!overlays || overlays.length === 0) return null;
784
+ return /* @__PURE__ */ jsx6(Fragment4, { children: overlays.map((item, i) => {
785
+ const from = msToFrames(item.startMs, fps);
786
+ const durationInFrames = Math.max(
787
+ 1,
788
+ msToFrames(item.endMs, fps) - from
789
+ );
790
+ return /* @__PURE__ */ jsx6(
791
+ Sequence2,
792
+ {
793
+ from,
794
+ durationInFrames,
795
+ name: `overlay-${i}`,
796
+ children: /* @__PURE__ */ jsx6(
797
+ OverlayText,
798
+ {
799
+ item,
800
+ durationInFrames,
801
+ fps,
802
+ safeZones,
803
+ captionsPosition
804
+ }
805
+ )
806
+ },
807
+ i
808
+ );
809
+ }) });
810
+ };
811
+
812
+ // ../src/layers/ImageOverlayLayer.tsx
813
+ import {
814
+ AbsoluteFill as AbsoluteFill7,
815
+ Img,
816
+ interpolate as interpolate5,
817
+ Sequence as Sequence3,
818
+ useCurrentFrame as useCurrentFrame4,
819
+ useVideoConfig as useVideoConfig4
820
+ } from "remotion";
821
+ import { Fragment as Fragment5, jsx as jsx7 } from "react/jsx-runtime";
822
+ var FADE_MS = 300;
823
+ var alignFor = (corner) => {
824
+ const vertical = corner.startsWith("top") ? "flex-start" : corner.startsWith("bottom") ? "flex-end" : "center";
825
+ const horizontal = corner.endsWith("left") ? "flex-start" : corner.endsWith("right") ? "flex-end" : "center";
826
+ return { vertical, horizontal };
827
+ };
828
+ var ImageOverlay = ({ item, durationInFrames, fps, safeZones }) => {
829
+ const frame = useCurrentFrame4();
830
+ const { width, height } = useVideoConfig4();
831
+ const fadeF = Math.max(1, msToFrames(FADE_MS, fps));
832
+ const opacity = interpolate5(
833
+ frame,
834
+ [0, fadeF, Math.max(fadeF, durationInFrames - fadeF), durationInFrames],
835
+ [0, 1, 1, 0],
836
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
837
+ ) * item.opacity;
838
+ const { vertical, horizontal } = alignFor(item.corner);
839
+ return /* @__PURE__ */ jsx7(
840
+ AbsoluteFill7,
841
+ {
842
+ style: {
843
+ justifyContent: vertical,
844
+ alignItems: horizontal,
845
+ paddingTop: height * safeZones.marginTopPercent,
846
+ paddingBottom: height * safeZones.marginBottomPercent,
847
+ paddingLeft: width * safeZones.marginSidePercent,
848
+ paddingRight: width * safeZones.marginSidePercent
849
+ },
850
+ children: /* @__PURE__ */ jsx7(
851
+ Img,
852
+ {
853
+ src: item.src,
854
+ style: {
855
+ height: height * item.heightPct,
856
+ width: "auto",
857
+ maxWidth: width * 0.8,
858
+ objectFit: "contain",
859
+ opacity
860
+ }
861
+ }
862
+ )
863
+ }
864
+ );
865
+ };
866
+ var ImageOverlayLayer = ({ imageOverlays, safeZones, fps }) => {
867
+ if (!imageOverlays || imageOverlays.length === 0) return null;
868
+ return /* @__PURE__ */ jsx7(Fragment5, { children: imageOverlays.map((item, i) => {
869
+ const from = msToFrames(item.startMs, fps);
870
+ const durationInFrames = Math.max(
871
+ 1,
872
+ msToFrames(item.endMs, fps) - from
873
+ );
874
+ return /* @__PURE__ */ jsx7(
875
+ Sequence3,
876
+ {
877
+ from,
878
+ durationInFrames,
879
+ name: `image-overlay-${i}`,
880
+ children: /* @__PURE__ */ jsx7(
881
+ ImageOverlay,
882
+ {
883
+ item,
884
+ durationInFrames,
885
+ fps,
886
+ safeZones
887
+ }
888
+ )
889
+ },
890
+ i
891
+ );
892
+ }) });
893
+ };
894
+
895
+ // ../src/layers/ProgressBarLayer.tsx
896
+ import { useCurrentFrame as useCurrentFrame5, useVideoConfig as useVideoConfig5 } from "remotion";
897
+ import { jsx as jsx8 } from "react/jsx-runtime";
898
+ var ProgressBarLayer = ({ progressBar, sourceDurationInSeconds, fps }) => {
899
+ const frame = useCurrentFrame5();
900
+ const { width, height } = useVideoConfig5();
901
+ if (!progressBar.enabled || sourceDurationInSeconds <= 0) return null;
902
+ const totalFrames = Math.max(1, Math.round(sourceDurationInSeconds * fps));
903
+ const progress = Math.min(1, frame / totalFrames);
904
+ return /* @__PURE__ */ jsx8(
905
+ "div",
906
+ {
907
+ style: {
908
+ position: "absolute",
909
+ top: 0,
910
+ left: 0,
911
+ width: `${progress * 100}%`,
912
+ // heightPx calibrado p/ lado menor 1080 → escala p/ o canvas real.
913
+ height: Math.max(2, progressBar.heightPx * uiScale(width, height)),
914
+ backgroundColor: progressBar.color,
915
+ opacity: 0.9
916
+ }
917
+ }
918
+ );
919
+ };
920
+
921
+ // ../src/layers/EndcardSequence.tsx
922
+ import {
923
+ AbsoluteFill as AbsoluteFill8,
924
+ Img as Img2,
925
+ interpolate as interpolate6,
926
+ Sequence as Sequence4,
927
+ spring as spring4,
928
+ useCurrentFrame as useCurrentFrame6,
929
+ useVideoConfig as useVideoConfig6
930
+ } from "remotion";
931
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
932
+ var DEFAULT_GRADIENT_ANGLE_DEG = 160;
933
+ var DEFAULT_LOGO_HEIGHT_PX = 140;
934
+ var backgroundStyle = (endcard) => {
935
+ if (endcard.gradient?.from && endcard.gradient?.to) {
936
+ const angle = endcard.gradient.angleDeg ?? DEFAULT_GRADIENT_ANGLE_DEG;
937
+ return {
938
+ background: `linear-gradient(${angle}deg, ${endcard.gradient.from}, ${endcard.gradient.to})`
939
+ };
940
+ }
941
+ return { backgroundColor: endcard.background ?? "#101010" };
942
+ };
943
+ var EndcardCard = ({
944
+ endcard,
945
+ fps
946
+ }) => {
947
+ const frame = useCurrentFrame6();
948
+ const { width, height } = useVideoConfig6();
949
+ const animate = endcard.animateIn ?? true;
950
+ const ui = uiScale(width, height);
951
+ const logoIn = animate ? spring4({
952
+ fps,
953
+ frame,
954
+ config: { damping: 12, stiffness: 170, mass: 0.7 },
955
+ durationInFrames: Math.round(fps * 0.5)
956
+ }) : 1;
957
+ const textDelay = animate ? Math.round(fps * 0.15) : 0;
958
+ const textIn = animate ? spring4({
959
+ fps,
960
+ frame,
961
+ delay: textDelay,
962
+ config: { damping: 200 },
963
+ durationInFrames: Math.round(fps * 0.5)
964
+ }) : 1;
965
+ const textY = interpolate6(textIn, [0, 1], [40 * ui, 0]);
966
+ return /* @__PURE__ */ jsxs3(
967
+ AbsoluteFill8,
968
+ {
969
+ style: {
970
+ ...backgroundStyle(endcard),
971
+ alignItems: "center",
972
+ justifyContent: "center",
973
+ padding: "8%"
974
+ },
975
+ children: [
976
+ endcard.logoUrl ? /* @__PURE__ */ jsx9(
977
+ Img2,
978
+ {
979
+ src: endcard.logoUrl,
980
+ style: {
981
+ height: (endcard.logoHeightPx ?? DEFAULT_LOGO_HEIGHT_PX) * ui,
982
+ objectFit: "contain",
983
+ marginBottom: 48 * ui,
984
+ opacity: logoIn,
985
+ transform: `scale(${interpolate6(logoIn, [0, 1], [0.6, 1])})`
986
+ }
987
+ }
988
+ ) : null,
989
+ /* @__PURE__ */ jsx9(
990
+ "div",
991
+ {
992
+ style: {
993
+ opacity: textIn,
994
+ transform: `translateY(${textY}px)`,
995
+ textAlign: "center",
996
+ fontFamily: resolveFontFamily(endcard.fontFamily ?? DEFAULT_OVERLAY_FONT),
997
+ fontWeight: 700,
998
+ fontSize: (endcard.fontSize ?? 88) * ui,
999
+ color: endcard.color ?? "#FFFFFF",
1000
+ textShadow: textOutline("#000000", Math.max(1, Math.round(2 * ui))),
1001
+ lineHeight: 1.2,
1002
+ whiteSpace: "pre-wrap"
1003
+ },
1004
+ children: endcard.text
1005
+ }
1006
+ )
1007
+ ]
1008
+ }
1009
+ );
1010
+ };
1011
+ var EndcardSequence = ({ endcard, sourceDurationInSeconds, fps }) => {
1012
+ if (!endcard.enabled || !endcard.text) return null;
1013
+ const from = Math.round(sourceDurationInSeconds * fps);
1014
+ const durationInFrames = Math.max(1, msToFrames(endcard.durationMs, fps));
1015
+ return /* @__PURE__ */ jsx9(Sequence4, { from, durationInFrames, name: "endcard", children: /* @__PURE__ */ jsx9(EndcardCard, { endcard, fps }) });
1016
+ };
1017
+
1018
+ // ../src/SmartStudioComposition.tsx
1019
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1020
+ var SmartStudioComposition = (props) => {
1021
+ const { source, output, captions, overlays, endcard, safeZones, reframe, zoom, cuts } = props;
1022
+ const theme = resolveTheme(props.stylePreset);
1023
+ const captionStyle = resolveCaptionStyle(theme, captions.style);
1024
+ const displayMode = resolveCaptionDisplayMode(theme, captions.displayMode);
1025
+ const combineMs = resolveCaptionCombineMs(theme, captions.combineTokensWithinMs);
1026
+ const themedOverlays = overlays.map((o) => resolveOverlayItem(theme, o));
1027
+ const themedEndcard = resolveEndcard(theme, endcard);
1028
+ const progressBar = resolveProgressBar(theme, props.progressBar);
1029
+ const cutTransition = resolveCutTransition(
1030
+ theme,
1031
+ cuts.transitionType,
1032
+ cuts.transitionMs
1033
+ );
1034
+ const videoFilter = props.colorGrade ? gradeFilter(props.colorGrade.preset, props.colorGrade.intensity) : "none";
1035
+ return /* @__PURE__ */ jsxs4(AbsoluteFill9, { style: { backgroundColor: "#000000" }, children: [
1036
+ /* @__PURE__ */ jsx10(AbsoluteFill9, { style: videoFilter !== "none" ? { filter: videoFilter } : void 0, children: cuts.enabled ? (
1037
+ // Cortes DENTRO da composição: CutsSequencer é reframe-aware por
1038
+ // segmento; o ZoomLayer envolve tudo (janelas já remapeadas p/ a
1039
+ // timeline pós-corte pelo Python).
1040
+ /* @__PURE__ */ jsx10(ZoomLayer, { zoom, fps: output.fps, children: /* @__PURE__ */ jsx10(
1041
+ CutsSequencer,
1042
+ {
1043
+ source,
1044
+ cuts,
1045
+ reframe,
1046
+ transition: cutTransition
1047
+ }
1048
+ ) })
1049
+ ) : /* @__PURE__ */ jsx10(ReframeContainer, { reframe, output, source, children: /* @__PURE__ */ jsx10(ZoomLayer, { zoom, fps: output.fps, children: /* @__PURE__ */ jsx10(
1050
+ VideoBase,
1051
+ {
1052
+ source,
1053
+ fit: reframe.enabled ? "contain" : "cover"
1054
+ }
1055
+ ) }) }) }),
1056
+ /* @__PURE__ */ jsx10(
1057
+ CaptionsLayer,
1058
+ {
1059
+ captions,
1060
+ style: captionStyle,
1061
+ displayMode,
1062
+ combineTokensWithinMs: combineMs,
1063
+ safeZones,
1064
+ fps: output.fps
1065
+ }
1066
+ ),
1067
+ /* @__PURE__ */ jsx10(
1068
+ OverlayLayer,
1069
+ {
1070
+ overlays: themedOverlays,
1071
+ safeZones,
1072
+ fps: output.fps,
1073
+ captionsPosition: captions.enabled && captions.tokens.length > 0 ? captionStyle.position : null
1074
+ }
1075
+ ),
1076
+ /* @__PURE__ */ jsx10(
1077
+ ImageOverlayLayer,
1078
+ {
1079
+ imageOverlays: props.imageOverlays,
1080
+ safeZones,
1081
+ fps: output.fps
1082
+ }
1083
+ ),
1084
+ /* @__PURE__ */ jsx10(
1085
+ ProgressBarLayer,
1086
+ {
1087
+ progressBar,
1088
+ sourceDurationInSeconds: source.durationInSeconds,
1089
+ fps: output.fps
1090
+ }
1091
+ ),
1092
+ /* @__PURE__ */ jsx10(
1093
+ EndcardSequence,
1094
+ {
1095
+ endcard: themedEndcard,
1096
+ sourceDurationInSeconds: source.durationInSeconds,
1097
+ fps: output.fps
1098
+ }
1099
+ )
1100
+ ] });
1101
+ };
1102
+
1103
+ // ../src/metadata.ts
1104
+ var FALLBACK_DURATION_IN_FRAMES = 300;
1105
+ var calculateSmartStudioMetadata = ({
1106
+ props
1107
+ }) => {
1108
+ const fps = props.output?.fps || props.source?.fps || 30;
1109
+ const sourceSeconds = props.source?.durationInSeconds || 0;
1110
+ const endcardSeconds = props.endcard?.enabled && props.endcard?.durationMs ? props.endcard.durationMs / 1e3 : 0;
1111
+ const totalSeconds = sourceSeconds + endcardSeconds;
1112
+ const durationInFrames = Math.max(
1113
+ 1,
1114
+ Math.round(totalSeconds * fps) || FALLBACK_DURATION_IN_FRAMES
1115
+ );
1116
+ return {
1117
+ durationInFrames,
1118
+ fps,
1119
+ width: props.output?.width || props.source?.width || 1080,
1120
+ height: props.output?.height || props.source?.height || 1920
1121
+ };
1122
+ };
1123
+
1124
+ // ../src/types.ts
1125
+ var DEFAULT_INPUT_PROPS = {
1126
+ source: {
1127
+ videoPath: "",
1128
+ width: 1080,
1129
+ height: 1920,
1130
+ durationInSeconds: 0,
1131
+ fps: 30
1132
+ },
1133
+ output: { width: 1080, height: 1920, fps: 30 },
1134
+ captions: {
1135
+ enabled: false,
1136
+ tokens: [],
1137
+ // style/displayMode/combineTokensWithinMs = overrides explícitos; os
1138
+ // defaults e o tema (stylePreset) são resolvidos em themes.ts.
1139
+ style: {}
1140
+ },
1141
+ overlays: [],
1142
+ endcard: {
1143
+ enabled: false,
1144
+ text: "",
1145
+ durationMs: 3e3
1146
+ },
1147
+ safeZones: {
1148
+ enabled: true,
1149
+ marginTopPercent: 0.08,
1150
+ marginBottomPercent: 0.25,
1151
+ marginSidePercent: 0.03
1152
+ },
1153
+ reframe: { enabled: false, mode: "blur_fill" },
1154
+ zoom: { enabled: false, startMs: 0, endMs: 0, factor: 1.15, animated: true },
1155
+ cuts: { enabled: false, segmentsKeepMs: [] }
1156
+ };
1157
+ export {
1158
+ DEFAULT_INPUT_PROPS,
1159
+ FALLBACK_DURATION_IN_FRAMES,
1160
+ STYLE_PRESETS,
1161
+ SmartStudioComposition,
1162
+ calculateSmartStudioMetadata
1163
+ };