@pixeldojo/mcp 0.19.0 → 0.21.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 -2
- package/dist/cli.cjs +288 -165
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +288 -165
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +287 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +287 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -460,14 +460,6 @@ function buildPayload(apiId, input, src, slot, audioSlot) {
|
|
|
460
460
|
async function run(input, ctx) {
|
|
461
461
|
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
462
462
|
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
463
|
-
if (input.upgrade_draft) return upgradeDraft(input, ctx);
|
|
464
|
-
if (input.draft && input.model && input.model !== DRAFT_MODEL) {
|
|
465
|
-
return {
|
|
466
|
-
kind: "text",
|
|
467
|
-
text: `Draft mode runs on ${DRAFT_MODEL} only. Drop model (or set it to ${DRAFT_MODEL}) to render a 480p draft.`
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
if (input.draft) input = { ...input, model: DRAFT_MODEL, resolution: DRAFT_RESOLUTION };
|
|
471
463
|
let resolved = input;
|
|
472
464
|
let referenceUrls = [];
|
|
473
465
|
try {
|
|
@@ -529,8 +521,6 @@ async function run(input, ctx) {
|
|
|
529
521
|
}
|
|
530
522
|
const body = buildPayload(apiId, resolved, src, refField, audioSlot);
|
|
531
523
|
const paramsNote = applyParams(body, input.params, input.model, apiId);
|
|
532
|
-
const draftDuration = input.draft ? Number(body.duration ?? DRAFT_DEFAULT_DURATION) : void 0;
|
|
533
|
-
if (input.draft) Object.assign(body, { draft: true, resolution: DRAFT_RESOLUTION, duration: draftDuration });
|
|
534
524
|
const note = [routeNote, paramsNote].filter(Boolean).join(" ");
|
|
535
525
|
const submitted = await ctx.client.run(apiId, body);
|
|
536
526
|
const result = await pollJobUntilDoneOrHandoff(
|
|
@@ -542,45 +532,15 @@ async function run(input, ctx) {
|
|
|
542
532
|
sleep: ctx.pollOverrides?.sleep
|
|
543
533
|
}
|
|
544
534
|
);
|
|
545
|
-
|
|
535
|
+
return prefixNote(
|
|
546
536
|
pollResultToSkillResult(
|
|
547
537
|
result,
|
|
548
538
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Generated. Asset: ${urls[0]}` : `Generated ${urls.length} assets. URLs: ${urls.join(", ")}`
|
|
549
539
|
),
|
|
550
540
|
note
|
|
551
541
|
);
|
|
552
|
-
return draftDuration === void 0 ? out : { ...out, text: `${out.text}
|
|
553
|
-
|
|
554
|
-
${draftLine(submitted.jobId, draftDuration)}` };
|
|
555
|
-
}
|
|
556
|
-
function draftLine(jobId, duration) {
|
|
557
|
-
return `This is a 480p draft (draft_job_id: ${jobId}, duration: ${duration}). When the user likes it, call pixeldojo_generate with {"upgrade_draft": "${jobId}", "duration": ${duration}} to render the same take at ${DRAFT_FINAL_RESOLUTION} (billed as a ${DRAFT_FINAL_RESOLUTION} run; upgrade it within a day).`;
|
|
558
|
-
}
|
|
559
|
-
async function upgradeDraft(input, ctx) {
|
|
560
|
-
if (input.duration === void 0) {
|
|
561
|
-
return {
|
|
562
|
-
kind: "text",
|
|
563
|
-
text: `upgrade_draft also needs duration: the draft's own length (its reply says "duration: N"; ${DRAFT_DEFAULT_DURATION} when it was made without one).`
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
const submitted = await ctx.client.run(DRAFT_MODEL, {
|
|
567
|
-
draft_job_id: input.upgrade_draft,
|
|
568
|
-
resolution: DRAFT_FINAL_RESOLUTION,
|
|
569
|
-
duration: input.duration
|
|
570
|
-
});
|
|
571
|
-
const result = await pollJobUntilDoneOrHandoff(submitted.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
|
|
572
|
-
now: ctx.pollOverrides?.now,
|
|
573
|
-
sleep: ctx.pollOverrides?.sleep
|
|
574
|
-
});
|
|
575
|
-
return prefixNote(
|
|
576
|
-
pollResultToSkillResult(
|
|
577
|
-
result,
|
|
578
|
-
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upgraded to ${DRAFT_FINAL_RESOLUTION}. Asset: ${urls[0]}`
|
|
579
|
-
),
|
|
580
|
-
`Rendering draft ${input.upgrade_draft} at ${DRAFT_FINAL_RESOLUTION}.`
|
|
581
|
-
);
|
|
582
542
|
}
|
|
583
|
-
var
|
|
543
|
+
var InputSchema, DEFAULT_MODEL, VIDEO_REFERENCE_MODEL, VIDEO_REFERENCE_SLOTS, VIDEO_INPUT_MODELS, InputJsonSchema, generateSkill;
|
|
584
544
|
var init_generate = __esm({
|
|
585
545
|
"src/skills/generate.ts"() {
|
|
586
546
|
init_poll();
|
|
@@ -588,12 +548,6 @@ var init_generate = __esm({
|
|
|
588
548
|
init_upload_helpers();
|
|
589
549
|
init_schema_snap();
|
|
590
550
|
init_params();
|
|
591
|
-
DRAFT_MODEL = "seedance-2-5";
|
|
592
|
-
DRAFT_RESOLUTION = "480p";
|
|
593
|
-
DRAFT_FINAL_RESOLUTION = "1080p";
|
|
594
|
-
DRAFT_DEFAULT_DURATION = 5;
|
|
595
|
-
DRAFT_DESCRIPTION = "Seedance 2.5 draft mode: render a cheap 480p draft (runs on seedance-2-5) so the user can try takes before paying for full resolution. The reply gives the draft's job id and duration; pass them to upgrade_draft to render that exact take at 1080p.";
|
|
596
|
-
UPGRADE_DESCRIPTION = "Job id of a finished Seedance 2.5 draft to render at 1080p, the same take with full detail. Also pass `duration` equal to the draft's (the draft reply states it). The draft's prompt and media are reused, so prompt and references are ignored. Billed as a 1080p run.";
|
|
597
551
|
InputSchema = zod.z.object({
|
|
598
552
|
prompt: zod.z.string().min(1).max(8e3).describe('Prompt describing what to generate. Write @Name to use a character or element the user saved in Character Studio ("@Mara on a beach"): its photos are attached automatically on models that take reference images. pixeldojo_library source=characters lists them.'),
|
|
599
553
|
model: zod.z.string().optional().describe(
|
|
@@ -612,8 +566,6 @@ var init_generate = __esm({
|
|
|
612
566
|
"Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames. With no model, a clip runs on seedance-2-reference; a named model with no clip input is swapped for it and the result says so."
|
|
613
567
|
),
|
|
614
568
|
params: ParamsInput,
|
|
615
|
-
draft: zod.z.boolean().optional().describe(DRAFT_DESCRIPTION),
|
|
616
|
-
upgrade_draft: zod.z.string().regex(/^[A-Za-z0-9_-]{1,128}$/).optional().describe(UPGRADE_DESCRIPTION),
|
|
617
569
|
image_path: zod.z.string().min(1).optional().describe(
|
|
618
570
|
"Absolute local file path to a reference image/video. The skill uploads it to PixelDojo first (24h temp storage) and then dispatches. Prefer this over image_url when the user has the file on disk."
|
|
619
571
|
),
|
|
@@ -667,8 +619,6 @@ var init_generate = __esm({
|
|
|
667
619
|
description: "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames. With no model, a clip runs on seedance-2-reference; a named model with no clip input is swapped for it and the result says so."
|
|
668
620
|
},
|
|
669
621
|
params: PARAMS_JSON_SCHEMA,
|
|
670
|
-
draft: { type: "boolean", description: DRAFT_DESCRIPTION },
|
|
671
|
-
upgrade_draft: { type: "string", description: UPGRADE_DESCRIPTION },
|
|
672
622
|
image_urls: {
|
|
673
623
|
type: "array",
|
|
674
624
|
items: { type: "string", format: "uri" },
|
|
@@ -688,8 +638,8 @@ var init_generate = __esm({
|
|
|
688
638
|
};
|
|
689
639
|
generateSkill = {
|
|
690
640
|
name: "pixeldojo_generate",
|
|
691
|
-
description: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image.
|
|
692
|
-
hostedDescription: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image.
|
|
641
|
+
description: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image. If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills, `video_url` for a source clip, `image_path`/`image_paths` for absolute local files (uploaded to 24h temp storage first). A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
|
|
642
|
+
hostedDescription: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image. If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills and `video_url` for a source clip, all public http(s) URLs. A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
|
|
693
643
|
inputZod: InputSchema,
|
|
694
644
|
inputJsonSchema: InputJsonSchema,
|
|
695
645
|
allowedModels: null,
|
|
@@ -1300,6 +1250,67 @@ var init_upscale = __esm({
|
|
|
1300
1250
|
}
|
|
1301
1251
|
});
|
|
1302
1252
|
async function run6(input, ctx) {
|
|
1253
|
+
const submitted = await ctx.client.run(MODEL, {
|
|
1254
|
+
prompt: "",
|
|
1255
|
+
video: input.video_url,
|
|
1256
|
+
...input.style ? { caption_style: input.style } : {},
|
|
1257
|
+
...input.highlight_color ? { highlight_color: input.highlight_color } : {}
|
|
1258
|
+
});
|
|
1259
|
+
const result = await pollJobUntilDoneOrHandoff(
|
|
1260
|
+
submitted.jobId,
|
|
1261
|
+
(id) => ctx.client.getJob(id),
|
|
1262
|
+
ctx.buildStatusUrl,
|
|
1263
|
+
{ now: ctx.pollOverrides?.now, sleep: ctx.pollOverrides?.sleep }
|
|
1264
|
+
);
|
|
1265
|
+
return pollResultToSkillResult(
|
|
1266
|
+
result,
|
|
1267
|
+
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Captioned. Asset: ${urls[0]}`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
var MODEL, STYLES, InputSchema6, InputJsonSchema6, captionSkill;
|
|
1271
|
+
var init_caption = __esm({
|
|
1272
|
+
"src/skills/caption.ts"() {
|
|
1273
|
+
init_poll();
|
|
1274
|
+
init_types();
|
|
1275
|
+
MODEL = "video-autocaption";
|
|
1276
|
+
STYLES = ["pop", "highlighter", "clean"];
|
|
1277
|
+
InputSchema6 = zod.z.object({
|
|
1278
|
+
video_url: zod.z.string().url().describe("URL of the video to caption. It must have speech."),
|
|
1279
|
+
style: zod.z.enum(STYLES).optional(),
|
|
1280
|
+
highlight_color: zod.z.string().optional()
|
|
1281
|
+
});
|
|
1282
|
+
InputJsonSchema6 = {
|
|
1283
|
+
type: "object",
|
|
1284
|
+
required: ["video_url"],
|
|
1285
|
+
properties: {
|
|
1286
|
+
video_url: {
|
|
1287
|
+
type: "string",
|
|
1288
|
+
format: "uri",
|
|
1289
|
+
description: "The video to caption (MP4 or MOV). It must have speech; a silent clip is refused uncharged."
|
|
1290
|
+
},
|
|
1291
|
+
style: {
|
|
1292
|
+
type: "string",
|
|
1293
|
+
enum: STYLES,
|
|
1294
|
+
description: "pop (default): bold caps, the spoken word lit and growing. highlighter: the spoken word on a block of the color. clean: sentence case on a dark box."
|
|
1295
|
+
},
|
|
1296
|
+
highlight_color: {
|
|
1297
|
+
type: "string",
|
|
1298
|
+
description: "The highlight as #RRGGBB (a brand color) or a name like yellow, green, pink. Defaults to yellow."
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
captionSkill = {
|
|
1303
|
+
name: "pixeldojo_caption",
|
|
1304
|
+
description: "Burn animated word-by-word captions onto a video with speech, the short-form look for TikTok, Reels and Shorts: the spoken word lights up, placed above the platform buttons. Pass video_url; optional style (pop, highlighter, clean) and highlight_color (#RRGGBB for a brand color). 5 credits.",
|
|
1305
|
+
inputZod: InputSchema6,
|
|
1306
|
+
inputJsonSchema: InputJsonSchema6,
|
|
1307
|
+
allowedModels: [MODEL],
|
|
1308
|
+
defaultModel: MODEL,
|
|
1309
|
+
run: run6
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
async function run7(input, ctx) {
|
|
1303
1314
|
const job = await ctx.client.getJob(input.jobId);
|
|
1304
1315
|
if (job.status === "completed") {
|
|
1305
1316
|
const urls = extractAssetUrls2(job);
|
|
@@ -1363,13 +1374,13 @@ function extractAssetUrls2(job) {
|
|
|
1363
1374
|
if (output?.video) return [output.video];
|
|
1364
1375
|
return [];
|
|
1365
1376
|
}
|
|
1366
|
-
var
|
|
1377
|
+
var InputSchema7, InputJsonSchema7, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
|
|
1367
1378
|
var init_status = __esm({
|
|
1368
1379
|
"src/skills/status.ts"() {
|
|
1369
|
-
|
|
1380
|
+
InputSchema7 = zod.z.object({
|
|
1370
1381
|
jobId: zod.z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo_* skill handoff.")
|
|
1371
1382
|
});
|
|
1372
|
-
|
|
1383
|
+
InputJsonSchema7 = {
|
|
1373
1384
|
type: "object",
|
|
1374
1385
|
properties: {
|
|
1375
1386
|
jobId: {
|
|
@@ -1384,16 +1395,16 @@ var init_status = __esm({
|
|
|
1384
1395
|
statusSkill = {
|
|
1385
1396
|
name: "pixeldojo_status",
|
|
1386
1397
|
description: 'Check once, without waiting, on a jobId that a generation tool returned instead of an asset after its 30s wait. Free. Returns asset URLs when complete, the failure reason if it failed, or the current state and job age if still running. Campaign ids go to pixeldojo_campaign_status; films report progress through pixeldojo_film with action "status".',
|
|
1387
|
-
inputZod:
|
|
1388
|
-
inputJsonSchema:
|
|
1398
|
+
inputZod: InputSchema7,
|
|
1399
|
+
inputJsonSchema: InputJsonSchema7,
|
|
1389
1400
|
// status is special — it doesn't dispatch to a model
|
|
1390
1401
|
allowedModels: [],
|
|
1391
1402
|
defaultModel: "",
|
|
1392
|
-
run:
|
|
1403
|
+
run: run7
|
|
1393
1404
|
};
|
|
1394
1405
|
}
|
|
1395
1406
|
});
|
|
1396
|
-
async function
|
|
1407
|
+
async function run8(input, ctx) {
|
|
1397
1408
|
const product = await ctx.client.extractProduct(input.url);
|
|
1398
1409
|
const summary = [
|
|
1399
1410
|
`Product: ${product.name}`,
|
|
@@ -1410,13 +1421,13 @@ function truncate(input, max) {
|
|
|
1410
1421
|
if (input.length <= max) return input;
|
|
1411
1422
|
return input.slice(0, max - 3) + "...";
|
|
1412
1423
|
}
|
|
1413
|
-
var
|
|
1424
|
+
var InputSchema8, InputJsonSchema8, fromUrlSkill;
|
|
1414
1425
|
var init_from_url = __esm({
|
|
1415
1426
|
"src/skills/from-url.ts"() {
|
|
1416
|
-
|
|
1427
|
+
InputSchema8 = zod.z.object({
|
|
1417
1428
|
url: zod.z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
|
|
1418
1429
|
});
|
|
1419
|
-
|
|
1430
|
+
InputJsonSchema8 = {
|
|
1420
1431
|
type: "object",
|
|
1421
1432
|
properties: {
|
|
1422
1433
|
url: {
|
|
@@ -1429,12 +1440,12 @@ var init_from_url = __esm({
|
|
|
1429
1440
|
fromUrlSkill = {
|
|
1430
1441
|
name: "pixeldojo_from_url",
|
|
1431
1442
|
description: "Extract a product profile from a product page URL: name, description, image URLs (the first three are listed), and the extraction method used. Works on Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata. Free. pixeldojo_campaign and pixeldojo_ad accept a product URL and run this extraction themselves; call this directly to check the product data first, or to reuse its images in other tools.",
|
|
1432
|
-
inputZod:
|
|
1433
|
-
inputJsonSchema:
|
|
1443
|
+
inputZod: InputSchema8,
|
|
1444
|
+
inputJsonSchema: InputJsonSchema8,
|
|
1434
1445
|
// No model dispatch — this skill calls a server-side scrape, not a generation.
|
|
1435
1446
|
allowedModels: [],
|
|
1436
1447
|
defaultModel: "",
|
|
1437
|
-
run:
|
|
1448
|
+
run: run8
|
|
1438
1449
|
};
|
|
1439
1450
|
}
|
|
1440
1451
|
});
|
|
@@ -1455,10 +1466,51 @@ async function filmApi(ctx, method, path, body) {
|
|
|
1455
1466
|
const data = await res.json().catch(() => ({}));
|
|
1456
1467
|
return { status: res.status, data };
|
|
1457
1468
|
}
|
|
1469
|
+
function noFilm(status2, data, filmId, retry) {
|
|
1470
|
+
if (status2 === 404) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1471
|
+
const input = { film_id: filmId, ...Object.fromEntries(Object.entries(retry).filter(([, v]) => v !== void 0)) };
|
|
1472
|
+
return {
|
|
1473
|
+
kind: "text",
|
|
1474
|
+
text: `Could not reach film ${filmId} just now (${status2 ? `HTTP ${status2}` : "no response"}). The film is safe and nothing changed. Call pixeldojo_film with ${JSON.stringify(input)} again in about 30 seconds. Do not plan or start a new film.`,
|
|
1475
|
+
...input.action === "status" ? { followUp: { input, afterSec: 30 } } : {}
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1458
1478
|
function keepGoing(filmId, afterSec, extra = {}) {
|
|
1459
1479
|
const set = Object.fromEntries(Object.entries(extra).filter(([, v]) => v !== void 0));
|
|
1460
1480
|
return { input: { film_id: filmId, action: "status", ...set }, afterSec };
|
|
1461
1481
|
}
|
|
1482
|
+
function flaggedClip(sh) {
|
|
1483
|
+
const qa = sh.clipQa;
|
|
1484
|
+
const file = (u) => u?.split("/").pop();
|
|
1485
|
+
return Boolean(qa && qa.ok === false && sh.videoUrl && file(qa.url) === file(sh.videoUrl));
|
|
1486
|
+
}
|
|
1487
|
+
function editReviewing(sh, nowMs) {
|
|
1488
|
+
const e = sh.editSuggestion;
|
|
1489
|
+
const file = (u) => u?.split("/").pop();
|
|
1490
|
+
return Boolean(e?.pending && sh.videoUrl && file(e.url) === file(sh.videoUrl) && nowMs - Date.parse(e.checkedAt) < 4 * 6e4);
|
|
1491
|
+
}
|
|
1492
|
+
function openEdit(sh) {
|
|
1493
|
+
const e = sh.editSuggestion;
|
|
1494
|
+
const file = (u) => u?.split("/").pop();
|
|
1495
|
+
return Boolean(e && !e.pending && !e.appliedAt && !e.skipped && e.inSec !== void 0 && sh.videoUrl && file(e.url) === file(sh.videoUrl));
|
|
1496
|
+
}
|
|
1497
|
+
function cutSec(sh) {
|
|
1498
|
+
if (sh.leftOut) return 0;
|
|
1499
|
+
const speed = sh.speed && sh.speed > 1 ? sh.speed : 1;
|
|
1500
|
+
return Math.max(1, (sh.videoDurationSec ?? sh.durationSec ?? 0) - (sh.trimStartSec ?? 0) - (sh.trimEndSec ?? 0)) / speed;
|
|
1501
|
+
}
|
|
1502
|
+
function editorCutting(f, nowMs) {
|
|
1503
|
+
const c = f.editorCut;
|
|
1504
|
+
const fresh = (iso) => Boolean(iso && nowMs - Date.parse(iso) < 4 * 6e4);
|
|
1505
|
+
if (c?.pending) return fresh(c.startedAt);
|
|
1506
|
+
const lastReview = shots(f).map((sh) => sh.editSuggestion?.checkedAt).filter((t) => Boolean(t)).sort().pop();
|
|
1507
|
+
if (!lastReview || !fresh(lastReview)) return false;
|
|
1508
|
+
return !(c?.builtAt && c.builtAt >= lastReview);
|
|
1509
|
+
}
|
|
1510
|
+
function runtimeText(f) {
|
|
1511
|
+
const sec = Math.round(shots(f).reduce((n, sh) => n + cutSec(sh), 0));
|
|
1512
|
+
return sec < 90 ? `${sec}s` : `${Math.floor(sec / 60)}m ${sec % 60}s`;
|
|
1513
|
+
}
|
|
1462
1514
|
function shotLabels(f) {
|
|
1463
1515
|
return new Map(f.scenes.flatMap((s, si) => s.shots.map((sh, i) => [sh.id, `${si + 1}.${i + 1}`])));
|
|
1464
1516
|
}
|
|
@@ -1566,6 +1618,7 @@ async function plan(input, ctx) {
|
|
|
1566
1618
|
aspect: input.aspect,
|
|
1567
1619
|
look: input.look ?? (music ? MUSIC_VIDEO_LOOKS[0] : "live-action"),
|
|
1568
1620
|
lengthTarget: input.length,
|
|
1621
|
+
...!music && input.pacing === "trailer" ? { pacing: "trailer" } : {},
|
|
1569
1622
|
assetIds: [],
|
|
1570
1623
|
...music ? {
|
|
1571
1624
|
// No script step names a music video, so it would stay "Untitled film".
|
|
@@ -1639,12 +1692,25 @@ ${err} Call action "status" to retry the cut.` };
|
|
|
1639
1692
|
return { kind: "text", text: plannedText(planned.film, ctx) };
|
|
1640
1693
|
}
|
|
1641
1694
|
async function produce(filmId, soundtrack, ctx) {
|
|
1642
|
-
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1643
|
-
if (!data.film) return {
|
|
1695
|
+
const { status: status2, data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1696
|
+
if (!data.film) return noFilm(status2, data, filmId, { action: "produce", soundtrack });
|
|
1644
1697
|
const planning = await resumePlanning(data.film, ctx, soundtrack);
|
|
1645
1698
|
if (planning) return planning;
|
|
1646
1699
|
const uncast = await uncastRolesOf(ctx, filmId);
|
|
1647
1700
|
if (uncast.length) return { kind: "text", text: `Nothing was charged. ${castNeededText(filmId, uncast)}` };
|
|
1701
|
+
let propsNote = "";
|
|
1702
|
+
if (!shots(data.film).some((sh) => sh.frameUrl)) {
|
|
1703
|
+
const locked = await filmApi(
|
|
1704
|
+
ctx,
|
|
1705
|
+
"POST",
|
|
1706
|
+
`/api/films/${filmId}/props`,
|
|
1707
|
+
{}
|
|
1708
|
+
).catch(() => null);
|
|
1709
|
+
const made = locked?.status === 200 ? locked.data.created ?? [] : [];
|
|
1710
|
+
if (made.length) {
|
|
1711
|
+
propsNote = ` Locked in ${made.length} recurring prop${made.length === 1 ? "" : "s"} first so ${made.length === 1 ? "it looks" : "they look"} the same in every shot: ${made.map((m) => `${m.name} (${m.shots} shots)`).join(", ")}${locked?.data.creditCost ? `, ${locked.data.creditCost} credits` : ""}.`;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1648
1714
|
const pending = shots(data.film).filter((sh) => sh.status === "idle" || sh.status === "failed" && !sh.frameUrl);
|
|
1649
1715
|
let fired = 0;
|
|
1650
1716
|
let refused;
|
|
@@ -1658,7 +1724,7 @@ async function produce(filmId, soundtrack, ctx) {
|
|
|
1658
1724
|
}
|
|
1659
1725
|
return {
|
|
1660
1726
|
kind: "text",
|
|
1661
|
-
text: `Production started on "${data.film.title}": ${fired} frame(s) dispatched (each is a normal frame charge; the server's Frame check reviews every one).${refused ? ` Some could not start: ${refused}` : ""} Poll pixeldojo_film with {"film_id": "${filmId}", "action": "status"} every ~30 seconds \u2014 each poll advances the pipeline.`,
|
|
1727
|
+
text: `Production started on "${data.film.title}": ${fired} frame(s) dispatched (each is a normal frame charge; the server's Frame check reviews every one).${propsNote}${refused ? ` Some could not start: ${refused}` : ""} Poll pixeldojo_film with {"film_id": "${filmId}", "action": "status"} every ~30 seconds \u2014 each poll advances the pipeline.`,
|
|
1662
1728
|
// The caller's soundtrack choice rides along: the follow-up status calls
|
|
1663
1729
|
// are the ones that reach the score.
|
|
1664
1730
|
followUp: keepGoing(filmId, 30, { soundtrack })
|
|
@@ -1667,7 +1733,7 @@ async function produce(filmId, soundtrack, ctx) {
|
|
|
1667
1733
|
function buildScorePlanBody(film) {
|
|
1668
1734
|
const scenes = film.scenes.filter((s) => s.shots.length > 0);
|
|
1669
1735
|
const sections = scenes.flatMap((scene, i) => {
|
|
1670
|
-
const runtime = Math.max(3e3, scene.shots.reduce((n, sh) => n + (sh
|
|
1736
|
+
const runtime = Math.max(3e3, Math.round(scene.shots.reduce((n, sh) => n + cutSec(sh), 0) * 1e3));
|
|
1671
1737
|
const styles = i === 0 ? ARC.opening : i === scenes.length - 1 ? ARC.finale : ARC.middle;
|
|
1672
1738
|
const parts = Math.max(1, Math.ceil(runtime / 12e4));
|
|
1673
1739
|
const partMs = Math.round(runtime / parts);
|
|
@@ -1690,7 +1756,7 @@ function buildScorePlanBody(film) {
|
|
|
1690
1756
|
};
|
|
1691
1757
|
}
|
|
1692
1758
|
function buildFlatMusicBody(film, all) {
|
|
1693
|
-
const runtimeSec = all.reduce((n, sh) => n + (sh
|
|
1759
|
+
const runtimeSec = all.reduce((n, sh) => n + cutSec(sh), 0);
|
|
1694
1760
|
const ms = Math.min(3e5, Math.max(3e4, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
|
|
1695
1761
|
return {
|
|
1696
1762
|
prompt: `${LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]}. Instrumental only, no vocals. One consistent theme, loopable as a background bed under dialogue.`,
|
|
@@ -1700,18 +1766,19 @@ function buildFlatMusicBody(film, all) {
|
|
|
1700
1766
|
};
|
|
1701
1767
|
}
|
|
1702
1768
|
async function advance(filmId, soundtrack, musicJobId, ctx) {
|
|
1703
|
-
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}?refresh=1`);
|
|
1769
|
+
const { status: status2, data } = await filmApi(ctx, "GET", `/api/films/${filmId}?refresh=1`);
|
|
1704
1770
|
const film = data.film;
|
|
1705
|
-
if (!film) return {
|
|
1771
|
+
if (!film) return noFilm(status2, data, filmId, { action: "status", soundtrack });
|
|
1706
1772
|
const all = shots(film);
|
|
1707
1773
|
if (film.status === "exported" && film.exportUrl) {
|
|
1708
1774
|
return {
|
|
1709
1775
|
kind: "text",
|
|
1710
1776
|
text: [
|
|
1711
|
-
`Film finished: "${film.title}"
|
|
1777
|
+
`Film finished: "${film.title}" (${runtimeText(film)})
|
|
1712
1778
|
Watch/download: ${film.exportUrl}
|
|
1713
1779
|
Workspace: ${ctx.baseUrl}/film-studio/${film.id}
|
|
1714
1780
|
It is saved in the Library folder "Film: ${film.title}".`,
|
|
1781
|
+
stillFlagged(film),
|
|
1715
1782
|
shotIndex(film)
|
|
1716
1783
|
].filter(Boolean).join("\n")
|
|
1717
1784
|
};
|
|
@@ -1767,7 +1834,45 @@ It is saved in the Library folder "Film: ${film.title}".`,
|
|
|
1767
1834
|
const clipQueue = framesDone ? all.filter((sh) => sh.status === "frame-ready" || sh.status === "failed" && frameDone(sh) && !isRefused(sh)) : [];
|
|
1768
1835
|
for (const sh of clipQueue.slice(0, Math.max(0, 4 - inFlight))) await dispatch(sh, "clip");
|
|
1769
1836
|
const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
|
|
1837
|
+
const toRedo = clipsDone ? all.filter((sh) => flaggedClip(sh) && !(sh.manualClipRedos ?? 0)) : [];
|
|
1838
|
+
if (toRedo.length) {
|
|
1839
|
+
const labels2 = shotLabels(film);
|
|
1840
|
+
const redone = [];
|
|
1841
|
+
let refusedRedo;
|
|
1842
|
+
for (const sh of toRedo) {
|
|
1843
|
+
const r = await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "clip" });
|
|
1844
|
+
if (r.status === 200) redone.push(`${labels2.get(sh.id)} (${sh.clipQa.note || sh.clipQa.issues.join(", ")})`);
|
|
1845
|
+
else if (!benignDispatchRefusal(r.status, r.data.error)) refusedRedo ??= r.data.error ?? `HTTP ${r.status}`;
|
|
1846
|
+
}
|
|
1847
|
+
if (redone.length) {
|
|
1848
|
+
return {
|
|
1849
|
+
kind: "text",
|
|
1850
|
+
text: `All ${all.length} clips rendered. The clip check flagged ${redone.length}, so each gets one more take before the final cut (a normal clip charge each): ${redone.join("; ")}.${refusedRedo ? ` Another could not start: ${refusedRedo}` : ""} Poll again in ~30s with {"film_id": "${filmId}", "action": "status"}.`,
|
|
1851
|
+
followUp: keepGoing(filmId, 30, { soundtrack })
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1770
1855
|
if (clipsDone) {
|
|
1856
|
+
const reviewing = all.filter((sh) => editReviewing(sh, Date.now())).length;
|
|
1857
|
+
if (reviewing) {
|
|
1858
|
+
return {
|
|
1859
|
+
kind: "text",
|
|
1860
|
+
text: `All ${all.length} clips ready. The editor is marking where ${reviewing === 1 ? "the last clip starts and lands" : `${reviewing} clips start and land`} so the cut drops the waiting. Poll again in ~20s with {"film_id": "${filmId}", "action": "status"}.`,
|
|
1861
|
+
followUp: keepGoing(filmId, 20, { soundtrack })
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
if (editorCutting(film, Date.now())) {
|
|
1865
|
+
return {
|
|
1866
|
+
kind: "text",
|
|
1867
|
+
text: `All ${all.length} clips ready. The editor is watching the whole film and making the cut. Poll again in ~30s with {"film_id": "${filmId}", "action": "status"}.`,
|
|
1868
|
+
followUp: keepGoing(filmId, 30, { soundtrack })
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
const openCut = Boolean(film.editorCut?.shots?.length && !film.editorCut.appliedAt && !film.editorCut.skipped);
|
|
1872
|
+
if (openCut || all.some(openEdit)) {
|
|
1873
|
+
const cut = await filmApi(ctx, "POST", `/api/films/${filmId}/edit`, { action: "apply" });
|
|
1874
|
+
if (cut.data.film) film.scenes = cut.data.film.scenes;
|
|
1875
|
+
}
|
|
1771
1876
|
if (all.some((sh) => needsSave(sh.videoUrl))) {
|
|
1772
1877
|
await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "clip" });
|
|
1773
1878
|
}
|
|
@@ -1796,7 +1901,7 @@ It is saved in the Library folder "Film: ${film.title}".`,
|
|
|
1796
1901
|
followUp: keepGoing(filmId, 20, { soundtrack, music_job_id: musicJobId })
|
|
1797
1902
|
};
|
|
1798
1903
|
}
|
|
1799
|
-
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film,
|
|
1904
|
+
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film, shots(film));
|
|
1800
1905
|
const dispatch2 = await ctx.client.run("text-to-music", body);
|
|
1801
1906
|
return {
|
|
1802
1907
|
kind: "text",
|
|
@@ -1837,6 +1942,12 @@ It is saved in the Library folder "Film: ${film.title}".`,
|
|
|
1837
1942
|
...moving && !paused ? { followUp: keepGoing(filmId, 30, { soundtrack }) } : {}
|
|
1838
1943
|
};
|
|
1839
1944
|
}
|
|
1945
|
+
function stillFlagged(f) {
|
|
1946
|
+
const labels = shotLabels(f);
|
|
1947
|
+
const flagged = shots(f).filter(flaggedClip);
|
|
1948
|
+
if (!flagged.length) return "";
|
|
1949
|
+
return `The clip check still flags ${flagged.length} after one redo each: ${flagged.map((sh) => `${labels.get(sh.id)} (${sh.clipQa.note || sh.clipQa.issues.join(", ")})`).join("; ")}. Ask the user before another take (action "redo", a clip charge each).`;
|
|
1950
|
+
}
|
|
1840
1951
|
function shotIndex(f) {
|
|
1841
1952
|
const all = shots(f);
|
|
1842
1953
|
if (all.length === 0 || all.length > 24) return "";
|
|
@@ -1845,9 +1956,10 @@ function shotIndex(f) {
|
|
|
1845
1956
|
const clock = (sec) => `${Math.floor(sec / 60)}:${String(Math.round(sec % 60)).padStart(2, "0")}`;
|
|
1846
1957
|
const lines = all.map((sh) => {
|
|
1847
1958
|
const from = at;
|
|
1848
|
-
at += sh
|
|
1959
|
+
at += cutSec(sh);
|
|
1849
1960
|
const what = sh.prompt.replace(/\s+/g, " ").trim();
|
|
1850
|
-
|
|
1961
|
+
const when = sh.leftOut ? "left out of the cut" : `about ${clock(from)}-${clock(at)}`;
|
|
1962
|
+
return ` ${labels.get(sh.id)} (${when}): ${what.length > 70 ? `${what.slice(0, 67)}...` : what}`;
|
|
1851
1963
|
});
|
|
1852
1964
|
return ['Shots (redo any with action "redo" and its label):', ...lines].join("\n");
|
|
1853
1965
|
}
|
|
@@ -1856,9 +1968,9 @@ async function redoShots(input, ctx) {
|
|
|
1856
1968
|
if (!input.shots?.length) {
|
|
1857
1969
|
return { kind: "text", text: 'Pass shots: the labels status reports, like ["2.3"] for scene 2, shot 3.' };
|
|
1858
1970
|
}
|
|
1859
|
-
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1971
|
+
const { status: status2, data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1860
1972
|
const film = data.film;
|
|
1861
|
-
if (!film) return {
|
|
1973
|
+
if (!film) return noFilm(status2, data, filmId, { action: "redo", shots: input.shots, redo: input.redo });
|
|
1862
1974
|
const labels = shotLabels(film);
|
|
1863
1975
|
const byLabel = new Map(shots(film).map((sh) => [labels.get(sh.id), sh]));
|
|
1864
1976
|
const byId = new Map(shots(film).map((sh) => [sh.id, sh]));
|
|
@@ -1896,7 +2008,7 @@ async function redoShots(input, ctx) {
|
|
|
1896
2008
|
followUp: keepGoing(filmId, 30, { soundtrack: input.soundtrack })
|
|
1897
2009
|
};
|
|
1898
2010
|
}
|
|
1899
|
-
async function
|
|
2011
|
+
async function run9(input, ctx) {
|
|
1900
2012
|
const action = input.action === "plan" && input.film_id ? input.cast?.length ? "cast" : "status" : input.action;
|
|
1901
2013
|
if (action === "plan") return plan(input, ctx);
|
|
1902
2014
|
if (!input.film_id) return { kind: "text", text: "produce/status/cast/redo need the film_id returned by the plan step." };
|
|
@@ -1916,7 +2028,7 @@ async function run8(input, ctx) {
|
|
|
1916
2028
|
if (action === "redo") return redoShots(input, ctx);
|
|
1917
2029
|
return advance(input.film_id, input.soundtrack, input.music_job_id, ctx);
|
|
1918
2030
|
}
|
|
1919
|
-
var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, MUSIC_CLIP_ENGINE_ID,
|
|
2031
|
+
var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, MUSIC_CLIP_ENGINE_ID, InputSchema9, InputJsonSchema9, shots, benignDispatchRefusal, count, frameDone, RETRY_CLEARS, REFUSED, isRefused, PLANNER_POLL_MS, PLANNER_MAX_POLLS, isMusicVideo, needsSlice, stillPlanningText, LOOK_MUSIC, ARC, filmSkill;
|
|
1920
2032
|
var init_film = __esm({
|
|
1921
2033
|
"src/skills/film.ts"() {
|
|
1922
2034
|
init_upload_helpers();
|
|
@@ -1924,7 +2036,7 @@ var init_film = __esm({
|
|
|
1924
2036
|
MUSIC_VIDEO_LOOKS = ["rap-video", "neon-club", "stage-performance", "retro-camcorder", "y2k-pop", "moody-rnb", "rock-grit", "dream-pop", "k-pop"];
|
|
1925
2037
|
LOOKS = [...STORY_LOOKS, ...MUSIC_VIDEO_LOOKS];
|
|
1926
2038
|
MUSIC_CLIP_ENGINE_ID = "minimax-h3-reference";
|
|
1927
|
-
|
|
2039
|
+
InputSchema9 = zod.z.object({
|
|
1928
2040
|
idea: zod.z.string().max(2e3).optional().describe("One-to-three sentence film idea. The studio writes the screenplay (free). Provide idea OR script for planning."),
|
|
1929
2041
|
script: zod.z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
|
|
1930
2042
|
look: zod.z.enum(LOOKS).optional().describe("Visual style carried through frames, clips, and soundtrack. Default live-action, or rap-video for a music video."),
|
|
@@ -1933,7 +2045,8 @@ var init_film = __esm({
|
|
|
1933
2045
|
song_duration_sec: zod.z.number().positive().max(7200).optional().describe("Music video mode (optional): the song length in seconds. Omit it and the studio measures the song."),
|
|
1934
2046
|
lyrics: zod.z.string().max(1e4).optional().describe("Music video mode: the song lyrics, so shots land on the right lines. Optional."),
|
|
1935
2047
|
aspect: zod.z.enum(["16:9", "9:16"]).default("16:9"),
|
|
1936
|
-
|
|
2048
|
+
pacing: zod.z.enum(["story", "trailer"]).default("story").describe("'story' (default) plays every shot's action whole. 'trailer' cuts it like a trailer or promo: most shots keep only their first 1.5-3 seconds."),
|
|
2049
|
+
length: zod.z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length of the finished film."),
|
|
1937
2050
|
film_id: zod.z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status/cast/redo."),
|
|
1938
2051
|
action: zod.z.enum(["plan", "produce", "status", "cast", "redo"]).default("plan").describe("'plan' creates + storyboards (free). 'cast' casts, replaces or removes characters on an existing film. 'produce' starts charged generation. 'status' advances production and reports progress; poll it every ~30s until it returns the finished film. 'redo' renders a new take of the listed shots (charged), then status re-saves and re-exports."),
|
|
1939
2052
|
shots: zod.z.array(zod.z.string().trim().min(1).max(40)).min(1).max(12).optional().describe('Redo: the shots to render again, by the labels status reports ("2.3" is scene 2, shot 3) or shot id.'),
|
|
@@ -1951,10 +2064,10 @@ var init_film = __esm({
|
|
|
1951
2064
|
soundtrack: zod.z.enum(["scene-synced", "flat", "none"]).default("scene-synced").describe("scene-synced composes one musical section per scene, pinned to the scene lengths (default). flat is a single cohesive bed. none skips music."),
|
|
1952
2065
|
music_job_id: zod.z.string().max(80).optional().describe("The music_job_id from the previous status reply while the score is composing, so that score is attached. A status call without it, once all clips are ready and no score is attached, starts and charges for a new score.")
|
|
1953
2066
|
});
|
|
1954
|
-
|
|
2067
|
+
InputJsonSchema9 = {
|
|
1955
2068
|
type: "object",
|
|
1956
2069
|
properties: {
|
|
1957
|
-
idea: { type: "string", description:
|
|
2070
|
+
idea: { type: "string", description: `Plan step: one-to-three sentence film idea. The studio writes the screenplay (free). Provide idea or script. Keep the user's own words for who each character is, including their gender and pronouns ("she", "he") and species; the writer has nothing else to go on.` },
|
|
1958
2071
|
script: { type: "string", description: "Plan step: a full screenplay (sluglines, action lines, dialogue) to plan instead of an idea." },
|
|
1959
2072
|
look: { type: "string", enum: [...LOOKS], description: `Plan step: visual style carried through frames, clips and soundtrack. Story looks: ${STORY_LOOKS.join(", ")} (default live-action). Music video looks: ${MUSIC_VIDEO_LOOKS.join(", ")} (default rap-video).` },
|
|
1960
2073
|
song_url: {
|
|
@@ -1971,7 +2084,12 @@ var init_film = __esm({
|
|
|
1971
2084
|
},
|
|
1972
2085
|
lyrics: { type: "string", description: "Plan step, music video mode (optional): the song lyrics, so shots land on the right lines." },
|
|
1973
2086
|
aspect: { type: "string", enum: ["16:9", "9:16"], description: "Plan step: frame aspect ratio. Default 16:9." },
|
|
1974
|
-
length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Plan step: target length
|
|
2087
|
+
length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Plan step: target length. Default 60s. Ignored for a music video, whose length is the song." },
|
|
2088
|
+
pacing: {
|
|
2089
|
+
type: "string",
|
|
2090
|
+
enum: ["story", "trailer"],
|
|
2091
|
+
description: "Plan step: 'story' (default) plays every shot's action whole, for any narrative film. 'trailer' cuts it like a trailer, promo or sizzle: most shots stay on screen 1.5-3 seconds. Ignored for a music video."
|
|
2092
|
+
},
|
|
1975
2093
|
film_id: { type: "string", description: 'The film_id returned by the plan step. Required for produce, status and redo; with it, action "plan" is treated as "status".' },
|
|
1976
2094
|
action: {
|
|
1977
2095
|
type: "string",
|
|
@@ -2030,7 +2148,7 @@ var init_film = __esm({
|
|
|
2030
2148
|
benignDispatchRefusal = (status2, error) => status2 === 429 || status2 === 409 && /already generating/i.test(error ?? "");
|
|
2031
2149
|
count = (f, pred) => shots(f).filter(pred).length;
|
|
2032
2150
|
frameDone = (sh) => Boolean(sh.frameUrl) && sh.status !== "idle" && sh.status !== "frame-generating";
|
|
2033
|
-
RETRY_CLEARS = /automatic audio check flagged|output audio\b[\s\S]*?sensitive information|temporarily unavailable/i;
|
|
2151
|
+
RETRY_CLEARS = /automatic audio check flagged|output audio\b[\s\S]*?sensitive information|temporarily unavailable|output audio\b[\s\S]*?copyright|soundtrack was blocked by the model provider/i;
|
|
2034
2152
|
REFUSED = /copyright|infringement|content filter|safety filter|moderation|blocked|flagged|sensitive|real (?:person|human)|nsfw|content policy|policy violation|inappropriate/i;
|
|
2035
2153
|
isRefused = (sh) => sh.status === "failed" && Boolean(sh.errorMessage) && !RETRY_CLEARS.test(sh.errorMessage) && REFUSED.test(sh.errorMessage);
|
|
2036
2154
|
PLANNER_POLL_MS = 3e3;
|
|
@@ -2060,11 +2178,11 @@ var init_film = __esm({
|
|
|
2060
2178
|
name: "pixeldojo_film",
|
|
2061
2179
|
description: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Or a music video: pass song_path (a local file) or song_url (your own PixelDojo upload from pixeldojo_upload, never a third-party link) with optional lyrics; the film is cut to that song, each clip lip-synced to its slice on the studio music video engine, and no score is composed: the export plays the song. Every named character must be cast (cast: your own images, a portrait prompt, or a Character Studio character) before frames render; cast entries can also replace or remove someone. Phased: plan is free; cast; produce starts charged generation; status advances production (poll every ~30s); redo renders a new take of chosen shots and status re-exports.",
|
|
2062
2180
|
hostedDescription: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Or a music video: pass song_url (your own PixelDojo upload made with POST /api/v1/upload, never a third-party link) with optional lyrics; the film is cut to that song, each clip lip-synced to its slice on the studio music video engine, and no score is composed: the export plays the song. Every named character must be cast (cast: your own images, a portrait prompt, or a Character Studio character) before frames render; cast entries can also replace or remove someone. Phased: plan is free; cast; produce starts charged generation; status advances production (poll every ~30s); redo renders a new take of chosen shots and status re-exports.",
|
|
2063
|
-
inputZod:
|
|
2064
|
-
inputJsonSchema:
|
|
2181
|
+
inputZod: InputSchema9,
|
|
2182
|
+
inputJsonSchema: InputJsonSchema9,
|
|
2065
2183
|
allowedModels: [],
|
|
2066
2184
|
defaultModel: "",
|
|
2067
|
-
run:
|
|
2185
|
+
run: run9
|
|
2068
2186
|
};
|
|
2069
2187
|
}
|
|
2070
2188
|
});
|
|
@@ -2151,7 +2269,7 @@ function buildVideoPayload(verb, apiId, i) {
|
|
|
2151
2269
|
};
|
|
2152
2270
|
}
|
|
2153
2271
|
}
|
|
2154
|
-
async function
|
|
2272
|
+
async function run10(input, ctx) {
|
|
2155
2273
|
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
2156
2274
|
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
2157
2275
|
const verb = input.verb;
|
|
@@ -2214,7 +2332,7 @@ async function run9(input, ctx) {
|
|
|
2214
2332
|
notes.join(" ")
|
|
2215
2333
|
);
|
|
2216
2334
|
}
|
|
2217
|
-
var VIDEO_STUDIO_ENGINES, VERBS, BACKWARD_ENGINES, clamp, NEEDS_VIDEO_DURATION, NO_IMAGE_ENGINES,
|
|
2335
|
+
var VIDEO_STUDIO_ENGINES, VERBS, BACKWARD_ENGINES, clamp, NEEDS_VIDEO_DURATION, NO_IMAGE_ENGINES, InputSchema10, engineList, InputJsonSchema10, BASE_DESCRIPTION, videoSkill;
|
|
2218
2336
|
var init_video = __esm({
|
|
2219
2337
|
"src/skills/video.ts"() {
|
|
2220
2338
|
init_poll();
|
|
@@ -2248,7 +2366,7 @@ var init_video = __esm({
|
|
|
2248
2366
|
"wan-video-character-swap"
|
|
2249
2367
|
]);
|
|
2250
2368
|
NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["flux-video-edit", "xai-video-edit"]);
|
|
2251
|
-
|
|
2369
|
+
InputSchema10 = zod.z.object({
|
|
2252
2370
|
verb: zod.z.enum(VERBS),
|
|
2253
2371
|
prompt: zod.z.string().max(8e3).optional(),
|
|
2254
2372
|
video_url: zod.z.string().url().optional(),
|
|
@@ -2263,7 +2381,7 @@ var init_video = __esm({
|
|
|
2263
2381
|
params: ParamsInput
|
|
2264
2382
|
});
|
|
2265
2383
|
engineList = (verb) => `${verb}: ${VIDEO_STUDIO_ENGINES[verb].join(", ")} (default ${VIDEO_STUDIO_ENGINES[verb][0]})`;
|
|
2266
|
-
|
|
2384
|
+
InputJsonSchema10 = {
|
|
2267
2385
|
type: "object",
|
|
2268
2386
|
properties: {
|
|
2269
2387
|
verb: {
|
|
@@ -2324,11 +2442,11 @@ var init_video = __esm({
|
|
|
2324
2442
|
name: "pixeldojo_video",
|
|
2325
2443
|
description: `${BASE_DESCRIPTION} Local files: video_path / image_path (uploaded to 24h temp storage first).`,
|
|
2326
2444
|
hostedDescription: `${BASE_DESCRIPTION} Media must be public http(s) URLs.`,
|
|
2327
|
-
inputZod:
|
|
2328
|
-
inputJsonSchema:
|
|
2445
|
+
inputZod: InputSchema10,
|
|
2446
|
+
inputJsonSchema: InputJsonSchema10,
|
|
2329
2447
|
allowedModels: Array.from(new Set(VERBS.flatMap((v) => VIDEO_STUDIO_ENGINES[v]))),
|
|
2330
2448
|
defaultModel: VIDEO_STUDIO_ENGINES.change[0],
|
|
2331
|
-
run:
|
|
2449
|
+
run: run10
|
|
2332
2450
|
};
|
|
2333
2451
|
}
|
|
2334
2452
|
});
|
|
@@ -2503,28 +2621,28 @@ async function produce2(id, ctx) {
|
|
|
2503
2621
|
const musicNote = ep.musicEnabled && ep.music?.status === "failed" ? " The music bed failed, so this cut has no music." : "";
|
|
2504
2622
|
return { kind: "text", text: `Export started, free${prior}.${musicNote} ${next(id, "produce", "~60s")}` };
|
|
2505
2623
|
}
|
|
2506
|
-
async function
|
|
2624
|
+
async function run11(input, ctx) {
|
|
2507
2625
|
const action = input.action === "create" && input.episode_id ? "status" : input.action;
|
|
2508
2626
|
if (action === "create") return create(input, ctx);
|
|
2509
2627
|
if (!input.episode_id) return { kind: "text", text: "produce and status need the episode_id returned by the create step." };
|
|
2510
2628
|
if (action === "produce") return produce2(input.episode_id, ctx);
|
|
2511
2629
|
return status(input.episode_id, ctx);
|
|
2512
2630
|
}
|
|
2513
|
-
var
|
|
2631
|
+
var STYLES2, CATEGORIES, ENGINES, MIN_SECONDS, MAX_SECONDS, MAX_IN_FLIGHT, InputSchema11, InputJsonSchema11, call, next, needsRender, PLAN_POLL_MS, PLAN_MAX_POLLS, stillPlanning, episodeSkill;
|
|
2514
2632
|
var init_episode = __esm({
|
|
2515
2633
|
"src/skills/episode.ts"() {
|
|
2516
|
-
|
|
2634
|
+
STYLES2 = ["editorial-motion", "paper-diorama", "hand-drawn", "storybook", "poster-vector", "soft-3d"];
|
|
2517
2635
|
CATEGORIES = ["education", "history", "kids", "storytelling", "custom"];
|
|
2518
2636
|
ENGINES = ["omni-draft", "omni-final", "still-motion"];
|
|
2519
2637
|
MIN_SECONDS = 60;
|
|
2520
2638
|
MAX_SECONDS = 300;
|
|
2521
2639
|
MAX_IN_FLIGHT = 4;
|
|
2522
|
-
|
|
2640
|
+
InputSchema11 = zod.z.object({
|
|
2523
2641
|
action: zod.z.enum(["create", "produce", "status"]).default("create"),
|
|
2524
2642
|
topic: zod.z.string().min(8).max(400).optional(),
|
|
2525
2643
|
episode_id: zod.z.string().max(40).optional(),
|
|
2526
2644
|
category: zod.z.enum(CATEGORIES).default("education"),
|
|
2527
|
-
style: zod.z.enum(
|
|
2645
|
+
style: zod.z.enum(STYLES2).default("editorial-motion"),
|
|
2528
2646
|
aspect: zod.z.enum(["16:9", "9:16"]).default("16:9"),
|
|
2529
2647
|
length_seconds: zod.z.number().int().min(MIN_SECONDS).max(MAX_SECONDS).default(120),
|
|
2530
2648
|
voice: zod.z.string().min(1).max(80).default("Wise_Woman"),
|
|
@@ -2532,7 +2650,7 @@ var init_episode = __esm({
|
|
|
2532
2650
|
music: zod.z.boolean().default(true),
|
|
2533
2651
|
title: zod.z.string().max(120).optional()
|
|
2534
2652
|
});
|
|
2535
|
-
|
|
2653
|
+
InputJsonSchema11 = {
|
|
2536
2654
|
type: "object",
|
|
2537
2655
|
properties: {
|
|
2538
2656
|
action: {
|
|
@@ -2543,7 +2661,7 @@ var init_episode = __esm({
|
|
|
2543
2661
|
topic: { type: "string", description: "Create step: what the episode is about, 8 to 400 characters." },
|
|
2544
2662
|
episode_id: { type: "string", description: "The episode_id from the create step. Required for produce and status." },
|
|
2545
2663
|
category: { type: "string", enum: [...CATEGORIES], description: "Create step: tone of the script. Default education." },
|
|
2546
|
-
style: { type: "string", enum: [...
|
|
2664
|
+
style: { type: "string", enum: [...STYLES2], description: "Create step: visual style of every scene. Default editorial-motion." },
|
|
2547
2665
|
aspect: { type: "string", enum: ["16:9", "9:16"], description: "Create step: 16:9 landscape (default) or 9:16 vertical." },
|
|
2548
2666
|
length_seconds: { type: "integer", minimum: MIN_SECONDS, maximum: MAX_SECONDS, description: "Create step: target length in seconds. Default 120." },
|
|
2549
2667
|
voice: { type: "string", description: "Create step: narrator voice id from the text-to-speech tool (e.g. Wise_Woman, Deep_Voice_Man, Friendly_Person). Default Wise_Woman." },
|
|
@@ -2565,15 +2683,15 @@ var init_episode = __esm({
|
|
|
2565
2683
|
episodeSkill = {
|
|
2566
2684
|
name: "pixeldojo_episode",
|
|
2567
2685
|
description: "Make a finished narrated faceless video (explainer, history, kids story) from one topic: script, scene visuals, AI narration, music bed, stitched export saved to the Library. Phased: create is free and returns a credit quote; produce is charged and advances one step per call (poll every ~30s until it returns the video URL); status is free.",
|
|
2568
|
-
inputZod:
|
|
2569
|
-
inputJsonSchema:
|
|
2686
|
+
inputZod: InputSchema11,
|
|
2687
|
+
inputJsonSchema: InputJsonSchema11,
|
|
2570
2688
|
allowedModels: [],
|
|
2571
2689
|
defaultModel: "",
|
|
2572
|
-
run:
|
|
2690
|
+
run: run11
|
|
2573
2691
|
};
|
|
2574
2692
|
}
|
|
2575
2693
|
});
|
|
2576
|
-
async function
|
|
2694
|
+
async function run12(input, ctx) {
|
|
2577
2695
|
const productImages = [
|
|
2578
2696
|
...input.product_image_urls ?? [],
|
|
2579
2697
|
...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx)))
|
|
@@ -2616,13 +2734,13 @@ async function run11(input, ctx) {
|
|
|
2616
2734
|
(urls) => urls.length === 0 ? `Ad job ${submitted.jobId} completed but returned no assets.` : `Ad rendered (style: ${styleLabel}${submitted.ad?.product?.name ? `, product: ${submitted.ad.product.name}` : ""}). Video: ${urls[0]}`
|
|
2617
2735
|
);
|
|
2618
2736
|
}
|
|
2619
|
-
var
|
|
2737
|
+
var InputSchema12, InputJsonSchema12, adSkill;
|
|
2620
2738
|
var init_ad = __esm({
|
|
2621
2739
|
"src/skills/ad.ts"() {
|
|
2622
2740
|
init_types();
|
|
2623
2741
|
init_poll();
|
|
2624
2742
|
init_upload_helpers();
|
|
2625
|
-
|
|
2743
|
+
InputSchema12 = zod.z.object({
|
|
2626
2744
|
style: zod.z.string().max(80).optional().describe(
|
|
2627
2745
|
"Ad style slug (default ugc-testimonial). Catalog: GET /api/v1/marketing/presets \u2014 e.g. ugc-testimonial, unboxing-reveal, unboxing-asmr, virtual-try-on, street-style, product-showcase, macro-details, crush-test, hyper-motion, before-after, day-in-the-life, the-gift, tv-spot."
|
|
2628
2746
|
),
|
|
@@ -2648,7 +2766,7 @@ var init_ad = __esm({
|
|
|
2648
2766
|
path: ["product_url"]
|
|
2649
2767
|
}
|
|
2650
2768
|
);
|
|
2651
|
-
|
|
2769
|
+
InputJsonSchema12 = {
|
|
2652
2770
|
type: "object",
|
|
2653
2771
|
properties: {
|
|
2654
2772
|
style: {
|
|
@@ -2735,16 +2853,16 @@ var init_ad = __esm({
|
|
|
2735
2853
|
name: "pixeldojo_ad",
|
|
2736
2854
|
description: "Marketing Studio: turn a product into a rendered video ad with voiceover. Requires `product_url` (name, description and images are extracted), or `product_name` plus product_image_urls / product_image_paths. Pick a `style` slug (default ugc-testimonial; full catalog at GET https://pixeldojo.ai/api/v1/marketing/presets), and optionally add presenter reference images, a location, a hook, and the exact spoken script. Returns the video URL, or a jobId to check with pixeldojo_status.",
|
|
2737
2855
|
hostedDescription: "Marketing Studio: turn a product into a rendered video ad with voiceover. Requires `product_url` (name, description and images are extracted), or `product_name` plus product_image_urls (public http(s) URLs). Pick a `style` slug (default ugc-testimonial; full catalog at GET https://pixeldojo.ai/api/v1/marketing/presets), and optionally add presenter reference images, a location, a hook, and the exact spoken script. Returns the video URL, or a jobId to check with pixeldojo_status.",
|
|
2738
|
-
inputZod:
|
|
2739
|
-
inputJsonSchema:
|
|
2856
|
+
inputZod: InputSchema12,
|
|
2857
|
+
inputJsonSchema: InputJsonSchema12,
|
|
2740
2858
|
// Composition skill — the server routes to the ad engine internally.
|
|
2741
2859
|
allowedModels: [],
|
|
2742
2860
|
defaultModel: "",
|
|
2743
|
-
run:
|
|
2861
|
+
run: run12
|
|
2744
2862
|
};
|
|
2745
2863
|
}
|
|
2746
2864
|
});
|
|
2747
|
-
async function
|
|
2865
|
+
async function run13(input, ctx) {
|
|
2748
2866
|
const client = ctx.client;
|
|
2749
2867
|
const result = await client.runCampaign(input);
|
|
2750
2868
|
if (result.kind === "campaign") {
|
|
@@ -2771,10 +2889,10 @@ async function run12(input, ctx) {
|
|
|
2771
2889
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
2772
2890
|
};
|
|
2773
2891
|
}
|
|
2774
|
-
var
|
|
2892
|
+
var InputSchema13, InputJsonSchema13, campaignSkill;
|
|
2775
2893
|
var init_campaign = __esm({
|
|
2776
2894
|
"src/skills/campaign.ts"() {
|
|
2777
|
-
|
|
2895
|
+
InputSchema13 = zod.z.object({
|
|
2778
2896
|
productUrl: zod.z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
2779
2897
|
product: zod.z.object({
|
|
2780
2898
|
name: zod.z.string().min(1).max(200),
|
|
@@ -2790,7 +2908,7 @@ var init_campaign = __esm({
|
|
|
2790
2908
|
message: "productUrl or product is required",
|
|
2791
2909
|
path: ["productUrl"]
|
|
2792
2910
|
});
|
|
2793
|
-
|
|
2911
|
+
InputJsonSchema13 = {
|
|
2794
2912
|
type: "object",
|
|
2795
2913
|
properties: {
|
|
2796
2914
|
productUrl: {
|
|
@@ -2841,17 +2959,17 @@ var init_campaign = __esm({
|
|
|
2841
2959
|
campaignSkill = {
|
|
2842
2960
|
name: "pixeldojo_campaign",
|
|
2843
2961
|
description: "Generate a coherent product launch package (hero image + N lifestyle variants + optional vertical video) from one URL or product profile. Composes hero / lifestyle / video models server-side; returns assets in one call or a handoff with campaignId to poll via pixeldojo_campaign_status.",
|
|
2844
|
-
inputZod:
|
|
2845
|
-
inputJsonSchema:
|
|
2962
|
+
inputZod: InputSchema13,
|
|
2963
|
+
inputJsonSchema: InputJsonSchema13,
|
|
2846
2964
|
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
2847
2965
|
// single allowed-models list applies here.
|
|
2848
2966
|
allowedModels: [],
|
|
2849
2967
|
defaultModel: "",
|
|
2850
|
-
run:
|
|
2968
|
+
run: run13
|
|
2851
2969
|
};
|
|
2852
2970
|
}
|
|
2853
2971
|
});
|
|
2854
|
-
async function
|
|
2972
|
+
async function run14(input, ctx) {
|
|
2855
2973
|
const client = ctx.client;
|
|
2856
2974
|
const result = await client.getCampaign(input.campaignId);
|
|
2857
2975
|
if (result.kind === "campaign") {
|
|
@@ -2881,13 +2999,13 @@ async function run13(input, ctx) {
|
|
|
2881
2999
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
2882
3000
|
};
|
|
2883
3001
|
}
|
|
2884
|
-
var
|
|
3002
|
+
var InputSchema14, InputJsonSchema14, campaignStatusSkill;
|
|
2885
3003
|
var init_campaign_status = __esm({
|
|
2886
3004
|
"src/skills/campaign-status.ts"() {
|
|
2887
|
-
|
|
3005
|
+
InputSchema14 = zod.z.object({
|
|
2888
3006
|
campaignId: zod.z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
|
|
2889
3007
|
});
|
|
2890
|
-
|
|
3008
|
+
InputJsonSchema14 = {
|
|
2891
3009
|
type: "object",
|
|
2892
3010
|
properties: {
|
|
2893
3011
|
campaignId: {
|
|
@@ -2900,15 +3018,15 @@ var init_campaign_status = __esm({
|
|
|
2900
3018
|
campaignStatusSkill = {
|
|
2901
3019
|
name: "pixeldojo_campaign_status",
|
|
2902
3020
|
description: "Poll a campaign by campaignId returned from pixeldojo_campaign. Returns assets when every sub-job is done, or a handoff describing what is still in flight.",
|
|
2903
|
-
inputZod:
|
|
2904
|
-
inputJsonSchema:
|
|
3021
|
+
inputZod: InputSchema14,
|
|
3022
|
+
inputJsonSchema: InputJsonSchema14,
|
|
2905
3023
|
allowedModels: [],
|
|
2906
3024
|
defaultModel: "",
|
|
2907
|
-
run:
|
|
3025
|
+
run: run14
|
|
2908
3026
|
};
|
|
2909
3027
|
}
|
|
2910
3028
|
});
|
|
2911
|
-
async function
|
|
3029
|
+
async function run15(input, ctx) {
|
|
2912
3030
|
let url;
|
|
2913
3031
|
try {
|
|
2914
3032
|
url = await uploadLocalFile(input.path, ctx);
|
|
@@ -2924,16 +3042,16 @@ async function run14(input, ctx) {
|
|
|
2924
3042
|
(temp storage; auto-deletes in ~24h. Pass this URL as image_url, video_url for a clip, or song_url on pixeldojo_film for a song, to any pixeldojo tool that takes one.)`
|
|
2925
3043
|
};
|
|
2926
3044
|
}
|
|
2927
|
-
var
|
|
3045
|
+
var InputSchema15, InputJsonSchema15, uploadSkill;
|
|
2928
3046
|
var init_upload = __esm({
|
|
2929
3047
|
"src/skills/upload.ts"() {
|
|
2930
3048
|
init_upload_helpers();
|
|
2931
|
-
|
|
3049
|
+
InputSchema15 = zod.z.object({
|
|
2932
3050
|
path: zod.z.string().min(1).max(4096).describe(
|
|
2933
3051
|
"Absolute local file path to upload. JPEG, PNG, WebP, GIF (\u226450MB), MP4, WebM, MOV (\u2264200MB) or MP3, WAV, M4A, AAC, OGG, FLAC audio (\u226450MB). Returns a public URL on the temp CDN; storage auto-expires after 24 hours."
|
|
2934
3052
|
)
|
|
2935
3053
|
});
|
|
2936
|
-
|
|
3054
|
+
InputJsonSchema15 = {
|
|
2937
3055
|
type: "object",
|
|
2938
3056
|
properties: {
|
|
2939
3057
|
path: {
|
|
@@ -2946,15 +3064,15 @@ var init_upload = __esm({
|
|
|
2946
3064
|
uploadSkill = {
|
|
2947
3065
|
name: "pixeldojo_upload",
|
|
2948
3066
|
description: "Upload a local image, video or audio file (JPEG, PNG, WebP, GIF up to 50MB; MP4, WebM, MOV up to 200MB; MP3, WAV, M4A, AAC, OGG, FLAC up to 50MB) and get back a public URL that expires after 24 hours. Free. Pass an audio URL as song_url to pixeldojo_film for a music video. Pass the URL as image_url (stills) or video_url (clips) to tools that take hosted URLs only, such as pixeldojo_character, pixeldojo_storyboard and pixeldojo_upscale. pixeldojo_generate, pixeldojo_edit and pixeldojo_ad take local paths directly and need no separate upload.",
|
|
2949
|
-
inputZod:
|
|
2950
|
-
inputJsonSchema:
|
|
3067
|
+
inputZod: InputSchema15,
|
|
3068
|
+
inputJsonSchema: InputJsonSchema15,
|
|
2951
3069
|
allowedModels: [],
|
|
2952
3070
|
defaultModel: "",
|
|
2953
|
-
run:
|
|
3071
|
+
run: run15
|
|
2954
3072
|
};
|
|
2955
3073
|
}
|
|
2956
3074
|
});
|
|
2957
|
-
async function
|
|
3075
|
+
async function run16(input, ctx) {
|
|
2958
3076
|
const requested = input.model ?? DEFAULT_MODEL6;
|
|
2959
3077
|
if (!ALLOWED4.includes(requested)) {
|
|
2960
3078
|
return {
|
|
@@ -2992,14 +3110,14 @@ async function run15(input, ctx) {
|
|
|
2992
3110
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
|
|
2993
3111
|
);
|
|
2994
3112
|
}
|
|
2995
|
-
var ALLOWED4, DEFAULT_MODEL6,
|
|
3113
|
+
var ALLOWED4, DEFAULT_MODEL6, InputSchema16, InputJsonSchema16, audioSkill;
|
|
2996
3114
|
var init_audio = __esm({
|
|
2997
3115
|
"src/skills/audio.ts"() {
|
|
2998
3116
|
init_poll();
|
|
2999
3117
|
init_types();
|
|
3000
3118
|
ALLOWED4 = ["seed-audio", "text-to-speech"];
|
|
3001
3119
|
DEFAULT_MODEL6 = "seed-audio";
|
|
3002
|
-
|
|
3120
|
+
InputSchema16 = zod.z.object({
|
|
3003
3121
|
prompt: zod.z.string().min(1).describe(
|
|
3004
3122
|
"seed-audio: a script to speak, or a scene description (dialogue, sound effects, music), up to 2048 characters; refer to audio_urls clips in order as @Audio1..@Audio3. text-to-speech: the exact text to speak, up to 10,000 characters."
|
|
3005
3123
|
),
|
|
@@ -3009,7 +3127,7 @@ var init_audio = __esm({
|
|
|
3009
3127
|
output_format: zod.z.string().optional().describe("Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."),
|
|
3010
3128
|
speed: zod.z.number().min(0.5).max(2).optional().describe("Speech speed multiplier, 0.5-2 (default 1). Both models.")
|
|
3011
3129
|
});
|
|
3012
|
-
|
|
3130
|
+
InputJsonSchema16 = {
|
|
3013
3131
|
type: "object",
|
|
3014
3132
|
properties: {
|
|
3015
3133
|
prompt: {
|
|
@@ -3047,15 +3165,15 @@ var init_audio = __esm({
|
|
|
3047
3165
|
audioSkill = {
|
|
3048
3166
|
name: "pixeldojo_audio",
|
|
3049
3167
|
description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain text-to-speech available). Voice cloning takes hosted reference clip URLs in audio_urls; local audio files are not accepted. Returns an audio URL, or a jobId for pixeldojo_status.",
|
|
3050
|
-
inputZod:
|
|
3051
|
-
inputJsonSchema:
|
|
3168
|
+
inputZod: InputSchema16,
|
|
3169
|
+
inputJsonSchema: InputJsonSchema16,
|
|
3052
3170
|
allowedModels: [...ALLOWED4],
|
|
3053
3171
|
defaultModel: DEFAULT_MODEL6,
|
|
3054
|
-
run:
|
|
3172
|
+
run: run16
|
|
3055
3173
|
};
|
|
3056
3174
|
}
|
|
3057
3175
|
});
|
|
3058
|
-
async function
|
|
3176
|
+
async function run17(input, ctx) {
|
|
3059
3177
|
if (input.source === "characters") {
|
|
3060
3178
|
const { characters } = await ctx.client.listCharacters({ q: input.q, limit: input.limit ?? 25 });
|
|
3061
3179
|
if (characters.length === 0) {
|
|
@@ -3106,17 +3224,17 @@ ${lines.join("\n")}
|
|
|
3106
3224
|
Pass any URL back into pixeldojo_generate, pixeldojo_edit, pixeldojo_character, or pixeldojo_upscale as a reference input.`
|
|
3107
3225
|
};
|
|
3108
3226
|
}
|
|
3109
|
-
var
|
|
3227
|
+
var InputSchema17, InputJsonSchema17, librarySkill;
|
|
3110
3228
|
var init_library = __esm({
|
|
3111
3229
|
"src/skills/library.ts"() {
|
|
3112
|
-
|
|
3230
|
+
InputSchema17 = zod.z.object({
|
|
3113
3231
|
source: zod.z.enum(["media", "characters"]).optional().describe("media (default): saved and recent results. characters: saved Character Studio characters and elements, usable as @Name in prompts."),
|
|
3114
3232
|
q: zod.z.string().optional().describe("Substring match against the prompt that produced the asset."),
|
|
3115
3233
|
model: zod.z.string().optional().describe("Restrict to a single model (apiId)."),
|
|
3116
3234
|
modality: zod.z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
|
|
3117
3235
|
limit: zod.z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
|
|
3118
3236
|
});
|
|
3119
|
-
|
|
3237
|
+
InputJsonSchema17 = {
|
|
3120
3238
|
type: "object",
|
|
3121
3239
|
properties: {
|
|
3122
3240
|
source: {
|
|
@@ -3141,15 +3259,15 @@ var init_library = __esm({
|
|
|
3141
3259
|
librarySkill = {
|
|
3142
3260
|
name: "pixeldojo_library",
|
|
3143
3261
|
description: "Search your Library, newest first. source=media (default): saved media (permanent) and recent API/MCP generations (deleted 1 hour after they finish), as URLs to reuse as references. source=characters: saved Character Studio characters and elements, each usable as @Name in any prompt.",
|
|
3144
|
-
inputZod:
|
|
3145
|
-
inputJsonSchema:
|
|
3262
|
+
inputZod: InputSchema17,
|
|
3263
|
+
inputJsonSchema: InputJsonSchema17,
|
|
3146
3264
|
allowedModels: [],
|
|
3147
3265
|
defaultModel: "",
|
|
3148
|
-
run:
|
|
3266
|
+
run: run17
|
|
3149
3267
|
};
|
|
3150
3268
|
}
|
|
3151
3269
|
});
|
|
3152
|
-
async function
|
|
3270
|
+
async function run18(input, ctx) {
|
|
3153
3271
|
const res = await ctx.client.saveMedia({
|
|
3154
3272
|
url: input.url,
|
|
3155
3273
|
prompt: input.prompt,
|
|
@@ -3163,17 +3281,17 @@ async function run17(input, ctx) {
|
|
|
3163
3281
|
It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
|
|
3164
3282
|
};
|
|
3165
3283
|
}
|
|
3166
|
-
var
|
|
3284
|
+
var InputSchema18, InputJsonSchema18, saveSkill;
|
|
3167
3285
|
var init_save = __esm({
|
|
3168
3286
|
"src/skills/save.ts"() {
|
|
3169
|
-
|
|
3287
|
+
InputSchema18 = zod.z.object({
|
|
3170
3288
|
url: zod.z.string().url().describe("The asset URL to save (image or video)."),
|
|
3171
3289
|
prompt: zod.z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
|
|
3172
3290
|
tool_name: zod.z.string().optional().describe("Tool/model slug that produced it."),
|
|
3173
3291
|
modality: zod.z.enum(["image", "video", "audio"]).optional(),
|
|
3174
3292
|
job_id: zod.z.string().optional().describe("The jobId that produced this asset, when known.")
|
|
3175
3293
|
});
|
|
3176
|
-
|
|
3294
|
+
InputJsonSchema18 = {
|
|
3177
3295
|
type: "object",
|
|
3178
3296
|
properties: {
|
|
3179
3297
|
url: { type: "string", format: "uri", description: "The result URL to keep (image or video from a PixelDojo job)." },
|
|
@@ -3187,15 +3305,15 @@ var init_save = __esm({
|
|
|
3187
3305
|
saveSkill = {
|
|
3188
3306
|
name: "pixeldojo_save",
|
|
3189
3307
|
description: `Save a generated image or video into the user's My Media library permanently ("save this", "keep it"). Results otherwise expire 1 hour after they finish. Pass the result URL; it is safety-checked before it is kept.`,
|
|
3190
|
-
inputZod:
|
|
3191
|
-
inputJsonSchema:
|
|
3308
|
+
inputZod: InputSchema18,
|
|
3309
|
+
inputJsonSchema: InputJsonSchema18,
|
|
3192
3310
|
allowedModels: [],
|
|
3193
3311
|
defaultModel: "",
|
|
3194
|
-
run:
|
|
3312
|
+
run: run18
|
|
3195
3313
|
};
|
|
3196
3314
|
}
|
|
3197
3315
|
});
|
|
3198
|
-
async function
|
|
3316
|
+
async function run19(input, ctx) {
|
|
3199
3317
|
const res = await ctx.client.saveCharacter({
|
|
3200
3318
|
name: input.name,
|
|
3201
3319
|
kind: input.kind ?? "character",
|
|
@@ -3207,17 +3325,17 @@ async function run18(input, ctx) {
|
|
|
3207
3325
|
text: `Saved ${res.kind} "${res.name}" to Character Studio (${res.image_urls.length} image${res.image_urls.length === 1 ? "" : "s"}). Use it in any prompt as ${res.mention}, for example "${res.mention} walking on a beach at sunset". It also appears at https://pixeldojo.ai/characters.`
|
|
3208
3326
|
};
|
|
3209
3327
|
}
|
|
3210
|
-
var KINDS,
|
|
3328
|
+
var KINDS, InputSchema19, InputJsonSchema19, saveCharacterSkill;
|
|
3211
3329
|
var init_save_character = __esm({
|
|
3212
3330
|
"src/skills/save-character.ts"() {
|
|
3213
3331
|
KINDS = ["character", "product", "outfit", "location", "object", "style"];
|
|
3214
|
-
|
|
3332
|
+
InputSchema19 = zod.z.object({
|
|
3215
3333
|
name: zod.z.string().min(1).max(60).describe("What to call it. Used as @Name in prompts."),
|
|
3216
3334
|
kind: zod.z.enum(KINDS).optional().describe("Default character."),
|
|
3217
3335
|
image_urls: zod.z.array(zod.z.string().url()).max(8).optional().describe("3 to 5 photos from different angles works best for a character."),
|
|
3218
3336
|
description: zod.z.string().max(1500).optional().describe("Appearance notes, or the scene text for an element.")
|
|
3219
3337
|
});
|
|
3220
|
-
|
|
3338
|
+
InputJsonSchema19 = {
|
|
3221
3339
|
type: "object",
|
|
3222
3340
|
properties: {
|
|
3223
3341
|
name: { type: "string", description: "What to call it. Used as @Name in prompts afterwards." },
|
|
@@ -3235,11 +3353,11 @@ var init_save_character = __esm({
|
|
|
3235
3353
|
saveCharacterSkill = {
|
|
3236
3354
|
name: "pixeldojo_save_character",
|
|
3237
3355
|
description: "Save a character (a person, from 3-5 photos) or an element (product, outfit, location, prop, style) to the user's Character Studio library. Afterwards write @Name in any prompt to use it. List saved ones with pixeldojo_library source=characters.",
|
|
3238
|
-
inputZod:
|
|
3239
|
-
inputJsonSchema:
|
|
3356
|
+
inputZod: InputSchema19,
|
|
3357
|
+
inputJsonSchema: InputJsonSchema19,
|
|
3240
3358
|
allowedModels: [],
|
|
3241
3359
|
defaultModel: "",
|
|
3242
|
-
run:
|
|
3360
|
+
run: run19
|
|
3243
3361
|
};
|
|
3244
3362
|
}
|
|
3245
3363
|
});
|
|
@@ -3488,6 +3606,7 @@ __export(skills_exports, {
|
|
|
3488
3606
|
audioSkill: () => audioSkill,
|
|
3489
3607
|
campaignSkill: () => campaignSkill,
|
|
3490
3608
|
campaignStatusSkill: () => campaignStatusSkill,
|
|
3609
|
+
captionSkill: () => captionSkill,
|
|
3491
3610
|
characterSkill: () => characterSkill,
|
|
3492
3611
|
editSkill: () => editSkill,
|
|
3493
3612
|
episodeSkill: () => episodeSkill,
|
|
@@ -3535,6 +3654,7 @@ var init_skills = __esm({
|
|
|
3535
3654
|
init_character();
|
|
3536
3655
|
init_storyboard();
|
|
3537
3656
|
init_upscale();
|
|
3657
|
+
init_caption();
|
|
3538
3658
|
init_status();
|
|
3539
3659
|
init_from_url();
|
|
3540
3660
|
init_film();
|
|
@@ -3555,6 +3675,7 @@ var init_skills = __esm({
|
|
|
3555
3675
|
characterSkill,
|
|
3556
3676
|
storyboardSkill,
|
|
3557
3677
|
upscaleSkill,
|
|
3678
|
+
captionSkill,
|
|
3558
3679
|
audioSkill,
|
|
3559
3680
|
librarySkill,
|
|
3560
3681
|
saveSkill,
|
|
@@ -3594,6 +3715,7 @@ var init_skills = __esm({
|
|
|
3594
3715
|
pixeldojo_character: render("Consistent character image"),
|
|
3595
3716
|
pixeldojo_storyboard: render("Storyboard preview video"),
|
|
3596
3717
|
pixeldojo_upscale: render("Upscale image or video"),
|
|
3718
|
+
pixeldojo_caption: render("Caption video"),
|
|
3597
3719
|
pixeldojo_audio: render("Generate music, speech or sound"),
|
|
3598
3720
|
pixeldojo_video: render("Change, animate or extend video"),
|
|
3599
3721
|
pixeldojo_ad: render("Make video ad"),
|
|
@@ -3664,7 +3786,7 @@ function makeRedactedLogger(target = console) {
|
|
|
3664
3786
|
}
|
|
3665
3787
|
|
|
3666
3788
|
// src/server.ts
|
|
3667
|
-
var PACKAGE_VERSION = "0.
|
|
3789
|
+
var PACKAGE_VERSION = "0.21.0" ;
|
|
3668
3790
|
var SERVER_INSTRUCTIONS = "Pixel Dojo generates images, video, audio and short films from one catalog of AI models, billed in credits from the connected Pixel Dojo account. A job that runs past the 30-second wait returns a jobId to check later; check it rather than submitting again, since every new submission is charged. Generated asset URLs are deleted one hour after they are created: pixeldojo_save keeps one in the user's My Media permanently (after a safety check), and finished films stay in the account library. In any prompt, @Name uses a character, product, outfit, location, prop or style the user saved in Character Studio: its photos are attached automatically. pixeldojo_library source=characters lists them, and pixeldojo_save_character saves a new one from 3 to 5 photos.";
|
|
3669
3791
|
function buildServer(options) {
|
|
3670
3792
|
if (!options.apiKey) {
|
|
@@ -3836,6 +3958,7 @@ function printInit() {
|
|
|
3836
3958
|
`# pixeldojo_character Consistent characters across shots.`,
|
|
3837
3959
|
`# pixeldojo_storyboard Multi-shot scenes from one brief.`,
|
|
3838
3960
|
`# pixeldojo_upscale Enhance any image or video.`,
|
|
3961
|
+
`# pixeldojo_caption Animated word-by-word captions on a video.`,
|
|
3839
3962
|
`# pixeldojo_audio Voices + effects + music in one clip.`,
|
|
3840
3963
|
`# pixeldojo_library Search your saved media + generations.`,
|
|
3841
3964
|
`# pixeldojo_*_workflow Save / run / list multi-step chains.`,
|