@mevdragon/vidfarm-devcli 0.2.14 → 0.3.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 +11 -4
- package/SKILL.director.md +97 -0
- package/SKILL.platform.md +66 -6
- package/dist/src/cli.js +135 -1527
- package/package.json +5 -24
- package/.env.example +0 -60
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +0 -275
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +0 -128
- package/auto-create-hyperframe-templates/input-manifest.schema.json +0 -56
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +0 -118
- package/auto-create-hyperframe-templates/production-graph.schema.json +0 -314
- package/auto-create-hyperframe-templates/runbook.md +0 -198
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +0 -368
- package/auto-create-hyperframe-templates/template-plan.schema.json +0 -311
- package/auto-create-hyperframe-templates/template-planner-prompt.md +0 -144
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +0 -599
- package/auto-create-templates/extractor-system-prompt.md +0 -119
- package/auto-create-templates/production-graph.schema.json +0 -276
- package/auto-create-templates/runbook.md +0 -140
- package/auto-create-templates/template-plan.schema.json +0 -301
- package/auto-create-templates/template-planner-prompt.md +0 -137
- package/dist/src/account-pages-legacy.js +0 -9338
- package/dist/src/account-pages-legacy.js.map +0 -1
- package/dist/src/account-pages.js +0 -52
- package/dist/src/account-pages.js.map +0 -1
- package/dist/src/app.js +0 -13711
- package/dist/src/app.js.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/composition-runtime.js +0 -581
- package/dist/src/composition-runtime.js.map +0 -1
- package/dist/src/config.js +0 -140
- package/dist/src/config.js.map +0 -1
- package/dist/src/context.js +0 -492
- package/dist/src/context.js.map +0 -1
- package/dist/src/dev-app-legacy.js +0 -694
- package/dist/src/dev-app-legacy.js.map +0 -1
- package/dist/src/dev-app.js +0 -6
- package/dist/src/dev-app.js.map +0 -1
- package/dist/src/dev-serve.js.map +0 -1
- package/dist/src/domain.js +0 -2
- package/dist/src/domain.js.map +0 -1
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat-history.js.map +0 -1
- package/dist/src/editor-chat.js +0 -312
- package/dist/src/editor-chat.js.map +0 -1
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/editor-dark-theme.js.map +0 -1
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/flockposter-cache-store.js.map +0 -1
- package/dist/src/frontend/homepage-client.js +0 -117
- package/dist/src/frontend/homepage-client.js.map +0 -1
- package/dist/src/frontend/homepage-shared.js +0 -4
- package/dist/src/frontend/homepage-shared.js.map +0 -1
- package/dist/src/frontend/homepage-store.js +0 -28
- package/dist/src/frontend/homepage-store.js.map +0 -1
- package/dist/src/frontend/homepage-view.js +0 -260
- package/dist/src/frontend/homepage-view.js.map +0 -1
- package/dist/src/frontend/page-runtime-client.js +0 -124
- package/dist/src/frontend/page-runtime-client.js.map +0 -1
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/page-runtime-store.js.map +0 -1
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/sentry.js.map +0 -1
- package/dist/src/frontend/template-editor-chat.js +0 -3760
- package/dist/src/frontend/template-editor-chat.js.map +0 -1
- package/dist/src/help-page.js +0 -333
- package/dist/src/help-page.js.map +0 -1
- package/dist/src/homepage.js +0 -919
- package/dist/src/homepage.js.map +0 -1
- package/dist/src/hyperframes/composition.js +0 -180
- package/dist/src/hyperframes/composition.js.map +0 -1
- package/dist/src/index.js +0 -16
- package/dist/src/index.js.map +0 -1
- package/dist/src/instrument.js +0 -30
- package/dist/src/instrument.js.map +0 -1
- package/dist/src/lib/crypto.js +0 -45
- package/dist/src/lib/crypto.js.map +0 -1
- package/dist/src/lib/dev-log.js +0 -54
- package/dist/src/lib/dev-log.js.map +0 -1
- package/dist/src/lib/display-name.js +0 -11
- package/dist/src/lib/display-name.js.map +0 -1
- package/dist/src/lib/ids.js +0 -24
- package/dist/src/lib/ids.js.map +0 -1
- package/dist/src/lib/images.js +0 -19
- package/dist/src/lib/images.js.map +0 -1
- package/dist/src/lib/json.js +0 -15
- package/dist/src/lib/json.js.map +0 -1
- package/dist/src/lib/template-dna.js +0 -543
- package/dist/src/lib/template-dna.js.map +0 -1
- package/dist/src/lib/template-paths.js +0 -28
- package/dist/src/lib/template-paths.js.map +0 -1
- package/dist/src/lib/template-style-options.js +0 -203
- package/dist/src/lib/template-style-options.js.map +0 -1
- package/dist/src/lib/time.js +0 -7
- package/dist/src/lib/time.js.map +0 -1
- package/dist/src/lib/video-quality-harness.js +0 -60
- package/dist/src/lib/video-quality-harness.js.map +0 -1
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-runtime.js.map +0 -1
- package/dist/src/page-shell.js +0 -1315
- package/dist/src/page-shell.js.map +0 -1
- package/dist/src/primitive-context.js +0 -299
- package/dist/src/primitive-context.js.map +0 -1
- package/dist/src/primitive-registry.js +0 -2181
- package/dist/src/primitive-registry.js.map +0 -1
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitive-sdk.js.map +0 -1
- package/dist/src/primitives/remotion/html-image.js +0 -30
- package/dist/src/primitives/remotion/html-image.js.map +0 -1
- package/dist/src/primitives/remotion/media-slideshow.js +0 -60
- package/dist/src/primitives/remotion/media-slideshow.js.map +0 -1
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/react-page-shell.js.map +0 -1
- package/dist/src/ready-post-schedule-component.js +0 -1515
- package/dist/src/ready-post-schedule-component.js.map +0 -1
- package/dist/src/registry.js +0 -296
- package/dist/src/registry.js.map +0 -1
- package/dist/src/runtime.js +0 -31
- package/dist/src/runtime.js.map +0 -1
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/api-call-history.js.map +0 -1
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/auth.js.map +0 -1
- package/dist/src/services/billing.js +0 -231
- package/dist/src/services/billing.js.map +0 -1
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/chat-threads.js.map +0 -1
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-access.js.map +0 -1
- package/dist/src/services/fork-manifest.js +0 -43
- package/dist/src/services/fork-manifest.js.map +0 -1
- package/dist/src/services/ghostcut.js +0 -132
- package/dist/src/services/ghostcut.js.map +0 -1
- package/dist/src/services/hyperframes.js +0 -1014
- package/dist/src/services/hyperframes.js.map +0 -1
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-capacity.js.map +0 -1
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/job-logs.js.map +0 -1
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/jobs.js.map +0 -1
- package/dist/src/services/media-processing.js +0 -743
- package/dist/src/services/media-processing.js.map +0 -1
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/primitive-media-lambda.js.map +0 -1
- package/dist/src/services/providers.js +0 -2870
- package/dist/src/services/providers.js.map +0 -1
- package/dist/src/services/rate-limits.js +0 -237
- package/dist/src/services/rate-limits.js.map +0 -1
- package/dist/src/services/remotion.js +0 -688
- package/dist/src/services/remotion.js.map +0 -1
- package/dist/src/services/serverless-auth.js +0 -374
- package/dist/src/services/serverless-auth.js.map +0 -1
- package/dist/src/services/serverless-jobs.js +0 -1074
- package/dist/src/services/serverless-jobs.js.map +0 -1
- package/dist/src/services/serverless-provider-keys.js +0 -401
- package/dist/src/services/serverless-provider-keys.js.map +0 -1
- package/dist/src/services/serverless-records.js +0 -1088
- package/dist/src/services/serverless-records.js.map +0 -1
- package/dist/src/services/serverless-template-configs.js +0 -67
- package/dist/src/services/serverless-template-configs.js.map +0 -1
- package/dist/src/services/storage.js +0 -383
- package/dist/src/services/storage.js.map +0 -1
- package/dist/src/services/template-certification.js +0 -475
- package/dist/src/services/template-certification.js.map +0 -1
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-loader.js.map +0 -1
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-runtime-bundles.js.map +0 -1
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/template-sources.js.map +0 -1
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/video-normalization.js.map +0 -1
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/services/webhooks.js.map +0 -1
- package/dist/src/template-editor-pages.js +0 -2491
- package/dist/src/template-editor-pages.js.map +0 -1
- package/dist/src/template-editor-shell.js +0 -2702
- package/dist/src/template-editor-shell.js.map +0 -1
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/template-sdk.js.map +0 -1
- package/dist/src/worker.js +0 -17
- package/dist/src/worker.js.map +0 -1
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/readme.secret.md +0 -89
- package/templates/vidfarm_template_0000/README.md +0 -106
- package/templates/vidfarm_template_0000/SKILL.md +0 -266
- package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/composition.json +0 -11
- package/templates/vidfarm_template_0000/src/lib/images.js +0 -221
- package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
- package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
- package/templates/vidfarm_template_0000/src/sdk.js +0 -3
- package/templates/vidfarm_template_0000/src/style-options.js +0 -200
- package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
- package/templates/vidfarm_template_0000/src/template.js +0 -1566
- package/templates/vidfarm_template_0000/template.config.json +0 -21
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import sharp from "sharp";
|
|
2
|
-
export async function normalizeToPortraitFrame(input, target = { width: 1080, height: 1920 }) {
|
|
3
|
-
const buffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
4
|
-
const oriented = await materializeSharp(sharp(buffer, { density: 144 }).rotate());
|
|
5
|
-
const trimmed = await trimFlatBorders(oriented);
|
|
6
|
-
const targetAspect = target.width / target.height;
|
|
7
|
-
const shouldPreserveFraming = await isCloseToAspect(trimmed, targetAspect, 0.025);
|
|
8
|
-
const cropped = shouldPreserveFraming ? trimmed : await cropToActiveImageRegion(trimmed);
|
|
9
|
-
const normalized = await resizeWithBlurredContain(cropped, target, targetAspect);
|
|
10
|
-
return {
|
|
11
|
-
bytes: normalized,
|
|
12
|
-
contentType: "image/png",
|
|
13
|
-
width: target.width,
|
|
14
|
-
height: target.height
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
async function resizeWithBlurredContain(image, target, targetAspect) {
|
|
18
|
-
const metadata = await image.metadata();
|
|
19
|
-
const width = metadata.width ?? 0;
|
|
20
|
-
const height = metadata.height ?? 0;
|
|
21
|
-
if (!width || !height) {
|
|
22
|
-
return image
|
|
23
|
-
.resize(target.width, target.height, {
|
|
24
|
-
fit: "cover",
|
|
25
|
-
position: sharp.strategy.attention
|
|
26
|
-
})
|
|
27
|
-
.png()
|
|
28
|
-
.toBuffer();
|
|
29
|
-
}
|
|
30
|
-
const aspect = width / height;
|
|
31
|
-
if (Math.abs(aspect - targetAspect) <= 0.015) {
|
|
32
|
-
return image
|
|
33
|
-
.resize(target.width, target.height, {
|
|
34
|
-
fit: "fill"
|
|
35
|
-
})
|
|
36
|
-
.png()
|
|
37
|
-
.toBuffer();
|
|
38
|
-
}
|
|
39
|
-
const foreground = await image
|
|
40
|
-
.clone()
|
|
41
|
-
.resize(target.width, target.height, {
|
|
42
|
-
fit: "contain",
|
|
43
|
-
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
44
|
-
})
|
|
45
|
-
.png()
|
|
46
|
-
.toBuffer();
|
|
47
|
-
const background = await image
|
|
48
|
-
.clone()
|
|
49
|
-
.resize(target.width, target.height, {
|
|
50
|
-
fit: "cover",
|
|
51
|
-
position: sharp.strategy.attention
|
|
52
|
-
})
|
|
53
|
-
.blur(20)
|
|
54
|
-
.modulate({ brightness: 0.92, saturation: 0.9 })
|
|
55
|
-
.png()
|
|
56
|
-
.toBuffer();
|
|
57
|
-
return sharp(background)
|
|
58
|
-
.composite([{ input: foreground }])
|
|
59
|
-
.png()
|
|
60
|
-
.toBuffer();
|
|
61
|
-
}
|
|
62
|
-
async function isCloseToAspect(image, targetAspect, tolerance) {
|
|
63
|
-
const metadata = await image.metadata();
|
|
64
|
-
const width = metadata.width ?? 0;
|
|
65
|
-
const height = metadata.height ?? 0;
|
|
66
|
-
if (!width || !height) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return Math.abs(width / height - targetAspect) <= tolerance;
|
|
70
|
-
}
|
|
71
|
-
async function trimFlatBorders(image) {
|
|
72
|
-
const metadata = await image.metadata();
|
|
73
|
-
const sourceWidth = metadata.width ?? 0;
|
|
74
|
-
const sourceHeight = metadata.height ?? 0;
|
|
75
|
-
if (!sourceWidth || !sourceHeight || sourceWidth < 3 || sourceHeight < 3) {
|
|
76
|
-
return image;
|
|
77
|
-
}
|
|
78
|
-
const { data, info } = await image
|
|
79
|
-
.clone()
|
|
80
|
-
.trim({ threshold: 16 })
|
|
81
|
-
.png()
|
|
82
|
-
.toBuffer({ resolveWithObject: true });
|
|
83
|
-
const widthRatio = info.width / sourceWidth;
|
|
84
|
-
const heightRatio = info.height / sourceHeight;
|
|
85
|
-
if (widthRatio > 0.985 && heightRatio > 0.985) {
|
|
86
|
-
return image;
|
|
87
|
-
}
|
|
88
|
-
return materializeSharp(sharp(data).rotate());
|
|
89
|
-
}
|
|
90
|
-
async function cropToActiveImageRegion(image) {
|
|
91
|
-
const metadata = await image.metadata();
|
|
92
|
-
const sourceWidth = metadata.width ?? 0;
|
|
93
|
-
const sourceHeight = metadata.height ?? 0;
|
|
94
|
-
if (!sourceWidth || !sourceHeight) {
|
|
95
|
-
return image;
|
|
96
|
-
}
|
|
97
|
-
const sampleWidth = 96;
|
|
98
|
-
const sampleHeight = 170;
|
|
99
|
-
const sample = await image
|
|
100
|
-
.clone()
|
|
101
|
-
.resize(sampleWidth, sampleHeight, { fit: "fill" })
|
|
102
|
-
.grayscale()
|
|
103
|
-
.raw()
|
|
104
|
-
.toBuffer();
|
|
105
|
-
const bounds = detectActiveBounds(sample, sampleWidth, sampleHeight);
|
|
106
|
-
if (!bounds) {
|
|
107
|
-
return image;
|
|
108
|
-
}
|
|
109
|
-
const left = Math.max(0, Math.floor((bounds.left / sampleWidth) * sourceWidth));
|
|
110
|
-
const top = Math.max(0, Math.floor((bounds.top / sampleHeight) * sourceHeight));
|
|
111
|
-
const width = Math.min(sourceWidth - left, Math.max(1, Math.ceil((bounds.width / sampleWidth) * sourceWidth)));
|
|
112
|
-
const height = Math.min(sourceHeight - top, Math.max(1, Math.ceil((bounds.height / sampleHeight) * sourceHeight)));
|
|
113
|
-
return extractOrFallback(image, { left, top, width, height }, { width: sourceWidth, height: sourceHeight });
|
|
114
|
-
}
|
|
115
|
-
async function materializeSharp(image) {
|
|
116
|
-
const buffer = await image.png().toBuffer();
|
|
117
|
-
return sharp(buffer);
|
|
118
|
-
}
|
|
119
|
-
async function extractOrFallback(image, area, source) {
|
|
120
|
-
if (source.width <= 0 || source.height <= 0) {
|
|
121
|
-
return image;
|
|
122
|
-
}
|
|
123
|
-
const left = clamp(Math.floor(area.left), 0, source.width - 1);
|
|
124
|
-
const top = clamp(Math.floor(area.top), 0, source.height - 1);
|
|
125
|
-
const width = clamp(Math.floor(area.width), 1, source.width - left);
|
|
126
|
-
const height = clamp(Math.floor(area.height), 1, source.height - top);
|
|
127
|
-
try {
|
|
128
|
-
return await materializeSharp(image.extract({ left, top, width, height }));
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return image;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function detectActiveBounds(sample, width, height) {
|
|
135
|
-
const rowActivity = new Array(height).fill(0);
|
|
136
|
-
const colActivity = new Array(width).fill(0);
|
|
137
|
-
for (let y = 0; y < height; y += 1) {
|
|
138
|
-
for (let x = 0; x < width; x += 1) {
|
|
139
|
-
const index = y * width + x;
|
|
140
|
-
const current = sample[index] ?? 0;
|
|
141
|
-
const left = x > 0 ? sample[index - 1] ?? current : current;
|
|
142
|
-
const up = y > 0 ? sample[index - width] ?? current : current;
|
|
143
|
-
const right = x < width - 1 ? sample[index + 1] ?? current : current;
|
|
144
|
-
const down = y < height - 1 ? sample[index + width] ?? current : current;
|
|
145
|
-
const energy = Math.abs(current - left) +
|
|
146
|
-
Math.abs(current - right) +
|
|
147
|
-
Math.abs(current - up) +
|
|
148
|
-
Math.abs(current - down);
|
|
149
|
-
rowActivity[y] += energy;
|
|
150
|
-
colActivity[x] += energy;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const smoothedRows = smoothSeries(rowActivity.map((value) => value / width), 9);
|
|
154
|
-
const smoothedCols = smoothSeries(colActivity.map((value) => value / height), 7);
|
|
155
|
-
const rowBounds = findActiveRange(smoothedRows, Math.round(height * 0.18));
|
|
156
|
-
const colBounds = findActiveRange(smoothedCols, Math.round(width * 0.16));
|
|
157
|
-
if (!rowBounds || !colBounds) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
const topMargin = rowBounds.start / height;
|
|
161
|
-
const bottomMargin = (height - rowBounds.end - 1) / height;
|
|
162
|
-
const leftMargin = colBounds.start / width;
|
|
163
|
-
const rightMargin = (width - colBounds.end - 1) / width;
|
|
164
|
-
const croppedWidth = colBounds.end - colBounds.start + 1;
|
|
165
|
-
const croppedHeight = rowBounds.end - rowBounds.start + 1;
|
|
166
|
-
const croppedAreaRatio = (croppedWidth * croppedHeight) / (width * height);
|
|
167
|
-
const significantMargins = topMargin > 0.11 || bottomMargin > 0.11 || leftMargin > 0.08 || rightMargin > 0.08;
|
|
168
|
-
if (!significantMargins && croppedAreaRatio > 0.9) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
left: colBounds.start,
|
|
173
|
-
top: rowBounds.start,
|
|
174
|
-
width: croppedWidth,
|
|
175
|
-
height: croppedHeight
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function findActiveRange(values, minSpan) {
|
|
179
|
-
const mean = values.reduce((sum, value) => sum + value, 0) / Math.max(values.length, 1);
|
|
180
|
-
const max = values.reduce((best, value) => Math.max(best, value), 0);
|
|
181
|
-
if (max <= 0) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
const threshold = mean + (max - mean) * 0.18;
|
|
185
|
-
let start = 0;
|
|
186
|
-
while (start < values.length && values[start] < threshold) {
|
|
187
|
-
start += 1;
|
|
188
|
-
}
|
|
189
|
-
let end = values.length - 1;
|
|
190
|
-
while (end >= 0 && values[end] < threshold) {
|
|
191
|
-
end -= 1;
|
|
192
|
-
}
|
|
193
|
-
if (start >= end) {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
const desiredSpan = Math.max(minSpan, end - start + 1);
|
|
197
|
-
const extra = desiredSpan - (end - start + 1);
|
|
198
|
-
const expandStart = Math.floor(extra / 2);
|
|
199
|
-
const expandEnd = extra - expandStart;
|
|
200
|
-
start = Math.max(0, start - expandStart - 4);
|
|
201
|
-
end = Math.min(values.length - 1, end + expandEnd + 4);
|
|
202
|
-
return { start, end };
|
|
203
|
-
}
|
|
204
|
-
function smoothSeries(values, radius) {
|
|
205
|
-
return values.map((_, index) => {
|
|
206
|
-
let total = 0;
|
|
207
|
-
let count = 0;
|
|
208
|
-
for (let offset = -radius; offset <= radius; offset += 1) {
|
|
209
|
-
const value = values[index + offset];
|
|
210
|
-
if (value === undefined) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
total += value;
|
|
214
|
-
count += 1;
|
|
215
|
-
}
|
|
216
|
-
return total / Math.max(count, 1);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
function clamp(value, min, max) {
|
|
220
|
-
return Math.min(Math.max(value, min), max);
|
|
221
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AbsoluteFill, Composition, Img, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
|
-
export const RemotionRoot = () => {
|
|
4
|
-
return (_jsx(Composition, { id: "template-0000", component: TemplateVideo, width: 1080, height: 1920, fps: 30, durationInFrames: 120, defaultProps: { slides: [] }, calculateMetadata: ({ props }) => ({
|
|
5
|
-
width: 1080,
|
|
6
|
-
height: 1920,
|
|
7
|
-
durationInFrames: Math.max(1, sumSlideFrames(props.slides, 30))
|
|
8
|
-
}) }));
|
|
9
|
-
};
|
|
10
|
-
const TemplateVideo = ({ slides }) => {
|
|
11
|
-
const { fps } = useVideoConfig();
|
|
12
|
-
let currentFrame = 0;
|
|
13
|
-
return (_jsx(AbsoluteFill, { style: { backgroundColor: "#120f0b" }, children: slides.map((slide, index) => {
|
|
14
|
-
const durationInFrames = msToFrames(slide.durationMs, fps);
|
|
15
|
-
const sequence = (_jsx(Sequence, { from: currentFrame, durationInFrames: durationInFrames, children: _jsx(SlideFrame, { slide: slide }) }, `${slide.imageUrl}-${index}`));
|
|
16
|
-
currentFrame += durationInFrames;
|
|
17
|
-
return sequence;
|
|
18
|
-
}) }));
|
|
19
|
-
};
|
|
20
|
-
const SlideFrame = ({ slide }) => {
|
|
21
|
-
const frame = useCurrentFrame();
|
|
22
|
-
const { fps } = useVideoConfig();
|
|
23
|
-
const entrance = spring({ fps, frame, config: { damping: 200, stiffness: 140 } });
|
|
24
|
-
const opacity = interpolate(entrance, [0, 1], [0.35, 1]);
|
|
25
|
-
const lift = interpolate(entrance, [0, 1], [22, 0]);
|
|
26
|
-
return (_jsxs(AbsoluteFill, { children: [_jsx(AbsoluteFill, { style: { opacity, transform: `translateY(${lift}px)` }, children: _jsx(Img, { src: slide.imageUrl, style: { width: "100%", height: "100%", objectFit: "fill" } }) }), _jsx(AbsoluteFill, { style: { background: "linear-gradient(180deg, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.06) 36%, rgba(0,0,0,0.34) 100%)" } })] }));
|
|
27
|
-
};
|
|
28
|
-
function msToFrames(durationMs, fps) {
|
|
29
|
-
return Math.max(1, Math.round((durationMs / 1000) * fps));
|
|
30
|
-
}
|
|
31
|
-
function sumSlideFrames(slides, fps) {
|
|
32
|
-
return slides.reduce((total, slide) => total + msToFrames(slide.durationMs, fps), 0);
|
|
33
|
-
}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
export const CAPTION_STANDARD_VERSION = "2026-05-31";
|
|
2
|
-
export const TEMPLATE_FONT_OPTIONS = [
|
|
3
|
-
{
|
|
4
|
-
id: "tiktok_sans_semibold",
|
|
5
|
-
label: "TikTok Sans SemiBold",
|
|
6
|
-
family: "TikTok Sans",
|
|
7
|
-
weight: 600,
|
|
8
|
-
assetFile: "TikTokSans-SemiBold.ttf",
|
|
9
|
-
fontsourceFile: "@fontsource/tiktok-sans/files/tiktok-sans-latin-600-normal.woff",
|
|
10
|
-
source: "fontsource_or_bundled_asset",
|
|
11
|
-
role: "tiktok_native_default",
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
id: "montserrat_semibold",
|
|
15
|
-
label: "Montserrat SemiBold",
|
|
16
|
-
family: "Montserrat",
|
|
17
|
-
weight: 600,
|
|
18
|
-
assetFile: "Montserrat[wght].ttf",
|
|
19
|
-
fontsourceFile: "@fontsource/montserrat/files/montserrat-latin-600-normal.woff",
|
|
20
|
-
source: "fontsource_or_bundled_asset",
|
|
21
|
-
role: "rounded_modern_caption",
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: "source_code_pro_bold",
|
|
25
|
-
label: "Source Code Pro Bold",
|
|
26
|
-
family: "Source Code Pro",
|
|
27
|
-
weight: 700,
|
|
28
|
-
assetFile: "SourceCodePro[wght].ttf",
|
|
29
|
-
fontsourceFile: "@fontsource/source-code-pro/files/source-code-pro-latin-700-normal.woff",
|
|
30
|
-
source: "fontsource_or_bundled_asset",
|
|
31
|
-
role: "monospace_caption",
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "yesteryear",
|
|
35
|
-
label: "Yesteryear",
|
|
36
|
-
family: "Yesteryear",
|
|
37
|
-
weight: 400,
|
|
38
|
-
assetFile: "Yesteryear-Regular.ttf",
|
|
39
|
-
source: "bundled_asset",
|
|
40
|
-
role: "script_caption",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "georgia_bold",
|
|
44
|
-
label: "Georgia-style Bold",
|
|
45
|
-
family: "Libre Baskerville",
|
|
46
|
-
weight: 700,
|
|
47
|
-
fontsourceFile: "@fontsource/libre-baskerville/files/libre-baskerville-latin-700-normal.woff",
|
|
48
|
-
source: "fontsource",
|
|
49
|
-
role: "serif_caption",
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: "abel",
|
|
53
|
-
label: "Abel",
|
|
54
|
-
family: "Abel",
|
|
55
|
-
weight: 400,
|
|
56
|
-
assetFile: "Abel-Regular.ttf",
|
|
57
|
-
source: "bundled_asset",
|
|
58
|
-
role: "condensed_caption",
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: "source_code_pro",
|
|
62
|
-
label: "Source Code Pro",
|
|
63
|
-
family: "Source Code Pro",
|
|
64
|
-
weight: 700,
|
|
65
|
-
assetFile: "SourceCodePro[wght].ttf",
|
|
66
|
-
fontsourceFile: "@fontsource/source-code-pro/files/source-code-pro-latin-700-normal.woff",
|
|
67
|
-
source: "fontsource_or_bundled_asset",
|
|
68
|
-
role: "legacy_alias",
|
|
69
|
-
aliasOf: "source_code_pro_bold",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: "montserrat",
|
|
73
|
-
label: "Montserrat",
|
|
74
|
-
family: "Montserrat",
|
|
75
|
-
weight: 600,
|
|
76
|
-
assetFile: "Montserrat[wght].ttf",
|
|
77
|
-
fontsourceFile: "@fontsource/montserrat/files/montserrat-latin-600-normal.woff",
|
|
78
|
-
source: "fontsource_or_bundled_asset",
|
|
79
|
-
role: "legacy_alias",
|
|
80
|
-
aliasOf: "montserrat_semibold",
|
|
81
|
-
},
|
|
82
|
-
];
|
|
83
|
-
export const TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS = [
|
|
84
|
-
{ id: "black", label: "Black", hex: "#000000" },
|
|
85
|
-
{ id: "red", label: "Red", hex: "#EA403F" },
|
|
86
|
-
{ id: "orange", label: "Orange", hex: "#FF933D" },
|
|
87
|
-
{ id: "yellow", label: "Yellow", hex: "#F2CD46" },
|
|
88
|
-
{ id: "lime_green", label: "Lime Green", hex: "#78C25E" },
|
|
89
|
-
{ id: "teal", label: "Teal", hex: "#77C8A6" },
|
|
90
|
-
{ id: "light_blue", label: "Light Blue", hex: "#3496F0" },
|
|
91
|
-
{ id: "dark_blue", label: "Dark Blue", hex: "#3496F0" },
|
|
92
|
-
{ id: "violet", label: "Violet", hex: "#5756D4" },
|
|
93
|
-
{ id: "pink", label: "Pink", hex: "#F7D7E9" },
|
|
94
|
-
{ id: "brown", label: "Brown", hex: "#A3895B" },
|
|
95
|
-
{ id: "dark_green", label: "Dark Green", hex: "#32523B" },
|
|
96
|
-
{ id: "blue_gray", label: "Blue Gray", hex: "#2F688C" },
|
|
97
|
-
{ id: "light_gray", label: "Light Gray", hex: "#92979E" },
|
|
98
|
-
{ id: "dark_gray", label: "Dark Gray", hex: "#333333" },
|
|
99
|
-
];
|
|
100
|
-
export const TEMPLATE_TEXT_BACKGROUND_RENDERING = {
|
|
101
|
-
mode: "inline_highlight_chip",
|
|
102
|
-
rule: "Text background colors must render as inline highlight chips that hug the text only.",
|
|
103
|
-
padding: "Use visible per-line padding around measured glyph bounds; do not render full-width bars, paragraph washes, or loose caption boxes.",
|
|
104
|
-
shape: "Use slightly curved corners, similar to native TikTok text highlight chips.",
|
|
105
|
-
implementation: "Wrap and measure the full caption block first. For every rendered line, draw one chip from that line's measured text bounds plus padding. The chip width must vary with the text line; it must not use the caption block width.",
|
|
106
|
-
strengths: {
|
|
107
|
-
none: {
|
|
108
|
-
paddingX: 0,
|
|
109
|
-
paddingY: 0,
|
|
110
|
-
radius: 0,
|
|
111
|
-
opacity: 0,
|
|
112
|
-
},
|
|
113
|
-
subtle: {
|
|
114
|
-
paddingX: 14,
|
|
115
|
-
paddingY: 8,
|
|
116
|
-
radius: 9,
|
|
117
|
-
opacity: 0.72,
|
|
118
|
-
},
|
|
119
|
-
standard: {
|
|
120
|
-
paddingX: 20,
|
|
121
|
-
paddingY: 12,
|
|
122
|
-
radius: 12,
|
|
123
|
-
opacity: 0.86,
|
|
124
|
-
},
|
|
125
|
-
strong: {
|
|
126
|
-
paddingX: 26,
|
|
127
|
-
paddingY: 14,
|
|
128
|
-
radius: 14,
|
|
129
|
-
opacity: 0.94,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
export const TEMPLATE_CAPTION_SHADOWS = {
|
|
134
|
-
native_black_shadow: {
|
|
135
|
-
mode: "radial_micro_shadow",
|
|
136
|
-
rule: "Native TikTok-style white captions need a soft black halo/outline around the glyphs, not a strong directional web drop shadow.",
|
|
137
|
-
cssTextShadow: "0 0 2px rgba(0,0,0,0.85), 0 0 4px rgba(0,0,0,0.72), 0 0 8px rgba(0,0,0,0.48)",
|
|
138
|
-
sharpCompositing: "Build shadow layers from the full multiline text block alpha mask, blur them, and composite them centered at the text block origin. Do not offset the shadow down/right except for source formats that explicitly require directional lighting.",
|
|
139
|
-
maxDirectionalOffsetPx: 1,
|
|
140
|
-
validation: "On a solid mid-gray background, the caption edge should darken evenly around all sides of each glyph. A shadow visible mainly below the text is a failure.",
|
|
141
|
-
},
|
|
142
|
-
none: {
|
|
143
|
-
mode: "none",
|
|
144
|
-
rule: "Use no shadow only when the source format uses dark text on a light inline chip or another high-contrast container.",
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
export const TEMPLATE_CAPTION_RENDERING_RULES = {
|
|
148
|
-
multilineText: {
|
|
149
|
-
rule: "Render each caption as one multiline text block after wrapping, not as independently trimmed per-line text images.",
|
|
150
|
-
reason: "Per-line trimmed rendering makes line height, trim bounds, and shadow placement vary per line, which can make shorter wrapped lines look heavier or larger at the same nominal font size.",
|
|
151
|
-
implementation: "Choose the font size, line height, wrap width, and line breaks once for the whole caption; render the full block on a stable canvas; apply the native radial shadow/outline to the whole text block; then place optional line chips from measured line bounds.",
|
|
152
|
-
validation: "Test a wrapped caption with one short line and one long line; both lines must keep the same glyph scale, weight, baseline rhythm, and shadow strength."
|
|
153
|
-
},
|
|
154
|
-
inlineHighlightChips: {
|
|
155
|
-
rule: "If a caption has a text background color, render inline highlight chips per wrapped line from measured glyph bounds.",
|
|
156
|
-
implementation: "Draw chips behind each measured line before the shadow and text layers. Chip geometry comes from measured line width plus standard padding; never draw a full-width caption panel or one rectangle behind the whole paragraph.",
|
|
157
|
-
validation: "Test a two-line caption where one line is much shorter. The shorter line's background must be visibly shorter too."
|
|
158
|
-
},
|
|
159
|
-
nativeShadow: {
|
|
160
|
-
rule: "Use a radial black halo/outline for native white TikTok captions. Avoid one-direction CSS or canvas shadows such as only 0 8px 24px.",
|
|
161
|
-
implementation: "Use multiple centered low-radius shadows or blurred alpha masks around the full multiline text block. Keep directional offsets at 0-1 px.",
|
|
162
|
-
validation: "The shadow must radiate around left, right, top, and bottom glyph edges; it must not read as a web card drop shadow."
|
|
163
|
-
},
|
|
164
|
-
tiktokSansSizing: {
|
|
165
|
-
rule: "Tune TikTok Sans wrapping, font size, and line spacing separately from Montserrat or generic sans defaults.",
|
|
166
|
-
reason: "TikTok Sans metrics differ enough from Montserrat that reused sizing assumptions can produce wrong wrap points, cramped leading, or off-weight captions."
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
export const TEMPLATE_CAPTION_STYLE_PRESETS = [
|
|
170
|
-
{
|
|
171
|
-
id: "tiktok_native_white",
|
|
172
|
-
label: "TikTok Native White",
|
|
173
|
-
fontId: "tiktok_sans_semibold",
|
|
174
|
-
fill: "#ffffff",
|
|
175
|
-
backgroundColorId: "none",
|
|
176
|
-
shadow: "native_black_shadow",
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
id: "tiktok_native_chip",
|
|
180
|
-
label: "TikTok Native Color Chip",
|
|
181
|
-
fontId: "tiktok_sans_semibold",
|
|
182
|
-
fill: "#ffffff",
|
|
183
|
-
defaultBackgroundColorId: "red",
|
|
184
|
-
backgroundRendering: "inline_highlight_chip",
|
|
185
|
-
chipStrength: "standard",
|
|
186
|
-
shadow: "native_black_shadow",
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
id: "tiktok_editor_gray",
|
|
190
|
-
label: "TikTok Editor Gray",
|
|
191
|
-
fontId: "tiktok_sans_semibold",
|
|
192
|
-
fill: "#111111",
|
|
193
|
-
defaultBackgroundColorId: "light_gray",
|
|
194
|
-
backgroundRendering: "inline_highlight_chip",
|
|
195
|
-
chipStrength: "subtle",
|
|
196
|
-
shadow: "none",
|
|
197
|
-
},
|
|
198
|
-
];
|
|
199
|
-
export const TEMPLATE_FONT_IDS = TEMPLATE_FONT_OPTIONS.map((option) => option.id);
|
|
200
|
-
export const TEMPLATE_TEXT_BACKGROUND_COLOR_IDS = TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS.map((option) => option.id);
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Generated by `vidfarm analyze-viral-dna` and `vidfarm analyze-visual-dna`.
|
|
2
|
-
// Keep source notes in `research/source_notes.md` and reference media in `research/preview/`.
|
|
3
|
-
export const templateLinkToOriginal = "";
|
|
4
|
-
export const templateSourceNotesPath = "research/source_notes.md";
|
|
5
|
-
export const templatePreviewMediaRelativePaths = [];
|
|
6
|
-
export const templateViralDna = "Run `vidfarm analyze-viral-dna --template-dir .` after adding source notes and preview media.";
|
|
7
|
-
export const templateVisualDna = "Run `vidfarm analyze-visual-dna --template-dir .` after adding source notes and preview media.";
|
|
8
|
-
export const templateViralDnaAnalysis = null;
|
|
9
|
-
export const templateVisualDnaAnalysis = null;
|