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.js
CHANGED
|
@@ -91,6 +91,17 @@ var MIRRORED_DIRECTION = {
|
|
|
91
91
|
"south-west": "south-east"
|
|
92
92
|
};
|
|
93
93
|
var CharacterModeSchema = z.enum(["standard", "v3", "pro"]);
|
|
94
|
+
var CHARACTER_PROPORTION_PRESETS = ["default", "chibi", "cartoon", "stylized", "realistic_male", "realistic_female", "heroic"];
|
|
95
|
+
var CharacterProportionsSchema = z.union([
|
|
96
|
+
z.enum(CHARACTER_PROPORTION_PRESETS),
|
|
97
|
+
z.object({
|
|
98
|
+
headSize: z.number().min(0.5).max(2).optional(),
|
|
99
|
+
armsLength: z.number().min(0.5).max(2).optional(),
|
|
100
|
+
legsLength: z.number().min(0.5).max(2).optional(),
|
|
101
|
+
shoulderWidth: z.number().min(0.5).max(2).optional(),
|
|
102
|
+
hipWidth: z.number().min(0.5).max(2).optional()
|
|
103
|
+
}).strict()
|
|
104
|
+
]);
|
|
94
105
|
var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
|
|
95
106
|
var CharacterStateSchema = z.object({
|
|
96
107
|
/** The base character, or another state, in the same style. */
|
|
@@ -117,11 +128,45 @@ var CharacterAnimationSchema = z.object({
|
|
|
117
128
|
/** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
|
|
118
129
|
keepFirstFrame: z.boolean().default(true),
|
|
119
130
|
/** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
|
|
120
|
-
mode: CharacterAnimationModeSchema.optional()
|
|
131
|
+
mode: CharacterAnimationModeSchema.optional(),
|
|
132
|
+
/**
|
|
133
|
+
* v3 only: a manifest-relative image of the pose to start from, instead
|
|
134
|
+
* of the character's rotation for this direction. Up to 256px.
|
|
135
|
+
*/
|
|
136
|
+
startFrame: z.string().min(1).optional(),
|
|
137
|
+
/**
|
|
138
|
+
* v3 only: a pose to animate toward. The loop then interpolates from the
|
|
139
|
+
* start frame (the rotation, or `startFrame`) to this image, which must
|
|
140
|
+
* be the same size as the start frame.
|
|
141
|
+
*/
|
|
142
|
+
endFrame: z.string().min(1).optional(),
|
|
143
|
+
/** What is being animated, when the character's own description would mislead the model. */
|
|
144
|
+
subject: z.string().min(1).optional(),
|
|
145
|
+
/** Template mode only: style hints for this loop, over the character's own. */
|
|
146
|
+
outline: z.string().min(1).optional(),
|
|
147
|
+
shading: z.string().min(1).optional(),
|
|
148
|
+
detail: z.string().min(1).optional(),
|
|
149
|
+
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
150
|
+
enhancePrompt: z.boolean().optional()
|
|
121
151
|
}).strict().superRefine((animation, context) => {
|
|
122
152
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
123
153
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
124
154
|
}
|
|
155
|
+
const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
|
|
156
|
+
for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
|
|
157
|
+
if (animation[key] !== void 0 && mode2 !== "v3") {
|
|
158
|
+
context.addIssue({
|
|
159
|
+
code: z.ZodIssueCode.custom,
|
|
160
|
+
message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
|
|
161
|
+
path: [key]
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const key of ["outline", "shading", "detail"]) {
|
|
166
|
+
if (animation[key] !== void 0 && mode2 !== "template") {
|
|
167
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
125
170
|
if (animation.template && animation.mode && animation.mode !== "template") {
|
|
126
171
|
context.addIssue({
|
|
127
172
|
code: z.ZodIssueCode.custom,
|
|
@@ -249,6 +294,18 @@ var StyleObjectSchema = z.object({
|
|
|
249
294
|
directions: z.union([z.literal(4), z.literal(8)]).optional(),
|
|
250
295
|
/** `character` only. Body template: `mannequin` (default) or a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`). */
|
|
251
296
|
template: z.string().min(1).optional(),
|
|
297
|
+
/** `character` only, `standard` humanoid bases. A preset or multipliers; an asset may override it. */
|
|
298
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
299
|
+
/**
|
|
300
|
+
* `character` only. How closely a `standard` base and a template loop
|
|
301
|
+
* follow their text, 1 to 20; PixelLab's default is 8. The v3 and pro
|
|
302
|
+
* engines do not take it.
|
|
303
|
+
*/
|
|
304
|
+
textGuidanceScale: z.number().min(1).max(20).optional(),
|
|
305
|
+
/** `character` only. Draw `standard` bases and every loop in isometric view. */
|
|
306
|
+
isometric: z.boolean().optional(),
|
|
307
|
+
/** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
|
|
308
|
+
enhancePrompt: z.boolean().optional(),
|
|
252
309
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
253
310
|
seed: z.number().int().optional(),
|
|
254
311
|
/**
|
|
@@ -408,6 +465,19 @@ var AssetSchema = z.object({
|
|
|
408
465
|
state: CharacterStateSchema.optional(),
|
|
409
466
|
/** `character` styles: this asset is a loop of another character asset in one direction. */
|
|
410
467
|
animation: CharacterAnimationSchema.optional(),
|
|
468
|
+
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
469
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
470
|
+
/**
|
|
471
|
+
* `character` styles, bases only: the character's own sprite, which
|
|
472
|
+
* PixelLab rotates into the other directions instead of drawing from
|
|
473
|
+
* the prompt. A manifest-relative PNG or JPEG of the south-facing
|
|
474
|
+
* sprite, or an object keyed by direction (`{ "south": ..., "east":
|
|
475
|
+
* ... }`); quadrupeds in `standard` mode need south and east, and only
|
|
476
|
+
* `standard` takes more than south. `standard` wants each image at the
|
|
477
|
+
* style's size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
478
|
+
* still guides the result.
|
|
479
|
+
*/
|
|
480
|
+
reference: z.union([z.string().min(1), z.record(CharacterDirectionSchema, z.string().min(1))]).optional(),
|
|
411
481
|
/**
|
|
412
482
|
* Another asset of the same style flipped left to right, made locally
|
|
413
483
|
* from that asset's downloaded files at no generation cost.
|
|
@@ -482,6 +552,16 @@ var AssetSchema = z.object({
|
|
|
482
552
|
path: ["prompt"]
|
|
483
553
|
});
|
|
484
554
|
}
|
|
555
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
556
|
+
context.addIssue({
|
|
557
|
+
code: z.ZodIssueCode.custom,
|
|
558
|
+
message: "a reference sprite belongs on a base; a state, animation, or mirror takes its look from its parent",
|
|
559
|
+
path: ["reference"]
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
|
|
563
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
|
|
564
|
+
}
|
|
485
565
|
});
|
|
486
566
|
var DEFAULT_HISTORY_LIMIT = 5;
|
|
487
567
|
var HistoryLimitSchema = z.number().int().min(0).max(100);
|
|
@@ -907,6 +987,17 @@ var PixelLabError = class extends ProviderError {
|
|
|
907
987
|
}
|
|
908
988
|
body;
|
|
909
989
|
};
|
|
990
|
+
function proportionsPayload(proportions) {
|
|
991
|
+
if (typeof proportions === "string") return { type: "preset", name: proportions };
|
|
992
|
+
return {
|
|
993
|
+
type: "custom",
|
|
994
|
+
...proportions.headSize !== void 0 ? { head_size: proportions.headSize } : {},
|
|
995
|
+
...proportions.armsLength !== void 0 ? { arms_length: proportions.armsLength } : {},
|
|
996
|
+
...proportions.legsLength !== void 0 ? { legs_length: proportions.legsLength } : {},
|
|
997
|
+
...proportions.shoulderWidth !== void 0 ? { shoulder_width: proportions.shoulderWidth } : {},
|
|
998
|
+
...proportions.hipWidth !== void 0 ? { hip_width: proportions.hipWidth } : {}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
910
1001
|
var PixelLabClient = class {
|
|
911
1002
|
constructor(apiKey, timeoutMs = 12e4) {
|
|
912
1003
|
this.apiKey = apiKey;
|
|
@@ -1115,10 +1206,17 @@ var PixelLabClient = class {
|
|
|
1115
1206
|
* A base character. Standard mode picks the 4- or 8-direction template
|
|
1116
1207
|
* endpoint; v3 and pro have their own and always draw 8. Every one answers
|
|
1117
1208
|
* at once with the character id and a background job.
|
|
1209
|
+
*
|
|
1210
|
+
* A `reference` is the author's own sprite: standard takes one per
|
|
1211
|
+
* direction and draws the rest, v3 rotates the south one, and pro
|
|
1212
|
+
* switches to its rotate method for it. A `styleReference` is pro's style
|
|
1213
|
+
* anchor; the other engines have no such input.
|
|
1118
1214
|
*/
|
|
1119
1215
|
async createCharacter(args) {
|
|
1120
1216
|
const imageSize = { width: args.size, height: args.size };
|
|
1121
1217
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
1218
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1219
|
+
const south = args.reference?.south;
|
|
1122
1220
|
let path33;
|
|
1123
1221
|
let body;
|
|
1124
1222
|
if (args.mode === "v3") {
|
|
@@ -1131,7 +1229,9 @@ var PixelLabClient = class {
|
|
|
1131
1229
|
...args.view ? { view: args.view } : {},
|
|
1132
1230
|
...args.outline ? { outline: args.outline } : {},
|
|
1133
1231
|
...args.detail ? { detail: args.detail } : {},
|
|
1134
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
1232
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1233
|
+
...south ? { reference_image: encode(south) } : {},
|
|
1234
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
1135
1235
|
};
|
|
1136
1236
|
} else if (args.mode === "pro") {
|
|
1137
1237
|
path33 = "/create-character-pro";
|
|
@@ -1140,8 +1240,10 @@ var PixelLabClient = class {
|
|
|
1140
1240
|
image_size: imageSize,
|
|
1141
1241
|
template_id: args.template,
|
|
1142
1242
|
no_background: args.noBackground ?? true,
|
|
1243
|
+
method: south ? "rotate_character" : "create_with_style",
|
|
1143
1244
|
...args.view ? { view: args.view } : {},
|
|
1144
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
1245
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1246
|
+
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
|
|
1145
1247
|
};
|
|
1146
1248
|
} else {
|
|
1147
1249
|
path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
@@ -1154,6 +1256,10 @@ var PixelLabClient = class {
|
|
|
1154
1256
|
...args.shading ? { shading: args.shading } : {},
|
|
1155
1257
|
...args.detail ? { detail: args.detail } : {},
|
|
1156
1258
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1259
|
+
...args.proportions !== void 0 ? { proportions: proportionsPayload(args.proportions) } : {},
|
|
1260
|
+
...args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1261
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1262
|
+
...args.reference && Object.keys(args.reference).length ? { directions: Object.fromEntries(Object.entries(args.reference).flatMap(([direction, image]) => image ? [[direction, encode(image)]] : [])) } : {},
|
|
1157
1263
|
...palette
|
|
1158
1264
|
};
|
|
1159
1265
|
}
|
|
@@ -1182,6 +1288,7 @@ var PixelLabClient = class {
|
|
|
1182
1288
|
* the action text; pro is the sequential engine. One job per direction.
|
|
1183
1289
|
*/
|
|
1184
1290
|
async animateCharacter(args) {
|
|
1291
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1185
1292
|
const raw = await this.request("/animate-character", {
|
|
1186
1293
|
method: "POST",
|
|
1187
1294
|
body: JSON.stringify({
|
|
@@ -1191,8 +1298,18 @@ var PixelLabClient = class {
|
|
|
1191
1298
|
directions: args.directions,
|
|
1192
1299
|
...args.template ? { template_animation_id: args.template } : {},
|
|
1193
1300
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
1301
|
+
...args.description ? { description: args.description } : {},
|
|
1194
1302
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
1195
1303
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1304
|
+
...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
1305
|
+
...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
1306
|
+
...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
1307
|
+
...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1308
|
+
...args.mode === "template" && args.outline ? { outline: args.outline } : {},
|
|
1309
|
+
...args.mode === "template" && args.shading ? { shading: args.shading } : {},
|
|
1310
|
+
...args.mode === "template" && args.detail ? { detail: args.detail } : {},
|
|
1311
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1312
|
+
...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
|
|
1196
1313
|
...args.seed != null ? { seed: args.seed } : {}
|
|
1197
1314
|
})
|
|
1198
1315
|
});
|
|
@@ -1789,12 +1906,122 @@ function skipSubBlocks(bytes, start) {
|
|
|
1789
1906
|
function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
1790
1907
|
return `${hash}${mediaExtension(mediaType)}`;
|
|
1791
1908
|
}
|
|
1909
|
+
function imageMetadata(buf) {
|
|
1910
|
+
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
1911
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
1912
|
+
}
|
|
1913
|
+
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
1914
|
+
let offset = 2;
|
|
1915
|
+
while (offset + 3 < buf.length) {
|
|
1916
|
+
if (buf[offset] !== 255) return null;
|
|
1917
|
+
while (buf[offset] === 255) offset++;
|
|
1918
|
+
const marker = buf[offset++];
|
|
1919
|
+
if (marker == null || marker === 217 || marker === 218) break;
|
|
1920
|
+
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
1921
|
+
if (offset + 2 > buf.length) return null;
|
|
1922
|
+
const length = buf.readUInt16BE(offset);
|
|
1923
|
+
if (length < 2 || offset + length > buf.length) return null;
|
|
1924
|
+
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
1925
|
+
if (isStartOfFrame) {
|
|
1926
|
+
if (length < 7) return null;
|
|
1927
|
+
return {
|
|
1928
|
+
width: buf.readUInt16BE(offset + 5),
|
|
1929
|
+
height: buf.readUInt16BE(offset + 3),
|
|
1930
|
+
format: "jpeg"
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
offset += length;
|
|
1934
|
+
}
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1792
1937
|
|
|
1793
1938
|
// src/providers/pixellab.ts
|
|
1794
1939
|
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
1795
1940
|
import { randomUUID } from "crypto";
|
|
1796
1941
|
import os from "os";
|
|
1797
1942
|
import path from "path";
|
|
1943
|
+
|
|
1944
|
+
// src/hash.ts
|
|
1945
|
+
import { createHash } from "crypto";
|
|
1946
|
+
import { createReadStream } from "fs";
|
|
1947
|
+
function sha256(data) {
|
|
1948
|
+
return createHash("sha256").update(data).digest("hex");
|
|
1949
|
+
}
|
|
1950
|
+
async function sha256File(path33) {
|
|
1951
|
+
return new Promise((resolve, reject) => {
|
|
1952
|
+
const hash = createHash("sha256");
|
|
1953
|
+
const stream = createReadStream(path33);
|
|
1954
|
+
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
1955
|
+
stream.on("error", reject);
|
|
1956
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
1960
|
+
return sha256(
|
|
1961
|
+
JSON.stringify({
|
|
1962
|
+
// Preserve every existing PixelLab hash while making a provider switch
|
|
1963
|
+
// invalidate the spec. Older manifests implicitly mean pixellab.
|
|
1964
|
+
provider: spec.provider === "pixellab" ? void 0 : spec.provider,
|
|
1965
|
+
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
1966
|
+
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
1967
|
+
generator: spec.generator,
|
|
1968
|
+
prompt: spec.prompt,
|
|
1969
|
+
width: spec.width,
|
|
1970
|
+
height: spec.height,
|
|
1971
|
+
view: spec.view,
|
|
1972
|
+
outline: spec.outline ?? null,
|
|
1973
|
+
shading: spec.shading ?? null,
|
|
1974
|
+
detail: spec.detail ?? null,
|
|
1975
|
+
seed: spec.seed ?? null,
|
|
1976
|
+
palette: spec.palette,
|
|
1977
|
+
// `noBackground` only reaches the wire for pixflux; the tile fields are
|
|
1978
|
+
// undefined for every other generator. `tileSize` is intentionally
|
|
1979
|
+
// absent; width/height are derived from it, so it is already covered.
|
|
1980
|
+
noBackground: spec.generator === "pixflux" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
1981
|
+
tileType: spec.tileType,
|
|
1982
|
+
tileView: spec.tileView,
|
|
1983
|
+
tileFeature: spec.tileFeature,
|
|
1984
|
+
outlineMode: spec.outlineMode,
|
|
1985
|
+
styleImages: styleImageHashes,
|
|
1986
|
+
// A character family: the engine and rotations of a base, the parent's
|
|
1987
|
+
// generated bytes for a state or animation (so a regenerated parent
|
|
1988
|
+
// makes its children stale), and each shape's own settings.
|
|
1989
|
+
character: spec.character ? {
|
|
1990
|
+
kind: spec.character.kind,
|
|
1991
|
+
mode: spec.character.mode,
|
|
1992
|
+
directions: spec.character.directions,
|
|
1993
|
+
template: spec.character.template,
|
|
1994
|
+
parent: spec.character.parentAssetId,
|
|
1995
|
+
parentSha256: spec.character.parentSha256 ?? null,
|
|
1996
|
+
state: spec.character.state,
|
|
1997
|
+
// Pose images enter as their hashes, never their paths.
|
|
1998
|
+
animation: spec.character.animation ? {
|
|
1999
|
+
...spec.character.animation,
|
|
2000
|
+
startFrame: spec.character.animation.startFrame?.sha256,
|
|
2001
|
+
endFrame: spec.character.animation.endFrame?.sha256
|
|
2002
|
+
} : void 0,
|
|
2003
|
+
// Absent keys keep the hashes of manifests that never set these.
|
|
2004
|
+
proportions: spec.character.proportions,
|
|
2005
|
+
textGuidanceScale: spec.character.textGuidanceScale,
|
|
2006
|
+
isometric: spec.character.isometric,
|
|
2007
|
+
enhancePrompt: spec.character.enhancePrompt,
|
|
2008
|
+
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
|
|
2009
|
+
} : void 0,
|
|
2010
|
+
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2011
|
+
// compares those directly, so only the choice of source is identity.
|
|
2012
|
+
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
2013
|
+
revision: spec.revision ? {
|
|
2014
|
+
mode: spec.revision.mode,
|
|
2015
|
+
from: spec.revision.sourceAssetId,
|
|
2016
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
2017
|
+
maskSha256: spec.revision.maskSha256,
|
|
2018
|
+
strength: spec.revision.strength
|
|
2019
|
+
} : void 0
|
|
2020
|
+
})
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/providers/pixellab.ts
|
|
1798
2025
|
function pixelLabObjectUrl(generator, objectId) {
|
|
1799
2026
|
if (!objectId) return null;
|
|
1800
2027
|
if (generator === "map" || generator === "1dir") {
|
|
@@ -1917,8 +2144,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
1917
2144
|
"high detail"
|
|
1918
2145
|
]);
|
|
1919
2146
|
}
|
|
1920
|
-
if (spec.generator === "character") this.validateCharacter(spec);
|
|
1921
|
-
if ((spec.generator === "map" || spec.generator === "pixflux"
|
|
2147
|
+
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
2148
|
+
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
1922
2149
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
1923
2150
|
}
|
|
1924
2151
|
for (const image of styleImages) {
|
|
@@ -1934,9 +2161,80 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
1934
2161
|
);
|
|
1935
2162
|
}
|
|
1936
2163
|
}
|
|
1937
|
-
validateCharacter(spec) {
|
|
2164
|
+
validateCharacter(spec, styleImages = []) {
|
|
1938
2165
|
const character = spec.character;
|
|
1939
2166
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
2167
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
2168
|
+
if (character.kind === "base" && character.proportions !== void 0) {
|
|
2169
|
+
if (character.mode !== "standard") {
|
|
2170
|
+
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
2171
|
+
}
|
|
2172
|
+
if (character.template !== "mannequin") {
|
|
2173
|
+
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
2177
|
+
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
2178
|
+
}
|
|
2179
|
+
if (character.animation) {
|
|
2180
|
+
const { startFrame, endFrame } = character.animation;
|
|
2181
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
2182
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
2183
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
if (endFrame) {
|
|
2187
|
+
const start = startFrame ?? (character.parentFile && existsSync(character.parentFile) ? imageMetadata(readFileSync(character.parentFile)) : null);
|
|
2188
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
2189
|
+
throw new Error(
|
|
2190
|
+
`${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`
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
if (character.reference) {
|
|
2196
|
+
const given = Object.keys(character.reference);
|
|
2197
|
+
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
2198
|
+
if (character.mode !== "standard") {
|
|
2199
|
+
if (given.length > 1) throw new Error(`${label}: PixelLab ${character.mode} rotates one south-facing reference; drop the other directions`);
|
|
2200
|
+
const limit = character.mode === "v3" ? 256 : 168;
|
|
2201
|
+
const south = character.reference.south;
|
|
2202
|
+
if (south.width > limit || south.height > limit) {
|
|
2203
|
+
throw new Error(`${label}: reference is ${south.width}x${south.height}; PixelLab ${character.mode} takes up to ${limit}px`);
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2207
|
+
for (const direction of given) {
|
|
2208
|
+
if (!allowed.includes(direction)) {
|
|
2209
|
+
throw new Error(`${label}: reference "${direction}" is not one of this character's ${character.directions} directions`);
|
|
2210
|
+
}
|
|
2211
|
+
const image = character.reference[direction];
|
|
2212
|
+
if (image.width !== spec.width || image.height !== spec.height) {
|
|
2213
|
+
throw new Error(
|
|
2214
|
+
`${label}: reference (${direction}) is ${image.width}x${image.height}; standard mode wants each image at the style's size, ${spec.width}x${spec.height}`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
if (character.template !== "mannequin" && !character.reference.east) {
|
|
2219
|
+
throw new Error(`${label}: a ${character.template} reference needs south and east images`);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
if (styleImages.length) {
|
|
2224
|
+
if (character.mode !== "pro") {
|
|
2225
|
+
throw new Error(
|
|
2226
|
+
`PixelLab ${character.mode} characters take no style images; give a base its own sprite with \`reference\`, or use mode pro for a style anchor`
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
if (styleImages.length > 1) throw new Error("PixelLab pro characters take one style image");
|
|
2230
|
+
if (character.kind === "base" && character.reference) {
|
|
2231
|
+
throw new Error(`${label}: a pro base rotating its own reference takes no style image`);
|
|
2232
|
+
}
|
|
2233
|
+
const image = styleImages[0];
|
|
2234
|
+
if (image.width > 168 || image.height > 168) {
|
|
2235
|
+
throw new Error(`PixelLab pro character style image is ${image.width}x${image.height}; the limit is 168px`);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
1940
2238
|
const maxSize = character.mode === "v3" ? 256 : 128;
|
|
1941
2239
|
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
1942
2240
|
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
@@ -1972,10 +2270,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
1972
2270
|
* asset so a later run can find it, and clears its own earlier take for
|
|
1973
2271
|
* that direction first, since PixelLab skips a direction that exists.
|
|
1974
2272
|
*/
|
|
1975
|
-
async submitCharacter(spec, context) {
|
|
2273
|
+
async submitCharacter(spec, styleImages, context) {
|
|
1976
2274
|
const character = spec.character;
|
|
1977
2275
|
if (character.kind === "base") {
|
|
1978
2276
|
const swatch = spec.palette.length && character.mode === "standard" ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2277
|
+
const styleImage = styleImages[0];
|
|
1979
2278
|
const res2 = await this.client.createCharacter({
|
|
1980
2279
|
mode: character.mode,
|
|
1981
2280
|
description: spec.prompt,
|
|
@@ -1988,7 +2287,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
1988
2287
|
detail: spec.detail,
|
|
1989
2288
|
seed: spec.seed,
|
|
1990
2289
|
noBackground: spec.noBackground,
|
|
1991
|
-
paletteSwatchBase64: swatch
|
|
2290
|
+
paletteSwatchBase64: swatch,
|
|
2291
|
+
proportions: character.proportions,
|
|
2292
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
2293
|
+
isometric: character.isometric,
|
|
2294
|
+
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
2295
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
2296
|
+
enhancePrompt: character.enhancePrompt
|
|
1992
2297
|
});
|
|
1993
2298
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
1994
2299
|
}
|
|
@@ -2030,7 +2335,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2030
2335
|
frameCount: animation.frames,
|
|
2031
2336
|
keepFirstFrame: animation.keepFirstFrame,
|
|
2032
2337
|
directions: [animation.direction],
|
|
2033
|
-
seed: spec.seed
|
|
2338
|
+
seed: spec.seed,
|
|
2339
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
2340
|
+
isometric: character.isometric,
|
|
2341
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
2342
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
2343
|
+
description: animation.subject,
|
|
2344
|
+
outline: animation.outline,
|
|
2345
|
+
shading: animation.shading,
|
|
2346
|
+
detail: animation.detail,
|
|
2347
|
+
enhancePrompt: animation.enhancePrompt,
|
|
2348
|
+
paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
|
|
2034
2349
|
});
|
|
2035
2350
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
2036
2351
|
return {
|
|
@@ -2120,7 +2435,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2120
2435
|
}
|
|
2121
2436
|
async submit(spec, styleImages, context) {
|
|
2122
2437
|
this.validate(spec, styleImages);
|
|
2123
|
-
if (spec.generator === "character") return this.submitCharacter(spec, context);
|
|
2438
|
+
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
2124
2439
|
if (spec.generator === "pixflux") {
|
|
2125
2440
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2126
2441
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -2387,6 +2702,24 @@ function firstUrl(urls) {
|
|
|
2387
2702
|
if (!urls) return null;
|
|
2388
2703
|
return Object.values(urls).find((u) => typeof u === "string") ?? null;
|
|
2389
2704
|
}
|
|
2705
|
+
function readReference(spec, reference) {
|
|
2706
|
+
const images = {};
|
|
2707
|
+
for (const [direction, image] of Object.entries(reference)) {
|
|
2708
|
+
const bytes = readFileSync(image.path);
|
|
2709
|
+
if (sha256(bytes) !== image.sha256) {
|
|
2710
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: reference (${direction}) changed after the manifest was resolved: ${image.path}`);
|
|
2711
|
+
}
|
|
2712
|
+
images[direction] = { base64: bytes.toString("base64"), format: image.format };
|
|
2713
|
+
}
|
|
2714
|
+
return images;
|
|
2715
|
+
}
|
|
2716
|
+
function readPose(spec, what, image) {
|
|
2717
|
+
const bytes = readFileSync(image.path);
|
|
2718
|
+
if (sha256(bytes) !== image.sha256) {
|
|
2719
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
2720
|
+
}
|
|
2721
|
+
return { base64: bytes.toString("base64"), format: image.format };
|
|
2722
|
+
}
|
|
2390
2723
|
function rotationSources(character) {
|
|
2391
2724
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2392
2725
|
const sources = [];
|
|
@@ -3258,77 +3591,6 @@ function redactScenarioError(value, secrets) {
|
|
|
3258
3591
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3259
3592
|
import { readFile } from "fs/promises";
|
|
3260
3593
|
import path2 from "path";
|
|
3261
|
-
|
|
3262
|
-
// src/hash.ts
|
|
3263
|
-
import { createHash } from "crypto";
|
|
3264
|
-
import { createReadStream } from "fs";
|
|
3265
|
-
function sha256(data) {
|
|
3266
|
-
return createHash("sha256").update(data).digest("hex");
|
|
3267
|
-
}
|
|
3268
|
-
async function sha256File(path33) {
|
|
3269
|
-
return new Promise((resolve, reject) => {
|
|
3270
|
-
const hash = createHash("sha256");
|
|
3271
|
-
const stream = createReadStream(path33);
|
|
3272
|
-
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3273
|
-
stream.on("error", reject);
|
|
3274
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
3275
|
-
});
|
|
3276
|
-
}
|
|
3277
|
-
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
3278
|
-
return sha256(
|
|
3279
|
-
JSON.stringify({
|
|
3280
|
-
// Preserve every existing PixelLab hash while making a provider switch
|
|
3281
|
-
// invalidate the spec. Older manifests implicitly mean pixellab.
|
|
3282
|
-
provider: spec.provider === "pixellab" ? void 0 : spec.provider,
|
|
3283
|
-
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
3284
|
-
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
3285
|
-
generator: spec.generator,
|
|
3286
|
-
prompt: spec.prompt,
|
|
3287
|
-
width: spec.width,
|
|
3288
|
-
height: spec.height,
|
|
3289
|
-
view: spec.view,
|
|
3290
|
-
outline: spec.outline ?? null,
|
|
3291
|
-
shading: spec.shading ?? null,
|
|
3292
|
-
detail: spec.detail ?? null,
|
|
3293
|
-
seed: spec.seed ?? null,
|
|
3294
|
-
palette: spec.palette,
|
|
3295
|
-
// `noBackground` only reaches the wire for pixflux; the tile fields are
|
|
3296
|
-
// undefined for every other generator. `tileSize` is intentionally
|
|
3297
|
-
// absent; width/height are derived from it, so it is already covered.
|
|
3298
|
-
noBackground: spec.generator === "pixflux" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
3299
|
-
tileType: spec.tileType,
|
|
3300
|
-
tileView: spec.tileView,
|
|
3301
|
-
tileFeature: spec.tileFeature,
|
|
3302
|
-
outlineMode: spec.outlineMode,
|
|
3303
|
-
styleImages: styleImageHashes,
|
|
3304
|
-
// A character family: the engine and rotations of a base, the parent's
|
|
3305
|
-
// generated bytes for a state or animation (so a regenerated parent
|
|
3306
|
-
// makes its children stale), and each shape's own settings.
|
|
3307
|
-
character: spec.character ? {
|
|
3308
|
-
kind: spec.character.kind,
|
|
3309
|
-
mode: spec.character.mode,
|
|
3310
|
-
directions: spec.character.directions,
|
|
3311
|
-
template: spec.character.template,
|
|
3312
|
-
parent: spec.character.parentAssetId,
|
|
3313
|
-
parentSha256: spec.character.parentSha256 ?? null,
|
|
3314
|
-
state: spec.character.state,
|
|
3315
|
-
animation: spec.character.animation
|
|
3316
|
-
} : void 0,
|
|
3317
|
-
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
3318
|
-
// compares those directly, so only the choice of source is identity.
|
|
3319
|
-
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
3320
|
-
revision: spec.revision ? {
|
|
3321
|
-
mode: spec.revision.mode,
|
|
3322
|
-
from: spec.revision.sourceAssetId,
|
|
3323
|
-
sourceSha256: spec.revision.sourceSha256,
|
|
3324
|
-
maskSha256: spec.revision.maskSha256,
|
|
3325
|
-
strength: spec.revision.strength
|
|
3326
|
-
} : void 0
|
|
3327
|
-
})
|
|
3328
|
-
);
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
|
-
// src/providers/comfyui.ts
|
|
3332
3594
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
3333
3595
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
3334
3596
|
"prompt",
|
|
@@ -4955,16 +5217,16 @@ async function resolveSpecs(loaded, filter) {
|
|
|
4955
5217
|
}
|
|
4956
5218
|
}
|
|
4957
5219
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
4958
|
-
async function loadStyleImage(rel) {
|
|
5220
|
+
async function loadStyleImage(rel, what = "Style image") {
|
|
4959
5221
|
const abs = path5.resolve(root, rel);
|
|
4960
5222
|
let hit = styleImageCache.get(abs);
|
|
4961
5223
|
if (!hit) {
|
|
4962
|
-
if (!existsSync4(abs)) throw new Error(
|
|
5224
|
+
if (!existsSync4(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
4963
5225
|
const buf = await readFile3(abs);
|
|
4964
5226
|
const metadata = imageMetadata(buf);
|
|
4965
|
-
if (!metadata) throw new Error(
|
|
5227
|
+
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
4966
5228
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
4967
|
-
throw new Error(
|
|
5229
|
+
throw new Error(`${what} has invalid dimensions: ${abs}`);
|
|
4968
5230
|
}
|
|
4969
5231
|
hit = { base64: buf.toString("base64"), hash: sha256(buf), ...metadata };
|
|
4970
5232
|
styleImageCache.set(abs, hit);
|
|
@@ -5071,7 +5333,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5071
5333
|
tileView: generator === "tiles" ? style.tileView : void 0,
|
|
5072
5334
|
tileFeature: generator === "tiles" ? style.tileFeature : void 0,
|
|
5073
5335
|
outlineMode: generator === "tiles" ? style.outlineMode : void 0,
|
|
5074
|
-
...generator === "character" ? { character: resolveCharacterShape(asset, style, characterKind) } : {},
|
|
5336
|
+
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
5075
5337
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
|
|
5076
5338
|
costUnit: "generations",
|
|
5077
5339
|
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
@@ -5288,43 +5550,34 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
5288
5550
|
async function styleImagesBase64(loaded, styleId) {
|
|
5289
5551
|
return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
|
|
5290
5552
|
}
|
|
5291
|
-
function
|
|
5292
|
-
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
5293
|
-
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
5294
|
-
}
|
|
5295
|
-
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
5296
|
-
let offset = 2;
|
|
5297
|
-
while (offset + 3 < buf.length) {
|
|
5298
|
-
if (buf[offset] !== 255) return null;
|
|
5299
|
-
while (buf[offset] === 255) offset++;
|
|
5300
|
-
const marker = buf[offset++];
|
|
5301
|
-
if (marker == null || marker === 217 || marker === 218) break;
|
|
5302
|
-
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
5303
|
-
if (offset + 2 > buf.length) return null;
|
|
5304
|
-
const length = buf.readUInt16BE(offset);
|
|
5305
|
-
if (length < 2 || offset + length > buf.length) return null;
|
|
5306
|
-
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
5307
|
-
if (isStartOfFrame) {
|
|
5308
|
-
if (length < 7) return null;
|
|
5309
|
-
return {
|
|
5310
|
-
width: buf.readUInt16BE(offset + 5),
|
|
5311
|
-
height: buf.readUInt16BE(offset + 3),
|
|
5312
|
-
format: "jpeg"
|
|
5313
|
-
};
|
|
5314
|
-
}
|
|
5315
|
-
offset += length;
|
|
5316
|
-
}
|
|
5317
|
-
return null;
|
|
5318
|
-
}
|
|
5319
|
-
function resolveCharacterShape(asset, style, kind) {
|
|
5553
|
+
async function resolveCharacterShape(asset, style, kind, files) {
|
|
5320
5554
|
const mode2 = style.mode ?? "standard";
|
|
5321
5555
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
5556
|
+
const proportions = asset.proportions ?? style.proportions;
|
|
5322
5557
|
const shape = {
|
|
5323
5558
|
kind,
|
|
5324
5559
|
mode: mode2,
|
|
5325
5560
|
directions,
|
|
5326
|
-
template: style.template ?? "mannequin"
|
|
5561
|
+
template: style.template ?? "mannequin",
|
|
5562
|
+
...proportions !== void 0 ? { proportions } : {},
|
|
5563
|
+
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
5564
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {},
|
|
5565
|
+
...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
|
|
5327
5566
|
};
|
|
5567
|
+
if (asset.reference) {
|
|
5568
|
+
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
5569
|
+
shape.reference = {};
|
|
5570
|
+
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
5571
|
+
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
5572
|
+
shape.reference[direction] = {
|
|
5573
|
+
path: path5.resolve(files.root, rel),
|
|
5574
|
+
sha256: image.hash,
|
|
5575
|
+
width: image.width,
|
|
5576
|
+
height: image.height,
|
|
5577
|
+
format: image.format
|
|
5578
|
+
};
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5328
5581
|
if (asset.state) {
|
|
5329
5582
|
shape.state = {
|
|
5330
5583
|
paletteFromReference: asset.state.paletteFromReference,
|
|
@@ -5333,13 +5586,24 @@ function resolveCharacterShape(asset, style, kind) {
|
|
|
5333
5586
|
}
|
|
5334
5587
|
if (asset.animation) {
|
|
5335
5588
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
5589
|
+
const pose = async (rel, what) => {
|
|
5590
|
+
const image = await files.load(rel, what);
|
|
5591
|
+
return { path: path5.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
5592
|
+
};
|
|
5336
5593
|
shape.animation = {
|
|
5337
5594
|
mode: animationMode,
|
|
5338
5595
|
...asset.animation.template ? { template: asset.animation.template } : {},
|
|
5339
5596
|
direction: asset.animation.direction,
|
|
5340
5597
|
frames: asset.animation.frames ?? 8,
|
|
5341
5598
|
fps: asset.animation.fps,
|
|
5342
|
-
keepFirstFrame: asset.animation.keepFirstFrame
|
|
5599
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
5600
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
5601
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
5602
|
+
...asset.animation.subject ? { subject: asset.animation.subject } : {},
|
|
5603
|
+
...asset.animation.outline ? { outline: asset.animation.outline } : {},
|
|
5604
|
+
...asset.animation.shading ? { shading: asset.animation.shading } : {},
|
|
5605
|
+
...asset.animation.detail ? { detail: asset.animation.detail } : {},
|
|
5606
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
5343
5607
|
};
|
|
5344
5608
|
}
|
|
5345
5609
|
return shape;
|
|
@@ -13601,10 +13865,12 @@ export {
|
|
|
13601
13865
|
BudgetError,
|
|
13602
13866
|
CHARACTER_DIRECTIONS_4,
|
|
13603
13867
|
CHARACTER_DIRECTIONS_8,
|
|
13868
|
+
CHARACTER_PROPORTION_PRESETS,
|
|
13604
13869
|
CharacterAnimationModeSchema,
|
|
13605
13870
|
CharacterAnimationSchema,
|
|
13606
13871
|
CharacterDirectionSchema,
|
|
13607
13872
|
CharacterModeSchema,
|
|
13873
|
+
CharacterProportionsSchema,
|
|
13608
13874
|
CharacterStateSchema,
|
|
13609
13875
|
ComfyUIProvider,
|
|
13610
13876
|
DEFAULT_HISTORY_LIMIT,
|
|
@@ -13771,6 +14037,7 @@ export {
|
|
|
13771
14037
|
portableOutputPath,
|
|
13772
14038
|
prepareReview,
|
|
13773
14039
|
primaryOutput,
|
|
14040
|
+
proportionsPayload,
|
|
13774
14041
|
providerCredentialEnvs,
|
|
13775
14042
|
providerFactory,
|
|
13776
14043
|
pushTags,
|