@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 +83 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +1163 -0
- package/package.json +42 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|