@mevdragon/vidfarm-devcli 0.1.0 → 0.2.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/.env.example +11 -4
- package/PLATFORM_SPEC.md +142 -2
- package/README.md +165 -16
- package/SKILL.developer.md +577 -0
- package/dist/infra/cdk/bin/vidfarm-prod.js +59 -0
- package/dist/infra/cdk/lib/vidfarm-prod-stack.js +212 -0
- package/dist/src/account-pages.js +578 -0
- package/dist/src/app.js +887 -66
- package/dist/src/cli.js +284 -5
- package/dist/src/config.js +24 -4
- package/dist/src/db.js +427 -18
- package/dist/src/dev-app.js +59 -12
- package/dist/src/homepage.js +441 -0
- package/dist/src/index.js +12 -7
- package/dist/src/lib/crypto.js +14 -0
- package/dist/src/lib/template-dna.js +542 -0
- package/dist/src/lib/template-style-options.js +49 -0
- package/dist/src/registry.js +54 -7
- package/dist/src/runtime.js +3 -1
- package/dist/src/services/auth.js +69 -5
- package/dist/src/services/jobs.js +23 -4
- package/dist/src/services/providers.js +74 -12
- package/dist/src/services/storage.js +52 -18
- package/dist/src/services/template-certification.js +160 -0
- package/dist/src/services/template-loader.js +37 -0
- package/dist/src/services/template-sources.js +135 -0
- package/dist/src/worker.js +19 -7
- package/dist/templates/template_0000/src/lib/images.js +242 -0
- package/dist/templates/template_0000/src/remotion/Root.js +33 -0
- package/dist/templates/template_0000/src/sdk.js +3 -0
- package/dist/templates/template_0000/src/style-options.js +51 -0
- package/dist/templates/template_0000/src/template-dna.js +9 -0
- package/dist/templates/template_0000/src/template.js +1217 -0
- package/package.json +9 -1
- package/templates/template_0000/README.md +121 -0
- package/templates/template_0000/SKILL.md +193 -0
- package/templates/template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/template_0000/composition.json +11 -0
- package/templates/template_0000/package-lock.json +5137 -0
- package/templates/template_0000/package.json +30 -0
- package/templates/template_0000/research/preview/.gitkeep +1 -0
- package/templates/template_0000/research/source_notes.md +7 -0
- package/templates/template_0000/scripts/create-site.mjs +27 -0
- package/templates/template_0000/scripts/render-cloud.mjs +72 -0
- package/templates/template_0000/src/lib/images.ts +284 -0
- package/templates/template_0000/src/remotion/Root.js +33 -0
- package/templates/template_0000/src/remotion/Root.tsx +75 -0
- package/templates/template_0000/src/remotion/index.tsx +4 -0
- package/templates/template_0000/src/sdk.ts +122 -0
- package/templates/template_0000/src/style-options.js +51 -0
- package/templates/template_0000/src/style-options.ts +60 -0
- package/templates/template_0000/src/template-dna.ts +15 -0
- package/templates/template_0000/src/template.ts +1747 -0
- package/templates/template_0000/template.config.json +26 -0
- package/templates/template_0000/tsconfig.json +19 -0
- package/dist/templates/template_0000/demo-template.js +0 -196
- package/dist/templates/template_0000/remotion/Root.js +0 -66
- /package/dist/templates/template_0000/{remotion → src/remotion}/index.js +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"template_id": "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db",
|
|
3
|
+
"slug_id": "template_0000",
|
|
4
|
+
"project_name": "vidfarm_template_0000",
|
|
5
|
+
"github_repo": "mevdragon/vidfarm_template_0000",
|
|
6
|
+
"source_branch": "production",
|
|
7
|
+
"skill_path": "SKILL.md",
|
|
8
|
+
"template_module_path": "src/template.js",
|
|
9
|
+
"release_controls": {
|
|
10
|
+
"shared_remotion_publish": "admin_only",
|
|
11
|
+
"platform_activation": "admin_only",
|
|
12
|
+
"production_docker_promotion": "admin_only"
|
|
13
|
+
},
|
|
14
|
+
"remotion": {
|
|
15
|
+
"region": "us-east-1",
|
|
16
|
+
"function_name": "remotion-render-4-0-355-mem2048mb-disk2048mb-180sec",
|
|
17
|
+
"bucket_name": "remotionlambda-useast1-ujg7c0h43q",
|
|
18
|
+
"site_name": "vidfarm-template-0000",
|
|
19
|
+
"serve_url": "https://remotionlambda-useast1-ujg7c0h43q.s3.us-east-1.amazonaws.com/sites/vidfarm-template-0000/index.html",
|
|
20
|
+
"composition_id": "template-0000",
|
|
21
|
+
"entry_point": "src/remotion/index.tsx",
|
|
22
|
+
"props_file": "composition.json",
|
|
23
|
+
"timeout_ms": 120000,
|
|
24
|
+
"frames_per_lambda": 100
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"types": [
|
|
12
|
+
"node"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"include": [
|
|
16
|
+
"src/**/*.ts",
|
|
17
|
+
"src/**/*.tsx"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { normalizeToPortraitFrame } from "../../src/lib/images.js";
|
|
5
|
-
import { defineTemplate } from "../../src/template-sdk.js";
|
|
6
|
-
const slideInputSchema = z.tuple([z.string().min(3), z.string().min(1)]);
|
|
7
|
-
const generateInputSchema = z.object({
|
|
8
|
-
slides: z.array(slideInputSchema).min(1).max(20),
|
|
9
|
-
secondsPerSlide: z.number().min(2).max(10).default(4)
|
|
10
|
-
});
|
|
11
|
-
const remotionEntryPoint = resolveRemotionEntryPoint();
|
|
12
|
-
export const demoTemplate = defineTemplate({
|
|
13
|
-
id: "demo-template",
|
|
14
|
-
version: "1.0.0",
|
|
15
|
-
description: "Opinionated slideshow generator that makes tall images, picks text placement, and renders a TikTok-ready Remotion video.",
|
|
16
|
-
configSchema: z.object({
|
|
17
|
-
defaultProvider: z.enum(["openai", "gemini"]).default("gemini"),
|
|
18
|
-
textModel: z.string().default("gemini-3.1-flash-lite"),
|
|
19
|
-
imageModel: z.string().default("gemini-2.5-flash-image"),
|
|
20
|
-
renderCompositionId: z.string().default("demo-template")
|
|
21
|
-
}),
|
|
22
|
-
operations: {
|
|
23
|
-
generate: {
|
|
24
|
-
description: "Generate a slideshow from [[imagePrompt, exactOverlayText], ...] and return public artifact URLs.",
|
|
25
|
-
inputSchema: generateInputSchema,
|
|
26
|
-
workflow: "generateWorkflow",
|
|
27
|
-
webhookSupport: true
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
jobs: {
|
|
31
|
-
async generateWorkflow(ctx, input) {
|
|
32
|
-
const payload = generateInputSchema.parse(input);
|
|
33
|
-
ctx.logger.progress(0.04, "Starting demo slideshow pipeline");
|
|
34
|
-
const provider = String(ctx.templateConfig.defaultProvider ?? "gemini");
|
|
35
|
-
const textModel = String(ctx.templateConfig.textModel ?? "gemini-3.1-flash-lite");
|
|
36
|
-
const imageModel = String(ctx.templateConfig.imageModel ?? "gemini-2.5-flash-image");
|
|
37
|
-
const compositionId = String(ctx.templateConfig.renderCompositionId ?? "demo-template");
|
|
38
|
-
const slides = [];
|
|
39
|
-
for (const [index, [imagePrompt, overlayText]] of payload.slides.entries()) {
|
|
40
|
-
const prompt = buildImagePrompt(imagePrompt, overlayText);
|
|
41
|
-
ctx.logger.progress(0.08 + (index / payload.slides.length) * 0.42, `Generating slide ${index + 1} image`);
|
|
42
|
-
const image = await ctx.providers.generateImage({
|
|
43
|
-
provider,
|
|
44
|
-
model: imageModel,
|
|
45
|
-
prompt,
|
|
46
|
-
size: sourceImageSizeForProvider(provider)
|
|
47
|
-
});
|
|
48
|
-
await ctx.billing.record({
|
|
49
|
-
type: "ai_generation",
|
|
50
|
-
costUsd: 0.04,
|
|
51
|
-
metadata: { stage: "image_generation", slideIndex: index, model: imageModel }
|
|
52
|
-
});
|
|
53
|
-
ctx.logger.progress(0.13 + (index / payload.slides.length) * 0.42, `Normalizing slide ${index + 1} to strict 9:16 portrait`);
|
|
54
|
-
const normalizedImage = await normalizeToPortraitFrame(image.bytes, { width: 1080, height: 1920 });
|
|
55
|
-
const imageArtifact = await ctx.storage.putBuffer(`slides/slide-${pad2(index + 1)}.png`, normalizedImage.bytes, {
|
|
56
|
-
contentType: normalizedImage.contentType,
|
|
57
|
-
kind: "image",
|
|
58
|
-
metadata: {
|
|
59
|
-
slideIndex: index,
|
|
60
|
-
prompt,
|
|
61
|
-
revisedPrompt: image.revisedPrompt,
|
|
62
|
-
width: normalizedImage.width,
|
|
63
|
-
height: normalizedImage.height,
|
|
64
|
-
aspectRatio: "9:16"
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
const layout = await safeAnalyzeLayout(ctx, {
|
|
68
|
-
imageUrl: imageArtifact.url,
|
|
69
|
-
overlayText,
|
|
70
|
-
textModel
|
|
71
|
-
});
|
|
72
|
-
await ctx.billing.record({
|
|
73
|
-
type: "ai_generation",
|
|
74
|
-
costUsd: 0.005,
|
|
75
|
-
metadata: { stage: "layout_analysis", slideIndex: index, model: textModel }
|
|
76
|
-
});
|
|
77
|
-
slides.push({
|
|
78
|
-
index,
|
|
79
|
-
imagePrompt,
|
|
80
|
-
overlayText,
|
|
81
|
-
imageUrl: imageArtifact.url,
|
|
82
|
-
prompt,
|
|
83
|
-
revisedPrompt: image.revisedPrompt,
|
|
84
|
-
layout
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
ctx.logger.progress(0.58, "Saving slideshow manifest");
|
|
88
|
-
const manifest = {
|
|
89
|
-
templateId: "demo-template",
|
|
90
|
-
size: { width: 1080, height: 1920, aspectRatio: "9:16" },
|
|
91
|
-
secondsPerSlide: payload.secondsPerSlide,
|
|
92
|
-
font: "Times New Roman",
|
|
93
|
-
slides
|
|
94
|
-
};
|
|
95
|
-
const manifestArtifact = await ctx.storage.putJson("manifests/demo-template.json", manifest);
|
|
96
|
-
ctx.logger.progress(0.72, "Submitting Remotion render");
|
|
97
|
-
const render = await ctx.remotion.render({
|
|
98
|
-
compositionId,
|
|
99
|
-
entryPoint: remotionEntryPoint,
|
|
100
|
-
outputKey: "renders/final.mp4",
|
|
101
|
-
inputProps: {
|
|
102
|
-
slides: slides.map((slide) => ({
|
|
103
|
-
imageUrl: slide.imageUrl,
|
|
104
|
-
text: slide.overlayText,
|
|
105
|
-
layout: slide.layout
|
|
106
|
-
})),
|
|
107
|
-
fps: 30,
|
|
108
|
-
secondsPerSlide: payload.secondsPerSlide
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
await ctx.billing.record({
|
|
112
|
-
type: "render",
|
|
113
|
-
costUsd: 0.35,
|
|
114
|
-
metadata: render.metadata
|
|
115
|
-
});
|
|
116
|
-
const files = slides.map((slide) => slide.imageUrl).filter((value) => Boolean(value));
|
|
117
|
-
if (manifestArtifact.url) {
|
|
118
|
-
files.push(manifestArtifact.url);
|
|
119
|
-
}
|
|
120
|
-
if (render.outputUrl) {
|
|
121
|
-
files.push(render.outputUrl);
|
|
122
|
-
}
|
|
123
|
-
ctx.logger.progress(1, "Demo slideshow complete", {
|
|
124
|
-
fileCount: files.length,
|
|
125
|
-
renderId: render.renderId
|
|
126
|
-
});
|
|
127
|
-
return {
|
|
128
|
-
progress: 1,
|
|
129
|
-
output: {
|
|
130
|
-
files,
|
|
131
|
-
render,
|
|
132
|
-
manifest: manifestArtifact,
|
|
133
|
-
slides
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
function buildImagePrompt(imagePrompt, overlayText) {
|
|
140
|
-
return [
|
|
141
|
-
"Create an exact 9:16 portrait slideshow image for a TikTok-style vertical video.",
|
|
142
|
-
"The composition must be designed for strict full-frame mobile portrait output at 1080x1920.",
|
|
143
|
-
"Do not produce square, landscape, or loose portrait framing.",
|
|
144
|
-
"Do not letterbox, pillarbox, add borders, or leave empty margins.",
|
|
145
|
-
"Keep the subject composition strong but leave one large clean zone with low visual detail for text overlay.",
|
|
146
|
-
"Do not place important faces, hands, or product details in the top or bottom caption-safe regions.",
|
|
147
|
-
"Do not render any words, letters, captions, subtitles, titles, logos, signage, labels, watermarks, or typography inside the image.",
|
|
148
|
-
"The final image must contain zero visible text.",
|
|
149
|
-
"Use cinematic lighting, crisp detail, and framing that already fits an exact 9:16 portrait frame.",
|
|
150
|
-
`User visual prompt: ${imagePrompt}`,
|
|
151
|
-
`Reserve room for a short editorial overlay approximately ${overlayText.length} characters long, but do not render the overlay text itself.`
|
|
152
|
-
].join("\n");
|
|
153
|
-
}
|
|
154
|
-
async function safeAnalyzeLayout(ctx, input) {
|
|
155
|
-
if (!input.imageUrl) {
|
|
156
|
-
return defaultLayout();
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
return await ctx.providers.analyzeImageLayout({
|
|
160
|
-
provider: String(ctx.templateConfig.defaultProvider ?? "gemini"),
|
|
161
|
-
model: input.textModel,
|
|
162
|
-
imageUrl: input.imageUrl,
|
|
163
|
-
overlayText: input.overlayText
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
ctx.logger.warn("Layout analysis failed, using default placement", {
|
|
168
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
169
|
-
});
|
|
170
|
-
return defaultLayout();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
function defaultLayout() {
|
|
174
|
-
return {
|
|
175
|
-
zone: "bottom",
|
|
176
|
-
align: "center",
|
|
177
|
-
maxWidthPercent: 82,
|
|
178
|
-
justification: "Fallback placement keeps the overlay in a readable lower safe zone."
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function sourceImageSizeForProvider(provider) {
|
|
182
|
-
if (provider === "openai") {
|
|
183
|
-
return "1024x1792";
|
|
184
|
-
}
|
|
185
|
-
return "1080x1920";
|
|
186
|
-
}
|
|
187
|
-
function pad2(value) {
|
|
188
|
-
return String(value).padStart(2, "0");
|
|
189
|
-
}
|
|
190
|
-
function resolveRemotionEntryPoint() {
|
|
191
|
-
const builtPath = fileURLToPath(new URL("./remotion/index.js", import.meta.url));
|
|
192
|
-
if (existsSync(builtPath)) {
|
|
193
|
-
return builtPath;
|
|
194
|
-
}
|
|
195
|
-
return fileURLToPath(new URL("./remotion/index.tsx", import.meta.url));
|
|
196
|
-
}
|
|
@@ -1,66 +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: "demo-template", component: DemoTemplateVideo, width: 1080, height: 1920, fps: 30, durationInFrames: 120, defaultProps: { slides: [], secondsPerSlide: 4 }, calculateMetadata: ({ props }) => ({
|
|
5
|
-
width: 1080,
|
|
6
|
-
height: 1920,
|
|
7
|
-
durationInFrames: Math.max(1, props.slides.length) * Math.max(1, Math.round((props.secondsPerSlide ?? 4) * 30))
|
|
8
|
-
}) }));
|
|
9
|
-
};
|
|
10
|
-
const DemoTemplateVideo = ({ slides, secondsPerSlide = 4 }) => {
|
|
11
|
-
const { fps } = useVideoConfig();
|
|
12
|
-
const framesPerSlide = Math.max(1, Math.round(secondsPerSlide * fps));
|
|
13
|
-
return (_jsx(AbsoluteFill, { style: { backgroundColor: "#120f0b" }, children: slides.map((slide, index) => (_jsx(Sequence, { from: index * framesPerSlide, durationInFrames: framesPerSlide, children: _jsx(SlideFrame, { slide: slide }) }, `${slide.imageUrl}-${index}`))) }));
|
|
14
|
-
};
|
|
15
|
-
const SlideFrame = ({ slide }) => {
|
|
16
|
-
const frame = useCurrentFrame();
|
|
17
|
-
const { fps } = useVideoConfig();
|
|
18
|
-
const entrance = spring({ fps, frame, config: { damping: 200, stiffness: 140 } });
|
|
19
|
-
const scale = interpolate(entrance, [0, 1], [1.06, 1]);
|
|
20
|
-
const opacity = interpolate(entrance, [0, 1], [0.2, 1]);
|
|
21
|
-
return (_jsxs(AbsoluteFill, { children: [_jsx(AbsoluteFill, { style: { opacity, transform: `scale(${scale})` }, children: _jsx(Img, { src: slide.imageUrl, style: { width: "100%", height: "100%", objectFit: "cover" } }) }), _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%)" } }), _jsx(TextOverlay, { slide: slide })] }));
|
|
22
|
-
};
|
|
23
|
-
const TextOverlay = ({ slide }) => {
|
|
24
|
-
const zoneStyle = slide.layout.zone === "top"
|
|
25
|
-
? { top: "8%", bottom: "auto" }
|
|
26
|
-
: slide.layout.zone === "center"
|
|
27
|
-
? { top: "50%", bottom: "auto", transform: "translateY(-50%)" }
|
|
28
|
-
: { top: "auto", bottom: "9%" };
|
|
29
|
-
return (_jsx("div", { style: {
|
|
30
|
-
position: "absolute",
|
|
31
|
-
left: "9%",
|
|
32
|
-
right: "9%",
|
|
33
|
-
display: "flex",
|
|
34
|
-
justifyContent: alignmentToJustify(slide.layout.align),
|
|
35
|
-
pointerEvents: "none",
|
|
36
|
-
...zoneStyle
|
|
37
|
-
}, children: _jsx("div", { style: {
|
|
38
|
-
maxWidth: `${slide.layout.maxWidthPercent}%`,
|
|
39
|
-
width: `${slide.layout.maxWidthPercent}%`,
|
|
40
|
-
boxSizing: "border-box",
|
|
41
|
-
padding: "28px 32px",
|
|
42
|
-
color: "#fffaf2",
|
|
43
|
-
fontFamily: '"Times New Roman", Times, serif',
|
|
44
|
-
fontSize: 72,
|
|
45
|
-
lineHeight: 1.08,
|
|
46
|
-
letterSpacing: "-0.03em",
|
|
47
|
-
textAlign: slide.layout.align,
|
|
48
|
-
overflowWrap: "anywhere",
|
|
49
|
-
wordBreak: "break-word",
|
|
50
|
-
whiteSpace: "pre-wrap",
|
|
51
|
-
background: "rgba(18, 14, 10, 0.28)",
|
|
52
|
-
boxShadow: "0 18px 44px rgba(0, 0, 0, 0.22)",
|
|
53
|
-
textShadow: "0 2px 6px rgba(0, 0, 0, 0.48)",
|
|
54
|
-
backdropFilter: "blur(8px)"
|
|
55
|
-
}, children: slide.text }) }));
|
|
56
|
-
};
|
|
57
|
-
function alignmentToJustify(align) {
|
|
58
|
-
switch (align) {
|
|
59
|
-
case "left":
|
|
60
|
-
return "flex-start";
|
|
61
|
-
case "right":
|
|
62
|
-
return "flex-end";
|
|
63
|
-
default:
|
|
64
|
-
return "center";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
File without changes
|