pixelkiln 0.43.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 +134 -35
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +134 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +513 -125
- package/dist/index.d.ts +513 -125
- package/dist/index.js +134 -35
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +27 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +33 -0
- package/schema/recipe.schema.json +3 -0
- package/skills/pixelkiln/references/pixellab.md +5 -0
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,8 @@ 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(),
|
|
273
309
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
274
310
|
seed: z.number().int().optional(),
|
|
275
311
|
/**
|
|
@@ -1194,7 +1230,8 @@ var PixelLabClient = class {
|
|
|
1194
1230
|
...args.outline ? { outline: args.outline } : {},
|
|
1195
1231
|
...args.detail ? { detail: args.detail } : {},
|
|
1196
1232
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1197
|
-
...south ? { reference_image: encode(south) } : {}
|
|
1233
|
+
...south ? { reference_image: encode(south) } : {},
|
|
1234
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
1198
1235
|
};
|
|
1199
1236
|
} else if (args.mode === "pro") {
|
|
1200
1237
|
path33 = "/create-character-pro";
|
|
@@ -1251,6 +1288,7 @@ var PixelLabClient = class {
|
|
|
1251
1288
|
* the action text; pro is the sequential engine. One job per direction.
|
|
1252
1289
|
*/
|
|
1253
1290
|
async animateCharacter(args) {
|
|
1291
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1254
1292
|
const raw = await this.request("/animate-character", {
|
|
1255
1293
|
method: "POST",
|
|
1256
1294
|
body: JSON.stringify({
|
|
@@ -1260,10 +1298,18 @@ var PixelLabClient = class {
|
|
|
1260
1298
|
directions: args.directions,
|
|
1261
1299
|
...args.template ? { template_animation_id: args.template } : {},
|
|
1262
1300
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
1301
|
+
...args.description ? { description: args.description } : {},
|
|
1263
1302
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
1264
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 } : {},
|
|
1265
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 } : {},
|
|
1266
1311
|
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1312
|
+
...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
|
|
1267
1313
|
...args.seed != null ? { seed: args.seed } : {}
|
|
1268
1314
|
})
|
|
1269
1315
|
});
|
|
@@ -1860,6 +1906,34 @@ function skipSubBlocks(bytes, start) {
|
|
|
1860
1906
|
function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
1861
1907
|
return `${hash}${mediaExtension(mediaType)}`;
|
|
1862
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
|
+
}
|
|
1863
1937
|
|
|
1864
1938
|
// src/providers/pixellab.ts
|
|
1865
1939
|
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -1920,11 +1994,17 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
1920
1994
|
parent: spec.character.parentAssetId,
|
|
1921
1995
|
parentSha256: spec.character.parentSha256 ?? null,
|
|
1922
1996
|
state: spec.character.state,
|
|
1923
|
-
|
|
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,
|
|
1924
2003
|
// Absent keys keep the hashes of manifests that never set these.
|
|
1925
2004
|
proportions: spec.character.proportions,
|
|
1926
2005
|
textGuidanceScale: spec.character.textGuidanceScale,
|
|
1927
2006
|
isometric: spec.character.isometric,
|
|
2007
|
+
enhancePrompt: spec.character.enhancePrompt,
|
|
1928
2008
|
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
|
|
1929
2009
|
} : void 0,
|
|
1930
2010
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
@@ -2093,6 +2173,25 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2093
2173
|
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
2094
2174
|
}
|
|
2095
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
|
+
}
|
|
2096
2195
|
if (character.reference) {
|
|
2097
2196
|
const given = Object.keys(character.reference);
|
|
2098
2197
|
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
@@ -2193,7 +2292,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2193
2292
|
textGuidanceScale: character.textGuidanceScale,
|
|
2194
2293
|
isometric: character.isometric,
|
|
2195
2294
|
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
2196
|
-
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0
|
|
2295
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
2296
|
+
enhancePrompt: character.enhancePrompt
|
|
2197
2297
|
});
|
|
2198
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 } } };
|
|
2199
2299
|
}
|
|
@@ -2237,7 +2337,15 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2237
2337
|
directions: [animation.direction],
|
|
2238
2338
|
seed: spec.seed,
|
|
2239
2339
|
textGuidanceScale: character.textGuidanceScale,
|
|
2240
|
-
isometric: character.isometric
|
|
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
|
|
2241
2349
|
});
|
|
2242
2350
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
2243
2351
|
return {
|
|
@@ -2605,6 +2713,13 @@ function readReference(spec, reference) {
|
|
|
2605
2713
|
}
|
|
2606
2714
|
return images;
|
|
2607
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
|
+
}
|
|
2608
2723
|
function rotationSources(character) {
|
|
2609
2724
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2610
2725
|
const sources = [];
|
|
@@ -5435,34 +5550,6 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
5435
5550
|
async function styleImagesBase64(loaded, styleId) {
|
|
5436
5551
|
return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
|
|
5437
5552
|
}
|
|
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
5553
|
async function resolveCharacterShape(asset, style, kind, files) {
|
|
5467
5554
|
const mode2 = style.mode ?? "standard";
|
|
5468
5555
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
@@ -5474,7 +5561,8 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
5474
5561
|
template: style.template ?? "mannequin",
|
|
5475
5562
|
...proportions !== void 0 ? { proportions } : {},
|
|
5476
5563
|
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
5477
|
-
...style.isometric !== void 0 ? { isometric: style.isometric } : {}
|
|
5564
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {},
|
|
5565
|
+
...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
|
|
5478
5566
|
};
|
|
5479
5567
|
if (asset.reference) {
|
|
5480
5568
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -5498,13 +5586,24 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
5498
5586
|
}
|
|
5499
5587
|
if (asset.animation) {
|
|
5500
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
|
+
};
|
|
5501
5593
|
shape.animation = {
|
|
5502
5594
|
mode: animationMode,
|
|
5503
5595
|
...asset.animation.template ? { template: asset.animation.template } : {},
|
|
5504
5596
|
direction: asset.animation.direction,
|
|
5505
5597
|
frames: asset.animation.frames ?? 8,
|
|
5506
5598
|
fps: asset.animation.fps,
|
|
5507
|
-
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 } : {}
|
|
5508
5607
|
};
|
|
5509
5608
|
}
|
|
5510
5609
|
return shape;
|