pixelkiln 0.42.0 → 0.44.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/dist/cli.js +311 -47
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +394 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1414 -132
- package/dist/index.d.ts +1414 -132
- package/dist/index.js +384 -117
- package/dist/index.js.map +1 -1
- package/docs/GENERATORS.md +8 -4
- package/docs/MANIFEST.md +73 -3
- package/package.json +1 -1
- package/schema/manifest.schema.json +217 -0
- package/schema/recipe.schema.json +58 -0
- package/skills/pixelkiln/references/pixellab.md +10 -0
package/dist/index.cjs
CHANGED
|
@@ -34,10 +34,12 @@ __export(index_exports, {
|
|
|
34
34
|
BudgetError: () => BudgetError,
|
|
35
35
|
CHARACTER_DIRECTIONS_4: () => CHARACTER_DIRECTIONS_4,
|
|
36
36
|
CHARACTER_DIRECTIONS_8: () => CHARACTER_DIRECTIONS_8,
|
|
37
|
+
CHARACTER_PROPORTION_PRESETS: () => CHARACTER_PROPORTION_PRESETS,
|
|
37
38
|
CharacterAnimationModeSchema: () => CharacterAnimationModeSchema,
|
|
38
39
|
CharacterAnimationSchema: () => CharacterAnimationSchema,
|
|
39
40
|
CharacterDirectionSchema: () => CharacterDirectionSchema,
|
|
40
41
|
CharacterModeSchema: () => CharacterModeSchema,
|
|
42
|
+
CharacterProportionsSchema: () => CharacterProportionsSchema,
|
|
41
43
|
CharacterStateSchema: () => CharacterStateSchema,
|
|
42
44
|
ComfyUIProvider: () => ComfyUIProvider,
|
|
43
45
|
DEFAULT_HISTORY_LIMIT: () => DEFAULT_HISTORY_LIMIT,
|
|
@@ -204,6 +206,7 @@ __export(index_exports, {
|
|
|
204
206
|
portableOutputPath: () => portableOutputPath,
|
|
205
207
|
prepareReview: () => prepareReview,
|
|
206
208
|
primaryOutput: () => primaryOutput,
|
|
209
|
+
proportionsPayload: () => proportionsPayload,
|
|
207
210
|
providerCredentialEnvs: () => providerCredentialEnvs,
|
|
208
211
|
providerFactory: () => providerFactory,
|
|
209
212
|
pushTags: () => pushTags,
|
|
@@ -387,6 +390,17 @@ var MIRRORED_DIRECTION = {
|
|
|
387
390
|
"south-west": "south-east"
|
|
388
391
|
};
|
|
389
392
|
var CharacterModeSchema = import_zod.z.enum(["standard", "v3", "pro"]);
|
|
393
|
+
var CHARACTER_PROPORTION_PRESETS = ["default", "chibi", "cartoon", "stylized", "realistic_male", "realistic_female", "heroic"];
|
|
394
|
+
var CharacterProportionsSchema = import_zod.z.union([
|
|
395
|
+
import_zod.z.enum(CHARACTER_PROPORTION_PRESETS),
|
|
396
|
+
import_zod.z.object({
|
|
397
|
+
headSize: import_zod.z.number().min(0.5).max(2).optional(),
|
|
398
|
+
armsLength: import_zod.z.number().min(0.5).max(2).optional(),
|
|
399
|
+
legsLength: import_zod.z.number().min(0.5).max(2).optional(),
|
|
400
|
+
shoulderWidth: import_zod.z.number().min(0.5).max(2).optional(),
|
|
401
|
+
hipWidth: import_zod.z.number().min(0.5).max(2).optional()
|
|
402
|
+
}).strict()
|
|
403
|
+
]);
|
|
390
404
|
var CharacterAnimationModeSchema = import_zod.z.enum(["template", "v3", "pro"]);
|
|
391
405
|
var CharacterStateSchema = import_zod.z.object({
|
|
392
406
|
/** The base character, or another state, in the same style. */
|
|
@@ -413,11 +427,45 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
413
427
|
/** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
|
|
414
428
|
keepFirstFrame: import_zod.z.boolean().default(true),
|
|
415
429
|
/** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
|
|
416
|
-
mode: CharacterAnimationModeSchema.optional()
|
|
430
|
+
mode: CharacterAnimationModeSchema.optional(),
|
|
431
|
+
/**
|
|
432
|
+
* v3 only: a manifest-relative image of the pose to start from, instead
|
|
433
|
+
* of the character's rotation for this direction. Up to 256px.
|
|
434
|
+
*/
|
|
435
|
+
startFrame: import_zod.z.string().min(1).optional(),
|
|
436
|
+
/**
|
|
437
|
+
* v3 only: a pose to animate toward. The loop then interpolates from the
|
|
438
|
+
* start frame (the rotation, or `startFrame`) to this image, which must
|
|
439
|
+
* be the same size as the start frame.
|
|
440
|
+
*/
|
|
441
|
+
endFrame: import_zod.z.string().min(1).optional(),
|
|
442
|
+
/** What is being animated, when the character's own description would mislead the model. */
|
|
443
|
+
subject: import_zod.z.string().min(1).optional(),
|
|
444
|
+
/** Template mode only: style hints for this loop, over the character's own. */
|
|
445
|
+
outline: import_zod.z.string().min(1).optional(),
|
|
446
|
+
shading: import_zod.z.string().min(1).optional(),
|
|
447
|
+
detail: import_zod.z.string().min(1).optional(),
|
|
448
|
+
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
449
|
+
enhancePrompt: import_zod.z.boolean().optional()
|
|
417
450
|
}).strict().superRefine((animation, context) => {
|
|
418
451
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
419
452
|
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
420
453
|
}
|
|
454
|
+
const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
|
|
455
|
+
for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
|
|
456
|
+
if (animation[key] !== void 0 && mode2 !== "v3") {
|
|
457
|
+
context.addIssue({
|
|
458
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
459
|
+
message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
|
|
460
|
+
path: [key]
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const key of ["outline", "shading", "detail"]) {
|
|
465
|
+
if (animation[key] !== void 0 && mode2 !== "template") {
|
|
466
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
421
469
|
if (animation.template && animation.mode && animation.mode !== "template") {
|
|
422
470
|
context.addIssue({
|
|
423
471
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -545,6 +593,18 @@ var StyleObjectSchema = import_zod.z.object({
|
|
|
545
593
|
directions: import_zod.z.union([import_zod.z.literal(4), import_zod.z.literal(8)]).optional(),
|
|
546
594
|
/** `character` only. Body template: `mannequin` (default) or a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`). */
|
|
547
595
|
template: import_zod.z.string().min(1).optional(),
|
|
596
|
+
/** `character` only, `standard` humanoid bases. A preset or multipliers; an asset may override it. */
|
|
597
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
598
|
+
/**
|
|
599
|
+
* `character` only. How closely a `standard` base and a template loop
|
|
600
|
+
* follow their text, 1 to 20; PixelLab's default is 8. The v3 and pro
|
|
601
|
+
* engines do not take it.
|
|
602
|
+
*/
|
|
603
|
+
textGuidanceScale: import_zod.z.number().min(1).max(20).optional(),
|
|
604
|
+
/** `character` only. Draw `standard` bases and every loop in isometric view. */
|
|
605
|
+
isometric: import_zod.z.boolean().optional(),
|
|
606
|
+
/** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
|
|
607
|
+
enhancePrompt: import_zod.z.boolean().optional(),
|
|
548
608
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
549
609
|
seed: import_zod.z.number().int().optional(),
|
|
550
610
|
/**
|
|
@@ -704,6 +764,19 @@ var AssetSchema = import_zod.z.object({
|
|
|
704
764
|
state: CharacterStateSchema.optional(),
|
|
705
765
|
/** `character` styles: this asset is a loop of another character asset in one direction. */
|
|
706
766
|
animation: CharacterAnimationSchema.optional(),
|
|
767
|
+
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
768
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
769
|
+
/**
|
|
770
|
+
* `character` styles, bases only: the character's own sprite, which
|
|
771
|
+
* PixelLab rotates into the other directions instead of drawing from
|
|
772
|
+
* the prompt. A manifest-relative PNG or JPEG of the south-facing
|
|
773
|
+
* sprite, or an object keyed by direction (`{ "south": ..., "east":
|
|
774
|
+
* ... }`); quadrupeds in `standard` mode need south and east, and only
|
|
775
|
+
* `standard` takes more than south. `standard` wants each image at the
|
|
776
|
+
* style's size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
777
|
+
* still guides the result.
|
|
778
|
+
*/
|
|
779
|
+
reference: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.record(CharacterDirectionSchema, import_zod.z.string().min(1))]).optional(),
|
|
707
780
|
/**
|
|
708
781
|
* Another asset of the same style flipped left to right, made locally
|
|
709
782
|
* from that asset's downloaded files at no generation cost.
|
|
@@ -778,6 +851,16 @@ var AssetSchema = import_zod.z.object({
|
|
|
778
851
|
path: ["prompt"]
|
|
779
852
|
});
|
|
780
853
|
}
|
|
854
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
855
|
+
context.addIssue({
|
|
856
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
857
|
+
message: "a reference sprite belongs on a base; a state, animation, or mirror takes its look from its parent",
|
|
858
|
+
path: ["reference"]
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
|
|
862
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
|
|
863
|
+
}
|
|
781
864
|
});
|
|
782
865
|
var DEFAULT_HISTORY_LIMIT = 5;
|
|
783
866
|
var HistoryLimitSchema = import_zod.z.number().int().min(0).max(100);
|
|
@@ -1203,6 +1286,17 @@ var PixelLabError = class extends ProviderError {
|
|
|
1203
1286
|
}
|
|
1204
1287
|
body;
|
|
1205
1288
|
};
|
|
1289
|
+
function proportionsPayload(proportions) {
|
|
1290
|
+
if (typeof proportions === "string") return { type: "preset", name: proportions };
|
|
1291
|
+
return {
|
|
1292
|
+
type: "custom",
|
|
1293
|
+
...proportions.headSize !== void 0 ? { head_size: proportions.headSize } : {},
|
|
1294
|
+
...proportions.armsLength !== void 0 ? { arms_length: proportions.armsLength } : {},
|
|
1295
|
+
...proportions.legsLength !== void 0 ? { legs_length: proportions.legsLength } : {},
|
|
1296
|
+
...proportions.shoulderWidth !== void 0 ? { shoulder_width: proportions.shoulderWidth } : {},
|
|
1297
|
+
...proportions.hipWidth !== void 0 ? { hip_width: proportions.hipWidth } : {}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1206
1300
|
var PixelLabClient = class {
|
|
1207
1301
|
constructor(apiKey, timeoutMs = 12e4) {
|
|
1208
1302
|
this.apiKey = apiKey;
|
|
@@ -1411,10 +1505,17 @@ var PixelLabClient = class {
|
|
|
1411
1505
|
* A base character. Standard mode picks the 4- or 8-direction template
|
|
1412
1506
|
* endpoint; v3 and pro have their own and always draw 8. Every one answers
|
|
1413
1507
|
* at once with the character id and a background job.
|
|
1508
|
+
*
|
|
1509
|
+
* A `reference` is the author's own sprite: standard takes one per
|
|
1510
|
+
* direction and draws the rest, v3 rotates the south one, and pro
|
|
1511
|
+
* switches to its rotate method for it. A `styleReference` is pro's style
|
|
1512
|
+
* anchor; the other engines have no such input.
|
|
1414
1513
|
*/
|
|
1415
1514
|
async createCharacter(args) {
|
|
1416
1515
|
const imageSize = { width: args.size, height: args.size };
|
|
1417
1516
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
1517
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1518
|
+
const south = args.reference?.south;
|
|
1418
1519
|
let path33;
|
|
1419
1520
|
let body;
|
|
1420
1521
|
if (args.mode === "v3") {
|
|
@@ -1427,7 +1528,9 @@ var PixelLabClient = class {
|
|
|
1427
1528
|
...args.view ? { view: args.view } : {},
|
|
1428
1529
|
...args.outline ? { outline: args.outline } : {},
|
|
1429
1530
|
...args.detail ? { detail: args.detail } : {},
|
|
1430
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
1531
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1532
|
+
...south ? { reference_image: encode(south) } : {},
|
|
1533
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
1431
1534
|
};
|
|
1432
1535
|
} else if (args.mode === "pro") {
|
|
1433
1536
|
path33 = "/create-character-pro";
|
|
@@ -1436,8 +1539,10 @@ var PixelLabClient = class {
|
|
|
1436
1539
|
image_size: imageSize,
|
|
1437
1540
|
template_id: args.template,
|
|
1438
1541
|
no_background: args.noBackground ?? true,
|
|
1542
|
+
method: south ? "rotate_character" : "create_with_style",
|
|
1439
1543
|
...args.view ? { view: args.view } : {},
|
|
1440
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
1544
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1545
|
+
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
|
|
1441
1546
|
};
|
|
1442
1547
|
} else {
|
|
1443
1548
|
path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
@@ -1450,6 +1555,10 @@ var PixelLabClient = class {
|
|
|
1450
1555
|
...args.shading ? { shading: args.shading } : {},
|
|
1451
1556
|
...args.detail ? { detail: args.detail } : {},
|
|
1452
1557
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1558
|
+
...args.proportions !== void 0 ? { proportions: proportionsPayload(args.proportions) } : {},
|
|
1559
|
+
...args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1560
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1561
|
+
...args.reference && Object.keys(args.reference).length ? { directions: Object.fromEntries(Object.entries(args.reference).flatMap(([direction, image]) => image ? [[direction, encode(image)]] : [])) } : {},
|
|
1453
1562
|
...palette
|
|
1454
1563
|
};
|
|
1455
1564
|
}
|
|
@@ -1478,6 +1587,7 @@ var PixelLabClient = class {
|
|
|
1478
1587
|
* the action text; pro is the sequential engine. One job per direction.
|
|
1479
1588
|
*/
|
|
1480
1589
|
async animateCharacter(args) {
|
|
1590
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1481
1591
|
const raw = await this.request("/animate-character", {
|
|
1482
1592
|
method: "POST",
|
|
1483
1593
|
body: JSON.stringify({
|
|
@@ -1487,8 +1597,18 @@ var PixelLabClient = class {
|
|
|
1487
1597
|
directions: args.directions,
|
|
1488
1598
|
...args.template ? { template_animation_id: args.template } : {},
|
|
1489
1599
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
1600
|
+
...args.description ? { description: args.description } : {},
|
|
1490
1601
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
1491
1602
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1603
|
+
...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
1604
|
+
...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
1605
|
+
...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
1606
|
+
...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1607
|
+
...args.mode === "template" && args.outline ? { outline: args.outline } : {},
|
|
1608
|
+
...args.mode === "template" && args.shading ? { shading: args.shading } : {},
|
|
1609
|
+
...args.mode === "template" && args.detail ? { detail: args.detail } : {},
|
|
1610
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1611
|
+
...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
|
|
1492
1612
|
...args.seed != null ? { seed: args.seed } : {}
|
|
1493
1613
|
})
|
|
1494
1614
|
});
|
|
@@ -2085,12 +2205,122 @@ function skipSubBlocks(bytes, start) {
|
|
|
2085
2205
|
function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
2086
2206
|
return `${hash}${mediaExtension(mediaType)}`;
|
|
2087
2207
|
}
|
|
2208
|
+
function imageMetadata(buf) {
|
|
2209
|
+
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
2210
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
2211
|
+
}
|
|
2212
|
+
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
2213
|
+
let offset = 2;
|
|
2214
|
+
while (offset + 3 < buf.length) {
|
|
2215
|
+
if (buf[offset] !== 255) return null;
|
|
2216
|
+
while (buf[offset] === 255) offset++;
|
|
2217
|
+
const marker = buf[offset++];
|
|
2218
|
+
if (marker == null || marker === 217 || marker === 218) break;
|
|
2219
|
+
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
2220
|
+
if (offset + 2 > buf.length) return null;
|
|
2221
|
+
const length = buf.readUInt16BE(offset);
|
|
2222
|
+
if (length < 2 || offset + length > buf.length) return null;
|
|
2223
|
+
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
2224
|
+
if (isStartOfFrame) {
|
|
2225
|
+
if (length < 7) return null;
|
|
2226
|
+
return {
|
|
2227
|
+
width: buf.readUInt16BE(offset + 5),
|
|
2228
|
+
height: buf.readUInt16BE(offset + 3),
|
|
2229
|
+
format: "jpeg"
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
offset += length;
|
|
2233
|
+
}
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2088
2236
|
|
|
2089
2237
|
// src/providers/pixellab.ts
|
|
2090
|
-
var
|
|
2091
|
-
var
|
|
2238
|
+
var import_node_fs2 = require("fs");
|
|
2239
|
+
var import_node_crypto2 = require("crypto");
|
|
2092
2240
|
var import_node_os = __toESM(require("os"), 1);
|
|
2093
2241
|
var import_node_path = __toESM(require("path"), 1);
|
|
2242
|
+
|
|
2243
|
+
// src/hash.ts
|
|
2244
|
+
var import_node_crypto = require("crypto");
|
|
2245
|
+
var import_node_fs = require("fs");
|
|
2246
|
+
function sha256(data) {
|
|
2247
|
+
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
2248
|
+
}
|
|
2249
|
+
async function sha256File(path33) {
|
|
2250
|
+
return new Promise((resolve, reject) => {
|
|
2251
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
2252
|
+
const stream = (0, import_node_fs.createReadStream)(path33);
|
|
2253
|
+
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
2254
|
+
stream.on("error", reject);
|
|
2255
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
2259
|
+
return sha256(
|
|
2260
|
+
JSON.stringify({
|
|
2261
|
+
// Preserve every existing PixelLab hash while making a provider switch
|
|
2262
|
+
// invalidate the spec. Older manifests implicitly mean pixellab.
|
|
2263
|
+
provider: spec.provider === "pixellab" ? void 0 : spec.provider,
|
|
2264
|
+
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
2265
|
+
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
2266
|
+
generator: spec.generator,
|
|
2267
|
+
prompt: spec.prompt,
|
|
2268
|
+
width: spec.width,
|
|
2269
|
+
height: spec.height,
|
|
2270
|
+
view: spec.view,
|
|
2271
|
+
outline: spec.outline ?? null,
|
|
2272
|
+
shading: spec.shading ?? null,
|
|
2273
|
+
detail: spec.detail ?? null,
|
|
2274
|
+
seed: spec.seed ?? null,
|
|
2275
|
+
palette: spec.palette,
|
|
2276
|
+
// `noBackground` only reaches the wire for pixflux; the tile fields are
|
|
2277
|
+
// undefined for every other generator. `tileSize` is intentionally
|
|
2278
|
+
// absent; width/height are derived from it, so it is already covered.
|
|
2279
|
+
noBackground: spec.generator === "pixflux" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
2280
|
+
tileType: spec.tileType,
|
|
2281
|
+
tileView: spec.tileView,
|
|
2282
|
+
tileFeature: spec.tileFeature,
|
|
2283
|
+
outlineMode: spec.outlineMode,
|
|
2284
|
+
styleImages: styleImageHashes,
|
|
2285
|
+
// A character family: the engine and rotations of a base, the parent's
|
|
2286
|
+
// generated bytes for a state or animation (so a regenerated parent
|
|
2287
|
+
// makes its children stale), and each shape's own settings.
|
|
2288
|
+
character: spec.character ? {
|
|
2289
|
+
kind: spec.character.kind,
|
|
2290
|
+
mode: spec.character.mode,
|
|
2291
|
+
directions: spec.character.directions,
|
|
2292
|
+
template: spec.character.template,
|
|
2293
|
+
parent: spec.character.parentAssetId,
|
|
2294
|
+
parentSha256: spec.character.parentSha256 ?? null,
|
|
2295
|
+
state: spec.character.state,
|
|
2296
|
+
// Pose images enter as their hashes, never their paths.
|
|
2297
|
+
animation: spec.character.animation ? {
|
|
2298
|
+
...spec.character.animation,
|
|
2299
|
+
startFrame: spec.character.animation.startFrame?.sha256,
|
|
2300
|
+
endFrame: spec.character.animation.endFrame?.sha256
|
|
2301
|
+
} : void 0,
|
|
2302
|
+
// Absent keys keep the hashes of manifests that never set these.
|
|
2303
|
+
proportions: spec.character.proportions,
|
|
2304
|
+
textGuidanceScale: spec.character.textGuidanceScale,
|
|
2305
|
+
isometric: spec.character.isometric,
|
|
2306
|
+
enhancePrompt: spec.character.enhancePrompt,
|
|
2307
|
+
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
|
|
2308
|
+
} : void 0,
|
|
2309
|
+
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2310
|
+
// compares those directly, so only the choice of source is identity.
|
|
2311
|
+
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
2312
|
+
revision: spec.revision ? {
|
|
2313
|
+
mode: spec.revision.mode,
|
|
2314
|
+
from: spec.revision.sourceAssetId,
|
|
2315
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
2316
|
+
maskSha256: spec.revision.maskSha256,
|
|
2317
|
+
strength: spec.revision.strength
|
|
2318
|
+
} : void 0
|
|
2319
|
+
})
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// src/providers/pixellab.ts
|
|
2094
2324
|
function pixelLabObjectUrl(generator, objectId) {
|
|
2095
2325
|
if (!objectId) return null;
|
|
2096
2326
|
if (generator === "map" || generator === "1dir") {
|
|
@@ -2171,7 +2401,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2171
2401
|
*/
|
|
2172
2402
|
static cacheDir() {
|
|
2173
2403
|
const dir = import_node_path.default.join(import_node_os.default.tmpdir(), "pixelkiln-pixflux");
|
|
2174
|
-
(0,
|
|
2404
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
2175
2405
|
return dir;
|
|
2176
2406
|
}
|
|
2177
2407
|
estimate(spec) {
|
|
@@ -2213,8 +2443,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2213
2443
|
"high detail"
|
|
2214
2444
|
]);
|
|
2215
2445
|
}
|
|
2216
|
-
if (spec.generator === "character") this.validateCharacter(spec);
|
|
2217
|
-
if ((spec.generator === "map" || spec.generator === "pixflux"
|
|
2446
|
+
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
2447
|
+
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
2218
2448
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
2219
2449
|
}
|
|
2220
2450
|
for (const image of styleImages) {
|
|
@@ -2230,9 +2460,80 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2230
2460
|
);
|
|
2231
2461
|
}
|
|
2232
2462
|
}
|
|
2233
|
-
validateCharacter(spec) {
|
|
2463
|
+
validateCharacter(spec, styleImages = []) {
|
|
2234
2464
|
const character = spec.character;
|
|
2235
2465
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
2466
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
2467
|
+
if (character.kind === "base" && character.proportions !== void 0) {
|
|
2468
|
+
if (character.mode !== "standard") {
|
|
2469
|
+
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
2470
|
+
}
|
|
2471
|
+
if (character.template !== "mannequin") {
|
|
2472
|
+
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
2476
|
+
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
2477
|
+
}
|
|
2478
|
+
if (character.animation) {
|
|
2479
|
+
const { startFrame, endFrame } = character.animation;
|
|
2480
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
2481
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
2482
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
if (endFrame) {
|
|
2486
|
+
const start = startFrame ?? (character.parentFile && (0, import_node_fs2.existsSync)(character.parentFile) ? imageMetadata((0, import_node_fs2.readFileSync)(character.parentFile)) : null);
|
|
2487
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
2488
|
+
throw new Error(
|
|
2489
|
+
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "character's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
if (character.reference) {
|
|
2495
|
+
const given = Object.keys(character.reference);
|
|
2496
|
+
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
2497
|
+
if (character.mode !== "standard") {
|
|
2498
|
+
if (given.length > 1) throw new Error(`${label}: PixelLab ${character.mode} rotates one south-facing reference; drop the other directions`);
|
|
2499
|
+
const limit = character.mode === "v3" ? 256 : 168;
|
|
2500
|
+
const south = character.reference.south;
|
|
2501
|
+
if (south.width > limit || south.height > limit) {
|
|
2502
|
+
throw new Error(`${label}: reference is ${south.width}x${south.height}; PixelLab ${character.mode} takes up to ${limit}px`);
|
|
2503
|
+
}
|
|
2504
|
+
} else {
|
|
2505
|
+
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2506
|
+
for (const direction of given) {
|
|
2507
|
+
if (!allowed.includes(direction)) {
|
|
2508
|
+
throw new Error(`${label}: reference "${direction}" is not one of this character's ${character.directions} directions`);
|
|
2509
|
+
}
|
|
2510
|
+
const image = character.reference[direction];
|
|
2511
|
+
if (image.width !== spec.width || image.height !== spec.height) {
|
|
2512
|
+
throw new Error(
|
|
2513
|
+
`${label}: reference (${direction}) is ${image.width}x${image.height}; standard mode wants each image at the style's size, ${spec.width}x${spec.height}`
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
if (character.template !== "mannequin" && !character.reference.east) {
|
|
2518
|
+
throw new Error(`${label}: a ${character.template} reference needs south and east images`);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
if (styleImages.length) {
|
|
2523
|
+
if (character.mode !== "pro") {
|
|
2524
|
+
throw new Error(
|
|
2525
|
+
`PixelLab ${character.mode} characters take no style images; give a base its own sprite with \`reference\`, or use mode pro for a style anchor`
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
if (styleImages.length > 1) throw new Error("PixelLab pro characters take one style image");
|
|
2529
|
+
if (character.kind === "base" && character.reference) {
|
|
2530
|
+
throw new Error(`${label}: a pro base rotating its own reference takes no style image`);
|
|
2531
|
+
}
|
|
2532
|
+
const image = styleImages[0];
|
|
2533
|
+
if (image.width > 168 || image.height > 168) {
|
|
2534
|
+
throw new Error(`PixelLab pro character style image is ${image.width}x${image.height}; the limit is 168px`);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2236
2537
|
const maxSize = character.mode === "v3" ? 256 : 128;
|
|
2237
2538
|
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
2238
2539
|
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
@@ -2268,10 +2569,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2268
2569
|
* asset so a later run can find it, and clears its own earlier take for
|
|
2269
2570
|
* that direction first, since PixelLab skips a direction that exists.
|
|
2270
2571
|
*/
|
|
2271
|
-
async submitCharacter(spec, context) {
|
|
2572
|
+
async submitCharacter(spec, styleImages, context) {
|
|
2272
2573
|
const character = spec.character;
|
|
2273
2574
|
if (character.kind === "base") {
|
|
2274
2575
|
const swatch = spec.palette.length && character.mode === "standard" ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2576
|
+
const styleImage = styleImages[0];
|
|
2275
2577
|
const res2 = await this.client.createCharacter({
|
|
2276
2578
|
mode: character.mode,
|
|
2277
2579
|
description: spec.prompt,
|
|
@@ -2284,7 +2586,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2284
2586
|
detail: spec.detail,
|
|
2285
2587
|
seed: spec.seed,
|
|
2286
2588
|
noBackground: spec.noBackground,
|
|
2287
|
-
paletteSwatchBase64: swatch
|
|
2589
|
+
paletteSwatchBase64: swatch,
|
|
2590
|
+
proportions: character.proportions,
|
|
2591
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
2592
|
+
isometric: character.isometric,
|
|
2593
|
+
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
2594
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
2595
|
+
enhancePrompt: character.enhancePrompt
|
|
2288
2596
|
});
|
|
2289
2597
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
2290
2598
|
}
|
|
@@ -2326,7 +2634,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2326
2634
|
frameCount: animation.frames,
|
|
2327
2635
|
keepFirstFrame: animation.keepFirstFrame,
|
|
2328
2636
|
directions: [animation.direction],
|
|
2329
|
-
seed: spec.seed
|
|
2637
|
+
seed: spec.seed,
|
|
2638
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
2639
|
+
isometric: character.isometric,
|
|
2640
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
2641
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
2642
|
+
description: animation.subject,
|
|
2643
|
+
outline: animation.outline,
|
|
2644
|
+
shading: animation.shading,
|
|
2645
|
+
detail: animation.detail,
|
|
2646
|
+
enhancePrompt: animation.enhancePrompt,
|
|
2647
|
+
paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
|
|
2330
2648
|
});
|
|
2331
2649
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
2332
2650
|
return {
|
|
@@ -2416,7 +2734,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2416
2734
|
}
|
|
2417
2735
|
async submit(spec, styleImages, context) {
|
|
2418
2736
|
this.validate(spec, styleImages);
|
|
2419
|
-
if (spec.generator === "character") return this.submitCharacter(spec, context);
|
|
2737
|
+
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
2420
2738
|
if (spec.generator === "pixflux") {
|
|
2421
2739
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2422
2740
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -2427,8 +2745,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2427
2745
|
paletteSwatchBase64: swatch,
|
|
2428
2746
|
seed: spec.seed
|
|
2429
2747
|
});
|
|
2430
|
-
const jobId = (0,
|
|
2431
|
-
(0,
|
|
2748
|
+
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
2749
|
+
(0, import_node_fs2.writeFileSync)(import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), png);
|
|
2432
2750
|
return { jobId };
|
|
2433
2751
|
}
|
|
2434
2752
|
if (spec.generator === "tiles") {
|
|
@@ -2470,7 +2788,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2470
2788
|
async poll(jobId, generator, context) {
|
|
2471
2789
|
if (generator === "pixflux") {
|
|
2472
2790
|
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
2473
|
-
if ((0,
|
|
2791
|
+
if ((0, import_node_fs2.existsSync)(file)) {
|
|
2474
2792
|
const sourceUrl = `file://${file}`;
|
|
2475
2793
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
2476
2794
|
}
|
|
@@ -2576,7 +2894,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2576
2894
|
return { objectId, sourceUrl: firstUrl(obj?.rotation_urls) ?? obj?.preview_url ?? null };
|
|
2577
2895
|
}
|
|
2578
2896
|
async download(url) {
|
|
2579
|
-
if (url.startsWith("file://")) return (0,
|
|
2897
|
+
if (url.startsWith("file://")) return (0, import_node_fs2.readFileSync)(url.slice("file://".length));
|
|
2580
2898
|
return this.client.download(url);
|
|
2581
2899
|
}
|
|
2582
2900
|
async balance() {
|
|
@@ -2683,6 +3001,24 @@ function firstUrl(urls) {
|
|
|
2683
3001
|
if (!urls) return null;
|
|
2684
3002
|
return Object.values(urls).find((u) => typeof u === "string") ?? null;
|
|
2685
3003
|
}
|
|
3004
|
+
function readReference(spec, reference) {
|
|
3005
|
+
const images = {};
|
|
3006
|
+
for (const [direction, image] of Object.entries(reference)) {
|
|
3007
|
+
const bytes = (0, import_node_fs2.readFileSync)(image.path);
|
|
3008
|
+
if (sha256(bytes) !== image.sha256) {
|
|
3009
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: reference (${direction}) changed after the manifest was resolved: ${image.path}`);
|
|
3010
|
+
}
|
|
3011
|
+
images[direction] = { base64: bytes.toString("base64"), format: image.format };
|
|
3012
|
+
}
|
|
3013
|
+
return images;
|
|
3014
|
+
}
|
|
3015
|
+
function readPose(spec, what, image) {
|
|
3016
|
+
const bytes = (0, import_node_fs2.readFileSync)(image.path);
|
|
3017
|
+
if (sha256(bytes) !== image.sha256) {
|
|
3018
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
3019
|
+
}
|
|
3020
|
+
return { base64: bytes.toString("base64"), format: image.format };
|
|
3021
|
+
}
|
|
2686
3022
|
function rotationSources(character) {
|
|
2687
3023
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2688
3024
|
const sources = [];
|
|
@@ -3554,77 +3890,6 @@ function redactScenarioError(value, secrets) {
|
|
|
3554
3890
|
var import_node_crypto3 = require("crypto");
|
|
3555
3891
|
var import_promises = require("fs/promises");
|
|
3556
3892
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
3557
|
-
|
|
3558
|
-
// src/hash.ts
|
|
3559
|
-
var import_node_crypto2 = require("crypto");
|
|
3560
|
-
var import_node_fs2 = require("fs");
|
|
3561
|
-
function sha256(data) {
|
|
3562
|
-
return (0, import_node_crypto2.createHash)("sha256").update(data).digest("hex");
|
|
3563
|
-
}
|
|
3564
|
-
async function sha256File(path33) {
|
|
3565
|
-
return new Promise((resolve, reject) => {
|
|
3566
|
-
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
3567
|
-
const stream = (0, import_node_fs2.createReadStream)(path33);
|
|
3568
|
-
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3569
|
-
stream.on("error", reject);
|
|
3570
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
3571
|
-
});
|
|
3572
|
-
}
|
|
3573
|
-
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
3574
|
-
return sha256(
|
|
3575
|
-
JSON.stringify({
|
|
3576
|
-
// Preserve every existing PixelLab hash while making a provider switch
|
|
3577
|
-
// invalidate the spec. Older manifests implicitly mean pixellab.
|
|
3578
|
-
provider: spec.provider === "pixellab" ? void 0 : spec.provider,
|
|
3579
|
-
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
3580
|
-
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
3581
|
-
generator: spec.generator,
|
|
3582
|
-
prompt: spec.prompt,
|
|
3583
|
-
width: spec.width,
|
|
3584
|
-
height: spec.height,
|
|
3585
|
-
view: spec.view,
|
|
3586
|
-
outline: spec.outline ?? null,
|
|
3587
|
-
shading: spec.shading ?? null,
|
|
3588
|
-
detail: spec.detail ?? null,
|
|
3589
|
-
seed: spec.seed ?? null,
|
|
3590
|
-
palette: spec.palette,
|
|
3591
|
-
// `noBackground` only reaches the wire for pixflux; the tile fields are
|
|
3592
|
-
// undefined for every other generator. `tileSize` is intentionally
|
|
3593
|
-
// absent; width/height are derived from it, so it is already covered.
|
|
3594
|
-
noBackground: spec.generator === "pixflux" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
3595
|
-
tileType: spec.tileType,
|
|
3596
|
-
tileView: spec.tileView,
|
|
3597
|
-
tileFeature: spec.tileFeature,
|
|
3598
|
-
outlineMode: spec.outlineMode,
|
|
3599
|
-
styleImages: styleImageHashes,
|
|
3600
|
-
// A character family: the engine and rotations of a base, the parent's
|
|
3601
|
-
// generated bytes for a state or animation (so a regenerated parent
|
|
3602
|
-
// makes its children stale), and each shape's own settings.
|
|
3603
|
-
character: spec.character ? {
|
|
3604
|
-
kind: spec.character.kind,
|
|
3605
|
-
mode: spec.character.mode,
|
|
3606
|
-
directions: spec.character.directions,
|
|
3607
|
-
template: spec.character.template,
|
|
3608
|
-
parent: spec.character.parentAssetId,
|
|
3609
|
-
parentSha256: spec.character.parentSha256 ?? null,
|
|
3610
|
-
state: spec.character.state,
|
|
3611
|
-
animation: spec.character.animation
|
|
3612
|
-
} : void 0,
|
|
3613
|
-
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
3614
|
-
// compares those directly, so only the choice of source is identity.
|
|
3615
|
-
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
3616
|
-
revision: spec.revision ? {
|
|
3617
|
-
mode: spec.revision.mode,
|
|
3618
|
-
from: spec.revision.sourceAssetId,
|
|
3619
|
-
sourceSha256: spec.revision.sourceSha256,
|
|
3620
|
-
maskSha256: spec.revision.maskSha256,
|
|
3621
|
-
strength: spec.revision.strength
|
|
3622
|
-
} : void 0
|
|
3623
|
-
})
|
|
3624
|
-
);
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
// src/providers/comfyui.ts
|
|
3628
3893
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
3629
3894
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
3630
3895
|
"prompt",
|
|
@@ -5251,16 +5516,16 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5251
5516
|
}
|
|
5252
5517
|
}
|
|
5253
5518
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
5254
|
-
async function loadStyleImage(rel) {
|
|
5519
|
+
async function loadStyleImage(rel, what = "Style image") {
|
|
5255
5520
|
const abs = import_node_path5.default.resolve(root, rel);
|
|
5256
5521
|
let hit = styleImageCache.get(abs);
|
|
5257
5522
|
if (!hit) {
|
|
5258
|
-
if (!(0, import_node_fs5.existsSync)(abs)) throw new Error(
|
|
5523
|
+
if (!(0, import_node_fs5.existsSync)(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
5259
5524
|
const buf = await (0, import_promises3.readFile)(abs);
|
|
5260
5525
|
const metadata = imageMetadata(buf);
|
|
5261
|
-
if (!metadata) throw new Error(
|
|
5526
|
+
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
5262
5527
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
5263
|
-
throw new Error(
|
|
5528
|
+
throw new Error(`${what} has invalid dimensions: ${abs}`);
|
|
5264
5529
|
}
|
|
5265
5530
|
hit = { base64: buf.toString("base64"), hash: sha256(buf), ...metadata };
|
|
5266
5531
|
styleImageCache.set(abs, hit);
|
|
@@ -5367,7 +5632,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5367
5632
|
tileView: generator === "tiles" ? style.tileView : void 0,
|
|
5368
5633
|
tileFeature: generator === "tiles" ? style.tileFeature : void 0,
|
|
5369
5634
|
outlineMode: generator === "tiles" ? style.outlineMode : void 0,
|
|
5370
|
-
...generator === "character" ? { character: resolveCharacterShape(asset, style, characterKind) } : {},
|
|
5635
|
+
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
5371
5636
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
|
|
5372
5637
|
costUnit: "generations",
|
|
5373
5638
|
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
@@ -5584,43 +5849,34 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
5584
5849
|
async function styleImagesBase64(loaded, styleId) {
|
|
5585
5850
|
return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
|
|
5586
5851
|
}
|
|
5587
|
-
function
|
|
5588
|
-
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
5589
|
-
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
5590
|
-
}
|
|
5591
|
-
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
5592
|
-
let offset = 2;
|
|
5593
|
-
while (offset + 3 < buf.length) {
|
|
5594
|
-
if (buf[offset] !== 255) return null;
|
|
5595
|
-
while (buf[offset] === 255) offset++;
|
|
5596
|
-
const marker = buf[offset++];
|
|
5597
|
-
if (marker == null || marker === 217 || marker === 218) break;
|
|
5598
|
-
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
5599
|
-
if (offset + 2 > buf.length) return null;
|
|
5600
|
-
const length = buf.readUInt16BE(offset);
|
|
5601
|
-
if (length < 2 || offset + length > buf.length) return null;
|
|
5602
|
-
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
5603
|
-
if (isStartOfFrame) {
|
|
5604
|
-
if (length < 7) return null;
|
|
5605
|
-
return {
|
|
5606
|
-
width: buf.readUInt16BE(offset + 5),
|
|
5607
|
-
height: buf.readUInt16BE(offset + 3),
|
|
5608
|
-
format: "jpeg"
|
|
5609
|
-
};
|
|
5610
|
-
}
|
|
5611
|
-
offset += length;
|
|
5612
|
-
}
|
|
5613
|
-
return null;
|
|
5614
|
-
}
|
|
5615
|
-
function resolveCharacterShape(asset, style, kind) {
|
|
5852
|
+
async function resolveCharacterShape(asset, style, kind, files) {
|
|
5616
5853
|
const mode2 = style.mode ?? "standard";
|
|
5617
5854
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
5855
|
+
const proportions = asset.proportions ?? style.proportions;
|
|
5618
5856
|
const shape = {
|
|
5619
5857
|
kind,
|
|
5620
5858
|
mode: mode2,
|
|
5621
5859
|
directions,
|
|
5622
|
-
template: style.template ?? "mannequin"
|
|
5860
|
+
template: style.template ?? "mannequin",
|
|
5861
|
+
...proportions !== void 0 ? { proportions } : {},
|
|
5862
|
+
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
5863
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {},
|
|
5864
|
+
...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
|
|
5623
5865
|
};
|
|
5866
|
+
if (asset.reference) {
|
|
5867
|
+
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
5868
|
+
shape.reference = {};
|
|
5869
|
+
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
5870
|
+
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
5871
|
+
shape.reference[direction] = {
|
|
5872
|
+
path: import_node_path5.default.resolve(files.root, rel),
|
|
5873
|
+
sha256: image.hash,
|
|
5874
|
+
width: image.width,
|
|
5875
|
+
height: image.height,
|
|
5876
|
+
format: image.format
|
|
5877
|
+
};
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5624
5880
|
if (asset.state) {
|
|
5625
5881
|
shape.state = {
|
|
5626
5882
|
paletteFromReference: asset.state.paletteFromReference,
|
|
@@ -5629,13 +5885,24 @@ function resolveCharacterShape(asset, style, kind) {
|
|
|
5629
5885
|
}
|
|
5630
5886
|
if (asset.animation) {
|
|
5631
5887
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
5888
|
+
const pose = async (rel, what) => {
|
|
5889
|
+
const image = await files.load(rel, what);
|
|
5890
|
+
return { path: import_node_path5.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
5891
|
+
};
|
|
5632
5892
|
shape.animation = {
|
|
5633
5893
|
mode: animationMode,
|
|
5634
5894
|
...asset.animation.template ? { template: asset.animation.template } : {},
|
|
5635
5895
|
direction: asset.animation.direction,
|
|
5636
5896
|
frames: asset.animation.frames ?? 8,
|
|
5637
5897
|
fps: asset.animation.fps,
|
|
5638
|
-
keepFirstFrame: asset.animation.keepFirstFrame
|
|
5898
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
5899
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
5900
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
5901
|
+
...asset.animation.subject ? { subject: asset.animation.subject } : {},
|
|
5902
|
+
...asset.animation.outline ? { outline: asset.animation.outline } : {},
|
|
5903
|
+
...asset.animation.shading ? { shading: asset.animation.shading } : {},
|
|
5904
|
+
...asset.animation.detail ? { detail: asset.animation.detail } : {},
|
|
5905
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
5639
5906
|
};
|
|
5640
5907
|
}
|
|
5641
5908
|
return shape;
|
|
@@ -13898,10 +14165,12 @@ async function workspaceStatus(ws, dir) {
|
|
|
13898
14165
|
BudgetError,
|
|
13899
14166
|
CHARACTER_DIRECTIONS_4,
|
|
13900
14167
|
CHARACTER_DIRECTIONS_8,
|
|
14168
|
+
CHARACTER_PROPORTION_PRESETS,
|
|
13901
14169
|
CharacterAnimationModeSchema,
|
|
13902
14170
|
CharacterAnimationSchema,
|
|
13903
14171
|
CharacterDirectionSchema,
|
|
13904
14172
|
CharacterModeSchema,
|
|
14173
|
+
CharacterProportionsSchema,
|
|
13905
14174
|
CharacterStateSchema,
|
|
13906
14175
|
ComfyUIProvider,
|
|
13907
14176
|
DEFAULT_HISTORY_LIMIT,
|
|
@@ -14068,6 +14337,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
14068
14337
|
portableOutputPath,
|
|
14069
14338
|
prepareReview,
|
|
14070
14339
|
primaryOutput,
|
|
14340
|
+
proportionsPayload,
|
|
14071
14341
|
providerCredentialEnvs,
|
|
14072
14342
|
providerFactory,
|
|
14073
14343
|
pushTags,
|