@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.js
CHANGED
|
@@ -458,14 +458,6 @@ function buildPayload(apiId, input, src, slot, audioSlot) {
|
|
|
458
458
|
async function run(input, ctx) {
|
|
459
459
|
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
460
460
|
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
461
|
-
if (input.upgrade_draft) return upgradeDraft(input, ctx);
|
|
462
|
-
if (input.draft && input.model && input.model !== DRAFT_MODEL) {
|
|
463
|
-
return {
|
|
464
|
-
kind: "text",
|
|
465
|
-
text: `Draft mode runs on ${DRAFT_MODEL} only. Drop model (or set it to ${DRAFT_MODEL}) to render a 480p draft.`
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
if (input.draft) input = { ...input, model: DRAFT_MODEL, resolution: DRAFT_RESOLUTION };
|
|
469
461
|
let resolved = input;
|
|
470
462
|
let referenceUrls = [];
|
|
471
463
|
try {
|
|
@@ -527,8 +519,6 @@ async function run(input, ctx) {
|
|
|
527
519
|
}
|
|
528
520
|
const body = buildPayload(apiId, resolved, src, refField, audioSlot);
|
|
529
521
|
const paramsNote = applyParams(body, input.params, input.model, apiId);
|
|
530
|
-
const draftDuration = input.draft ? Number(body.duration ?? DRAFT_DEFAULT_DURATION) : void 0;
|
|
531
|
-
if (input.draft) Object.assign(body, { draft: true, resolution: DRAFT_RESOLUTION, duration: draftDuration });
|
|
532
522
|
const note = [routeNote, paramsNote].filter(Boolean).join(" ");
|
|
533
523
|
const submitted = await ctx.client.run(apiId, body);
|
|
534
524
|
const result = await pollJobUntilDoneOrHandoff(
|
|
@@ -540,45 +530,15 @@ async function run(input, ctx) {
|
|
|
540
530
|
sleep: ctx.pollOverrides?.sleep
|
|
541
531
|
}
|
|
542
532
|
);
|
|
543
|
-
|
|
533
|
+
return prefixNote(
|
|
544
534
|
pollResultToSkillResult(
|
|
545
535
|
result,
|
|
546
536
|
(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(", ")}`
|
|
547
537
|
),
|
|
548
538
|
note
|
|
549
539
|
);
|
|
550
|
-
return draftDuration === void 0 ? out : { ...out, text: `${out.text}
|
|
551
|
-
|
|
552
|
-
${draftLine(submitted.jobId, draftDuration)}` };
|
|
553
|
-
}
|
|
554
|
-
function draftLine(jobId, duration) {
|
|
555
|
-
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).`;
|
|
556
|
-
}
|
|
557
|
-
async function upgradeDraft(input, ctx) {
|
|
558
|
-
if (input.duration === void 0) {
|
|
559
|
-
return {
|
|
560
|
-
kind: "text",
|
|
561
|
-
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).`
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
const submitted = await ctx.client.run(DRAFT_MODEL, {
|
|
565
|
-
draft_job_id: input.upgrade_draft,
|
|
566
|
-
resolution: DRAFT_FINAL_RESOLUTION,
|
|
567
|
-
duration: input.duration
|
|
568
|
-
});
|
|
569
|
-
const result = await pollJobUntilDoneOrHandoff(submitted.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
|
|
570
|
-
now: ctx.pollOverrides?.now,
|
|
571
|
-
sleep: ctx.pollOverrides?.sleep
|
|
572
|
-
});
|
|
573
|
-
return prefixNote(
|
|
574
|
-
pollResultToSkillResult(
|
|
575
|
-
result,
|
|
576
|
-
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upgraded to ${DRAFT_FINAL_RESOLUTION}. Asset: ${urls[0]}`
|
|
577
|
-
),
|
|
578
|
-
`Rendering draft ${input.upgrade_draft} at ${DRAFT_FINAL_RESOLUTION}.`
|
|
579
|
-
);
|
|
580
540
|
}
|
|
581
|
-
var
|
|
541
|
+
var InputSchema, DEFAULT_MODEL, VIDEO_REFERENCE_MODEL, VIDEO_REFERENCE_SLOTS, VIDEO_INPUT_MODELS, InputJsonSchema, generateSkill;
|
|
582
542
|
var init_generate = __esm({
|
|
583
543
|
"src/skills/generate.ts"() {
|
|
584
544
|
init_poll();
|
|
@@ -586,12 +546,6 @@ var init_generate = __esm({
|
|
|
586
546
|
init_upload_helpers();
|
|
587
547
|
init_schema_snap();
|
|
588
548
|
init_params();
|
|
589
|
-
DRAFT_MODEL = "seedance-2-5";
|
|
590
|
-
DRAFT_RESOLUTION = "480p";
|
|
591
|
-
DRAFT_FINAL_RESOLUTION = "1080p";
|
|
592
|
-
DRAFT_DEFAULT_DURATION = 5;
|
|
593
|
-
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.";
|
|
594
|
-
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.";
|
|
595
549
|
InputSchema = z.object({
|
|
596
550
|
prompt: 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.'),
|
|
597
551
|
model: z.string().optional().describe(
|
|
@@ -610,8 +564,6 @@ var init_generate = __esm({
|
|
|
610
564
|
"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."
|
|
611
565
|
),
|
|
612
566
|
params: ParamsInput,
|
|
613
|
-
draft: z.boolean().optional().describe(DRAFT_DESCRIPTION),
|
|
614
|
-
upgrade_draft: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/).optional().describe(UPGRADE_DESCRIPTION),
|
|
615
567
|
image_path: z.string().min(1).optional().describe(
|
|
616
568
|
"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."
|
|
617
569
|
),
|
|
@@ -665,8 +617,6 @@ var init_generate = __esm({
|
|
|
665
617
|
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."
|
|
666
618
|
},
|
|
667
619
|
params: PARAMS_JSON_SCHEMA,
|
|
668
|
-
draft: { type: "boolean", description: DRAFT_DESCRIPTION },
|
|
669
|
-
upgrade_draft: { type: "string", description: UPGRADE_DESCRIPTION },
|
|
670
620
|
image_urls: {
|
|
671
621
|
type: "array",
|
|
672
622
|
items: { type: "string", format: "uri" },
|
|
@@ -686,8 +636,8 @@ var init_generate = __esm({
|
|
|
686
636
|
};
|
|
687
637
|
generateSkill = {
|
|
688
638
|
name: "pixeldojo_generate",
|
|
689
|
-
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.
|
|
690
|
-
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.
|
|
639
|
+
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.",
|
|
640
|
+
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.",
|
|
691
641
|
inputZod: InputSchema,
|
|
692
642
|
inputJsonSchema: InputJsonSchema,
|
|
693
643
|
allowedModels: null,
|
|
@@ -1298,6 +1248,67 @@ var init_upscale = __esm({
|
|
|
1298
1248
|
}
|
|
1299
1249
|
});
|
|
1300
1250
|
async function run6(input, ctx) {
|
|
1251
|
+
const submitted = await ctx.client.run(MODEL, {
|
|
1252
|
+
prompt: "",
|
|
1253
|
+
video: input.video_url,
|
|
1254
|
+
...input.style ? { caption_style: input.style } : {},
|
|
1255
|
+
...input.highlight_color ? { highlight_color: input.highlight_color } : {}
|
|
1256
|
+
});
|
|
1257
|
+
const result = await pollJobUntilDoneOrHandoff(
|
|
1258
|
+
submitted.jobId,
|
|
1259
|
+
(id) => ctx.client.getJob(id),
|
|
1260
|
+
ctx.buildStatusUrl,
|
|
1261
|
+
{ now: ctx.pollOverrides?.now, sleep: ctx.pollOverrides?.sleep }
|
|
1262
|
+
);
|
|
1263
|
+
return pollResultToSkillResult(
|
|
1264
|
+
result,
|
|
1265
|
+
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Captioned. Asset: ${urls[0]}`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
var MODEL, STYLES, InputSchema6, InputJsonSchema6, captionSkill;
|
|
1269
|
+
var init_caption = __esm({
|
|
1270
|
+
"src/skills/caption.ts"() {
|
|
1271
|
+
init_poll();
|
|
1272
|
+
init_types();
|
|
1273
|
+
MODEL = "video-autocaption";
|
|
1274
|
+
STYLES = ["pop", "highlighter", "clean"];
|
|
1275
|
+
InputSchema6 = z.object({
|
|
1276
|
+
video_url: z.string().url().describe("URL of the video to caption. It must have speech."),
|
|
1277
|
+
style: z.enum(STYLES).optional(),
|
|
1278
|
+
highlight_color: z.string().optional()
|
|
1279
|
+
});
|
|
1280
|
+
InputJsonSchema6 = {
|
|
1281
|
+
type: "object",
|
|
1282
|
+
required: ["video_url"],
|
|
1283
|
+
properties: {
|
|
1284
|
+
video_url: {
|
|
1285
|
+
type: "string",
|
|
1286
|
+
format: "uri",
|
|
1287
|
+
description: "The video to caption (MP4 or MOV). It must have speech; a silent clip is refused uncharged."
|
|
1288
|
+
},
|
|
1289
|
+
style: {
|
|
1290
|
+
type: "string",
|
|
1291
|
+
enum: STYLES,
|
|
1292
|
+
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."
|
|
1293
|
+
},
|
|
1294
|
+
highlight_color: {
|
|
1295
|
+
type: "string",
|
|
1296
|
+
description: "The highlight as #RRGGBB (a brand color) or a name like yellow, green, pink. Defaults to yellow."
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
captionSkill = {
|
|
1301
|
+
name: "pixeldojo_caption",
|
|
1302
|
+
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.",
|
|
1303
|
+
inputZod: InputSchema6,
|
|
1304
|
+
inputJsonSchema: InputJsonSchema6,
|
|
1305
|
+
allowedModels: [MODEL],
|
|
1306
|
+
defaultModel: MODEL,
|
|
1307
|
+
run: run6
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
async function run7(input, ctx) {
|
|
1301
1312
|
const job = await ctx.client.getJob(input.jobId);
|
|
1302
1313
|
if (job.status === "completed") {
|
|
1303
1314
|
const urls = extractAssetUrls2(job);
|
|
@@ -1361,13 +1372,13 @@ function extractAssetUrls2(job) {
|
|
|
1361
1372
|
if (output?.video) return [output.video];
|
|
1362
1373
|
return [];
|
|
1363
1374
|
}
|
|
1364
|
-
var
|
|
1375
|
+
var InputSchema7, InputJsonSchema7, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
|
|
1365
1376
|
var init_status = __esm({
|
|
1366
1377
|
"src/skills/status.ts"() {
|
|
1367
|
-
|
|
1378
|
+
InputSchema7 = z.object({
|
|
1368
1379
|
jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo_* skill handoff.")
|
|
1369
1380
|
});
|
|
1370
|
-
|
|
1381
|
+
InputJsonSchema7 = {
|
|
1371
1382
|
type: "object",
|
|
1372
1383
|
properties: {
|
|
1373
1384
|
jobId: {
|
|
@@ -1382,16 +1393,16 @@ var init_status = __esm({
|
|
|
1382
1393
|
statusSkill = {
|
|
1383
1394
|
name: "pixeldojo_status",
|
|
1384
1395
|
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".',
|
|
1385
|
-
inputZod:
|
|
1386
|
-
inputJsonSchema:
|
|
1396
|
+
inputZod: InputSchema7,
|
|
1397
|
+
inputJsonSchema: InputJsonSchema7,
|
|
1387
1398
|
// status is special — it doesn't dispatch to a model
|
|
1388
1399
|
allowedModels: [],
|
|
1389
1400
|
defaultModel: "",
|
|
1390
|
-
run:
|
|
1401
|
+
run: run7
|
|
1391
1402
|
};
|
|
1392
1403
|
}
|
|
1393
1404
|
});
|
|
1394
|
-
async function
|
|
1405
|
+
async function run8(input, ctx) {
|
|
1395
1406
|
const product = await ctx.client.extractProduct(input.url);
|
|
1396
1407
|
const summary = [
|
|
1397
1408
|
`Product: ${product.name}`,
|
|
@@ -1408,13 +1419,13 @@ function truncate(input, max) {
|
|
|
1408
1419
|
if (input.length <= max) return input;
|
|
1409
1420
|
return input.slice(0, max - 3) + "...";
|
|
1410
1421
|
}
|
|
1411
|
-
var
|
|
1422
|
+
var InputSchema8, InputJsonSchema8, fromUrlSkill;
|
|
1412
1423
|
var init_from_url = __esm({
|
|
1413
1424
|
"src/skills/from-url.ts"() {
|
|
1414
|
-
|
|
1425
|
+
InputSchema8 = z.object({
|
|
1415
1426
|
url: z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
|
|
1416
1427
|
});
|
|
1417
|
-
|
|
1428
|
+
InputJsonSchema8 = {
|
|
1418
1429
|
type: "object",
|
|
1419
1430
|
properties: {
|
|
1420
1431
|
url: {
|
|
@@ -1427,12 +1438,12 @@ var init_from_url = __esm({
|
|
|
1427
1438
|
fromUrlSkill = {
|
|
1428
1439
|
name: "pixeldojo_from_url",
|
|
1429
1440
|
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.",
|
|
1430
|
-
inputZod:
|
|
1431
|
-
inputJsonSchema:
|
|
1441
|
+
inputZod: InputSchema8,
|
|
1442
|
+
inputJsonSchema: InputJsonSchema8,
|
|
1432
1443
|
// No model dispatch — this skill calls a server-side scrape, not a generation.
|
|
1433
1444
|
allowedModels: [],
|
|
1434
1445
|
defaultModel: "",
|
|
1435
|
-
run:
|
|
1446
|
+
run: run8
|
|
1436
1447
|
};
|
|
1437
1448
|
}
|
|
1438
1449
|
});
|
|
@@ -1453,10 +1464,51 @@ async function filmApi(ctx, method, path, body) {
|
|
|
1453
1464
|
const data = await res.json().catch(() => ({}));
|
|
1454
1465
|
return { status: res.status, data };
|
|
1455
1466
|
}
|
|
1467
|
+
function noFilm(status2, data, filmId, retry) {
|
|
1468
|
+
if (status2 === 404) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1469
|
+
const input = { film_id: filmId, ...Object.fromEntries(Object.entries(retry).filter(([, v]) => v !== void 0)) };
|
|
1470
|
+
return {
|
|
1471
|
+
kind: "text",
|
|
1472
|
+
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.`,
|
|
1473
|
+
...input.action === "status" ? { followUp: { input, afterSec: 30 } } : {}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1456
1476
|
function keepGoing(filmId, afterSec, extra = {}) {
|
|
1457
1477
|
const set = Object.fromEntries(Object.entries(extra).filter(([, v]) => v !== void 0));
|
|
1458
1478
|
return { input: { film_id: filmId, action: "status", ...set }, afterSec };
|
|
1459
1479
|
}
|
|
1480
|
+
function flaggedClip(sh) {
|
|
1481
|
+
const qa = sh.clipQa;
|
|
1482
|
+
const file = (u) => u?.split("/").pop();
|
|
1483
|
+
return Boolean(qa && qa.ok === false && sh.videoUrl && file(qa.url) === file(sh.videoUrl));
|
|
1484
|
+
}
|
|
1485
|
+
function editReviewing(sh, nowMs) {
|
|
1486
|
+
const e = sh.editSuggestion;
|
|
1487
|
+
const file = (u) => u?.split("/").pop();
|
|
1488
|
+
return Boolean(e?.pending && sh.videoUrl && file(e.url) === file(sh.videoUrl) && nowMs - Date.parse(e.checkedAt) < 4 * 6e4);
|
|
1489
|
+
}
|
|
1490
|
+
function openEdit(sh) {
|
|
1491
|
+
const e = sh.editSuggestion;
|
|
1492
|
+
const file = (u) => u?.split("/").pop();
|
|
1493
|
+
return Boolean(e && !e.pending && !e.appliedAt && !e.skipped && e.inSec !== void 0 && sh.videoUrl && file(e.url) === file(sh.videoUrl));
|
|
1494
|
+
}
|
|
1495
|
+
function cutSec(sh) {
|
|
1496
|
+
if (sh.leftOut) return 0;
|
|
1497
|
+
const speed = sh.speed && sh.speed > 1 ? sh.speed : 1;
|
|
1498
|
+
return Math.max(1, (sh.videoDurationSec ?? sh.durationSec ?? 0) - (sh.trimStartSec ?? 0) - (sh.trimEndSec ?? 0)) / speed;
|
|
1499
|
+
}
|
|
1500
|
+
function editorCutting(f, nowMs) {
|
|
1501
|
+
const c = f.editorCut;
|
|
1502
|
+
const fresh = (iso) => Boolean(iso && nowMs - Date.parse(iso) < 4 * 6e4);
|
|
1503
|
+
if (c?.pending) return fresh(c.startedAt);
|
|
1504
|
+
const lastReview = shots(f).map((sh) => sh.editSuggestion?.checkedAt).filter((t) => Boolean(t)).sort().pop();
|
|
1505
|
+
if (!lastReview || !fresh(lastReview)) return false;
|
|
1506
|
+
return !(c?.builtAt && c.builtAt >= lastReview);
|
|
1507
|
+
}
|
|
1508
|
+
function runtimeText(f) {
|
|
1509
|
+
const sec = Math.round(shots(f).reduce((n, sh) => n + cutSec(sh), 0));
|
|
1510
|
+
return sec < 90 ? `${sec}s` : `${Math.floor(sec / 60)}m ${sec % 60}s`;
|
|
1511
|
+
}
|
|
1460
1512
|
function shotLabels(f) {
|
|
1461
1513
|
return new Map(f.scenes.flatMap((s, si) => s.shots.map((sh, i) => [sh.id, `${si + 1}.${i + 1}`])));
|
|
1462
1514
|
}
|
|
@@ -1564,6 +1616,7 @@ async function plan(input, ctx) {
|
|
|
1564
1616
|
aspect: input.aspect,
|
|
1565
1617
|
look: input.look ?? (music ? MUSIC_VIDEO_LOOKS[0] : "live-action"),
|
|
1566
1618
|
lengthTarget: input.length,
|
|
1619
|
+
...!music && input.pacing === "trailer" ? { pacing: "trailer" } : {},
|
|
1567
1620
|
assetIds: [],
|
|
1568
1621
|
...music ? {
|
|
1569
1622
|
// No script step names a music video, so it would stay "Untitled film".
|
|
@@ -1637,12 +1690,25 @@ ${err} Call action "status" to retry the cut.` };
|
|
|
1637
1690
|
return { kind: "text", text: plannedText(planned.film, ctx) };
|
|
1638
1691
|
}
|
|
1639
1692
|
async function produce(filmId, soundtrack, ctx) {
|
|
1640
|
-
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1641
|
-
if (!data.film) return {
|
|
1693
|
+
const { status: status2, data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1694
|
+
if (!data.film) return noFilm(status2, data, filmId, { action: "produce", soundtrack });
|
|
1642
1695
|
const planning = await resumePlanning(data.film, ctx, soundtrack);
|
|
1643
1696
|
if (planning) return planning;
|
|
1644
1697
|
const uncast = await uncastRolesOf(ctx, filmId);
|
|
1645
1698
|
if (uncast.length) return { kind: "text", text: `Nothing was charged. ${castNeededText(filmId, uncast)}` };
|
|
1699
|
+
let propsNote = "";
|
|
1700
|
+
if (!shots(data.film).some((sh) => sh.frameUrl)) {
|
|
1701
|
+
const locked = await filmApi(
|
|
1702
|
+
ctx,
|
|
1703
|
+
"POST",
|
|
1704
|
+
`/api/films/${filmId}/props`,
|
|
1705
|
+
{}
|
|
1706
|
+
).catch(() => null);
|
|
1707
|
+
const made = locked?.status === 200 ? locked.data.created ?? [] : [];
|
|
1708
|
+
if (made.length) {
|
|
1709
|
+
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` : ""}.`;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1646
1712
|
const pending = shots(data.film).filter((sh) => sh.status === "idle" || sh.status === "failed" && !sh.frameUrl);
|
|
1647
1713
|
let fired = 0;
|
|
1648
1714
|
let refused;
|
|
@@ -1656,7 +1722,7 @@ async function produce(filmId, soundtrack, ctx) {
|
|
|
1656
1722
|
}
|
|
1657
1723
|
return {
|
|
1658
1724
|
kind: "text",
|
|
1659
|
-
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.`,
|
|
1725
|
+
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.`,
|
|
1660
1726
|
// The caller's soundtrack choice rides along: the follow-up status calls
|
|
1661
1727
|
// are the ones that reach the score.
|
|
1662
1728
|
followUp: keepGoing(filmId, 30, { soundtrack })
|
|
@@ -1665,7 +1731,7 @@ async function produce(filmId, soundtrack, ctx) {
|
|
|
1665
1731
|
function buildScorePlanBody(film) {
|
|
1666
1732
|
const scenes = film.scenes.filter((s) => s.shots.length > 0);
|
|
1667
1733
|
const sections = scenes.flatMap((scene, i) => {
|
|
1668
|
-
const runtime = Math.max(3e3, scene.shots.reduce((n, sh) => n + (sh
|
|
1734
|
+
const runtime = Math.max(3e3, Math.round(scene.shots.reduce((n, sh) => n + cutSec(sh), 0) * 1e3));
|
|
1669
1735
|
const styles = i === 0 ? ARC.opening : i === scenes.length - 1 ? ARC.finale : ARC.middle;
|
|
1670
1736
|
const parts = Math.max(1, Math.ceil(runtime / 12e4));
|
|
1671
1737
|
const partMs = Math.round(runtime / parts);
|
|
@@ -1688,7 +1754,7 @@ function buildScorePlanBody(film) {
|
|
|
1688
1754
|
};
|
|
1689
1755
|
}
|
|
1690
1756
|
function buildFlatMusicBody(film, all) {
|
|
1691
|
-
const runtimeSec = all.reduce((n, sh) => n + (sh
|
|
1757
|
+
const runtimeSec = all.reduce((n, sh) => n + cutSec(sh), 0);
|
|
1692
1758
|
const ms = Math.min(3e5, Math.max(3e4, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
|
|
1693
1759
|
return {
|
|
1694
1760
|
prompt: `${LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]}. Instrumental only, no vocals. One consistent theme, loopable as a background bed under dialogue.`,
|
|
@@ -1698,18 +1764,19 @@ function buildFlatMusicBody(film, all) {
|
|
|
1698
1764
|
};
|
|
1699
1765
|
}
|
|
1700
1766
|
async function advance(filmId, soundtrack, musicJobId, ctx) {
|
|
1701
|
-
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}?refresh=1`);
|
|
1767
|
+
const { status: status2, data } = await filmApi(ctx, "GET", `/api/films/${filmId}?refresh=1`);
|
|
1702
1768
|
const film = data.film;
|
|
1703
|
-
if (!film) return {
|
|
1769
|
+
if (!film) return noFilm(status2, data, filmId, { action: "status", soundtrack });
|
|
1704
1770
|
const all = shots(film);
|
|
1705
1771
|
if (film.status === "exported" && film.exportUrl) {
|
|
1706
1772
|
return {
|
|
1707
1773
|
kind: "text",
|
|
1708
1774
|
text: [
|
|
1709
|
-
`Film finished: "${film.title}"
|
|
1775
|
+
`Film finished: "${film.title}" (${runtimeText(film)})
|
|
1710
1776
|
Watch/download: ${film.exportUrl}
|
|
1711
1777
|
Workspace: ${ctx.baseUrl}/film-studio/${film.id}
|
|
1712
1778
|
It is saved in the Library folder "Film: ${film.title}".`,
|
|
1779
|
+
stillFlagged(film),
|
|
1713
1780
|
shotIndex(film)
|
|
1714
1781
|
].filter(Boolean).join("\n")
|
|
1715
1782
|
};
|
|
@@ -1765,7 +1832,45 @@ It is saved in the Library folder "Film: ${film.title}".`,
|
|
|
1765
1832
|
const clipQueue = framesDone ? all.filter((sh) => sh.status === "frame-ready" || sh.status === "failed" && frameDone(sh) && !isRefused(sh)) : [];
|
|
1766
1833
|
for (const sh of clipQueue.slice(0, Math.max(0, 4 - inFlight))) await dispatch(sh, "clip");
|
|
1767
1834
|
const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
|
|
1835
|
+
const toRedo = clipsDone ? all.filter((sh) => flaggedClip(sh) && !(sh.manualClipRedos ?? 0)) : [];
|
|
1836
|
+
if (toRedo.length) {
|
|
1837
|
+
const labels2 = shotLabels(film);
|
|
1838
|
+
const redone = [];
|
|
1839
|
+
let refusedRedo;
|
|
1840
|
+
for (const sh of toRedo) {
|
|
1841
|
+
const r = await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "clip" });
|
|
1842
|
+
if (r.status === 200) redone.push(`${labels2.get(sh.id)} (${sh.clipQa.note || sh.clipQa.issues.join(", ")})`);
|
|
1843
|
+
else if (!benignDispatchRefusal(r.status, r.data.error)) refusedRedo ??= r.data.error ?? `HTTP ${r.status}`;
|
|
1844
|
+
}
|
|
1845
|
+
if (redone.length) {
|
|
1846
|
+
return {
|
|
1847
|
+
kind: "text",
|
|
1848
|
+
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"}.`,
|
|
1849
|
+
followUp: keepGoing(filmId, 30, { soundtrack })
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1768
1853
|
if (clipsDone) {
|
|
1854
|
+
const reviewing = all.filter((sh) => editReviewing(sh, Date.now())).length;
|
|
1855
|
+
if (reviewing) {
|
|
1856
|
+
return {
|
|
1857
|
+
kind: "text",
|
|
1858
|
+
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"}.`,
|
|
1859
|
+
followUp: keepGoing(filmId, 20, { soundtrack })
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
if (editorCutting(film, Date.now())) {
|
|
1863
|
+
return {
|
|
1864
|
+
kind: "text",
|
|
1865
|
+
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"}.`,
|
|
1866
|
+
followUp: keepGoing(filmId, 30, { soundtrack })
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
const openCut = Boolean(film.editorCut?.shots?.length && !film.editorCut.appliedAt && !film.editorCut.skipped);
|
|
1870
|
+
if (openCut || all.some(openEdit)) {
|
|
1871
|
+
const cut = await filmApi(ctx, "POST", `/api/films/${filmId}/edit`, { action: "apply" });
|
|
1872
|
+
if (cut.data.film) film.scenes = cut.data.film.scenes;
|
|
1873
|
+
}
|
|
1769
1874
|
if (all.some((sh) => needsSave(sh.videoUrl))) {
|
|
1770
1875
|
await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "clip" });
|
|
1771
1876
|
}
|
|
@@ -1794,7 +1899,7 @@ It is saved in the Library folder "Film: ${film.title}".`,
|
|
|
1794
1899
|
followUp: keepGoing(filmId, 20, { soundtrack, music_job_id: musicJobId })
|
|
1795
1900
|
};
|
|
1796
1901
|
}
|
|
1797
|
-
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film,
|
|
1902
|
+
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film, shots(film));
|
|
1798
1903
|
const dispatch2 = await ctx.client.run("text-to-music", body);
|
|
1799
1904
|
return {
|
|
1800
1905
|
kind: "text",
|
|
@@ -1835,6 +1940,12 @@ It is saved in the Library folder "Film: ${film.title}".`,
|
|
|
1835
1940
|
...moving && !paused ? { followUp: keepGoing(filmId, 30, { soundtrack }) } : {}
|
|
1836
1941
|
};
|
|
1837
1942
|
}
|
|
1943
|
+
function stillFlagged(f) {
|
|
1944
|
+
const labels = shotLabels(f);
|
|
1945
|
+
const flagged = shots(f).filter(flaggedClip);
|
|
1946
|
+
if (!flagged.length) return "";
|
|
1947
|
+
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).`;
|
|
1948
|
+
}
|
|
1838
1949
|
function shotIndex(f) {
|
|
1839
1950
|
const all = shots(f);
|
|
1840
1951
|
if (all.length === 0 || all.length > 24) return "";
|
|
@@ -1843,9 +1954,10 @@ function shotIndex(f) {
|
|
|
1843
1954
|
const clock = (sec) => `${Math.floor(sec / 60)}:${String(Math.round(sec % 60)).padStart(2, "0")}`;
|
|
1844
1955
|
const lines = all.map((sh) => {
|
|
1845
1956
|
const from = at;
|
|
1846
|
-
at += sh
|
|
1957
|
+
at += cutSec(sh);
|
|
1847
1958
|
const what = sh.prompt.replace(/\s+/g, " ").trim();
|
|
1848
|
-
|
|
1959
|
+
const when = sh.leftOut ? "left out of the cut" : `about ${clock(from)}-${clock(at)}`;
|
|
1960
|
+
return ` ${labels.get(sh.id)} (${when}): ${what.length > 70 ? `${what.slice(0, 67)}...` : what}`;
|
|
1849
1961
|
});
|
|
1850
1962
|
return ['Shots (redo any with action "redo" and its label):', ...lines].join("\n");
|
|
1851
1963
|
}
|
|
@@ -1854,9 +1966,9 @@ async function redoShots(input, ctx) {
|
|
|
1854
1966
|
if (!input.shots?.length) {
|
|
1855
1967
|
return { kind: "text", text: 'Pass shots: the labels status reports, like ["2.3"] for scene 2, shot 3.' };
|
|
1856
1968
|
}
|
|
1857
|
-
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1969
|
+
const { status: status2, data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1858
1970
|
const film = data.film;
|
|
1859
|
-
if (!film) return {
|
|
1971
|
+
if (!film) return noFilm(status2, data, filmId, { action: "redo", shots: input.shots, redo: input.redo });
|
|
1860
1972
|
const labels = shotLabels(film);
|
|
1861
1973
|
const byLabel = new Map(shots(film).map((sh) => [labels.get(sh.id), sh]));
|
|
1862
1974
|
const byId = new Map(shots(film).map((sh) => [sh.id, sh]));
|
|
@@ -1894,7 +2006,7 @@ async function redoShots(input, ctx) {
|
|
|
1894
2006
|
followUp: keepGoing(filmId, 30, { soundtrack: input.soundtrack })
|
|
1895
2007
|
};
|
|
1896
2008
|
}
|
|
1897
|
-
async function
|
|
2009
|
+
async function run9(input, ctx) {
|
|
1898
2010
|
const action = input.action === "plan" && input.film_id ? input.cast?.length ? "cast" : "status" : input.action;
|
|
1899
2011
|
if (action === "plan") return plan(input, ctx);
|
|
1900
2012
|
if (!input.film_id) return { kind: "text", text: "produce/status/cast/redo need the film_id returned by the plan step." };
|
|
@@ -1914,7 +2026,7 @@ async function run8(input, ctx) {
|
|
|
1914
2026
|
if (action === "redo") return redoShots(input, ctx);
|
|
1915
2027
|
return advance(input.film_id, input.soundtrack, input.music_job_id, ctx);
|
|
1916
2028
|
}
|
|
1917
|
-
var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, MUSIC_CLIP_ENGINE_ID,
|
|
2029
|
+
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;
|
|
1918
2030
|
var init_film = __esm({
|
|
1919
2031
|
"src/skills/film.ts"() {
|
|
1920
2032
|
init_upload_helpers();
|
|
@@ -1922,7 +2034,7 @@ var init_film = __esm({
|
|
|
1922
2034
|
MUSIC_VIDEO_LOOKS = ["rap-video", "neon-club", "stage-performance", "retro-camcorder", "y2k-pop", "moody-rnb", "rock-grit", "dream-pop", "k-pop"];
|
|
1923
2035
|
LOOKS = [...STORY_LOOKS, ...MUSIC_VIDEO_LOOKS];
|
|
1924
2036
|
MUSIC_CLIP_ENGINE_ID = "minimax-h3-reference";
|
|
1925
|
-
|
|
2037
|
+
InputSchema9 = z.object({
|
|
1926
2038
|
idea: z.string().max(2e3).optional().describe("One-to-three sentence film idea. The studio writes the screenplay (free). Provide idea OR script for planning."),
|
|
1927
2039
|
script: z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
|
|
1928
2040
|
look: z.enum(LOOKS).optional().describe("Visual style carried through frames, clips, and soundtrack. Default live-action, or rap-video for a music video."),
|
|
@@ -1931,7 +2043,8 @@ var init_film = __esm({
|
|
|
1931
2043
|
song_duration_sec: z.number().positive().max(7200).optional().describe("Music video mode (optional): the song length in seconds. Omit it and the studio measures the song."),
|
|
1932
2044
|
lyrics: z.string().max(1e4).optional().describe("Music video mode: the song lyrics, so shots land on the right lines. Optional."),
|
|
1933
2045
|
aspect: z.enum(["16:9", "9:16"]).default("16:9"),
|
|
1934
|
-
|
|
2046
|
+
pacing: 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."),
|
|
2047
|
+
length: z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length of the finished film."),
|
|
1935
2048
|
film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status/cast/redo."),
|
|
1936
2049
|
action: 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."),
|
|
1937
2050
|
shots: z.array(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.'),
|
|
@@ -1949,10 +2062,10 @@ var init_film = __esm({
|
|
|
1949
2062
|
soundtrack: 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."),
|
|
1950
2063
|
music_job_id: 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.")
|
|
1951
2064
|
});
|
|
1952
|
-
|
|
2065
|
+
InputJsonSchema9 = {
|
|
1953
2066
|
type: "object",
|
|
1954
2067
|
properties: {
|
|
1955
|
-
idea: { type: "string", description:
|
|
2068
|
+
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.` },
|
|
1956
2069
|
script: { type: "string", description: "Plan step: a full screenplay (sluglines, action lines, dialogue) to plan instead of an idea." },
|
|
1957
2070
|
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).` },
|
|
1958
2071
|
song_url: {
|
|
@@ -1969,7 +2082,12 @@ var init_film = __esm({
|
|
|
1969
2082
|
},
|
|
1970
2083
|
lyrics: { type: "string", description: "Plan step, music video mode (optional): the song lyrics, so shots land on the right lines." },
|
|
1971
2084
|
aspect: { type: "string", enum: ["16:9", "9:16"], description: "Plan step: frame aspect ratio. Default 16:9." },
|
|
1972
|
-
length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Plan step: target length
|
|
2085
|
+
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." },
|
|
2086
|
+
pacing: {
|
|
2087
|
+
type: "string",
|
|
2088
|
+
enum: ["story", "trailer"],
|
|
2089
|
+
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."
|
|
2090
|
+
},
|
|
1973
2091
|
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".' },
|
|
1974
2092
|
action: {
|
|
1975
2093
|
type: "string",
|
|
@@ -2028,7 +2146,7 @@ var init_film = __esm({
|
|
|
2028
2146
|
benignDispatchRefusal = (status2, error) => status2 === 429 || status2 === 409 && /already generating/i.test(error ?? "");
|
|
2029
2147
|
count = (f, pred) => shots(f).filter(pred).length;
|
|
2030
2148
|
frameDone = (sh) => Boolean(sh.frameUrl) && sh.status !== "idle" && sh.status !== "frame-generating";
|
|
2031
|
-
RETRY_CLEARS = /automatic audio check flagged|output audio\b[\s\S]*?sensitive information|temporarily unavailable/i;
|
|
2149
|
+
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;
|
|
2032
2150
|
REFUSED = /copyright|infringement|content filter|safety filter|moderation|blocked|flagged|sensitive|real (?:person|human)|nsfw|content policy|policy violation|inappropriate/i;
|
|
2033
2151
|
isRefused = (sh) => sh.status === "failed" && Boolean(sh.errorMessage) && !RETRY_CLEARS.test(sh.errorMessage) && REFUSED.test(sh.errorMessage);
|
|
2034
2152
|
PLANNER_POLL_MS = 3e3;
|
|
@@ -2058,11 +2176,11 @@ var init_film = __esm({
|
|
|
2058
2176
|
name: "pixeldojo_film",
|
|
2059
2177
|
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.",
|
|
2060
2178
|
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.",
|
|
2061
|
-
inputZod:
|
|
2062
|
-
inputJsonSchema:
|
|
2179
|
+
inputZod: InputSchema9,
|
|
2180
|
+
inputJsonSchema: InputJsonSchema9,
|
|
2063
2181
|
allowedModels: [],
|
|
2064
2182
|
defaultModel: "",
|
|
2065
|
-
run:
|
|
2183
|
+
run: run9
|
|
2066
2184
|
};
|
|
2067
2185
|
}
|
|
2068
2186
|
});
|
|
@@ -2149,7 +2267,7 @@ function buildVideoPayload(verb, apiId, i) {
|
|
|
2149
2267
|
};
|
|
2150
2268
|
}
|
|
2151
2269
|
}
|
|
2152
|
-
async function
|
|
2270
|
+
async function run10(input, ctx) {
|
|
2153
2271
|
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
2154
2272
|
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
2155
2273
|
const verb = input.verb;
|
|
@@ -2212,7 +2330,7 @@ async function run9(input, ctx) {
|
|
|
2212
2330
|
notes.join(" ")
|
|
2213
2331
|
);
|
|
2214
2332
|
}
|
|
2215
|
-
var VIDEO_STUDIO_ENGINES, VERBS, BACKWARD_ENGINES, clamp, NEEDS_VIDEO_DURATION, NO_IMAGE_ENGINES,
|
|
2333
|
+
var VIDEO_STUDIO_ENGINES, VERBS, BACKWARD_ENGINES, clamp, NEEDS_VIDEO_DURATION, NO_IMAGE_ENGINES, InputSchema10, engineList, InputJsonSchema10, BASE_DESCRIPTION, videoSkill;
|
|
2216
2334
|
var init_video = __esm({
|
|
2217
2335
|
"src/skills/video.ts"() {
|
|
2218
2336
|
init_poll();
|
|
@@ -2246,7 +2364,7 @@ var init_video = __esm({
|
|
|
2246
2364
|
"wan-video-character-swap"
|
|
2247
2365
|
]);
|
|
2248
2366
|
NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["flux-video-edit", "xai-video-edit"]);
|
|
2249
|
-
|
|
2367
|
+
InputSchema10 = z.object({
|
|
2250
2368
|
verb: z.enum(VERBS),
|
|
2251
2369
|
prompt: z.string().max(8e3).optional(),
|
|
2252
2370
|
video_url: z.string().url().optional(),
|
|
@@ -2261,7 +2379,7 @@ var init_video = __esm({
|
|
|
2261
2379
|
params: ParamsInput
|
|
2262
2380
|
});
|
|
2263
2381
|
engineList = (verb) => `${verb}: ${VIDEO_STUDIO_ENGINES[verb].join(", ")} (default ${VIDEO_STUDIO_ENGINES[verb][0]})`;
|
|
2264
|
-
|
|
2382
|
+
InputJsonSchema10 = {
|
|
2265
2383
|
type: "object",
|
|
2266
2384
|
properties: {
|
|
2267
2385
|
verb: {
|
|
@@ -2322,11 +2440,11 @@ var init_video = __esm({
|
|
|
2322
2440
|
name: "pixeldojo_video",
|
|
2323
2441
|
description: `${BASE_DESCRIPTION} Local files: video_path / image_path (uploaded to 24h temp storage first).`,
|
|
2324
2442
|
hostedDescription: `${BASE_DESCRIPTION} Media must be public http(s) URLs.`,
|
|
2325
|
-
inputZod:
|
|
2326
|
-
inputJsonSchema:
|
|
2443
|
+
inputZod: InputSchema10,
|
|
2444
|
+
inputJsonSchema: InputJsonSchema10,
|
|
2327
2445
|
allowedModels: Array.from(new Set(VERBS.flatMap((v) => VIDEO_STUDIO_ENGINES[v]))),
|
|
2328
2446
|
defaultModel: VIDEO_STUDIO_ENGINES.change[0],
|
|
2329
|
-
run:
|
|
2447
|
+
run: run10
|
|
2330
2448
|
};
|
|
2331
2449
|
}
|
|
2332
2450
|
});
|
|
@@ -2501,28 +2619,28 @@ async function produce2(id, ctx) {
|
|
|
2501
2619
|
const musicNote = ep.musicEnabled && ep.music?.status === "failed" ? " The music bed failed, so this cut has no music." : "";
|
|
2502
2620
|
return { kind: "text", text: `Export started, free${prior}.${musicNote} ${next(id, "produce", "~60s")}` };
|
|
2503
2621
|
}
|
|
2504
|
-
async function
|
|
2622
|
+
async function run11(input, ctx) {
|
|
2505
2623
|
const action = input.action === "create" && input.episode_id ? "status" : input.action;
|
|
2506
2624
|
if (action === "create") return create(input, ctx);
|
|
2507
2625
|
if (!input.episode_id) return { kind: "text", text: "produce and status need the episode_id returned by the create step." };
|
|
2508
2626
|
if (action === "produce") return produce2(input.episode_id, ctx);
|
|
2509
2627
|
return status(input.episode_id, ctx);
|
|
2510
2628
|
}
|
|
2511
|
-
var
|
|
2629
|
+
var STYLES2, CATEGORIES, ENGINES, MIN_SECONDS, MAX_SECONDS, MAX_IN_FLIGHT, InputSchema11, InputJsonSchema11, call, next, needsRender, PLAN_POLL_MS, PLAN_MAX_POLLS, stillPlanning, episodeSkill;
|
|
2512
2630
|
var init_episode = __esm({
|
|
2513
2631
|
"src/skills/episode.ts"() {
|
|
2514
|
-
|
|
2632
|
+
STYLES2 = ["editorial-motion", "paper-diorama", "hand-drawn", "storybook", "poster-vector", "soft-3d"];
|
|
2515
2633
|
CATEGORIES = ["education", "history", "kids", "storytelling", "custom"];
|
|
2516
2634
|
ENGINES = ["omni-draft", "omni-final", "still-motion"];
|
|
2517
2635
|
MIN_SECONDS = 60;
|
|
2518
2636
|
MAX_SECONDS = 300;
|
|
2519
2637
|
MAX_IN_FLIGHT = 4;
|
|
2520
|
-
|
|
2638
|
+
InputSchema11 = z.object({
|
|
2521
2639
|
action: z.enum(["create", "produce", "status"]).default("create"),
|
|
2522
2640
|
topic: z.string().min(8).max(400).optional(),
|
|
2523
2641
|
episode_id: z.string().max(40).optional(),
|
|
2524
2642
|
category: z.enum(CATEGORIES).default("education"),
|
|
2525
|
-
style: z.enum(
|
|
2643
|
+
style: z.enum(STYLES2).default("editorial-motion"),
|
|
2526
2644
|
aspect: z.enum(["16:9", "9:16"]).default("16:9"),
|
|
2527
2645
|
length_seconds: z.number().int().min(MIN_SECONDS).max(MAX_SECONDS).default(120),
|
|
2528
2646
|
voice: z.string().min(1).max(80).default("Wise_Woman"),
|
|
@@ -2530,7 +2648,7 @@ var init_episode = __esm({
|
|
|
2530
2648
|
music: z.boolean().default(true),
|
|
2531
2649
|
title: z.string().max(120).optional()
|
|
2532
2650
|
});
|
|
2533
|
-
|
|
2651
|
+
InputJsonSchema11 = {
|
|
2534
2652
|
type: "object",
|
|
2535
2653
|
properties: {
|
|
2536
2654
|
action: {
|
|
@@ -2541,7 +2659,7 @@ var init_episode = __esm({
|
|
|
2541
2659
|
topic: { type: "string", description: "Create step: what the episode is about, 8 to 400 characters." },
|
|
2542
2660
|
episode_id: { type: "string", description: "The episode_id from the create step. Required for produce and status." },
|
|
2543
2661
|
category: { type: "string", enum: [...CATEGORIES], description: "Create step: tone of the script. Default education." },
|
|
2544
|
-
style: { type: "string", enum: [...
|
|
2662
|
+
style: { type: "string", enum: [...STYLES2], description: "Create step: visual style of every scene. Default editorial-motion." },
|
|
2545
2663
|
aspect: { type: "string", enum: ["16:9", "9:16"], description: "Create step: 16:9 landscape (default) or 9:16 vertical." },
|
|
2546
2664
|
length_seconds: { type: "integer", minimum: MIN_SECONDS, maximum: MAX_SECONDS, description: "Create step: target length in seconds. Default 120." },
|
|
2547
2665
|
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." },
|
|
@@ -2563,15 +2681,15 @@ var init_episode = __esm({
|
|
|
2563
2681
|
episodeSkill = {
|
|
2564
2682
|
name: "pixeldojo_episode",
|
|
2565
2683
|
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.",
|
|
2566
|
-
inputZod:
|
|
2567
|
-
inputJsonSchema:
|
|
2684
|
+
inputZod: InputSchema11,
|
|
2685
|
+
inputJsonSchema: InputJsonSchema11,
|
|
2568
2686
|
allowedModels: [],
|
|
2569
2687
|
defaultModel: "",
|
|
2570
|
-
run:
|
|
2688
|
+
run: run11
|
|
2571
2689
|
};
|
|
2572
2690
|
}
|
|
2573
2691
|
});
|
|
2574
|
-
async function
|
|
2692
|
+
async function run12(input, ctx) {
|
|
2575
2693
|
const productImages = [
|
|
2576
2694
|
...input.product_image_urls ?? [],
|
|
2577
2695
|
...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx)))
|
|
@@ -2614,13 +2732,13 @@ async function run11(input, ctx) {
|
|
|
2614
2732
|
(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]}`
|
|
2615
2733
|
);
|
|
2616
2734
|
}
|
|
2617
|
-
var
|
|
2735
|
+
var InputSchema12, InputJsonSchema12, adSkill;
|
|
2618
2736
|
var init_ad = __esm({
|
|
2619
2737
|
"src/skills/ad.ts"() {
|
|
2620
2738
|
init_types();
|
|
2621
2739
|
init_poll();
|
|
2622
2740
|
init_upload_helpers();
|
|
2623
|
-
|
|
2741
|
+
InputSchema12 = z.object({
|
|
2624
2742
|
style: z.string().max(80).optional().describe(
|
|
2625
2743
|
"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."
|
|
2626
2744
|
),
|
|
@@ -2646,7 +2764,7 @@ var init_ad = __esm({
|
|
|
2646
2764
|
path: ["product_url"]
|
|
2647
2765
|
}
|
|
2648
2766
|
);
|
|
2649
|
-
|
|
2767
|
+
InputJsonSchema12 = {
|
|
2650
2768
|
type: "object",
|
|
2651
2769
|
properties: {
|
|
2652
2770
|
style: {
|
|
@@ -2733,16 +2851,16 @@ var init_ad = __esm({
|
|
|
2733
2851
|
name: "pixeldojo_ad",
|
|
2734
2852
|
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.",
|
|
2735
2853
|
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.",
|
|
2736
|
-
inputZod:
|
|
2737
|
-
inputJsonSchema:
|
|
2854
|
+
inputZod: InputSchema12,
|
|
2855
|
+
inputJsonSchema: InputJsonSchema12,
|
|
2738
2856
|
// Composition skill — the server routes to the ad engine internally.
|
|
2739
2857
|
allowedModels: [],
|
|
2740
2858
|
defaultModel: "",
|
|
2741
|
-
run:
|
|
2859
|
+
run: run12
|
|
2742
2860
|
};
|
|
2743
2861
|
}
|
|
2744
2862
|
});
|
|
2745
|
-
async function
|
|
2863
|
+
async function run13(input, ctx) {
|
|
2746
2864
|
const client = ctx.client;
|
|
2747
2865
|
const result = await client.runCampaign(input);
|
|
2748
2866
|
if (result.kind === "campaign") {
|
|
@@ -2769,10 +2887,10 @@ async function run12(input, ctx) {
|
|
|
2769
2887
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
2770
2888
|
};
|
|
2771
2889
|
}
|
|
2772
|
-
var
|
|
2890
|
+
var InputSchema13, InputJsonSchema13, campaignSkill;
|
|
2773
2891
|
var init_campaign = __esm({
|
|
2774
2892
|
"src/skills/campaign.ts"() {
|
|
2775
|
-
|
|
2893
|
+
InputSchema13 = z.object({
|
|
2776
2894
|
productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
2777
2895
|
product: z.object({
|
|
2778
2896
|
name: z.string().min(1).max(200),
|
|
@@ -2788,7 +2906,7 @@ var init_campaign = __esm({
|
|
|
2788
2906
|
message: "productUrl or product is required",
|
|
2789
2907
|
path: ["productUrl"]
|
|
2790
2908
|
});
|
|
2791
|
-
|
|
2909
|
+
InputJsonSchema13 = {
|
|
2792
2910
|
type: "object",
|
|
2793
2911
|
properties: {
|
|
2794
2912
|
productUrl: {
|
|
@@ -2839,17 +2957,17 @@ var init_campaign = __esm({
|
|
|
2839
2957
|
campaignSkill = {
|
|
2840
2958
|
name: "pixeldojo_campaign",
|
|
2841
2959
|
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.",
|
|
2842
|
-
inputZod:
|
|
2843
|
-
inputJsonSchema:
|
|
2960
|
+
inputZod: InputSchema13,
|
|
2961
|
+
inputJsonSchema: InputJsonSchema13,
|
|
2844
2962
|
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
2845
2963
|
// single allowed-models list applies here.
|
|
2846
2964
|
allowedModels: [],
|
|
2847
2965
|
defaultModel: "",
|
|
2848
|
-
run:
|
|
2966
|
+
run: run13
|
|
2849
2967
|
};
|
|
2850
2968
|
}
|
|
2851
2969
|
});
|
|
2852
|
-
async function
|
|
2970
|
+
async function run14(input, ctx) {
|
|
2853
2971
|
const client = ctx.client;
|
|
2854
2972
|
const result = await client.getCampaign(input.campaignId);
|
|
2855
2973
|
if (result.kind === "campaign") {
|
|
@@ -2879,13 +2997,13 @@ async function run13(input, ctx) {
|
|
|
2879
2997
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
2880
2998
|
};
|
|
2881
2999
|
}
|
|
2882
|
-
var
|
|
3000
|
+
var InputSchema14, InputJsonSchema14, campaignStatusSkill;
|
|
2883
3001
|
var init_campaign_status = __esm({
|
|
2884
3002
|
"src/skills/campaign-status.ts"() {
|
|
2885
|
-
|
|
3003
|
+
InputSchema14 = z.object({
|
|
2886
3004
|
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
|
|
2887
3005
|
});
|
|
2888
|
-
|
|
3006
|
+
InputJsonSchema14 = {
|
|
2889
3007
|
type: "object",
|
|
2890
3008
|
properties: {
|
|
2891
3009
|
campaignId: {
|
|
@@ -2898,15 +3016,15 @@ var init_campaign_status = __esm({
|
|
|
2898
3016
|
campaignStatusSkill = {
|
|
2899
3017
|
name: "pixeldojo_campaign_status",
|
|
2900
3018
|
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.",
|
|
2901
|
-
inputZod:
|
|
2902
|
-
inputJsonSchema:
|
|
3019
|
+
inputZod: InputSchema14,
|
|
3020
|
+
inputJsonSchema: InputJsonSchema14,
|
|
2903
3021
|
allowedModels: [],
|
|
2904
3022
|
defaultModel: "",
|
|
2905
|
-
run:
|
|
3023
|
+
run: run14
|
|
2906
3024
|
};
|
|
2907
3025
|
}
|
|
2908
3026
|
});
|
|
2909
|
-
async function
|
|
3027
|
+
async function run15(input, ctx) {
|
|
2910
3028
|
let url;
|
|
2911
3029
|
try {
|
|
2912
3030
|
url = await uploadLocalFile(input.path, ctx);
|
|
@@ -2922,16 +3040,16 @@ async function run14(input, ctx) {
|
|
|
2922
3040
|
(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.)`
|
|
2923
3041
|
};
|
|
2924
3042
|
}
|
|
2925
|
-
var
|
|
3043
|
+
var InputSchema15, InputJsonSchema15, uploadSkill;
|
|
2926
3044
|
var init_upload = __esm({
|
|
2927
3045
|
"src/skills/upload.ts"() {
|
|
2928
3046
|
init_upload_helpers();
|
|
2929
|
-
|
|
3047
|
+
InputSchema15 = z.object({
|
|
2930
3048
|
path: z.string().min(1).max(4096).describe(
|
|
2931
3049
|
"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."
|
|
2932
3050
|
)
|
|
2933
3051
|
});
|
|
2934
|
-
|
|
3052
|
+
InputJsonSchema15 = {
|
|
2935
3053
|
type: "object",
|
|
2936
3054
|
properties: {
|
|
2937
3055
|
path: {
|
|
@@ -2944,15 +3062,15 @@ var init_upload = __esm({
|
|
|
2944
3062
|
uploadSkill = {
|
|
2945
3063
|
name: "pixeldojo_upload",
|
|
2946
3064
|
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.",
|
|
2947
|
-
inputZod:
|
|
2948
|
-
inputJsonSchema:
|
|
3065
|
+
inputZod: InputSchema15,
|
|
3066
|
+
inputJsonSchema: InputJsonSchema15,
|
|
2949
3067
|
allowedModels: [],
|
|
2950
3068
|
defaultModel: "",
|
|
2951
|
-
run:
|
|
3069
|
+
run: run15
|
|
2952
3070
|
};
|
|
2953
3071
|
}
|
|
2954
3072
|
});
|
|
2955
|
-
async function
|
|
3073
|
+
async function run16(input, ctx) {
|
|
2956
3074
|
const requested = input.model ?? DEFAULT_MODEL6;
|
|
2957
3075
|
if (!ALLOWED4.includes(requested)) {
|
|
2958
3076
|
return {
|
|
@@ -2990,14 +3108,14 @@ async function run15(input, ctx) {
|
|
|
2990
3108
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
|
|
2991
3109
|
);
|
|
2992
3110
|
}
|
|
2993
|
-
var ALLOWED4, DEFAULT_MODEL6,
|
|
3111
|
+
var ALLOWED4, DEFAULT_MODEL6, InputSchema16, InputJsonSchema16, audioSkill;
|
|
2994
3112
|
var init_audio = __esm({
|
|
2995
3113
|
"src/skills/audio.ts"() {
|
|
2996
3114
|
init_poll();
|
|
2997
3115
|
init_types();
|
|
2998
3116
|
ALLOWED4 = ["seed-audio", "text-to-speech"];
|
|
2999
3117
|
DEFAULT_MODEL6 = "seed-audio";
|
|
3000
|
-
|
|
3118
|
+
InputSchema16 = z.object({
|
|
3001
3119
|
prompt: z.string().min(1).describe(
|
|
3002
3120
|
"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."
|
|
3003
3121
|
),
|
|
@@ -3007,7 +3125,7 @@ var init_audio = __esm({
|
|
|
3007
3125
|
output_format: z.string().optional().describe("Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."),
|
|
3008
3126
|
speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier, 0.5-2 (default 1). Both models.")
|
|
3009
3127
|
});
|
|
3010
|
-
|
|
3128
|
+
InputJsonSchema16 = {
|
|
3011
3129
|
type: "object",
|
|
3012
3130
|
properties: {
|
|
3013
3131
|
prompt: {
|
|
@@ -3045,15 +3163,15 @@ var init_audio = __esm({
|
|
|
3045
3163
|
audioSkill = {
|
|
3046
3164
|
name: "pixeldojo_audio",
|
|
3047
3165
|
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.",
|
|
3048
|
-
inputZod:
|
|
3049
|
-
inputJsonSchema:
|
|
3166
|
+
inputZod: InputSchema16,
|
|
3167
|
+
inputJsonSchema: InputJsonSchema16,
|
|
3050
3168
|
allowedModels: [...ALLOWED4],
|
|
3051
3169
|
defaultModel: DEFAULT_MODEL6,
|
|
3052
|
-
run:
|
|
3170
|
+
run: run16
|
|
3053
3171
|
};
|
|
3054
3172
|
}
|
|
3055
3173
|
});
|
|
3056
|
-
async function
|
|
3174
|
+
async function run17(input, ctx) {
|
|
3057
3175
|
if (input.source === "characters") {
|
|
3058
3176
|
const { characters } = await ctx.client.listCharacters({ q: input.q, limit: input.limit ?? 25 });
|
|
3059
3177
|
if (characters.length === 0) {
|
|
@@ -3104,17 +3222,17 @@ ${lines.join("\n")}
|
|
|
3104
3222
|
Pass any URL back into pixeldojo_generate, pixeldojo_edit, pixeldojo_character, or pixeldojo_upscale as a reference input.`
|
|
3105
3223
|
};
|
|
3106
3224
|
}
|
|
3107
|
-
var
|
|
3225
|
+
var InputSchema17, InputJsonSchema17, librarySkill;
|
|
3108
3226
|
var init_library = __esm({
|
|
3109
3227
|
"src/skills/library.ts"() {
|
|
3110
|
-
|
|
3228
|
+
InputSchema17 = z.object({
|
|
3111
3229
|
source: z.enum(["media", "characters"]).optional().describe("media (default): saved and recent results. characters: saved Character Studio characters and elements, usable as @Name in prompts."),
|
|
3112
3230
|
q: z.string().optional().describe("Substring match against the prompt that produced the asset."),
|
|
3113
3231
|
model: z.string().optional().describe("Restrict to a single model (apiId)."),
|
|
3114
3232
|
modality: z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
|
|
3115
3233
|
limit: z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
|
|
3116
3234
|
});
|
|
3117
|
-
|
|
3235
|
+
InputJsonSchema17 = {
|
|
3118
3236
|
type: "object",
|
|
3119
3237
|
properties: {
|
|
3120
3238
|
source: {
|
|
@@ -3139,15 +3257,15 @@ var init_library = __esm({
|
|
|
3139
3257
|
librarySkill = {
|
|
3140
3258
|
name: "pixeldojo_library",
|
|
3141
3259
|
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.",
|
|
3142
|
-
inputZod:
|
|
3143
|
-
inputJsonSchema:
|
|
3260
|
+
inputZod: InputSchema17,
|
|
3261
|
+
inputJsonSchema: InputJsonSchema17,
|
|
3144
3262
|
allowedModels: [],
|
|
3145
3263
|
defaultModel: "",
|
|
3146
|
-
run:
|
|
3264
|
+
run: run17
|
|
3147
3265
|
};
|
|
3148
3266
|
}
|
|
3149
3267
|
});
|
|
3150
|
-
async function
|
|
3268
|
+
async function run18(input, ctx) {
|
|
3151
3269
|
const res = await ctx.client.saveMedia({
|
|
3152
3270
|
url: input.url,
|
|
3153
3271
|
prompt: input.prompt,
|
|
@@ -3161,17 +3279,17 @@ async function run17(input, ctx) {
|
|
|
3161
3279
|
It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
|
|
3162
3280
|
};
|
|
3163
3281
|
}
|
|
3164
|
-
var
|
|
3282
|
+
var InputSchema18, InputJsonSchema18, saveSkill;
|
|
3165
3283
|
var init_save = __esm({
|
|
3166
3284
|
"src/skills/save.ts"() {
|
|
3167
|
-
|
|
3285
|
+
InputSchema18 = z.object({
|
|
3168
3286
|
url: z.string().url().describe("The asset URL to save (image or video)."),
|
|
3169
3287
|
prompt: z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
|
|
3170
3288
|
tool_name: z.string().optional().describe("Tool/model slug that produced it."),
|
|
3171
3289
|
modality: z.enum(["image", "video", "audio"]).optional(),
|
|
3172
3290
|
job_id: z.string().optional().describe("The jobId that produced this asset, when known.")
|
|
3173
3291
|
});
|
|
3174
|
-
|
|
3292
|
+
InputJsonSchema18 = {
|
|
3175
3293
|
type: "object",
|
|
3176
3294
|
properties: {
|
|
3177
3295
|
url: { type: "string", format: "uri", description: "The result URL to keep (image or video from a PixelDojo job)." },
|
|
@@ -3185,15 +3303,15 @@ var init_save = __esm({
|
|
|
3185
3303
|
saveSkill = {
|
|
3186
3304
|
name: "pixeldojo_save",
|
|
3187
3305
|
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.`,
|
|
3188
|
-
inputZod:
|
|
3189
|
-
inputJsonSchema:
|
|
3306
|
+
inputZod: InputSchema18,
|
|
3307
|
+
inputJsonSchema: InputJsonSchema18,
|
|
3190
3308
|
allowedModels: [],
|
|
3191
3309
|
defaultModel: "",
|
|
3192
|
-
run:
|
|
3310
|
+
run: run18
|
|
3193
3311
|
};
|
|
3194
3312
|
}
|
|
3195
3313
|
});
|
|
3196
|
-
async function
|
|
3314
|
+
async function run19(input, ctx) {
|
|
3197
3315
|
const res = await ctx.client.saveCharacter({
|
|
3198
3316
|
name: input.name,
|
|
3199
3317
|
kind: input.kind ?? "character",
|
|
@@ -3205,17 +3323,17 @@ async function run18(input, ctx) {
|
|
|
3205
3323
|
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.`
|
|
3206
3324
|
};
|
|
3207
3325
|
}
|
|
3208
|
-
var KINDS,
|
|
3326
|
+
var KINDS, InputSchema19, InputJsonSchema19, saveCharacterSkill;
|
|
3209
3327
|
var init_save_character = __esm({
|
|
3210
3328
|
"src/skills/save-character.ts"() {
|
|
3211
3329
|
KINDS = ["character", "product", "outfit", "location", "object", "style"];
|
|
3212
|
-
|
|
3330
|
+
InputSchema19 = z.object({
|
|
3213
3331
|
name: z.string().min(1).max(60).describe("What to call it. Used as @Name in prompts."),
|
|
3214
3332
|
kind: z.enum(KINDS).optional().describe("Default character."),
|
|
3215
3333
|
image_urls: z.array(z.string().url()).max(8).optional().describe("3 to 5 photos from different angles works best for a character."),
|
|
3216
3334
|
description: z.string().max(1500).optional().describe("Appearance notes, or the scene text for an element.")
|
|
3217
3335
|
});
|
|
3218
|
-
|
|
3336
|
+
InputJsonSchema19 = {
|
|
3219
3337
|
type: "object",
|
|
3220
3338
|
properties: {
|
|
3221
3339
|
name: { type: "string", description: "What to call it. Used as @Name in prompts afterwards." },
|
|
@@ -3233,11 +3351,11 @@ var init_save_character = __esm({
|
|
|
3233
3351
|
saveCharacterSkill = {
|
|
3234
3352
|
name: "pixeldojo_save_character",
|
|
3235
3353
|
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.",
|
|
3236
|
-
inputZod:
|
|
3237
|
-
inputJsonSchema:
|
|
3354
|
+
inputZod: InputSchema19,
|
|
3355
|
+
inputJsonSchema: InputJsonSchema19,
|
|
3238
3356
|
allowedModels: [],
|
|
3239
3357
|
defaultModel: "",
|
|
3240
|
-
run:
|
|
3358
|
+
run: run19
|
|
3241
3359
|
};
|
|
3242
3360
|
}
|
|
3243
3361
|
});
|
|
@@ -3486,6 +3604,7 @@ __export(skills_exports, {
|
|
|
3486
3604
|
audioSkill: () => audioSkill,
|
|
3487
3605
|
campaignSkill: () => campaignSkill,
|
|
3488
3606
|
campaignStatusSkill: () => campaignStatusSkill,
|
|
3607
|
+
captionSkill: () => captionSkill,
|
|
3489
3608
|
characterSkill: () => characterSkill,
|
|
3490
3609
|
editSkill: () => editSkill,
|
|
3491
3610
|
episodeSkill: () => episodeSkill,
|
|
@@ -3533,6 +3652,7 @@ var init_skills = __esm({
|
|
|
3533
3652
|
init_character();
|
|
3534
3653
|
init_storyboard();
|
|
3535
3654
|
init_upscale();
|
|
3655
|
+
init_caption();
|
|
3536
3656
|
init_status();
|
|
3537
3657
|
init_from_url();
|
|
3538
3658
|
init_film();
|
|
@@ -3553,6 +3673,7 @@ var init_skills = __esm({
|
|
|
3553
3673
|
characterSkill,
|
|
3554
3674
|
storyboardSkill,
|
|
3555
3675
|
upscaleSkill,
|
|
3676
|
+
captionSkill,
|
|
3556
3677
|
audioSkill,
|
|
3557
3678
|
librarySkill,
|
|
3558
3679
|
saveSkill,
|
|
@@ -3592,6 +3713,7 @@ var init_skills = __esm({
|
|
|
3592
3713
|
pixeldojo_character: render("Consistent character image"),
|
|
3593
3714
|
pixeldojo_storyboard: render("Storyboard preview video"),
|
|
3594
3715
|
pixeldojo_upscale: render("Upscale image or video"),
|
|
3716
|
+
pixeldojo_caption: render("Caption video"),
|
|
3595
3717
|
pixeldojo_audio: render("Generate music, speech or sound"),
|
|
3596
3718
|
pixeldojo_video: render("Change, animate or extend video"),
|
|
3597
3719
|
pixeldojo_ad: render("Make video ad"),
|
|
@@ -3662,7 +3784,7 @@ function makeRedactedLogger(target = console) {
|
|
|
3662
3784
|
}
|
|
3663
3785
|
|
|
3664
3786
|
// src/server.ts
|
|
3665
|
-
var PACKAGE_VERSION = "0.
|
|
3787
|
+
var PACKAGE_VERSION = "0.21.0" ;
|
|
3666
3788
|
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.";
|
|
3667
3789
|
function buildServer(options) {
|
|
3668
3790
|
if (!options.apiKey) {
|
|
@@ -3834,6 +3956,7 @@ function printInit() {
|
|
|
3834
3956
|
`# pixeldojo_character Consistent characters across shots.`,
|
|
3835
3957
|
`# pixeldojo_storyboard Multi-shot scenes from one brief.`,
|
|
3836
3958
|
`# pixeldojo_upscale Enhance any image or video.`,
|
|
3959
|
+
`# pixeldojo_caption Animated word-by-word captions on a video.`,
|
|
3837
3960
|
`# pixeldojo_audio Voices + effects + music in one clip.`,
|
|
3838
3961
|
`# pixeldojo_library Search your saved media + generations.`,
|
|
3839
3962
|
`# pixeldojo_*_workflow Save / run / list multi-step chains.`,
|