@mevdragon/vidfarm-devcli 0.5.3 → 0.6.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 +3 -3
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +1184 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
- package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +15 -0
- package/dist/src/account-pages-legacy.js +9396 -0
- package/dist/src/account-pages.js +61 -0
- package/dist/src/app.js +14378 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/composition-runtime.js +613 -0
- package/dist/src/config.js +166 -0
- package/dist/src/context.js +447 -0
- package/dist/src/dev-app-legacy.js +739 -0
- package/dist/src/dev-app.js +6 -0
- package/dist/src/domain.js +2 -0
- package/dist/src/editor-chat-history.js +82 -0
- package/dist/src/editor-chat.js +449 -0
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/frontend/debug.js +71 -0
- package/dist/src/frontend/flockposter-cache-store.js +124 -0
- package/dist/src/frontend/homepage-client.js +182 -0
- package/dist/src/frontend/homepage-shared.js +4 -0
- package/dist/src/frontend/homepage-store.js +28 -0
- package/dist/src/frontend/homepage-view.js +547 -0
- package/dist/src/frontend/page-runtime-client.js +132 -0
- package/dist/src/frontend/page-runtime-store.js +9 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/template-editor-chat.js +3960 -0
- package/dist/src/help-page.js +346 -0
- package/dist/src/homepage.js +1235 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/index.js +16 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/page-runtime.js +2 -0
- package/dist/src/page-shell.js +1381 -0
- package/dist/src/primitive-context.js +357 -0
- package/dist/src/primitive-registry.js +2436 -0
- package/dist/src/primitive-sdk.js +4 -0
- package/dist/src/primitives/hyperframes-media.js +108 -0
- package/dist/src/react-page-shell.js +35 -0
- package/dist/src/ready-post-schedule-component.js +1540 -0
- package/dist/src/registry.js +296 -0
- package/dist/src/runtime.js +35 -0
- package/dist/src/services/api-call-history.js +249 -0
- package/dist/src/services/auth.js +152 -0
- package/dist/src/services/billing-pricing.js +39 -0
- package/dist/src/services/billing.js +228 -0
- package/dist/src/services/cast.js +127 -0
- package/dist/src/services/chat-threads.js +92 -0
- package/dist/src/services/composition-sanitize.js +124 -0
- package/dist/src/services/composition-watch.js +79 -0
- package/dist/src/services/fork-access.js +93 -0
- package/dist/src/services/fork-manifest.js +42 -0
- package/dist/src/services/ghostcut.js +179 -0
- package/dist/src/services/hyperframes.js +2307 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-logs.js +197 -0
- package/dist/src/services/jobs.js +136 -0
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +766 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/providers.js +2926 -0
- package/dist/src/services/rate-limits.js +262 -0
- package/dist/src/services/serverless-auth.js +382 -0
- package/dist/src/services/serverless-jobs.js +1082 -0
- package/dist/src/services/serverless-provider-keys.js +409 -0
- package/dist/src/services/serverless-records.js +1385 -0
- package/dist/src/services/serverless-template-configs.js +75 -0
- package/dist/src/services/storage.js +383 -0
- package/dist/src/services/template-certification.js +413 -0
- package/dist/src/services/template-loader.js +99 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-sources.js +1017 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/webhooks.js +62 -0
- package/dist/src/template-editor-pages.js +2576 -0
- package/dist/src/template-editor-shell.js +2840 -0
- package/dist/src/template-sdk.js +4 -0
- package/dist/src/worker.js +17 -0
- package/package.json +6 -4
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
- package/src/assets/SELLING_WITH_HOOKS.md +377 -0
- package/src/assets/SELLING_WITH_VSLS.md +606 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
dotenv.config();
|
|
6
|
+
function normalizeEnvValue(value) {
|
|
7
|
+
if (typeof value !== "string") {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
const unquoted = ((trimmed.startsWith("\"") && trimmed.endsWith("\""))
|
|
12
|
+
|| (trimmed.startsWith("'") && trimmed.endsWith("'")))
|
|
13
|
+
? trimmed.slice(1, -1).trim()
|
|
14
|
+
: trimmed;
|
|
15
|
+
return unquoted === "" ? undefined : unquoted;
|
|
16
|
+
}
|
|
17
|
+
const optionalSampleRate = z.preprocess((value) => value === "" || value == null ? undefined : value, z.coerce.number().min(0).max(1).optional());
|
|
18
|
+
const schema = z.object({
|
|
19
|
+
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
|
20
|
+
VIDFARM_DEPLOYMENT_TARGET: z.string().optional(),
|
|
21
|
+
VIDFARM_RUNTIME_BACKEND: z.literal("serverless").default("serverless"),
|
|
22
|
+
VIDFARM_DEBUG_LOGS: z.string().default("false"),
|
|
23
|
+
VIDFARM_LOAD_LOCAL_TEMPLATES: z.enum(["auto", "true", "false"]).default("auto"),
|
|
24
|
+
PORT: z.coerce.number().default(3000),
|
|
25
|
+
VIDFARM_DATA_DIR: z.string().default("./data"),
|
|
26
|
+
ENCRYPTION_SECRET: z.string().default("development-encryption-secret-change-me"),
|
|
27
|
+
API_KEY_SALT: z.string().default("development-api-key-salt"),
|
|
28
|
+
WEBHOOK_SECRET: z.string().default("development-webhook-secret"),
|
|
29
|
+
DEFAULT_JOB_DELAY_SECONDS: z.coerce.number().default(20),
|
|
30
|
+
MAX_JOB_WALL_CLOCK_SECONDS: z.coerce.number().default(6 * 60 * 60),
|
|
31
|
+
STALE_RUNNING_JOB_SECONDS: z.coerce.number().default(60 * 60),
|
|
32
|
+
PROVIDER_LEASE_WAIT_TIMEOUT_SECONDS: z.coerce.number().default(5 * 60),
|
|
33
|
+
PROVIDER_LEASE_INLINE_WAIT_SECONDS: z.coerce.number().default(5),
|
|
34
|
+
PROVIDER_LEASE_STEP_WAIT_SECONDS: z.coerce.number().int().min(1).default(60),
|
|
35
|
+
PROVIDER_LEASE_MAX_STEP_WAIT_SECONDS: z.coerce.number().int().min(1).default(12 * 60 * 60),
|
|
36
|
+
MAX_ACTIVE_JOBS_PER_CUSTOMER: z.coerce.number().int().min(1).default(30),
|
|
37
|
+
STORAGE_DRIVER: z.enum(["local", "s3"]).default("local"),
|
|
38
|
+
// Records/auth backend. Defaults to `dynamo` so deployed environments are
|
|
39
|
+
// unaffected; `vidfarm serve` sets this to `local` to run fully on disk.
|
|
40
|
+
RECORDS_DRIVER: z.enum(["local", "dynamo"]).default("dynamo"),
|
|
41
|
+
AWS_REGION: z.string().default("us-east-1"),
|
|
42
|
+
AWS_S3_BUCKET: z.string().default(""),
|
|
43
|
+
AWS_S3_ENDPOINT: z.string().optional(),
|
|
44
|
+
AWS_ACCESS_KEY_ID: z.string().optional(),
|
|
45
|
+
AWS_SECRET_ACCESS_KEY: z.string().optional(),
|
|
46
|
+
PUBLIC_BASE_URL: z.string().optional(),
|
|
47
|
+
PUBLIC_EDITOR_CHAT_URL: z.string().url().optional(),
|
|
48
|
+
VIDFARM_USER_ID: z.string().optional(),
|
|
49
|
+
VIDFARM_API_KEY: z.string().optional(),
|
|
50
|
+
VIDFARM_STAGING_BOOTSTRAP_USER_ID: z.string().optional(),
|
|
51
|
+
VIDFARM_STAGING_BOOTSTRAP_EMAIL: z.string().optional(),
|
|
52
|
+
VIDFARM_STAGING_BOOTSTRAP_API_KEY: z.string().optional(),
|
|
53
|
+
EDITOR_CHAT_URL: z.string().url().optional(),
|
|
54
|
+
RESEND_API_KEY: z.string().optional(),
|
|
55
|
+
RESEND_FROM_EMAIL: z.string().default("vidfarm@fwd.zoomgtm.com"),
|
|
56
|
+
FLOCKPOSTER_API_URL: z.string().url().optional(),
|
|
57
|
+
VIDFARM_ZIP_LAMBDA_URL: z.string().url().optional(),
|
|
58
|
+
VIDFARM_ZIP_LAMBDA_SECRET: z.string().optional(),
|
|
59
|
+
VIDFARM_PRIMITIVE_MEDIA_LAMBDA_URL: z.string().url().optional(),
|
|
60
|
+
VIDFARM_PRIMITIVE_MEDIA_LAMBDA_SECRET: z.string().optional(),
|
|
61
|
+
VIDFARM_BILLING_URL: z.string().url().optional(),
|
|
62
|
+
VIDFARM_BILLING_SECRET: z.string().optional(),
|
|
63
|
+
VIDFARM_MIN_RENDER_WALLET_BALANCE_USD: z.coerce.number().default(1),
|
|
64
|
+
OPENAI_API_KEY: z.string().optional(),
|
|
65
|
+
OPENROUTER_API_KEY: z.string().optional(),
|
|
66
|
+
GEMINI_API_KEY: z.string().optional(),
|
|
67
|
+
PERPLEXITY_API_KEY: z.string().optional(),
|
|
68
|
+
GHOSTCUT_KEY: z.string().optional(),
|
|
69
|
+
GHOSTCUT_SECRET: z.string().optional(),
|
|
70
|
+
GHOSTCUT_USD_PER_30S: z.coerce.number().min(0).default(0.10),
|
|
71
|
+
RAPIDAPI_KEY: z.string().optional(),
|
|
72
|
+
RAPIDAPI_VIDEO_DOWNLOAD_URL: z.string().url().default("https://snap-video3.p.rapidapi.com/download"),
|
|
73
|
+
RAPIDAPI_VIDEO_DOWNLOAD_HOST: z.string().default("snap-video3.p.rapidapi.com"),
|
|
74
|
+
RAPIDAPI_VIDEO_DOWNLOAD_BASE_COST_USD: z.coerce.number().min(0).default(0.005),
|
|
75
|
+
RAPIDAPI_VIDEO_DOWNLOAD_LAMBDA_COST_USD: z.coerce.number().min(0).default(0.0002),
|
|
76
|
+
RAPIDAPI_REMOVE_BACKGROUND_URL: z.string().url().default("https://remove-background18.p.rapidapi.com/public/remove-background/url"),
|
|
77
|
+
RAPIDAPI_REMOVE_BACKGROUND_HOST: z.string().default("remove-background18.p.rapidapi.com"),
|
|
78
|
+
RAPIDAPI_REMOVE_BACKGROUND_COST_USD: z.coerce.number().min(0).default(0.005),
|
|
79
|
+
SUPERAGENCY_KEY: z.string().optional(),
|
|
80
|
+
ARTIFACT_SIGNING_SECRET: z.string().optional(),
|
|
81
|
+
HYPERFRAMES_LAMBDA_STACK_NAME: z.string().default("hyperframes-vidfarm-prod"),
|
|
82
|
+
HYPERFRAMES_RENDER_REGION: z.string().optional(),
|
|
83
|
+
HYPERFRAMES_RENDER_BUCKET_NAME: z.string().optional(),
|
|
84
|
+
HYPERFRAMES_LAMBDA_BUCKET_NAME: z.string().optional(),
|
|
85
|
+
HYPERFRAMES_RENDER_STATE_MACHINE_ARN: z.string().optional(),
|
|
86
|
+
HYPERFRAMES_STATE_MACHINE_ARN: z.string().optional(),
|
|
87
|
+
HYPERFRAMES_RENDER_MEMORY_MB: z.coerce.number().int().min(512).default(3072),
|
|
88
|
+
// Force the in-process (single-machine, headless-Chromium) HyperFrames
|
|
89
|
+
// renderer even when a cloud render stack is configured. When unset, the
|
|
90
|
+
// renderer auto-selects local whenever no cloud render stack is wired (the
|
|
91
|
+
// `vidfarm serve` case). Accepts "1"/"true"/"yes".
|
|
92
|
+
VIDFARM_LOCAL_RENDER: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.transform((value) => /^(1|true|yes)$/i.test(String(value ?? ""))),
|
|
96
|
+
MOCK_PROVIDER_RESPONSES: z.string().optional(),
|
|
97
|
+
VIDFARM_ADMIN_EMAILS: z.string().default(""),
|
|
98
|
+
VIDFARM_GITHUB_TOKEN: z.string().optional(),
|
|
99
|
+
GITHUB_TOKEN: z.string().optional(),
|
|
100
|
+
TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources"),
|
|
101
|
+
VIDFARM_RATE_LIMITS_TABLE_NAME: z.string().optional(),
|
|
102
|
+
VIDFARM_SERVERLESS_MAIN_TABLE_NAME: z.string().optional(),
|
|
103
|
+
VIDFARM_JOB_STATE_MACHINE_ARN: z.string().optional(),
|
|
104
|
+
SENTRY_DSN: z.string().optional(),
|
|
105
|
+
SENTRY_ENVIRONMENT: z.string().optional(),
|
|
106
|
+
SENTRY_RELEASE: z.string().optional(),
|
|
107
|
+
SENTRY_TRACES_SAMPLE_RATE: optionalSampleRate,
|
|
108
|
+
SENTRY_ENABLE_LOGS: z.string().default("true"),
|
|
109
|
+
SENTRY_SEND_DEFAULT_PII: z.string().default("false"),
|
|
110
|
+
SENTRY_DEBUG_ROUTE: z.string().default("false"),
|
|
111
|
+
PUBLIC_SENTRY_DSN: z.string().optional(),
|
|
112
|
+
PUBLIC_SENTRY_TRACES_SAMPLE_RATE: optionalSampleRate,
|
|
113
|
+
PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: optionalSampleRate,
|
|
114
|
+
PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: optionalSampleRate,
|
|
115
|
+
PUBLIC_SENTRY_ENABLE_LOGS: z.string().default("true"),
|
|
116
|
+
PUBLIC_SENTRY_SEND_DEFAULT_PII: z.string().default("true"),
|
|
117
|
+
PUBLIC_SENTRY_TRACE_PROPAGATION_TARGETS: z.string().default("")
|
|
118
|
+
}).superRefine((data, ctx) => {
|
|
119
|
+
if (data.NODE_ENV === "production" && data.VIDFARM_RUNTIME_BACKEND === "serverless") {
|
|
120
|
+
if (!data.VIDFARM_SERVERLESS_MAIN_TABLE_NAME) {
|
|
121
|
+
ctx.addIssue({
|
|
122
|
+
code: z.ZodIssueCode.custom,
|
|
123
|
+
path: ["VIDFARM_SERVERLESS_MAIN_TABLE_NAME"],
|
|
124
|
+
message: "VIDFARM_SERVERLESS_MAIN_TABLE_NAME is required when VIDFARM_RUNTIME_BACKEND=serverless in production"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!data.VIDFARM_RATE_LIMITS_TABLE_NAME) {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: z.ZodIssueCode.custom,
|
|
130
|
+
path: ["VIDFARM_RATE_LIMITS_TABLE_NAME"],
|
|
131
|
+
message: "VIDFARM_RATE_LIMITS_TABLE_NAME is required when VIDFARM_RUNTIME_BACKEND=serverless in production"
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!data.VIDFARM_JOB_STATE_MACHINE_ARN) {
|
|
135
|
+
ctx.addIssue({
|
|
136
|
+
code: z.ZodIssueCode.custom,
|
|
137
|
+
path: ["VIDFARM_JOB_STATE_MACHINE_ARN"],
|
|
138
|
+
message: "VIDFARM_JOB_STATE_MACHINE_ARN is required when VIDFARM_RUNTIME_BACKEND=serverless in production"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const normalizedEnv = Object.fromEntries(Object.entries(process.env).map(([key, value]) => [key, normalizeEnvValue(value)]));
|
|
144
|
+
const parsed = schema.parse(normalizedEnv);
|
|
145
|
+
const dataDir = path.resolve(parsed.VIDFARM_DATA_DIR);
|
|
146
|
+
mkdirSync(dataDir, { recursive: true });
|
|
147
|
+
const templateSourceRoot = path.resolve(parsed.TEMPLATE_SOURCE_ROOT);
|
|
148
|
+
mkdirSync(templateSourceRoot, { recursive: true });
|
|
149
|
+
const publicBaseUrl = parsed.PUBLIC_BASE_URL || `http://localhost:${parsed.PORT}`;
|
|
150
|
+
export const config = {
|
|
151
|
+
...parsed,
|
|
152
|
+
VIDFARM_DATA_DIR: dataDir,
|
|
153
|
+
TEMPLATE_SOURCE_ROOT: templateSourceRoot,
|
|
154
|
+
PUBLIC_BASE_URL: publicBaseUrl,
|
|
155
|
+
ARTIFACT_SIGNING_SECRET: parsed.ARTIFACT_SIGNING_SECRET || parsed.SUPERAGENCY_KEY || "",
|
|
156
|
+
isProduction: parsed.NODE_ENV === "production",
|
|
157
|
+
mockProviders: parsed.MOCK_PROVIDER_RESPONSES === "true" ||
|
|
158
|
+
(parsed.MOCK_PROVIDER_RESPONSES == null && parsed.NODE_ENV !== "production"),
|
|
159
|
+
minRenderWalletBalanceUsd: parsed.VIDFARM_MIN_RENDER_WALLET_BALANCE_USD,
|
|
160
|
+
debugLogs: parsed.VIDFARM_DEBUG_LOGS === "true" || parsed.VIDFARM_DEPLOYMENT_TARGET === "serverless-staging",
|
|
161
|
+
adminEmails: parsed.VIDFARM_ADMIN_EMAILS
|
|
162
|
+
.split(",")
|
|
163
|
+
.map((value) => value.trim().toLowerCase())
|
|
164
|
+
.filter(Boolean),
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import * as Sentry from "@sentry/node";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { devErrorFields, devLog } from "./lib/dev-log.js";
|
|
6
|
+
import { createId } from "./lib/ids.js";
|
|
7
|
+
import { estimateNormalizationCostUsd, normalizeVideoAsset } from "./services/video-normalization.js";
|
|
8
|
+
export function createTemplateJobContext(input) {
|
|
9
|
+
const templateConfig = {};
|
|
10
|
+
const prefix = input.storage.templateJobPrefix(input.template.id, input.customer.id, input.job.id);
|
|
11
|
+
const buildArtifactUrl = (storageKey, fallbackUrl) => fallbackUrl ?? input.storage.getPublicUrl(storageKey) ?? buildTemplateMediaUrl(storageKey);
|
|
12
|
+
const templatePreviewMediaUrls = resolveTemplatePreviewMediaUrls(input.template, input.storage);
|
|
13
|
+
const jobPreviewMediaPolicy = parseTemplatePreviewMediaPolicy(input.job.payload._templatePreviewMediaPolicy);
|
|
14
|
+
// Billing records are written after the billable work (render, normalize,
|
|
15
|
+
// etc.) has already succeeded. A transient billing failure must never fail
|
|
16
|
+
// the job — the customer would retry and pay for the expensive work twice.
|
|
17
|
+
const recordBillingNonFatal = async (record) => {
|
|
18
|
+
try {
|
|
19
|
+
await input.billing.record(record);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
Sentry.captureException(error);
|
|
23
|
+
devLog("billing.record_failed_nonfatal", {
|
|
24
|
+
job_id: input.job.id,
|
|
25
|
+
customer_id: input.customer.id,
|
|
26
|
+
template_id: input.template.id,
|
|
27
|
+
cost_center_slug: record.costCenterSlug ?? null,
|
|
28
|
+
idempotency_key: record.idempotencyKey ?? null,
|
|
29
|
+
...devErrorFields(error)
|
|
30
|
+
}, "warn");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
env: config.isProduction ? "production" : "development",
|
|
35
|
+
customer: input.customer,
|
|
36
|
+
template: {
|
|
37
|
+
id: input.template.id,
|
|
38
|
+
slugId: input.template.slugId,
|
|
39
|
+
previewMediaUrls: templatePreviewMediaUrls,
|
|
40
|
+
previewMediaPolicy: jobPreviewMediaPolicy
|
|
41
|
+
},
|
|
42
|
+
templateConfig,
|
|
43
|
+
logger: {
|
|
44
|
+
debug(message, metadata = {}) {
|
|
45
|
+
void input.jobs.addLogEvent({
|
|
46
|
+
id: createId("evt"),
|
|
47
|
+
jobId: input.job.id,
|
|
48
|
+
level: "debug",
|
|
49
|
+
message,
|
|
50
|
+
metadata,
|
|
51
|
+
progress: null,
|
|
52
|
+
artifactKey: null,
|
|
53
|
+
createdAt: new Date().toISOString()
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
info(message, metadata = {}) {
|
|
57
|
+
void input.jobs.addLogEvent({
|
|
58
|
+
id: createId("evt"),
|
|
59
|
+
jobId: input.job.id,
|
|
60
|
+
level: "info",
|
|
61
|
+
message,
|
|
62
|
+
metadata,
|
|
63
|
+
progress: null,
|
|
64
|
+
artifactKey: null,
|
|
65
|
+
createdAt: new Date().toISOString()
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
warn(message, metadata = {}) {
|
|
69
|
+
void input.jobs.addLogEvent({
|
|
70
|
+
id: createId("evt"),
|
|
71
|
+
jobId: input.job.id,
|
|
72
|
+
level: "warn",
|
|
73
|
+
message,
|
|
74
|
+
metadata,
|
|
75
|
+
progress: null,
|
|
76
|
+
artifactKey: null,
|
|
77
|
+
createdAt: new Date().toISOString()
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
error(message, metadata = {}) {
|
|
81
|
+
void input.jobs.addLogEvent({
|
|
82
|
+
id: createId("evt"),
|
|
83
|
+
jobId: input.job.id,
|
|
84
|
+
level: "error",
|
|
85
|
+
message,
|
|
86
|
+
metadata,
|
|
87
|
+
progress: null,
|
|
88
|
+
artifactKey: null,
|
|
89
|
+
createdAt: new Date().toISOString()
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
progress(progress, message, metadata = {}) {
|
|
93
|
+
void input.jobs.addLogEvent({
|
|
94
|
+
id: createId("evt"),
|
|
95
|
+
jobId: input.job.id,
|
|
96
|
+
level: "info",
|
|
97
|
+
message,
|
|
98
|
+
metadata,
|
|
99
|
+
progress,
|
|
100
|
+
artifactKey: typeof metadata.artifactKey === "string" ? metadata.artifactKey : null,
|
|
101
|
+
createdAt: new Date().toISOString()
|
|
102
|
+
});
|
|
103
|
+
void input.jobs.updateJobStatus({
|
|
104
|
+
id: input.job.id,
|
|
105
|
+
status: "running",
|
|
106
|
+
progress
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
jobs: {
|
|
111
|
+
async enqueueChild({ operationName, workflowName, payload, providerHint }) {
|
|
112
|
+
const child = await input.jobs.enqueueChildJob({
|
|
113
|
+
templateId: input.template.id,
|
|
114
|
+
operationName,
|
|
115
|
+
workflowName,
|
|
116
|
+
tracer: input.job.tracer,
|
|
117
|
+
payload,
|
|
118
|
+
customerId: input.customer.id,
|
|
119
|
+
parentJobId: input.job.id,
|
|
120
|
+
providerHint
|
|
121
|
+
});
|
|
122
|
+
return { jobId: child.id };
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
storage: {
|
|
126
|
+
async putJson(key, value) {
|
|
127
|
+
const stored = await input.storage.putJson(`${prefix}/${key}`, value);
|
|
128
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
129
|
+
await insertArtifact("json", stored.key, artifactUrl, {});
|
|
130
|
+
return {
|
|
131
|
+
...stored,
|
|
132
|
+
url: artifactUrl
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
async putText(key, value, contentType = "text/plain; charset=utf-8") {
|
|
136
|
+
const stored = await input.storage.putText(`${prefix}/${key}`, value, contentType);
|
|
137
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
138
|
+
await insertArtifact("text", stored.key, artifactUrl, { contentType });
|
|
139
|
+
return {
|
|
140
|
+
...stored,
|
|
141
|
+
url: artifactUrl
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
async putBuffer(key, value, options = {}) {
|
|
145
|
+
const stored = await input.storage.putBuffer(`${prefix}/${key}`, value, options.contentType);
|
|
146
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
147
|
+
await insertArtifact(options.kind ?? "binary", stored.key, artifactUrl, {
|
|
148
|
+
...(options.metadata ?? {}),
|
|
149
|
+
contentType: options.contentType ?? null
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
...stored,
|
|
153
|
+
url: artifactUrl
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
getPublicUrl(key) {
|
|
157
|
+
const storageKey = `${prefix}/${key}`;
|
|
158
|
+
return input.storage.getPublicUrl(storageKey) ?? buildTemplateMediaUrl(storageKey);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
billing: {
|
|
162
|
+
async record(record) {
|
|
163
|
+
await recordBillingNonFatal({
|
|
164
|
+
customerId: input.customer.id,
|
|
165
|
+
jobId: input.job.id,
|
|
166
|
+
tracer: input.job.tracer,
|
|
167
|
+
templateId: input.template.id,
|
|
168
|
+
type: record.type,
|
|
169
|
+
costUsd: record.costUsd,
|
|
170
|
+
chargeUsd: record.chargeUsd ?? Number((record.costUsd * 1.2).toFixed(4)),
|
|
171
|
+
costCenterSlug: record.costCenterSlug,
|
|
172
|
+
idempotencyKey: record.idempotencyKey,
|
|
173
|
+
occurredAtMs: record.occurredAtMs,
|
|
174
|
+
metadata: record.metadata
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
providers: {
|
|
179
|
+
listAvailableProviders() {
|
|
180
|
+
return input.providers.listAvailableProviders(input.customer.id);
|
|
181
|
+
},
|
|
182
|
+
async generateText(request) {
|
|
183
|
+
return input.providers.generateText({
|
|
184
|
+
customerId: input.customer.id,
|
|
185
|
+
jobId: input.job.id,
|
|
186
|
+
workerId: input.workerId,
|
|
187
|
+
...request
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
async generateSpeech(request) {
|
|
191
|
+
return input.providers.generateSpeech({
|
|
192
|
+
customerId: input.customer.id,
|
|
193
|
+
jobId: input.job.id,
|
|
194
|
+
workerId: input.workerId,
|
|
195
|
+
...request
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
async generateImage(request) {
|
|
199
|
+
return input.providers.generateImage({
|
|
200
|
+
customerId: input.customer.id,
|
|
201
|
+
jobId: input.job.id,
|
|
202
|
+
workerId: input.workerId,
|
|
203
|
+
...request
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
async analyzeImageLayout(request) {
|
|
207
|
+
return input.providers.analyzeImageLayout({
|
|
208
|
+
customerId: input.customer.id,
|
|
209
|
+
jobId: input.job.id,
|
|
210
|
+
workerId: input.workerId,
|
|
211
|
+
...request
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
async transcribeSpeech(request) {
|
|
215
|
+
return input.providers.transcribeSpeech({
|
|
216
|
+
customerId: input.customer.id,
|
|
217
|
+
jobId: input.job.id,
|
|
218
|
+
workerId: input.workerId,
|
|
219
|
+
...request
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
hyperframes: {
|
|
224
|
+
async render(request) {
|
|
225
|
+
const hyperframesOutputKey = `renders/vidfarm/templates/${input.template.id}/${input.customer.id}/${input.job.id}/output.mp4`;
|
|
226
|
+
const render = await input.hyperframes.render({
|
|
227
|
+
...request,
|
|
228
|
+
outputKey: hyperframesOutputKey,
|
|
229
|
+
wait: true
|
|
230
|
+
});
|
|
231
|
+
let outputUrl = render.outputUrl;
|
|
232
|
+
let platformOutputKey = null;
|
|
233
|
+
if (render.outputS3Uri) {
|
|
234
|
+
const output = await input.hyperframes.readRenderOutput(render.outputS3Uri);
|
|
235
|
+
platformOutputKey = `${prefix}/${request.outputKey || "renders/output.mp4"}`;
|
|
236
|
+
const stored = await input.storage.putBuffer(platformOutputKey, output.bytes, output.contentType);
|
|
237
|
+
outputUrl = buildArtifactUrl(stored.key, stored.url);
|
|
238
|
+
await insertArtifact("video", stored.key, outputUrl, {
|
|
239
|
+
operation: "hyperframes-render",
|
|
240
|
+
contentType: output.contentType,
|
|
241
|
+
renderId: render.renderId,
|
|
242
|
+
outputS3Uri: render.outputS3Uri
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const estimatedCostUsd = typeof render.metadata.estimatedCostUsd === "number" ? render.metadata.estimatedCostUsd : null;
|
|
246
|
+
const chargeUsd = typeof render.metadata.chargeUsd === "number" ? render.metadata.chargeUsd : null;
|
|
247
|
+
if (estimatedCostUsd !== null && estimatedCostUsd > 0) {
|
|
248
|
+
await recordBillingNonFatal({
|
|
249
|
+
customerId: input.customer.id,
|
|
250
|
+
jobId: input.job.id,
|
|
251
|
+
tracer: input.job.tracer,
|
|
252
|
+
templateId: input.template.id,
|
|
253
|
+
type: "render",
|
|
254
|
+
costUsd: estimatedCostUsd,
|
|
255
|
+
chargeUsd: typeof chargeUsd === "number" ? chargeUsd : undefined,
|
|
256
|
+
costCenterSlug: "hyperframes_lambda",
|
|
257
|
+
idempotencyKey: `hyperframes_lambda:${render.renderId}`,
|
|
258
|
+
occurredAtMs: Date.now(),
|
|
259
|
+
metadata: {
|
|
260
|
+
...render.metadata,
|
|
261
|
+
render_id: render.renderId,
|
|
262
|
+
output_url: outputUrl,
|
|
263
|
+
signed_output_url: render.outputUrl,
|
|
264
|
+
output_s3_uri: render.outputS3Uri,
|
|
265
|
+
platform_output_key: platformOutputKey
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
renderId: render.renderId,
|
|
271
|
+
outputUrl,
|
|
272
|
+
outputS3Uri: render.outputS3Uri,
|
|
273
|
+
metadata: {
|
|
274
|
+
...render.metadata,
|
|
275
|
+
outputUrl,
|
|
276
|
+
signedOutputUrl: render.outputUrl,
|
|
277
|
+
platformOutputKey
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
media: {
|
|
283
|
+
async normalizeVideo(request) {
|
|
284
|
+
const normalized = await normalizeVideoAsset({
|
|
285
|
+
sourceUrl: request.sourceUrl,
|
|
286
|
+
maxWidth: request.maxWidth,
|
|
287
|
+
maxHeight: request.maxHeight,
|
|
288
|
+
crf: request.crf,
|
|
289
|
+
preset: request.preset,
|
|
290
|
+
maxVideoBitrateKbps: request.maxVideoBitrateKbps,
|
|
291
|
+
audioBitrateKbps: request.audioBitrateKbps
|
|
292
|
+
});
|
|
293
|
+
const stored = await input.storage.putBuffer(`${prefix}/${request.outputKey}`, normalized.bytes, normalized.contentType);
|
|
294
|
+
const artifactUrl = buildArtifactUrl(stored.key, stored.url);
|
|
295
|
+
await insertArtifact("video", stored.key, artifactUrl, {
|
|
296
|
+
...(request.metadata ?? {}),
|
|
297
|
+
operation: "normalize-video",
|
|
298
|
+
contentType: normalized.contentType,
|
|
299
|
+
normalization: normalized.metadata
|
|
300
|
+
});
|
|
301
|
+
await recordBillingNonFatal({
|
|
302
|
+
customerId: input.customer.id,
|
|
303
|
+
jobId: input.job.id,
|
|
304
|
+
tracer: input.job.tracer,
|
|
305
|
+
templateId: input.template.id,
|
|
306
|
+
type: "cpu_estimate",
|
|
307
|
+
costUsd: estimateNormalizationCostUsd(normalized.metadata),
|
|
308
|
+
costCenterSlug: "video_normalization",
|
|
309
|
+
idempotencyKey: `video_normalization:${input.job.id}:${request.outputKey}`,
|
|
310
|
+
occurredAtMs: Date.now(),
|
|
311
|
+
metadata: {
|
|
312
|
+
...(request.metadata ?? {}),
|
|
313
|
+
operation: "normalize-video",
|
|
314
|
+
source_url: request.sourceUrl,
|
|
315
|
+
output_key: `${prefix}/${request.outputKey}`,
|
|
316
|
+
normalization: normalized.metadata
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
...stored,
|
|
321
|
+
url: artifactUrl,
|
|
322
|
+
contentType: normalized.contentType,
|
|
323
|
+
normalization: normalized.metadata
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
async function insertArtifact(kind, storageKey, publicUrl, metadata) {
|
|
329
|
+
await input.jobs.insertArtifact({
|
|
330
|
+
id: createId("art"),
|
|
331
|
+
jobId: input.job.id,
|
|
332
|
+
customerId: input.customer.id,
|
|
333
|
+
templateId: input.template.id,
|
|
334
|
+
kind,
|
|
335
|
+
storageKey,
|
|
336
|
+
publicUrl,
|
|
337
|
+
metadata
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function parseTemplatePreviewMediaPolicy(value) {
|
|
342
|
+
if (value === "include" || value === "exclude" || value === "auto") {
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
if (value === true) {
|
|
346
|
+
return "include";
|
|
347
|
+
}
|
|
348
|
+
if (value === false) {
|
|
349
|
+
return "exclude";
|
|
350
|
+
}
|
|
351
|
+
return "auto";
|
|
352
|
+
}
|
|
353
|
+
function resolveTemplatePreviewMediaUrls(template, storage) {
|
|
354
|
+
return uniqueStrings(template.about.preview_media
|
|
355
|
+
.filter(isImagePreviewReference)
|
|
356
|
+
.map((entry) => resolveTemplatePreviewMediaUrl(template, storage, entry))
|
|
357
|
+
.filter((entry) => Boolean(entry)));
|
|
358
|
+
}
|
|
359
|
+
function resolveTemplatePreviewMediaUrl(template, storage, entry) {
|
|
360
|
+
if (/^https?:\/\//i.test(entry)) {
|
|
361
|
+
return entry;
|
|
362
|
+
}
|
|
363
|
+
const normalizedEntry = entry.replace(/^\/+/, "");
|
|
364
|
+
if (normalizedEntry.startsWith("templates/")) {
|
|
365
|
+
return resolveBundledTemplateAssetFromStorageKey(template, normalizedEntry)
|
|
366
|
+
? buildTemplateMediaUrl(normalizedEntry)
|
|
367
|
+
: storage.getPublicUrl(normalizedEntry) ?? buildTemplateMediaUrl(normalizedEntry);
|
|
368
|
+
}
|
|
369
|
+
const templateAboutPrefix = storage.templateAboutKey(template.id, "");
|
|
370
|
+
const storageAssetPath = normalizedEntry.startsWith(templateAboutPrefix)
|
|
371
|
+
? normalizedEntry.slice(templateAboutPrefix.length)
|
|
372
|
+
: normalizedEntry.startsWith("about/")
|
|
373
|
+
? normalizedEntry.slice("about/".length)
|
|
374
|
+
: normalizedEntry;
|
|
375
|
+
const storageKey = storage.templateAboutKey(template.id, storageAssetPath);
|
|
376
|
+
return resolveBundledTemplateAsset(template, storageAssetPath)
|
|
377
|
+
? buildTemplateMediaUrl(storageKey)
|
|
378
|
+
: storage.getPublicUrl(storageKey) ?? buildTemplateMediaUrl(storageKey);
|
|
379
|
+
}
|
|
380
|
+
function isImagePreviewReference(entry) {
|
|
381
|
+
return /\.(png|jpe?g|webp)(?:$|[?#])/i.test(entry);
|
|
382
|
+
}
|
|
383
|
+
function uniqueStrings(values) {
|
|
384
|
+
return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
|
|
385
|
+
}
|
|
386
|
+
function buildTemplateMediaUrl(storageKey) {
|
|
387
|
+
return `${config.PUBLIC_BASE_URL.replace(/\/$/, "")}/template-media?key=${encodeURIComponent(storageKey)}`;
|
|
388
|
+
}
|
|
389
|
+
function resolveBundledTemplateAssetFromStorageKey(template, storageKey) {
|
|
390
|
+
const match = /^templates\/([^/]+)\/about\/(.+)$/.exec(storageKey.replace(/^\/+/, ""));
|
|
391
|
+
if (!match || match[1] !== template.id) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
return resolveBundledTemplateAsset(template, match[2]);
|
|
395
|
+
}
|
|
396
|
+
function resolveBundledTemplateAsset(template, assetPath) {
|
|
397
|
+
if (!template.skillPath || !assetPath) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const normalizedAssetPath = assetPath.replace(/^\/+/, "");
|
|
401
|
+
if (!normalizedAssetPath || normalizedAssetPath.includes("\\") || normalizedAssetPath.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
let templateRoot = path.resolve(template.skillPath);
|
|
405
|
+
try {
|
|
406
|
+
if (existsSync(templateRoot) && !statSync(templateRoot).isDirectory()) {
|
|
407
|
+
templateRoot = path.dirname(templateRoot);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const candidates = normalizedAssetPath.startsWith("about/")
|
|
414
|
+
? [normalizedAssetPath]
|
|
415
|
+
: [`about/${normalizedAssetPath}`, normalizedAssetPath];
|
|
416
|
+
for (const candidate of candidates) {
|
|
417
|
+
const resolved = path.resolve(templateRoot, candidate);
|
|
418
|
+
if ((resolved === templateRoot || resolved.startsWith(`${templateRoot}${path.sep}`)) && existsSync(resolved)) {
|
|
419
|
+
return resolved;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
function findTemplateRoot(modulePath) {
|
|
425
|
+
let current = path.dirname(modulePath);
|
|
426
|
+
for (let index = 0; index < 10; index += 1) {
|
|
427
|
+
if (existsSync(path.join(current, "template.config.json"))) {
|
|
428
|
+
return current;
|
|
429
|
+
}
|
|
430
|
+
const parent = path.dirname(current);
|
|
431
|
+
if (parent === current) {
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
current = parent;
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
function nonBlank(value) {
|
|
439
|
+
const trimmed = value?.trim();
|
|
440
|
+
return trimmed ? trimmed : undefined;
|
|
441
|
+
}
|
|
442
|
+
function normalizePositiveInteger(value) {
|
|
443
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
444
|
+
? Math.floor(value)
|
|
445
|
+
: undefined;
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=context.js.map
|