@pixeldojo/mcp 0.9.1 → 0.11.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 +13 -1
- package/dist/cli.cjs +338 -99
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +338 -99
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +337 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +337 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -183,19 +183,23 @@ var init_upload_helpers = __esm({
|
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
// src/skills/schema-snap.ts
|
|
186
|
-
function
|
|
186
|
+
function getModelInfo(apiId, client) {
|
|
187
187
|
const cached = schemaCache.get(apiId);
|
|
188
188
|
if (cached) return cached;
|
|
189
189
|
if (typeof client?.getModelSchema !== "function") return Promise.resolve(null);
|
|
190
|
-
const pending = Promise.resolve().then(() => client.getModelSchema(apiId)).then(
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
const pending = Promise.resolve().then(() => client.getModelSchema(apiId)).then((res) => {
|
|
191
|
+
const body = res;
|
|
192
|
+
return { props: body.schema?.properties ?? {}, media: body.media };
|
|
193
|
+
}).catch(() => {
|
|
193
194
|
schemaCache.delete(apiId);
|
|
194
195
|
return null;
|
|
195
196
|
});
|
|
196
197
|
schemaCache.set(apiId, pending);
|
|
197
198
|
return pending;
|
|
198
199
|
}
|
|
200
|
+
async function getFieldConstraints(apiId, client) {
|
|
201
|
+
return (await getModelInfo(apiId, client))?.props ?? null;
|
|
202
|
+
}
|
|
199
203
|
function parseAspectRatio(ar) {
|
|
200
204
|
const m = /^\s*(\d+)\s*:\s*(\d+)\s*$/.exec(ar);
|
|
201
205
|
if (!m) return null;
|
|
@@ -231,11 +235,20 @@ async function snapAspectRatio(apiId, requested, client, opts = {}) {
|
|
|
231
235
|
if (!field.enum || field.enum.length === 0) return requested;
|
|
232
236
|
return nearestAspectFromEnum(requested, field.enum);
|
|
233
237
|
}
|
|
234
|
-
async function
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
async function resolveMediaSlot(apiId, kind, client) {
|
|
239
|
+
const info = await getModelInfo(apiId, client);
|
|
240
|
+
if (info === null) return void 0;
|
|
241
|
+
const declared = info.media?.[kind];
|
|
242
|
+
if (declared) {
|
|
243
|
+
return {
|
|
244
|
+
name: declared.key,
|
|
245
|
+
isArray: info.props[declared.key]?.type === "array",
|
|
246
|
+
also: declared.also
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (info.media && kind !== "audio") return null;
|
|
250
|
+
for (const name of FALLBACK_KEYS[kind]) {
|
|
251
|
+
const field = info.props[name];
|
|
239
252
|
if (field) return { name, isArray: field.type === "array" };
|
|
240
253
|
}
|
|
241
254
|
return null;
|
|
@@ -295,21 +308,29 @@ async function snapDuration(apiId, requested, client, opts = {}) {
|
|
|
295
308
|
}
|
|
296
309
|
return requested;
|
|
297
310
|
}
|
|
311
|
+
function isVideoUrl(url) {
|
|
312
|
+
return /\.(mp4|mov|webm|m4v)(?:[?#]|$)/i.test(url);
|
|
313
|
+
}
|
|
314
|
+
function isAudioUrl(url) {
|
|
315
|
+
return /\.(mp3|wav|m4a|aac|ogg|flac)(?:[?#]|$)/i.test(url);
|
|
316
|
+
}
|
|
298
317
|
async function resolveImageRefs(apiId, urls, client) {
|
|
299
318
|
if (urls.length === 0) return null;
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
302
|
-
const
|
|
319
|
+
const info = await getModelInfo(apiId, client);
|
|
320
|
+
if (info === null) return null;
|
|
321
|
+
const props = info.props;
|
|
322
|
+
const key = info.media ? info.media.image?.key : REF_KEYS.find((k) => props[k] !== void 0);
|
|
303
323
|
if (!key) return null;
|
|
304
|
-
const
|
|
324
|
+
const also = info.media?.image?.also;
|
|
325
|
+
const field = props[key] ?? {};
|
|
305
326
|
if (field.type === "array") {
|
|
306
327
|
const cap = typeof field.maxItems === "number" ? field.maxItems : urls.length;
|
|
307
328
|
const kept = urls.slice(0, Math.max(1, cap));
|
|
308
|
-
return { key, value: kept, dropped: urls.length - kept.length };
|
|
329
|
+
return { key, value: kept, dropped: urls.length - kept.length, also };
|
|
309
330
|
}
|
|
310
|
-
return { key, value: urls[0], dropped: urls.length - 1 };
|
|
331
|
+
return { key, value: urls[0], dropped: urls.length - 1, also };
|
|
311
332
|
}
|
|
312
|
-
var schemaCache, REFERENCE_FIELD_PRIORITY, REF_KEYS;
|
|
333
|
+
var schemaCache, REFERENCE_FIELD_PRIORITY, FALLBACK_KEYS, REF_KEYS;
|
|
313
334
|
var init_schema_snap = __esm({
|
|
314
335
|
"src/skills/schema-snap.ts"() {
|
|
315
336
|
schemaCache = /* @__PURE__ */ new Map();
|
|
@@ -325,6 +346,11 @@ var init_schema_snap = __esm({
|
|
|
325
346
|
"image_input",
|
|
326
347
|
"images"
|
|
327
348
|
];
|
|
349
|
+
FALLBACK_KEYS = {
|
|
350
|
+
image: REFERENCE_FIELD_PRIORITY,
|
|
351
|
+
video: ["reference_videos", "video_url", "video"],
|
|
352
|
+
audio: ["reference_audios", "audio_url"]
|
|
353
|
+
};
|
|
328
354
|
REF_KEYS = [
|
|
329
355
|
"image_urls",
|
|
330
356
|
"images",
|
|
@@ -337,7 +363,51 @@ var init_schema_snap = __esm({
|
|
|
337
363
|
];
|
|
338
364
|
}
|
|
339
365
|
});
|
|
340
|
-
function
|
|
366
|
+
function paramsWithoutModel(params, model) {
|
|
367
|
+
if (!params || Object.keys(params).length === 0) return null;
|
|
368
|
+
if (model) return null;
|
|
369
|
+
return "Those settings are model-specific. Tell me which model to use and I will apply them.";
|
|
370
|
+
}
|
|
371
|
+
function applyParams(body, params, paramsModel, apiId) {
|
|
372
|
+
if (!params || Object.keys(params).length === 0) return void 0;
|
|
373
|
+
if (paramsModel !== apiId) {
|
|
374
|
+
return `This ran on ${apiId}, so I left those settings off: they are ${paramsModel}'s fields. Ask again naming ${apiId} if you want them applied.`;
|
|
375
|
+
}
|
|
376
|
+
const rest = { ...params };
|
|
377
|
+
delete rest.prompt;
|
|
378
|
+
Object.assign(body, rest);
|
|
379
|
+
return "prompt" in params ? "I kept the prompt from the skill call and ignored the one in params." : void 0;
|
|
380
|
+
}
|
|
381
|
+
function prefixNote(result, note) {
|
|
382
|
+
if (!note) return result;
|
|
383
|
+
return { ...result, text: `${note} ${result.text}`.trim() };
|
|
384
|
+
}
|
|
385
|
+
var ParamsInput, PARAMS_JSON_SCHEMA;
|
|
386
|
+
var init_params = __esm({
|
|
387
|
+
"src/skills/params.ts"() {
|
|
388
|
+
ParamsInput = z.record(z.unknown()).optional();
|
|
389
|
+
PARAMS_JSON_SCHEMA = {
|
|
390
|
+
type: "object",
|
|
391
|
+
additionalProperties: true,
|
|
392
|
+
description: 'Advanced settings for the model that runs this, named exactly as that model documents them (e.g. {"resolution": "1080p", "seed": 42}). Set `model` in the same call: field names differ per model, so params without one are refused. Applied last, so these override anything derived from the other inputs. An invalid field comes back named with its allowed values and nothing is charged, so fix it and call again.'
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
function fallbackSlot(apiId, kind) {
|
|
397
|
+
return apiId === VIDEO_REFERENCE_MODEL ? VIDEO_REFERENCE_SLOTS[kind] : void 0;
|
|
398
|
+
}
|
|
399
|
+
function sourceMedia(videoUrl, referenceUrls) {
|
|
400
|
+
const clip = videoUrl && !isAudioUrl(videoUrl) ? videoUrl : void 0;
|
|
401
|
+
const stills = referenceUrls.filter((u) => !isAudioUrl(u));
|
|
402
|
+
const url = clip ?? stills[0];
|
|
403
|
+
return {
|
|
404
|
+
url,
|
|
405
|
+
isClip: Boolean(clip) || !!url && isVideoUrl(url),
|
|
406
|
+
audioUrl: [videoUrl, ...referenceUrls].find((u) => u && isAudioUrl(u)),
|
|
407
|
+
stills
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function buildPayload(apiId, input, src, slot, audioSlot) {
|
|
341
411
|
const body = { prompt: input.prompt };
|
|
342
412
|
if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
|
|
343
413
|
if (input.preset) body.preset = input.preset;
|
|
@@ -345,9 +415,9 @@ function buildPayload(apiId, input, refField, referenceUrls = []) {
|
|
|
345
415
|
case "grok-video-extend":
|
|
346
416
|
case "wan-video-edit":
|
|
347
417
|
case "seedance-video-edit":
|
|
348
|
-
if (
|
|
349
|
-
body.video_url =
|
|
350
|
-
body.image_url =
|
|
418
|
+
if (src.url) {
|
|
419
|
+
body.video_url = src.url;
|
|
420
|
+
body.image_url = src.url;
|
|
351
421
|
}
|
|
352
422
|
if (input.duration !== void 0) {
|
|
353
423
|
body.video_duration = input.duration;
|
|
@@ -355,14 +425,19 @@ function buildPayload(apiId, input, refField, referenceUrls = []) {
|
|
|
355
425
|
}
|
|
356
426
|
break;
|
|
357
427
|
default:
|
|
358
|
-
if (
|
|
359
|
-
const refs =
|
|
360
|
-
if (
|
|
361
|
-
body[
|
|
428
|
+
if (src.url) {
|
|
429
|
+
const refs = src.isClip ? [src.url] : src.stills.length > 0 ? src.stills : [src.url];
|
|
430
|
+
if (slot) {
|
|
431
|
+
body[slot.name] = slot.isArray ? refs : refs[0];
|
|
432
|
+
if (slot.also) Object.assign(body, slot.also);
|
|
362
433
|
} else {
|
|
363
434
|
body.image_url = refs[0];
|
|
364
435
|
}
|
|
365
436
|
}
|
|
437
|
+
if (src.audioUrl && audioSlot) {
|
|
438
|
+
body[audioSlot.name] = audioSlot.isArray ? [src.audioUrl] : src.audioUrl;
|
|
439
|
+
if (audioSlot.also) Object.assign(body, audioSlot.also);
|
|
440
|
+
}
|
|
366
441
|
if (input.duration !== void 0) body.duration = input.duration;
|
|
367
442
|
if (input.resolution) body.resolution = input.resolution;
|
|
368
443
|
break;
|
|
@@ -370,7 +445,8 @@ function buildPayload(apiId, input, refField, referenceUrls = []) {
|
|
|
370
445
|
return body;
|
|
371
446
|
}
|
|
372
447
|
async function run(input, ctx) {
|
|
373
|
-
const
|
|
448
|
+
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
449
|
+
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
374
450
|
let resolved = input;
|
|
375
451
|
let referenceUrls = [];
|
|
376
452
|
try {
|
|
@@ -391,11 +467,22 @@ async function run(input, ctx) {
|
|
|
391
467
|
}
|
|
392
468
|
throw err;
|
|
393
469
|
}
|
|
470
|
+
const src = sourceMedia(input.video_url, referenceUrls);
|
|
471
|
+
let apiId = input.model ?? DEFAULT_MODEL;
|
|
472
|
+
let routeNote;
|
|
473
|
+
if ((src.isClip || src.audioUrl) && !VIDEO_INPUT_MODELS.has(apiId)) {
|
|
474
|
+
if (!input.model) {
|
|
475
|
+
apiId = VIDEO_REFERENCE_MODEL;
|
|
476
|
+
} else if (src.isClip && await resolveMediaSlot(input.model, "video", ctx.client) === null) {
|
|
477
|
+
apiId = VIDEO_REFERENCE_MODEL;
|
|
478
|
+
routeNote = `${input.model} can't work from a video clip, so I used ${VIDEO_REFERENCE_MODEL} instead.`;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
394
481
|
if (apiId === "grok-video-extend") {
|
|
395
|
-
if (!
|
|
482
|
+
if (!src.url) {
|
|
396
483
|
return {
|
|
397
484
|
kind: "text",
|
|
398
|
-
text: 'pixeldojo_generate with model="grok-video-extend" requires
|
|
485
|
+
text: 'pixeldojo_generate with model="grok-video-extend" requires video_url (the source video URL to extend, or image_path for a local file).'
|
|
399
486
|
};
|
|
400
487
|
}
|
|
401
488
|
if (resolved.duration === void 0) {
|
|
@@ -406,17 +493,22 @@ async function run(input, ctx) {
|
|
|
406
493
|
}
|
|
407
494
|
}
|
|
408
495
|
let refField;
|
|
496
|
+
let audioSlot;
|
|
409
497
|
if (!VIDEO_INPUT_MODELS.has(apiId)) {
|
|
410
|
-
const [aspect_ratio, duration, resolution, ref] = await Promise.all([
|
|
498
|
+
const [aspect_ratio, duration, resolution, ref, audio] = await Promise.all([
|
|
411
499
|
snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client),
|
|
412
500
|
snapDuration(apiId, resolved.duration, ctx.client),
|
|
413
501
|
snapResolution(apiId, resolved.resolution, ctx.client),
|
|
414
|
-
|
|
502
|
+
src.url ? resolveMediaSlot(apiId, src.isClip ? "video" : "image", ctx.client) : Promise.resolve(void 0),
|
|
503
|
+
src.audioUrl ? resolveMediaSlot(apiId, "audio", ctx.client) : Promise.resolve(void 0)
|
|
415
504
|
]);
|
|
416
505
|
resolved = { ...resolved, aspect_ratio, duration, resolution };
|
|
417
|
-
refField = ref;
|
|
506
|
+
refField = ref === void 0 && src.url ? fallbackSlot(apiId, src.isClip ? "video" : "image") : ref;
|
|
507
|
+
audioSlot = audio === void 0 && src.audioUrl ? fallbackSlot(apiId, "audio") : audio;
|
|
418
508
|
}
|
|
419
|
-
const body = buildPayload(apiId, resolved, refField,
|
|
509
|
+
const body = buildPayload(apiId, resolved, src, refField, audioSlot);
|
|
510
|
+
const paramsNote = applyParams(body, input.params, input.model, apiId);
|
|
511
|
+
const note = [routeNote, paramsNote].filter(Boolean).join(" ");
|
|
420
512
|
const submitted = await ctx.client.run(apiId, body);
|
|
421
513
|
const result = await pollJobUntilDoneOrHandoff(
|
|
422
514
|
submitted.jobId,
|
|
@@ -427,18 +519,22 @@ async function run(input, ctx) {
|
|
|
427
519
|
sleep: ctx.pollOverrides?.sleep
|
|
428
520
|
}
|
|
429
521
|
);
|
|
430
|
-
return
|
|
431
|
-
|
|
432
|
-
|
|
522
|
+
return prefixNote(
|
|
523
|
+
pollResultToSkillResult(
|
|
524
|
+
result,
|
|
525
|
+
(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(", ")}`
|
|
526
|
+
),
|
|
527
|
+
note
|
|
433
528
|
);
|
|
434
529
|
}
|
|
435
|
-
var InputSchema, DEFAULT_MODEL, VIDEO_INPUT_MODELS, InputJsonSchema, generateSkill;
|
|
530
|
+
var InputSchema, DEFAULT_MODEL, VIDEO_REFERENCE_MODEL, VIDEO_REFERENCE_SLOTS, VIDEO_INPUT_MODELS, InputJsonSchema, generateSkill;
|
|
436
531
|
var init_generate = __esm({
|
|
437
532
|
"src/skills/generate.ts"() {
|
|
438
533
|
init_poll();
|
|
439
534
|
init_types();
|
|
440
535
|
init_upload_helpers();
|
|
441
536
|
init_schema_snap();
|
|
537
|
+
init_params();
|
|
442
538
|
InputSchema = z.object({
|
|
443
539
|
prompt: z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
|
|
444
540
|
model: z.string().optional().describe(
|
|
@@ -453,6 +549,10 @@ var init_generate = __esm({
|
|
|
453
549
|
'Output resolution when the model supports it (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits. Omit to use the model default.'
|
|
454
550
|
),
|
|
455
551
|
image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
|
|
552
|
+
video_url: z.string().url().optional().describe(
|
|
553
|
+
"Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames. A clip routes to a model that can actually read one."
|
|
554
|
+
),
|
|
555
|
+
params: ParamsInput,
|
|
456
556
|
image_path: z.string().min(1).optional().describe(
|
|
457
557
|
"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."
|
|
458
558
|
),
|
|
@@ -464,6 +564,12 @@ var init_generate = __esm({
|
|
|
464
564
|
)
|
|
465
565
|
});
|
|
466
566
|
DEFAULT_MODEL = "google-nano-banana";
|
|
567
|
+
VIDEO_REFERENCE_MODEL = "seedance-2-reference";
|
|
568
|
+
VIDEO_REFERENCE_SLOTS = {
|
|
569
|
+
image: { name: "reference_images", isArray: true },
|
|
570
|
+
video: { name: "reference_videos", isArray: true },
|
|
571
|
+
audio: { name: "reference_audios", isArray: true }
|
|
572
|
+
};
|
|
467
573
|
VIDEO_INPUT_MODELS = /* @__PURE__ */ new Set(["grok-video-extend", "wan-video-edit", "seedance-video-edit"]);
|
|
468
574
|
InputJsonSchema = {
|
|
469
575
|
type: "object",
|
|
@@ -484,6 +590,12 @@ var init_generate = __esm({
|
|
|
484
590
|
description: 'Output resolution when supported (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits.'
|
|
485
591
|
},
|
|
486
592
|
image_url: { type: "string", format: "uri" },
|
|
593
|
+
video_url: {
|
|
594
|
+
type: "string",
|
|
595
|
+
format: "uri",
|
|
596
|
+
description: "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames."
|
|
597
|
+
},
|
|
598
|
+
params: PARAMS_JSON_SCHEMA,
|
|
487
599
|
image_urls: {
|
|
488
600
|
type: "array",
|
|
489
601
|
items: { type: "string", format: "uri" },
|
|
@@ -503,7 +615,7 @@ var init_generate = __esm({
|
|
|
503
615
|
};
|
|
504
616
|
generateSkill = {
|
|
505
617
|
name: "pixeldojo_generate",
|
|
506
|
-
description: "Generate any image or video from a prompt. Auto-routes to a sensible default model; pass `model` to pick a specific apiId from the catalog. For reference
|
|
618
|
+
description: "Generate any image or video from a prompt. Auto-routes to a sensible default model; pass `model` to pick a specific apiId from the catalog. For reference flows: pass `image_url` for a still, `video_url` for a source clip, or `image_path` (absolute local path) and the skill uploads it to 24h temp storage automatically. A clip or an audio reference routes to an engine that can read one. Model-specific settings go in `params` alongside an explicit `model`.",
|
|
507
619
|
inputZod: InputSchema,
|
|
508
620
|
inputJsonSchema: InputJsonSchema,
|
|
509
621
|
allowedModels: null,
|
|
@@ -570,9 +682,12 @@ async function buildBody(apiId, input, ctx) {
|
|
|
570
682
|
}
|
|
571
683
|
body.prompt = input.prompt;
|
|
572
684
|
body[refs.key] = refs.value;
|
|
685
|
+
if (refs.also) Object.assign(body, refs.also);
|
|
573
686
|
return { body, dropped: refs.dropped };
|
|
574
687
|
}
|
|
575
688
|
async function run2(input, ctx) {
|
|
689
|
+
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
690
|
+
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
576
691
|
let resolved = input;
|
|
577
692
|
try {
|
|
578
693
|
if (input.image_paths && input.image_paths.length > 0) {
|
|
@@ -604,6 +719,7 @@ async function run2(input, ctx) {
|
|
|
604
719
|
};
|
|
605
720
|
const { body, dropped } = await buildBody(apiId, resolved, ctx);
|
|
606
721
|
const droppedNote = dropped > 0 ? ` (${dropped} reference${dropped === 1 ? "" : "s"} dropped \u2014 ${apiId} accepts fewer)` : "";
|
|
722
|
+
const paramsNote = applyParams(body, input.params, input.model, apiId);
|
|
607
723
|
const submitted = await ctx.client.run(apiId, body);
|
|
608
724
|
const result = await pollJobUntilDoneOrHandoff(
|
|
609
725
|
submitted.jobId,
|
|
@@ -614,9 +730,12 @@ async function run2(input, ctx) {
|
|
|
614
730
|
sleep: ctx.pollOverrides?.sleep
|
|
615
731
|
}
|
|
616
732
|
);
|
|
617
|
-
return
|
|
618
|
-
|
|
619
|
-
|
|
733
|
+
return prefixNote(
|
|
734
|
+
pollResultToSkillResult(
|
|
735
|
+
result,
|
|
736
|
+
(urls) => urls.length === 0 ? `Edit job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Edit complete${droppedNote}. Asset: ${urls[0]}` : `Edit produced ${urls.length} assets${droppedNote}. URLs: ${urls.join(", ")}`
|
|
737
|
+
),
|
|
738
|
+
paramsNote
|
|
620
739
|
);
|
|
621
740
|
}
|
|
622
741
|
var DEFAULT_MODEL2, ALLOWED_MODELS, InputSchema2, InputJsonSchema2, editSkill;
|
|
@@ -626,6 +745,7 @@ var init_edit = __esm({
|
|
|
626
745
|
init_types();
|
|
627
746
|
init_upload_helpers();
|
|
628
747
|
init_schema_snap();
|
|
748
|
+
init_params();
|
|
629
749
|
DEFAULT_MODEL2 = "wan-2.7-image-edit";
|
|
630
750
|
ALLOWED_MODELS = [
|
|
631
751
|
"wan-2.7-image-edit",
|
|
@@ -649,7 +769,8 @@ var init_edit = __esm({
|
|
|
649
769
|
model: z.enum(ALLOWED_MODELS).optional().describe(
|
|
650
770
|
"Override the default edit model. Defaults to wan-2.7-image-edit (most popular)."
|
|
651
771
|
),
|
|
652
|
-
aspect_ratio: z.string().optional().describe('Aspect ratio when supported (e.g. "1:1", "16:9", "match_input_image").')
|
|
772
|
+
aspect_ratio: z.string().optional().describe('Aspect ratio when supported (e.g. "1:1", "16:9", "match_input_image").'),
|
|
773
|
+
params: ParamsInput
|
|
653
774
|
});
|
|
654
775
|
InputJsonSchema2 = {
|
|
655
776
|
type: "object",
|
|
@@ -683,7 +804,8 @@ var init_edit = __esm({
|
|
|
683
804
|
enum: [...ALLOWED_MODELS],
|
|
684
805
|
description: "Override edit model. Default: wan-2.7-image-edit."
|
|
685
806
|
},
|
|
686
|
-
aspect_ratio: { type: "string" }
|
|
807
|
+
aspect_ratio: { type: "string" },
|
|
808
|
+
params: PARAMS_JSON_SCHEMA
|
|
687
809
|
},
|
|
688
810
|
required: ["prompt"]
|
|
689
811
|
};
|
|
@@ -699,6 +821,8 @@ var init_edit = __esm({
|
|
|
699
821
|
}
|
|
700
822
|
});
|
|
701
823
|
async function run3(input, ctx) {
|
|
824
|
+
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
825
|
+
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
702
826
|
const requested = input.model ?? DEFAULT_MODEL3;
|
|
703
827
|
if (!ALLOWED.includes(requested)) {
|
|
704
828
|
return {
|
|
@@ -707,24 +831,40 @@ async function run3(input, ctx) {
|
|
|
707
831
|
};
|
|
708
832
|
}
|
|
709
833
|
const apiId = requested;
|
|
710
|
-
|
|
834
|
+
const refUrls = (input.reference_image_urls && input.reference_image_urls.length > 0 ? input.reference_image_urls : input.reference_image_url ? [input.reference_image_url] : []).filter((u, i, all) => all.indexOf(u) === i);
|
|
835
|
+
const wrongKind = refUrls.find((u) => isVideoUrl(u) || isAudioUrl(u));
|
|
836
|
+
if (wrongKind) {
|
|
837
|
+
return {
|
|
838
|
+
kind: "text",
|
|
839
|
+
text: `pixeldojo_character works on still images, and ${wrongKind} is not one. Use pixeldojo_generate or pixeldojo_storyboard with video_url to work from a clip (or image_url for an audio track \u2014 they route to the reference engine). For this treatment, give me a still frame.`
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
if (REQUIRES_REFERENCE.has(apiId) && refUrls.length === 0) {
|
|
711
843
|
return {
|
|
712
844
|
kind: "text",
|
|
713
|
-
text: `pixeldojo_character with model="${apiId}" requires reference_image_url
|
|
845
|
+
text: `pixeldojo_character with model="${apiId}" requires a reference image (reference_image_url / reference_image_urls \u2014 the source image of the character to edit). For generation from a prompt alone, use model="${DEFAULT_MODEL3}".`
|
|
714
846
|
};
|
|
715
847
|
}
|
|
716
848
|
const body = { prompt: input.prompt };
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
849
|
+
let dropped = 0;
|
|
850
|
+
if (refUrls.length > 0) {
|
|
851
|
+
const refs = await resolveImageRefs(apiId, refUrls, ctx.client);
|
|
852
|
+
if (refs) {
|
|
853
|
+
body[refs.key] = refs.value;
|
|
854
|
+
if (refs.also) Object.assign(body, refs.also);
|
|
855
|
+
dropped = refs.dropped;
|
|
720
856
|
} else {
|
|
721
|
-
body
|
|
857
|
+
body[FALLBACK_REF_KEY[apiId]] = refUrls;
|
|
722
858
|
}
|
|
723
859
|
}
|
|
724
860
|
const snappedAspect = await snapAspectRatio(apiId, input.aspect_ratio, ctx.client, {
|
|
725
861
|
onUnknown: "omit"
|
|
726
862
|
});
|
|
727
863
|
if (snappedAspect !== void 0) body.aspect_ratio = snappedAspect;
|
|
864
|
+
const note = [
|
|
865
|
+
dropped > 0 ? `${dropped} reference${dropped === 1 ? "" : "s"} dropped \u2014 ${apiId} accepts fewer.` : void 0,
|
|
866
|
+
applyParams(body, input.params, input.model, apiId)
|
|
867
|
+
].filter(Boolean).join(" ");
|
|
728
868
|
const submitted = await ctx.client.run(apiId, body);
|
|
729
869
|
const result = await pollJobUntilDoneOrHandoff(
|
|
730
870
|
submitted.jobId,
|
|
@@ -735,27 +875,49 @@ async function run3(input, ctx) {
|
|
|
735
875
|
sleep: ctx.pollOverrides?.sleep
|
|
736
876
|
}
|
|
737
877
|
);
|
|
738
|
-
return
|
|
739
|
-
|
|
740
|
-
|
|
878
|
+
return prefixNote(
|
|
879
|
+
pollResultToSkillResult(
|
|
880
|
+
result,
|
|
881
|
+
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Character shot ready. Asset: ${urls[0]}`
|
|
882
|
+
),
|
|
883
|
+
note
|
|
741
884
|
);
|
|
742
885
|
}
|
|
743
|
-
var ALLOWED, DEFAULT_MODEL3, REQUIRES_REFERENCE, InputSchema3, InputJsonSchema3, characterSkill;
|
|
886
|
+
var ALLOWED, DEFAULT_MODEL3, MAX_REFERENCES, REQUIRES_REFERENCE, FALLBACK_REF_KEY, InputSchema3, InputJsonSchema3, characterSkill;
|
|
744
887
|
var init_character = __esm({
|
|
745
888
|
"src/skills/character.ts"() {
|
|
746
889
|
init_poll();
|
|
747
890
|
init_types();
|
|
748
891
|
init_schema_snap();
|
|
749
|
-
|
|
892
|
+
init_params();
|
|
893
|
+
ALLOWED = [
|
|
894
|
+
"seedream-5",
|
|
895
|
+
"seedream-4",
|
|
896
|
+
"google-nano-banana",
|
|
897
|
+
"wan-2.7-image-edit",
|
|
898
|
+
"qwen-image-2.0-pro"
|
|
899
|
+
];
|
|
750
900
|
DEFAULT_MODEL3 = "seedream-5";
|
|
751
|
-
|
|
901
|
+
MAX_REFERENCES = 9;
|
|
902
|
+
REQUIRES_REFERENCE = /* @__PURE__ */ new Set(["wan-2.7-image-edit"]);
|
|
903
|
+
FALLBACK_REF_KEY = {
|
|
904
|
+
"seedream-5": "image_urls",
|
|
905
|
+
"seedream-4": "image_urls",
|
|
906
|
+
"google-nano-banana": "image_input",
|
|
907
|
+
"wan-2.7-image-edit": "image_urls",
|
|
908
|
+
"qwen-image-2.0-pro": "reference_images"
|
|
909
|
+
};
|
|
752
910
|
InputSchema3 = z.object({
|
|
753
911
|
prompt: z.string().min(1).max(8e3),
|
|
754
912
|
model: z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`),
|
|
755
913
|
reference_image_url: z.string().url().optional().describe(
|
|
756
|
-
"
|
|
914
|
+
"Single reference image of the character. Prefer reference_image_urls \u2014 more angles lock the face better."
|
|
915
|
+
),
|
|
916
|
+
reference_image_urls: z.array(z.string().url()).min(1).max(MAX_REFERENCES).optional().describe(
|
|
917
|
+
`All references for this shot (1-${MAX_REFERENCES}), most important first. Multiple angles of the same person lock the face far better than one photo. Models that take fewer are clamped to their own cap.`
|
|
757
918
|
),
|
|
758
|
-
aspect_ratio: z.string().optional().describe(
|
|
919
|
+
aspect_ratio: z.string().optional().describe("Honored where the model declares the field."),
|
|
920
|
+
params: ParamsInput
|
|
759
921
|
});
|
|
760
922
|
InputJsonSchema3 = {
|
|
761
923
|
type: "object",
|
|
@@ -766,14 +928,26 @@ var init_character = __esm({
|
|
|
766
928
|
enum: ALLOWED,
|
|
767
929
|
description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
|
|
768
930
|
},
|
|
769
|
-
reference_image_url: {
|
|
770
|
-
|
|
931
|
+
reference_image_url: {
|
|
932
|
+
type: "string",
|
|
933
|
+
format: "uri",
|
|
934
|
+
description: "Single reference. Prefer reference_image_urls."
|
|
935
|
+
},
|
|
936
|
+
reference_image_urls: {
|
|
937
|
+
type: "array",
|
|
938
|
+
items: { type: "string", format: "uri" },
|
|
939
|
+
minItems: 1,
|
|
940
|
+
maxItems: MAX_REFERENCES,
|
|
941
|
+
description: "All references for this shot, most important first. Multiple angles of the same person lock the face far better than one photo."
|
|
942
|
+
},
|
|
943
|
+
aspect_ratio: { type: "string" },
|
|
944
|
+
params: PARAMS_JSON_SCHEMA
|
|
771
945
|
},
|
|
772
946
|
required: ["prompt"]
|
|
773
947
|
};
|
|
774
948
|
characterSkill = {
|
|
775
949
|
name: "pixeldojo_character",
|
|
776
|
-
description: "Generate or edit consistent characters across shots. Pass
|
|
950
|
+
description: "Generate or edit consistent characters across shots. Pass reference_image_urls (up to 9) to lock identity \u2014 send every photo of the person you have, since more angles hold the face far better than one. Defaults to seedream-5.",
|
|
777
951
|
inputZod: InputSchema3,
|
|
778
952
|
inputJsonSchema: InputJsonSchema3,
|
|
779
953
|
allowedModels: [...ALLOWED],
|
|
@@ -783,6 +957,8 @@ var init_character = __esm({
|
|
|
783
957
|
}
|
|
784
958
|
});
|
|
785
959
|
async function run4(input, ctx) {
|
|
960
|
+
const paramsGate = paramsWithoutModel(input.params, input.model);
|
|
961
|
+
if (paramsGate) return { kind: "text", text: paramsGate };
|
|
786
962
|
const requested = input.model ?? DEFAULT_MODEL4;
|
|
787
963
|
if (!ALLOWED2.includes(requested)) {
|
|
788
964
|
return {
|
|
@@ -790,6 +966,20 @@ async function run4(input, ctx) {
|
|
|
790
966
|
text: `Unknown model "${requested}" for pixeldojo_storyboard. Allowed: ${ALLOWED2.join(", ")}.`
|
|
791
967
|
};
|
|
792
968
|
}
|
|
969
|
+
if (input.image_url || input.video_url) {
|
|
970
|
+
return generateSkill.run(
|
|
971
|
+
{
|
|
972
|
+
prompt: input.brief,
|
|
973
|
+
model: input.model ?? VIDEO_REFERENCE_MODEL,
|
|
974
|
+
image_url: input.image_url,
|
|
975
|
+
video_url: input.video_url,
|
|
976
|
+
aspect_ratio: input.aspect_ratio,
|
|
977
|
+
duration: input.duration,
|
|
978
|
+
params: input.params
|
|
979
|
+
},
|
|
980
|
+
ctx
|
|
981
|
+
);
|
|
982
|
+
}
|
|
793
983
|
const apiId = requested;
|
|
794
984
|
const [snappedAspect, snappedDuration] = await Promise.all([
|
|
795
985
|
snapAspectRatio(apiId, input.aspect_ratio, ctx.client),
|
|
@@ -798,6 +988,7 @@ async function run4(input, ctx) {
|
|
|
798
988
|
const body = { prompt: input.brief };
|
|
799
989
|
if (snappedAspect !== void 0) body.aspect_ratio = snappedAspect;
|
|
800
990
|
if (snappedDuration !== void 0) body.duration = snappedDuration;
|
|
991
|
+
const note = applyParams(body, input.params, input.model, apiId);
|
|
801
992
|
const submitted = await ctx.client.run(apiId, body);
|
|
802
993
|
const result = await pollJobUntilDoneOrHandoff(
|
|
803
994
|
submitted.jobId,
|
|
@@ -808,9 +999,12 @@ async function run4(input, ctx) {
|
|
|
808
999
|
sleep: ctx.pollOverrides?.sleep
|
|
809
1000
|
}
|
|
810
1001
|
);
|
|
811
|
-
return
|
|
812
|
-
|
|
813
|
-
|
|
1002
|
+
return prefixNote(
|
|
1003
|
+
pollResultToSkillResult(
|
|
1004
|
+
result,
|
|
1005
|
+
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Storyboard preview ready. Asset: ${urls[0]}`
|
|
1006
|
+
),
|
|
1007
|
+
note
|
|
814
1008
|
);
|
|
815
1009
|
}
|
|
816
1010
|
var ALLOWED2, DEFAULT_MODEL4, InputSchema4, InputJsonSchema4, storyboardSkill;
|
|
@@ -819,18 +1013,24 @@ var init_storyboard = __esm({
|
|
|
819
1013
|
init_poll();
|
|
820
1014
|
init_types();
|
|
821
1015
|
init_schema_snap();
|
|
1016
|
+
init_generate();
|
|
1017
|
+
init_params();
|
|
822
1018
|
ALLOWED2 = [
|
|
823
1019
|
"veo-3.1-fast",
|
|
824
1020
|
"veo-3.1-standard",
|
|
825
1021
|
"kling-video-v3-standard-text",
|
|
826
|
-
"seedance-2-high"
|
|
1022
|
+
"seedance-2-high",
|
|
1023
|
+
VIDEO_REFERENCE_MODEL
|
|
827
1024
|
];
|
|
828
1025
|
DEFAULT_MODEL4 = "veo-3.1-fast";
|
|
829
1026
|
InputSchema4 = z.object({
|
|
830
1027
|
brief: z.string().min(1).max(8e3).describe("Storyboard brief: scene, shots, mood, action."),
|
|
831
1028
|
model: z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`),
|
|
832
1029
|
aspect_ratio: z.string().optional(),
|
|
833
|
-
duration: z.number().int().positive().optional()
|
|
1030
|
+
duration: z.number().int().positive().optional(),
|
|
1031
|
+
image_url: z.string().url().optional().describe("Reference still to build the shot from (character, product, location)."),
|
|
1032
|
+
video_url: z.string().url().optional().describe("Source clip to work from. Use this for videos; image_url is for still frames."),
|
|
1033
|
+
params: ParamsInput
|
|
834
1034
|
});
|
|
835
1035
|
InputJsonSchema4 = {
|
|
836
1036
|
type: "object",
|
|
@@ -845,13 +1045,24 @@ var init_storyboard = __esm({
|
|
|
845
1045
|
description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
|
|
846
1046
|
},
|
|
847
1047
|
aspect_ratio: { type: "string" },
|
|
848
|
-
duration: { type: "integer" }
|
|
1048
|
+
duration: { type: "integer" },
|
|
1049
|
+
image_url: {
|
|
1050
|
+
type: "string",
|
|
1051
|
+
format: "uri",
|
|
1052
|
+
description: "Reference still to build the shot from."
|
|
1053
|
+
},
|
|
1054
|
+
video_url: {
|
|
1055
|
+
type: "string",
|
|
1056
|
+
format: "uri",
|
|
1057
|
+
description: "Source clip to work from. Use this for videos; image_url is for stills."
|
|
1058
|
+
},
|
|
1059
|
+
params: PARAMS_JSON_SCHEMA
|
|
849
1060
|
},
|
|
850
1061
|
required: ["brief"]
|
|
851
1062
|
};
|
|
852
1063
|
storyboardSkill = {
|
|
853
1064
|
name: "pixeldojo_storyboard",
|
|
854
|
-
description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
|
|
1065
|
+
description: "Generate a video storyboard preview from a single brief. Pass image_url for a reference still or video_url for a source clip. v1: single-shot preview; multi-shot orchestration coming.",
|
|
855
1066
|
inputZod: InputSchema4,
|
|
856
1067
|
inputJsonSchema: InputJsonSchema4,
|
|
857
1068
|
allowedModels: [...ALLOWED2],
|
|
@@ -861,7 +1072,8 @@ var init_storyboard = __esm({
|
|
|
861
1072
|
}
|
|
862
1073
|
});
|
|
863
1074
|
async function run5(input, ctx) {
|
|
864
|
-
const
|
|
1075
|
+
const clipUrl = input.video_url ?? (input.image_url && isVideoUrl(input.image_url) ? input.image_url : void 0);
|
|
1076
|
+
const requested = input.model ?? (clipUrl ? VIDEO_MODEL : DEFAULT_MODEL5);
|
|
865
1077
|
if (!ALLOWED3.includes(requested)) {
|
|
866
1078
|
return {
|
|
867
1079
|
kind: "text",
|
|
@@ -869,11 +1081,33 @@ async function run5(input, ctx) {
|
|
|
869
1081
|
};
|
|
870
1082
|
}
|
|
871
1083
|
const apiId = requested;
|
|
1084
|
+
if (apiId === VIDEO_MODEL) {
|
|
1085
|
+
if (!clipUrl) {
|
|
1086
|
+
return {
|
|
1087
|
+
kind: "text",
|
|
1088
|
+
text: 'pixeldojo_upscale with model="video-upscaler" requires video_url (the source clip).'
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
if (input.video_duration === void 0) {
|
|
1092
|
+
return {
|
|
1093
|
+
kind: "text",
|
|
1094
|
+
text: 'pixeldojo_upscale with model="video-upscaler" requires video_duration (source clip length in seconds \u2014 it sets the credit cost, 1 credit/second).'
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
} else if (!input.image_url || input.image_url === clipUrl) {
|
|
1098
|
+
return {
|
|
1099
|
+
kind: "text",
|
|
1100
|
+
text: input.image_url ? `${apiId} upscales still images, but the source is a video clip. Use model="video-upscaler" with video_url and video_duration (seconds).` : `pixeldojo_upscale with model="${apiId}" requires image_url (the source image).`
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
872
1103
|
const body = {
|
|
873
1104
|
prompt: "",
|
|
874
|
-
image: input.image_url
|
|
1105
|
+
...apiId === VIDEO_MODEL ? { video: clipUrl } : { image: input.image_url }
|
|
875
1106
|
};
|
|
876
|
-
if (apiId ===
|
|
1107
|
+
if (apiId === VIDEO_MODEL) {
|
|
1108
|
+
body.video_duration = input.video_duration;
|
|
1109
|
+
if (input.target_resolution) body.target_resolution = input.target_resolution;
|
|
1110
|
+
} else if (apiId === "magnific-upscaler") {
|
|
877
1111
|
const allowed = [2, 4, 8, 16];
|
|
878
1112
|
const requestedScale = input.scale_factor ?? 2;
|
|
879
1113
|
const nearest = allowed.reduce(
|
|
@@ -901,34 +1135,49 @@ async function run5(input, ctx) {
|
|
|
901
1135
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upscaled. Asset: ${urls[0]}`
|
|
902
1136
|
);
|
|
903
1137
|
}
|
|
904
|
-
var ALLOWED3, DEFAULT_MODEL5, InputSchema5, InputJsonSchema5, upscaleSkill;
|
|
1138
|
+
var ALLOWED3, DEFAULT_MODEL5, VIDEO_MODEL, VIDEO_RESOLUTIONS, InputSchema5, InputJsonSchema5, upscaleSkill;
|
|
905
1139
|
var init_upscale = __esm({
|
|
906
1140
|
"src/skills/upscale.ts"() {
|
|
907
1141
|
init_poll();
|
|
908
1142
|
init_types();
|
|
909
|
-
|
|
1143
|
+
init_schema_snap();
|
|
1144
|
+
ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance", "video-upscaler"];
|
|
910
1145
|
DEFAULT_MODEL5 = "magnific-upscaler";
|
|
1146
|
+
VIDEO_MODEL = "video-upscaler";
|
|
1147
|
+
VIDEO_RESOLUTIONS = ["720p", "1080p", "2k", "4k"];
|
|
911
1148
|
InputSchema5 = z.object({
|
|
912
|
-
image_url: z.string().url().describe("URL of the source image to upscale."),
|
|
913
|
-
|
|
1149
|
+
image_url: z.string().url().optional().describe("URL of the source image to upscale."),
|
|
1150
|
+
video_url: z.string().url().optional().describe("URL of a source video clip \u2014 routes to video-upscaler. MP4 only, max 500 MB."),
|
|
1151
|
+
model: z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5} (a clip defaults to ${VIDEO_MODEL}).`),
|
|
914
1152
|
scale_factor: z.number().min(1).max(16).optional().describe(
|
|
915
|
-
"
|
|
1153
|
+
"Image models only. Magnific snaps to the nearest of 2/4/8/16 (default 2); creative-upscale accepts 1-10 (default 2); face-enhance accepts 1-8 (default 4)."
|
|
916
1154
|
),
|
|
917
|
-
creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
|
|
1155
|
+
creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0."),
|
|
1156
|
+
video_duration: z.number().positive().optional().describe("video-upscaler only, REQUIRED there: source clip length in seconds (its credit meter)."),
|
|
1157
|
+
target_resolution: z.enum(VIDEO_RESOLUTIONS).optional().describe("video-upscaler only: target output resolution. Defaults to 1080p.")
|
|
918
1158
|
});
|
|
919
1159
|
InputJsonSchema5 = {
|
|
920
1160
|
type: "object",
|
|
921
1161
|
properties: {
|
|
922
1162
|
image_url: { type: "string", format: "uri" },
|
|
1163
|
+
video_url: {
|
|
1164
|
+
type: "string",
|
|
1165
|
+
format: "uri",
|
|
1166
|
+
description: "Source video clip \u2014 routes to video-upscaler."
|
|
1167
|
+
},
|
|
923
1168
|
model: { type: "string", enum: ALLOWED3 },
|
|
924
1169
|
scale_factor: { type: "number", minimum: 1, maximum: 16 },
|
|
925
|
-
creativity: { type: "number", minimum: 0, maximum: 1 }
|
|
926
|
-
|
|
927
|
-
|
|
1170
|
+
creativity: { type: "number", minimum: 0, maximum: 1 },
|
|
1171
|
+
video_duration: {
|
|
1172
|
+
type: "number",
|
|
1173
|
+
description: "video-upscaler only, required there: source clip length in seconds."
|
|
1174
|
+
},
|
|
1175
|
+
target_resolution: { type: "string", enum: VIDEO_RESOLUTIONS }
|
|
1176
|
+
}
|
|
928
1177
|
};
|
|
929
1178
|
upscaleSkill = {
|
|
930
1179
|
name: "pixeldojo_upscale",
|
|
931
|
-
description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
|
|
1180
|
+
description: "Enhance an image or video. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits; video-upscaler (FlashVSR, up to 4K) for clips \u2014 pass video_url plus video_duration in seconds.",
|
|
932
1181
|
inputZod: InputSchema5,
|
|
933
1182
|
inputJsonSchema: InputJsonSchema5,
|
|
934
1183
|
allowedModels: [...ALLOWED3],
|
|
@@ -1076,6 +1325,9 @@ var init_from_url = __esm({
|
|
|
1076
1325
|
};
|
|
1077
1326
|
}
|
|
1078
1327
|
});
|
|
1328
|
+
function needsSave(url) {
|
|
1329
|
+
return Boolean(url && url.startsWith("https://temp.pixeldojo.ai/"));
|
|
1330
|
+
}
|
|
1079
1331
|
async function filmApi(ctx, method, path, body) {
|
|
1080
1332
|
const res = await fetch(`${ctx.baseUrl}${path}`, {
|
|
1081
1333
|
method,
|
|
@@ -1172,7 +1424,7 @@ function buildScorePlanBody(film) {
|
|
|
1172
1424
|
}
|
|
1173
1425
|
function buildFlatMusicBody(film, all) {
|
|
1174
1426
|
const runtimeSec = all.reduce((n, sh) => n + (sh.durationSec || 0), 0);
|
|
1175
|
-
const ms = Math.min(
|
|
1427
|
+
const ms = Math.min(3e5, Math.max(3e4, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
|
|
1176
1428
|
return {
|
|
1177
1429
|
prompt: `${LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]}. Instrumental only, no vocals. One consistent theme, loopable as a background bed under dialogue.`,
|
|
1178
1430
|
music_length_ms: ms,
|
|
@@ -1202,24 +1454,8 @@ It is saved in the Library folder "Film: ${film.title}".`
|
|
|
1202
1454
|
await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "frame" });
|
|
1203
1455
|
}
|
|
1204
1456
|
const framesDone = all.every((sh) => ["frame-ready", "clip-generating", "clip-ready"].includes(sh.status));
|
|
1205
|
-
if (framesDone && all.some((sh) => sh.status === "frame-ready" &&
|
|
1206
|
-
await filmApi(ctx, "
|
|
1207
|
-
scenes: film.scenes.map((s) => ({
|
|
1208
|
-
id: s.id,
|
|
1209
|
-
title: s.title,
|
|
1210
|
-
location: s.location,
|
|
1211
|
-
shots: s.shots.map((sh) => ({
|
|
1212
|
-
id: sh.id,
|
|
1213
|
-
prompt: sh.prompt,
|
|
1214
|
-
characterIds: sh.characterIds ?? [],
|
|
1215
|
-
elementIds: sh.elementIds ?? [],
|
|
1216
|
-
engine: sh.engine,
|
|
1217
|
-
cameraPreset: sh.cameraPreset,
|
|
1218
|
-
approved: true,
|
|
1219
|
-
durationSec: sh.durationSec
|
|
1220
|
-
}))
|
|
1221
|
-
}))
|
|
1222
|
-
});
|
|
1457
|
+
if (framesDone && all.some((sh) => sh.status === "frame-ready" && needsSave(sh.frameUrl))) {
|
|
1458
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "frame" });
|
|
1223
1459
|
}
|
|
1224
1460
|
const inFlight = count(film, (sh) => sh.status === "clip-generating");
|
|
1225
1461
|
const clipQueue = all.filter((sh) => (sh.status === "frame-ready" || sh.status === "failed" && Boolean(sh.frameUrl)) && framesDone);
|
|
@@ -1228,6 +1464,9 @@ It is saved in the Library folder "Film: ${film.title}".`
|
|
|
1228
1464
|
}
|
|
1229
1465
|
const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
|
|
1230
1466
|
if (clipsDone) {
|
|
1467
|
+
if (all.some((sh) => needsSave(sh.videoUrl))) {
|
|
1468
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "clip" });
|
|
1469
|
+
}
|
|
1231
1470
|
const hasMusic = film.audioStems.some((st) => st.kind === "music");
|
|
1232
1471
|
if (soundtrack !== "none" && !hasMusic) {
|
|
1233
1472
|
if (musicJobId) {
|
|
@@ -2181,7 +2420,7 @@ function makeRedactedLogger(target = console) {
|
|
|
2181
2420
|
}
|
|
2182
2421
|
|
|
2183
2422
|
// src/server.ts
|
|
2184
|
-
var PACKAGE_VERSION = "0.
|
|
2423
|
+
var PACKAGE_VERSION = "0.11.0" ;
|
|
2185
2424
|
function buildServer(options) {
|
|
2186
2425
|
if (!options.apiKey) {
|
|
2187
2426
|
throw new Error(
|