@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/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @klikeai/cut-preview
2
+
3
+ Composição Remotion (`SmartStudioComposition`) + `calculateSmartStudioMetadata`
4
+ empacotadas para o **preview ao vivo** do Klike Cut rodar no `@remotion/player`
5
+ (browser/Lovable). O backend serve os `inputProps` em
6
+ `GET /klike-cut/sessions/{id}/preview/player`; o front renderiza a composição ao
7
+ vivo com estes símbolos.
8
+
9
+ > Troque o escopo `@klikeai` pelo namespace npm da sua organização antes de publicar.
10
+
11
+ ## Build
12
+
13
+ ```bash
14
+ cd remotion/preview-package
15
+ npm install
16
+ npm run build # gera dist/index.js (ESM) + dist/index.d.ts (tsup)
17
+ ```
18
+
19
+ ## Publicar (passo a passo)
20
+
21
+ 1. **Escolha o nome/namespace** em `package.json` (`name`). Escopado
22
+ (`@suaorg/cut-preview`) é o recomendado — registre o escopo cedo p/ evitar
23
+ squatting.
24
+ 2. `npm login` com a conta que tem acesso ao escopo.
25
+ 3. Publique (escopado público exige `--access public`):
26
+ ```bash
27
+ npm publish --access public
28
+ ```
29
+ 4. **Versionamento é o contrato:** a `version` do pacote DEVE bater com
30
+ `COMPOSITION_VERSION` em
31
+ `app/services/klike_cut/preview_player.py`. A cada mudança da composição
32
+ (qualquer TSX em `../src`): `build` → bump da `version` E do
33
+ `COMPOSITION_VERSION` → `npm publish` → redeploy do backend. O front compara
34
+ as versões e cai no preview MP4 quando o pacote instalado está defasado.
35
+ 5. (Opcional) Automatize com CI publicando em tag `v*`.
36
+
37
+ ## Consumo no frontend (Lovable)
38
+
39
+ Instale na MESMA versão de `remotion` (4.0.399) e adicione o Player:
40
+
41
+ ```bash
42
+ npm install @remotion/player remotion@4.0.399 \
43
+ @remotion/captions@4.0.399 @remotion/transitions@4.0.399 \
44
+ @remotion/google-fonts@4.0.399 @remotion/layout-utils@4.0.399 \
45
+ @klikeai/cut-preview
46
+ ```
47
+
48
+ ```tsx
49
+ import {Player} from '@remotion/player';
50
+ import {SmartStudioComposition, calculateSmartStudioMetadata}
51
+ from '@klikeai/cut-preview';
52
+
53
+ const {durationInFrames, fps, width, height} =
54
+ calculateSmartStudioMetadata({props: inputProps});
55
+
56
+ <Player
57
+ component={SmartStudioComposition}
58
+ inputProps={inputProps}
59
+ durationInFrames={durationInFrames}
60
+ fps={fps}
61
+ compositionWidth={width}
62
+ compositionHeight={height}
63
+ controls
64
+ style={{width: '100%'}}
65
+ />
66
+ ```
67
+
68
+ ## Riscos / cuidados
69
+
70
+ - **Exposição pública:** o código da composição (lógica de renderização visual)
71
+ fica público no npm. Não há segredo/credencial — é só apresentação —, mas é IP
72
+ visual. Se incomodar: pacote **privado** (npm pago / GitHub Packages) ou copiar
73
+ o `../src` direto no projeto do front.
74
+ - **Drift de versão:** front com pacote < `COMPOSITION_VERSION` do backend →
75
+ `inputProps` podem não casar. Mitigado pela checagem de versão + fallback ao
76
+ MP4 (o backend responde `composition_version` no payload).
77
+ - **peerDeps:** o front precisa de `remotion`/`@remotion/*` na MESMA versão
78
+ (4.0.399) + React 18/19. Versões divergentes = erro em runtime do Remotion.
79
+ - **Manutenção:** toda mudança da composição = rebuild + republish + bump +
80
+ redeploy. Por isso o preview MP4 continua como rede de segurança.
81
+ - **Assets `http(s)`:** `source.videoPath`, `endcard.logoUrl` e
82
+ `imageOverlays[].src` DEVEM ser URLs (o backend já entrega presignado). No
83
+ browser não existe o servidor de asset local do render SSR.
@@ -0,0 +1,268 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Contrato de inputProps Smart Studio ↔ Remotion.
5
+ *
6
+ * Espelha o JSON que o RemotionRenderer (Python) escreve em props.json.
7
+ * REGRA: todo tempo vem em MILISSEGUNDOS; a conversão ms→frame acontece num
8
+ * único ponto (utilitário msToFrames), usando o fps da composição. Cores em
9
+ * hex CSS (#RRGGBB), não no formato ASS.
10
+ *
11
+ * Fase A cobre só as camadas gráficas: captions, overlays e endcard. Os campos
12
+ * de cuts/zoom/reframe/transitions existem no contrato para as Fases B/C, mas
13
+ * na Fase A chegam desabilitados (esses passes rodam no FFmpeg antes).
14
+ */
15
+ type Position = 'top' | 'center' | 'bottom';
16
+ type CaptionDisplayMode = 'highlight' | 'plain' | 'word_by_word' | 'karaoke';
17
+ interface SourceProps {
18
+ /** Path local do vídeo de entrada (o tempfile já baixado do S3 pelo Python). */
19
+ videoPath: string;
20
+ width: number;
21
+ height: number;
22
+ durationInSeconds: number;
23
+ fps: number;
24
+ }
25
+ interface OutputProps {
26
+ width: number;
27
+ height: number;
28
+ fps: number;
29
+ }
30
+ interface CaptionToken {
31
+ text: string;
32
+ startMs: number;
33
+ endMs: number;
34
+ confidence?: number;
35
+ }
36
+ /**
37
+ * Template visual da legenda (ortogonal ao displayMode):
38
+ * - classic: contorno duro (text-shadow 8 direções)
39
+ * - pill: bloco da página sobre fundo arredondado translúcido
40
+ * - box: token ATIVO ganha caixa sólida colorida (estilo CapCut)
41
+ * - glow: brilho suave na cor de destaque (sem contorno duro)
42
+ */
43
+ type CaptionTemplate = 'classic' | 'pill' | 'box' | 'glow';
44
+ /**
45
+ * Estilo das legendas. TODOS os campos são opcionais: o que vier daqui é
46
+ * OVERRIDE explícito do request — defaults e tema (stylePreset) são
47
+ * resolvidos no React (themes.ts), a fonte da verdade visual.
48
+ */
49
+ interface CaptionStyle {
50
+ fontFamily?: string;
51
+ fontSize?: number;
52
+ primaryColor?: string;
53
+ highlightColor?: string;
54
+ outlineColor?: string;
55
+ bold?: boolean;
56
+ uppercase?: boolean;
57
+ italic?: boolean;
58
+ outlineWidth?: number;
59
+ activeScale?: number;
60
+ animateIn?: boolean;
61
+ template?: CaptionTemplate;
62
+ /** Âncora vertical do bloco (sempre dentro das safe zones). */
63
+ position?: 'bottom' | 'center' | 'top';
64
+ }
65
+ interface CaptionsProps {
66
+ enabled: boolean;
67
+ /** Override explícito; ausente = tema/default (themes.ts). */
68
+ displayMode?: CaptionDisplayMode;
69
+ /** Janela (ms) para agrupar tokens em "páginas" estilo TikTok. */
70
+ combineTokensWithinMs?: number;
71
+ tokens: CaptionToken[];
72
+ style: CaptionStyle;
73
+ }
74
+ type OverlayEnterAnimation = 'fade' | 'slide-up' | 'slide-down' | 'zoom' | 'spring-pop';
75
+ interface OverlayItem {
76
+ text: string;
77
+ startMs: number;
78
+ endMs: number;
79
+ position: Position;
80
+ fontSize?: number;
81
+ color?: string;
82
+ /** ms de fade in/out (default suave se omitido). */
83
+ fadeMs?: number;
84
+ fontFamily?: string;
85
+ bold?: boolean;
86
+ italic?: boolean;
87
+ outlineColor?: string;
88
+ outlineWidth?: number;
89
+ /** Tipo de entrada; default "spring-pop". */
90
+ enterAnimation?: OverlayEnterAnimation;
91
+ /** ms do fade de saída (default = fadeMs). */
92
+ fadeOutMs?: number;
93
+ }
94
+ interface EndcardGradient {
95
+ from: string;
96
+ to: string;
97
+ /** Ângulo do linear-gradient em graus (default 160). */
98
+ angleDeg?: number;
99
+ }
100
+ interface EndcardProps {
101
+ enabled: boolean;
102
+ text: string;
103
+ durationMs: number;
104
+ /** Estilo = override explícito; defaults/tema resolvidos em themes.ts. */
105
+ background?: string;
106
+ color?: string;
107
+ fontSize?: number;
108
+ /** Gradiente de fundo; quando presente, tem precedência sobre background. */
109
+ gradient?: EndcardGradient;
110
+ /** URL http(s) do logo (o render.mjs converte path local em URL servida). */
111
+ logoUrl?: string;
112
+ logoHeightPx?: number;
113
+ fontFamily?: string;
114
+ /** Entrada animada (spring) do logo/texto (default true). */
115
+ animateIn?: boolean;
116
+ }
117
+ /** Barra de progresso do vídeo (acessório de tema; borda superior). */
118
+ interface ProgressBarProps {
119
+ enabled: boolean;
120
+ color: string;
121
+ heightPx: number;
122
+ }
123
+ /**
124
+ * Safe zone do feed vertical — SEMPRE aplicada aos textos (legendas e
125
+ * overlays), independente de flag: a UI das plataformas ocupa o topo e
126
+ * principalmente o rodapé. Percentuais RELATIVOS À ALTURA (top/bottom) e à
127
+ * LARGURA (side) — os layers convertem para px via useVideoConfig (padding em
128
+ * % no CSS é relativo à largura, o que quebraria as margens verticais).
129
+ */
130
+ interface SafeZonesProps {
131
+ enabled: boolean;
132
+ marginTopPercent: number;
133
+ marginBottomPercent: number;
134
+ marginSidePercent: number;
135
+ }
136
+ /** Reservados p/ Fases B/C — na Fase A chegam {enabled:false}. */
137
+ interface ReframeProps {
138
+ enabled: boolean;
139
+ mode: 'blur_fill';
140
+ }
141
+ interface ZoomMoment {
142
+ startMs: number;
143
+ endMs: number;
144
+ factor: number;
145
+ }
146
+ interface ZoomProps {
147
+ enabled: boolean;
148
+ startMs: number;
149
+ endMs: number;
150
+ factor: number;
151
+ animated: boolean;
152
+ /** Multi-instância: janelas de zoom (sem sobreposição). Quando presente,
153
+ * tem precedência sobre startMs/endMs/factor (que ficam como legado). */
154
+ moments?: ZoomMoment[];
155
+ }
156
+ type CutTransitionType = 'none' | 'fade' | 'wipe' | 'slide';
157
+ interface CutsProps {
158
+ enabled: boolean;
159
+ /** Segmentos a MANTER (ms na timeline do vídeo FONTE), ordenados. */
160
+ segmentsKeepMs: [number, number][];
161
+ /** Duração da transição entre segmentos (0/ausente = corte seco). */
162
+ transitionMs?: number;
163
+ /** Tipo explícito; ausente = tema decide (themes.ts). */
164
+ transitionType?: CutTransitionType;
165
+ }
166
+ type ColorGradePreset = 'warm' | 'cold' | 'vintage' | 'bw' | 'high_contrast' | 'vivid';
167
+ /** Filtro de cor aplicado ao VÍDEO (não afeta textos nem endcard). */
168
+ interface ColorGradeProps {
169
+ preset: ColorGradePreset;
170
+ /** 0-1 — interpola os filtros CSS do preset. */
171
+ intensity: number;
172
+ }
173
+ type ImageCorner = 'top_left' | 'top_right' | 'bottom_left' | 'bottom_right' | 'center';
174
+ /** Imagem/logo sobreposta (add_image_overlay). */
175
+ interface ImageOverlayItem {
176
+ /** URL http(s) — o render.mjs serve paths locais por HTTP. */
177
+ src: string;
178
+ startMs: number;
179
+ endMs: number;
180
+ corner: ImageCorner;
181
+ /** Altura como fração da ALTURA do canvas (0.05–0.3). */
182
+ heightPct: number;
183
+ opacity: number;
184
+ }
185
+ interface SmartStudioInputProps {
186
+ source: SourceProps;
187
+ output: OutputProps;
188
+ /** Nome do preset de tema (themes.ts). Ausente/desconhecido = defaults. */
189
+ stylePreset?: string;
190
+ captions: CaptionsProps;
191
+ overlays: OverlayItem[];
192
+ endcard: EndcardProps;
193
+ safeZones: SafeZonesProps;
194
+ /** Override explícito da progress bar; tema pode ligar por padrão. */
195
+ progressBar?: Partial<ProgressBarProps>;
196
+ reframe: ReframeProps;
197
+ zoom: ZoomProps;
198
+ cuts: CutsProps;
199
+ /** Filtro de cor sobre o vídeo (color_grade). */
200
+ colorGrade?: ColorGradeProps;
201
+ /** Imagens/logos sobrepostos (add_image_overlay). */
202
+ imageOverlays?: ImageOverlayItem[];
203
+ [key: string]: unknown;
204
+ }
205
+ /** Default usado pelo Studio e como base de merge. Vídeo precisa existir. */
206
+ declare const DEFAULT_INPUT_PROPS: SmartStudioInputProps;
207
+
208
+ /**
209
+ * Composição raiz do Smart Studio.
210
+ *
211
+ * Resolve o TEMA (props.stylePreset via themes.ts — a fonte da verdade visual)
212
+ * e mescla: defaults → tema → overrides explícitos do request. As layers
213
+ * recebem valores JÁ RESOLVIDOS.
214
+ *
215
+ * Empilha as camadas (z-order = ordem no JSX, de baixo p/ cima):
216
+ * 1. ReframeContainer/ZoomLayer/VideoBase — o vídeo de origem (já cortado
217
+ * pelo FFmpeg), com reframe 9:16 (blur fill) e punch-in zoom opcionais
218
+ * 2. CaptionsLayer — legendas word-level do Whisper
219
+ * 3. OverlayLayer — lettering/hook/CTA
220
+ * 4. ProgressBarLayer — barra de progresso (acessório de tema)
221
+ * 5. EndcardSequence — cartão final de CTA (sequência extra ao fim)
222
+ *
223
+ * Reframe/Zoom chegam desabilitados quando o FFmpeg já os aplicou (fallback)
224
+ * ou quando o plano não os pede.
225
+ */
226
+
227
+ declare const SmartStudioComposition: React.FC<SmartStudioInputProps>;
228
+
229
+ /**
230
+ * calculateMetadata da composição Smart Studio — extraído de Root.tsx para
231
+ * ser exportado pelo pacote de preview (klike-cut-preview).
232
+ *
233
+ * REGRA-CHAVE do contrato: o frontend (Remotion Player) calcula duração/fps/
234
+ * dimensões com ESTA MESMA função que o selectComposition roda no render
235
+ * server-side — elimina a classe de bug "arredondamento de frames divergiu
236
+ * entre preview e render".
237
+ */
238
+
239
+ declare const FALLBACK_DURATION_IN_FRAMES = 300;
240
+ interface SmartStudioMetadata {
241
+ durationInFrames: number;
242
+ fps: number;
243
+ width: number;
244
+ height: number;
245
+ }
246
+ declare const calculateSmartStudioMetadata: ({ props, }: {
247
+ props: SmartStudioInputProps;
248
+ }) => SmartStudioMetadata;
249
+
250
+ /**
251
+ * Temas (StylePreset) do Smart Studio — FONTE DA VERDADE visual.
252
+ *
253
+ * Um tema resolve, num único lugar, a identidade visual coerente do vídeo:
254
+ * fonte + paleta + template de legenda + animação de overlay + endcard +
255
+ * acessórios (progress bar). O Python NÃO conhece os valores dos temas — só
256
+ * passa o nome do preset em props.stylePreset (escolhido pelo Planner ou
257
+ * sobrescrito no request).
258
+ *
259
+ * Ordem de resolução (menor → maior precedência):
260
+ * DEFAULTS (deste arquivo) → THEMES[stylePreset] → overrides explícitos do
261
+ * request (props.captions.style / props.overlays[i] / props.endcard).
262
+ * Ou seja: o usuário sempre pode sobrescrever qualquer valor do tema.
263
+ */
264
+
265
+ declare const STYLE_PRESETS: readonly ["bold_impact", "clean_saas", "ugc_casual", "premium_dark"];
266
+ type StylePresetName = (typeof STYLE_PRESETS)[number];
267
+
268
+ export { type CaptionDisplayMode, type CaptionStyle, type CaptionTemplate, type CaptionToken, type CaptionsProps, type ColorGradePreset, type ColorGradeProps, type CutTransitionType, type CutsProps, DEFAULT_INPUT_PROPS, type EndcardGradient, type EndcardProps, FALLBACK_DURATION_IN_FRAMES, type ImageCorner, type ImageOverlayItem, type OutputProps, type OverlayEnterAnimation, type OverlayItem, type Position, type ProgressBarProps, type ReframeProps, STYLE_PRESETS, type SafeZonesProps, SmartStudioComposition, type SmartStudioInputProps, type SmartStudioMetadata, type SourceProps, type StylePresetName, type ZoomMoment, type ZoomProps, calculateSmartStudioMetadata };