pixelkiln 0.43.0 → 0.45.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 +257 -53
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +256 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +665 -126
- package/dist/index.d.ts +665 -126
- package/dist/index.js +256 -52
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +52 -4
- package/package.json +1 -1
- package/schema/manifest.schema.json +49 -0
- package/schema/recipe.schema.json +7 -0
- package/skills/pixelkiln/references/pixellab.md +9 -1
package/dist/index.js
CHANGED
|
@@ -128,11 +128,45 @@ var CharacterAnimationSchema = z.object({
|
|
|
128
128
|
/** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
|
|
129
129
|
keepFirstFrame: z.boolean().default(true),
|
|
130
130
|
/** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
|
|
131
|
-
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()
|
|
132
151
|
}).strict().superRefine((animation, context) => {
|
|
133
152
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
134
153
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
135
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
|
+
}
|
|
136
170
|
if (animation.template && animation.mode && animation.mode !== "template") {
|
|
137
171
|
context.addIssue({
|
|
138
172
|
code: z.ZodIssueCode.custom,
|
|
@@ -270,6 +304,14 @@ var StyleObjectSchema = z.object({
|
|
|
270
304
|
textGuidanceScale: z.number().min(1).max(20).optional(),
|
|
271
305
|
/** `character` only. Draw `standard` bases and every loop in isometric view. */
|
|
272
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(),
|
|
309
|
+
/**
|
|
310
|
+
* `character` only, `pro` bases: the asset id of a generated 8-direction
|
|
311
|
+
* character in this style whose look every base drawn from text or a
|
|
312
|
+
* concept follows. An asset may override it; the anchor itself ignores it.
|
|
313
|
+
*/
|
|
314
|
+
styleCharacter: z.string().min(1).optional(),
|
|
273
315
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
274
316
|
seed: z.number().int().optional(),
|
|
275
317
|
/**
|
|
@@ -442,6 +484,15 @@ var AssetSchema = z.object({
|
|
|
442
484
|
* still guides the result.
|
|
443
485
|
*/
|
|
444
486
|
reference: z.union([z.string().min(1), z.record(CharacterDirectionSchema, z.string().min(1))]).optional(),
|
|
487
|
+
/**
|
|
488
|
+
* `character` styles, `pro` bases only: a manifest-relative concept
|
|
489
|
+
* image (a painting, a photo, a sketch; up to 1024px) that seeds the
|
|
490
|
+
* design instead of the prompt alone. A style image or `styleCharacter`
|
|
491
|
+
* may still anchor the look.
|
|
492
|
+
*/
|
|
493
|
+
concept: z.string().min(1).optional(),
|
|
494
|
+
/** `character` styles, `pro` bases: this base's style anchor, over the style's. */
|
|
495
|
+
styleCharacter: z.string().min(1).optional(),
|
|
445
496
|
/**
|
|
446
497
|
* Another asset of the same style flipped left to right, made locally
|
|
447
498
|
* from that asset's downloaded files at no generation cost.
|
|
@@ -523,6 +574,20 @@ var AssetSchema = z.object({
|
|
|
523
574
|
path: ["reference"]
|
|
524
575
|
});
|
|
525
576
|
}
|
|
577
|
+
if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
|
|
578
|
+
context.addIssue({
|
|
579
|
+
code: z.ZodIssueCode.custom,
|
|
580
|
+
message: "a concept image belongs on a base; a state, animation, or mirror takes its look from its parent",
|
|
581
|
+
path: ["concept"]
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (asset.concept && asset.reference) {
|
|
585
|
+
context.addIssue({
|
|
586
|
+
code: z.ZodIssueCode.custom,
|
|
587
|
+
message: "concept and reference are mutually exclusive: a concept seeds a new design, a reference is the sprite to rotate",
|
|
588
|
+
path: ["concept"]
|
|
589
|
+
});
|
|
590
|
+
}
|
|
526
591
|
if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
|
|
527
592
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
|
|
528
593
|
}
|
|
@@ -1194,7 +1259,8 @@ var PixelLabClient = class {
|
|
|
1194
1259
|
...args.outline ? { outline: args.outline } : {},
|
|
1195
1260
|
...args.detail ? { detail: args.detail } : {},
|
|
1196
1261
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1197
|
-
...south ? { reference_image: encode(south) } : {}
|
|
1262
|
+
...south ? { reference_image: encode(south) } : {},
|
|
1263
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
1198
1264
|
};
|
|
1199
1265
|
} else if (args.mode === "pro") {
|
|
1200
1266
|
path33 = "/create-character-pro";
|
|
@@ -1203,10 +1269,12 @@ var PixelLabClient = class {
|
|
|
1203
1269
|
image_size: imageSize,
|
|
1204
1270
|
template_id: args.template,
|
|
1205
1271
|
no_background: args.noBackground ?? true,
|
|
1206
|
-
method: south ? "rotate_character" : "create_with_style",
|
|
1272
|
+
method: south ? "rotate_character" : args.concept ? "create_from_concept" : "create_with_style",
|
|
1207
1273
|
...args.view ? { view: args.view } : {},
|
|
1208
1274
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1209
|
-
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
|
|
1275
|
+
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {},
|
|
1276
|
+
...!south && args.concept ? { concept_image: encode(args.concept) } : {},
|
|
1277
|
+
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
1210
1278
|
};
|
|
1211
1279
|
} else {
|
|
1212
1280
|
path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
@@ -1251,6 +1319,7 @@ var PixelLabClient = class {
|
|
|
1251
1319
|
* the action text; pro is the sequential engine. One job per direction.
|
|
1252
1320
|
*/
|
|
1253
1321
|
async animateCharacter(args) {
|
|
1322
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1254
1323
|
const raw = await this.request("/animate-character", {
|
|
1255
1324
|
method: "POST",
|
|
1256
1325
|
body: JSON.stringify({
|
|
@@ -1260,10 +1329,18 @@ var PixelLabClient = class {
|
|
|
1260
1329
|
directions: args.directions,
|
|
1261
1330
|
...args.template ? { template_animation_id: args.template } : {},
|
|
1262
1331
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
1332
|
+
...args.description ? { description: args.description } : {},
|
|
1263
1333
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
1264
1334
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1335
|
+
...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
1336
|
+
...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
1337
|
+
...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
1265
1338
|
...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1339
|
+
...args.mode === "template" && args.outline ? { outline: args.outline } : {},
|
|
1340
|
+
...args.mode === "template" && args.shading ? { shading: args.shading } : {},
|
|
1341
|
+
...args.mode === "template" && args.detail ? { detail: args.detail } : {},
|
|
1266
1342
|
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1343
|
+
...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
|
|
1267
1344
|
...args.seed != null ? { seed: args.seed } : {}
|
|
1268
1345
|
})
|
|
1269
1346
|
});
|
|
@@ -1860,6 +1937,34 @@ function skipSubBlocks(bytes, start) {
|
|
|
1860
1937
|
function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
1861
1938
|
return `${hash}${mediaExtension(mediaType)}`;
|
|
1862
1939
|
}
|
|
1940
|
+
function imageMetadata(buf) {
|
|
1941
|
+
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
1942
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
1943
|
+
}
|
|
1944
|
+
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
1945
|
+
let offset = 2;
|
|
1946
|
+
while (offset + 3 < buf.length) {
|
|
1947
|
+
if (buf[offset] !== 255) return null;
|
|
1948
|
+
while (buf[offset] === 255) offset++;
|
|
1949
|
+
const marker = buf[offset++];
|
|
1950
|
+
if (marker == null || marker === 217 || marker === 218) break;
|
|
1951
|
+
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
1952
|
+
if (offset + 2 > buf.length) return null;
|
|
1953
|
+
const length = buf.readUInt16BE(offset);
|
|
1954
|
+
if (length < 2 || offset + length > buf.length) return null;
|
|
1955
|
+
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
1956
|
+
if (isStartOfFrame) {
|
|
1957
|
+
if (length < 7) return null;
|
|
1958
|
+
return {
|
|
1959
|
+
width: buf.readUInt16BE(offset + 5),
|
|
1960
|
+
height: buf.readUInt16BE(offset + 3),
|
|
1961
|
+
format: "jpeg"
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
offset += length;
|
|
1965
|
+
}
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1863
1968
|
|
|
1864
1969
|
// src/providers/pixellab.ts
|
|
1865
1970
|
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -1920,12 +2025,20 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
1920
2025
|
parent: spec.character.parentAssetId,
|
|
1921
2026
|
parentSha256: spec.character.parentSha256 ?? null,
|
|
1922
2027
|
state: spec.character.state,
|
|
1923
|
-
|
|
2028
|
+
// Pose images enter as their hashes, never their paths.
|
|
2029
|
+
animation: spec.character.animation ? {
|
|
2030
|
+
...spec.character.animation,
|
|
2031
|
+
startFrame: spec.character.animation.startFrame?.sha256,
|
|
2032
|
+
endFrame: spec.character.animation.endFrame?.sha256
|
|
2033
|
+
} : void 0,
|
|
1924
2034
|
// Absent keys keep the hashes of manifests that never set these.
|
|
1925
2035
|
proportions: spec.character.proportions,
|
|
1926
2036
|
textGuidanceScale: spec.character.textGuidanceScale,
|
|
1927
2037
|
isometric: spec.character.isometric,
|
|
1928
|
-
|
|
2038
|
+
enhancePrompt: spec.character.enhancePrompt,
|
|
2039
|
+
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
|
|
2040
|
+
concept: spec.character.concept?.sha256,
|
|
2041
|
+
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
1929
2042
|
} : void 0,
|
|
1930
2043
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
1931
2044
|
// compares those directly, so only the choice of source is identity.
|
|
@@ -2093,6 +2206,38 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2093
2206
|
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
2094
2207
|
}
|
|
2095
2208
|
}
|
|
2209
|
+
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
2210
|
+
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
2211
|
+
}
|
|
2212
|
+
if (character.animation) {
|
|
2213
|
+
const { startFrame, endFrame } = character.animation;
|
|
2214
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
2215
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
2216
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
if (endFrame) {
|
|
2220
|
+
const start = startFrame ?? (character.parentFile && existsSync(character.parentFile) ? imageMetadata(readFileSync(character.parentFile)) : null);
|
|
2221
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
2222
|
+
throw new Error(
|
|
2223
|
+
`${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`
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (character.concept) {
|
|
2229
|
+
if (character.mode !== "pro") throw new Error(`${label}: a concept image is a pro input; the ${character.mode} engine draws from text or a reference`);
|
|
2230
|
+
if (character.concept.width > 1024 || character.concept.height > 1024) {
|
|
2231
|
+
throw new Error(`${label}: concept image is ${character.concept.width}x${character.concept.height}; PixelLab pro takes up to 1024px`);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
if (character.styleAnchor) {
|
|
2235
|
+
if (character.mode !== "pro") throw new Error(`${label}: styleCharacter is a pro input; the ${character.mode} engine has no style anchor`);
|
|
2236
|
+
if (character.reference) throw new Error(`${label}: a pro base rotating its own reference takes no styleCharacter`);
|
|
2237
|
+
if (character.styleAnchor.spec.character?.directions !== 8) {
|
|
2238
|
+
throw new Error(`${label}: styleCharacter ${character.styleAnchor.assetId} has ${character.styleAnchor.spec.character?.directions} directions; PixelLab wants an 8-direction anchor`);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2096
2241
|
if (character.reference) {
|
|
2097
2242
|
const given = Object.keys(character.reference);
|
|
2098
2243
|
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
@@ -2193,7 +2338,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2193
2338
|
textGuidanceScale: character.textGuidanceScale,
|
|
2194
2339
|
isometric: character.isometric,
|
|
2195
2340
|
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
2196
|
-
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0
|
|
2341
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
2342
|
+
enhancePrompt: character.enhancePrompt,
|
|
2343
|
+
concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
|
|
2344
|
+
styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0
|
|
2197
2345
|
});
|
|
2198
2346
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
2199
2347
|
}
|
|
@@ -2237,7 +2385,15 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2237
2385
|
directions: [animation.direction],
|
|
2238
2386
|
seed: spec.seed,
|
|
2239
2387
|
textGuidanceScale: character.textGuidanceScale,
|
|
2240
|
-
isometric: character.isometric
|
|
2388
|
+
isometric: character.isometric,
|
|
2389
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
2390
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
2391
|
+
description: animation.subject,
|
|
2392
|
+
outline: animation.outline,
|
|
2393
|
+
shading: animation.shading,
|
|
2394
|
+
detail: animation.detail,
|
|
2395
|
+
enhancePrompt: animation.enhancePrompt,
|
|
2396
|
+
paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
|
|
2241
2397
|
});
|
|
2242
2398
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
2243
2399
|
return {
|
|
@@ -2605,6 +2761,21 @@ function readReference(spec, reference) {
|
|
|
2605
2761
|
}
|
|
2606
2762
|
return images;
|
|
2607
2763
|
}
|
|
2764
|
+
function requireStyleObjectId(spec, context) {
|
|
2765
|
+
if (!context?.styleObjectId) {
|
|
2766
|
+
throw new Error(
|
|
2767
|
+
`${spec.styleId}/${spec.assetId} anchors its style on ${spec.character.styleAnchor.assetId}, which has no PixelLab character id in the lockfile yet; generate the anchor first`
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
return context.styleObjectId;
|
|
2771
|
+
}
|
|
2772
|
+
function readPose(spec, what, image) {
|
|
2773
|
+
const bytes = readFileSync(image.path);
|
|
2774
|
+
if (sha256(bytes) !== image.sha256) {
|
|
2775
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
2776
|
+
}
|
|
2777
|
+
return { base64: bytes.toString("base64"), format: image.format };
|
|
2778
|
+
}
|
|
2608
2779
|
function rotationSources(character) {
|
|
2609
2780
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2610
2781
|
const sources = [];
|
|
@@ -5297,6 +5468,18 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5297
5468
|
finalized.add(assetId);
|
|
5298
5469
|
return resolved;
|
|
5299
5470
|
}
|
|
5471
|
+
const anchorId = asset.styleCharacter ?? style.styleCharacter;
|
|
5472
|
+
if (resolved.character?.kind === "base" && anchorId && anchorId !== assetId) {
|
|
5473
|
+
const anchorSpec = await finalize(anchorId);
|
|
5474
|
+
if (!anchorSpec.character || anchorSpec.character.kind === "animation") {
|
|
5475
|
+
throw new Error(`assets.${assetId}: styleCharacter ${anchorId} is not a character base or state`);
|
|
5476
|
+
}
|
|
5477
|
+
const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
|
|
5478
|
+
resolved.character = {
|
|
5479
|
+
...resolved.character,
|
|
5480
|
+
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: existsSync4(file) ? await sha256File(file) : null }
|
|
5481
|
+
};
|
|
5482
|
+
}
|
|
5300
5483
|
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
5301
5484
|
if (resolved.character && characterParent) {
|
|
5302
5485
|
const parentSpec = await finalize(characterParent);
|
|
@@ -5435,34 +5618,6 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
5435
5618
|
async function styleImagesBase64(loaded, styleId) {
|
|
5436
5619
|
return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
|
|
5437
5620
|
}
|
|
5438
|
-
function imageMetadata(buf) {
|
|
5439
|
-
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
5440
|
-
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
5441
|
-
}
|
|
5442
|
-
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
5443
|
-
let offset = 2;
|
|
5444
|
-
while (offset + 3 < buf.length) {
|
|
5445
|
-
if (buf[offset] !== 255) return null;
|
|
5446
|
-
while (buf[offset] === 255) offset++;
|
|
5447
|
-
const marker = buf[offset++];
|
|
5448
|
-
if (marker == null || marker === 217 || marker === 218) break;
|
|
5449
|
-
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
5450
|
-
if (offset + 2 > buf.length) return null;
|
|
5451
|
-
const length = buf.readUInt16BE(offset);
|
|
5452
|
-
if (length < 2 || offset + length > buf.length) return null;
|
|
5453
|
-
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
5454
|
-
if (isStartOfFrame) {
|
|
5455
|
-
if (length < 7) return null;
|
|
5456
|
-
return {
|
|
5457
|
-
width: buf.readUInt16BE(offset + 5),
|
|
5458
|
-
height: buf.readUInt16BE(offset + 3),
|
|
5459
|
-
format: "jpeg"
|
|
5460
|
-
};
|
|
5461
|
-
}
|
|
5462
|
-
offset += length;
|
|
5463
|
-
}
|
|
5464
|
-
return null;
|
|
5465
|
-
}
|
|
5466
5621
|
async function resolveCharacterShape(asset, style, kind, files) {
|
|
5467
5622
|
const mode2 = style.mode ?? "standard";
|
|
5468
5623
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
@@ -5474,8 +5629,13 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
5474
5629
|
template: style.template ?? "mannequin",
|
|
5475
5630
|
...proportions !== void 0 ? { proportions } : {},
|
|
5476
5631
|
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
5477
|
-
...style.isometric !== void 0 ? { isometric: style.isometric } : {}
|
|
5632
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {},
|
|
5633
|
+
...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
|
|
5478
5634
|
};
|
|
5635
|
+
if (asset.concept) {
|
|
5636
|
+
const image = await files.load(asset.concept, "Concept image");
|
|
5637
|
+
shape.concept = { path: path5.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
5638
|
+
}
|
|
5479
5639
|
if (asset.reference) {
|
|
5480
5640
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
5481
5641
|
shape.reference = {};
|
|
@@ -5498,13 +5658,24 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
5498
5658
|
}
|
|
5499
5659
|
if (asset.animation) {
|
|
5500
5660
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
5661
|
+
const pose = async (rel, what) => {
|
|
5662
|
+
const image = await files.load(rel, what);
|
|
5663
|
+
return { path: path5.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
5664
|
+
};
|
|
5501
5665
|
shape.animation = {
|
|
5502
5666
|
mode: animationMode,
|
|
5503
5667
|
...asset.animation.template ? { template: asset.animation.template } : {},
|
|
5504
5668
|
direction: asset.animation.direction,
|
|
5505
5669
|
frames: asset.animation.frames ?? 8,
|
|
5506
5670
|
fps: asset.animation.fps,
|
|
5507
|
-
keepFirstFrame: asset.animation.keepFirstFrame
|
|
5671
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
5672
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
5673
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
5674
|
+
...asset.animation.subject ? { subject: asset.animation.subject } : {},
|
|
5675
|
+
...asset.animation.outline ? { outline: asset.animation.outline } : {},
|
|
5676
|
+
...asset.animation.shading ? { shading: asset.animation.shading } : {},
|
|
5677
|
+
...asset.animation.detail ? { detail: asset.animation.detail } : {},
|
|
5678
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
5508
5679
|
};
|
|
5509
5680
|
}
|
|
5510
5681
|
return shape;
|
|
@@ -6984,9 +7155,24 @@ import path11 from "path";
|
|
|
6984
7155
|
async function inspectRevisionReadiness(spec, lock) {
|
|
6985
7156
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
6986
7157
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
7158
|
+
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
6987
7159
|
if (!spec.revision) return null;
|
|
6988
7160
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
6989
7161
|
}
|
|
7162
|
+
async function inspectStyleAnchor(spec, lock, seen) {
|
|
7163
|
+
const anchor = spec.character.styleAnchor;
|
|
7164
|
+
const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
|
|
7165
|
+
const dependency = await inspectParent(anchor.spec, lock, seen);
|
|
7166
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
7167
|
+
if (!anchor.sha256 || !existsSync7(anchor.file)) return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
7168
|
+
if (await sha256File(anchor.file) !== anchor.sha256) {
|
|
7169
|
+
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
7170
|
+
}
|
|
7171
|
+
if (!lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)]?.objectId) {
|
|
7172
|
+
return { ready: false, reason: `${label} has no PixelLab character id recorded` };
|
|
7173
|
+
}
|
|
7174
|
+
return { ready: true, reason: `${label} is current` };
|
|
7175
|
+
}
|
|
6990
7176
|
async function inspectMirror(spec, lock, seen) {
|
|
6991
7177
|
const source = spec.mirror.sourceSpec;
|
|
6992
7178
|
const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
|
|
@@ -7058,6 +7244,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
7058
7244
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
7059
7245
|
if (!inputs.ready) return inputs;
|
|
7060
7246
|
}
|
|
7247
|
+
if (spec.character?.styleAnchor) {
|
|
7248
|
+
const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
|
|
7249
|
+
if (!inputs.ready) return inputs;
|
|
7250
|
+
}
|
|
7061
7251
|
if (spec.quality) {
|
|
7062
7252
|
const quality = await inspectQualityProfile(spec, lock);
|
|
7063
7253
|
return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
|
|
@@ -7097,6 +7287,14 @@ async function anyOutputModified(entry, spec) {
|
|
|
7097
7287
|
}
|
|
7098
7288
|
return false;
|
|
7099
7289
|
}
|
|
7290
|
+
function dependencies(spec) {
|
|
7291
|
+
return [
|
|
7292
|
+
spec.mirror?.sourceSpec,
|
|
7293
|
+
spec.revision?.sourceSpec,
|
|
7294
|
+
spec.character?.parentSpec,
|
|
7295
|
+
spec.character?.styleAnchor?.spec
|
|
7296
|
+
].filter((parent) => Boolean(parent));
|
|
7297
|
+
}
|
|
7100
7298
|
function resumeCommandForStatus(status) {
|
|
7101
7299
|
if (status === "pending" || status === "processing") return "poll";
|
|
7102
7300
|
if (status === "review") return "pick";
|
|
@@ -7205,16 +7403,19 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
7205
7403
|
const stateOf = new Map(items.map((i) => [i.key, i.state]));
|
|
7206
7404
|
for (const item of items) {
|
|
7207
7405
|
if (!isActionable(item)) continue;
|
|
7208
|
-
const parent
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
item.
|
|
7217
|
-
|
|
7406
|
+
for (const parent of dependencies(item.spec)) {
|
|
7407
|
+
const parentKey = lockKey(parent.styleId, parent.assetId);
|
|
7408
|
+
const parentState = stateOf.get(parentKey);
|
|
7409
|
+
if (parentState && isActionable({ state: parentState })) {
|
|
7410
|
+
item.state = "blocked";
|
|
7411
|
+
item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
|
|
7412
|
+
break;
|
|
7413
|
+
}
|
|
7414
|
+
if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
|
|
7415
|
+
item.state = "blocked";
|
|
7416
|
+
item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
|
|
7417
|
+
break;
|
|
7418
|
+
}
|
|
7218
7419
|
}
|
|
7219
7420
|
}
|
|
7220
7421
|
const actionable = items.filter(isActionable);
|
|
@@ -9693,10 +9894,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
9693
9894
|
try {
|
|
9694
9895
|
const refs = styleImages.get(spec.styleId) ?? [];
|
|
9695
9896
|
const parentEntry = spec.character?.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
|
|
9897
|
+
const anchor = spec.character?.styleAnchor;
|
|
9898
|
+
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
9696
9899
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
9697
9900
|
const { jobId, metadata } = await provider.submit(spec, refs, {
|
|
9698
9901
|
...previousJobId ? { previousJobId } : {},
|
|
9699
9902
|
...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
|
|
9903
|
+
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
9700
9904
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
9701
9905
|
...previousMetadata ? { previousMetadata } : {},
|
|
9702
9906
|
checkpoint: async (checkpoint) => {
|
|
@@ -12305,7 +12509,7 @@ var pin_default = {
|
|
|
12305
12509
|
godot: "4.7.2",
|
|
12306
12510
|
extensionsApi: 9,
|
|
12307
12511
|
protocol: 4,
|
|
12308
|
-
release: "editor-pixelorama-v1.2.2-pk.
|
|
12512
|
+
release: "editor-pixelorama-v1.2.2-pk.26",
|
|
12309
12513
|
files: {
|
|
12310
12514
|
"index.apple-touch-icon.png": {
|
|
12311
12515
|
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
@@ -12320,7 +12524,7 @@ var pin_default = {
|
|
|
12320
12524
|
bytes: 7298
|
|
12321
12525
|
},
|
|
12322
12526
|
"index.html": {
|
|
12323
|
-
sha256: "
|
|
12527
|
+
sha256: "326101ab02bb4a29a7964214c9307c7dff26e9d66b8ebaaf6e7a3987e84a05b2",
|
|
12324
12528
|
bytes: 5442
|
|
12325
12529
|
},
|
|
12326
12530
|
"index.icon.png": {
|
|
@@ -12332,8 +12536,8 @@ var pin_default = {
|
|
|
12332
12536
|
bytes: 279815
|
|
12333
12537
|
},
|
|
12334
12538
|
"index.pck": {
|
|
12335
|
-
sha256: "
|
|
12336
|
-
bytes:
|
|
12539
|
+
sha256: "e26292650bb996965acd1ba70e74d2b4020b063d14f1276701312c1d34cb0a83",
|
|
12540
|
+
bytes: 6431932
|
|
12337
12541
|
},
|
|
12338
12542
|
"index.png": {
|
|
12339
12543
|
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|