pixelkiln 0.70.1 → 0.72.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/index.js CHANGED
@@ -125,7 +125,7 @@ var CharacterProportionsSchema = z.union([
125
125
  hipWidth: z.number().min(0.5).max(2).optional()
126
126
  }).strict()
127
127
  ]);
128
- var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
128
+ var CharacterAnimationModeSchema = z.enum(["template", "skeleton-v3", "v3", "pro"]);
129
129
  var UiPieceSchema = z.discriminatedUnion("kind", [
130
130
  z.object({
131
131
  id: z.string().min(1),
@@ -239,7 +239,11 @@ var CharacterAnimationSchema = z.object({
239
239
  fps: z.number().int().min(1).max(60).default(8),
240
240
  /** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
241
241
  keepFirstFrame: z.boolean().default(true),
242
- /** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
242
+ /**
243
+ * `template` when a template is named, otherwise `v3`; `skeleton-v3`
244
+ * poses a template with PixelLab's skeleton model instead; `pro` for the
245
+ * sequential high-quality engine.
246
+ */
243
247
  mode: CharacterAnimationModeSchema.optional(),
244
248
  /**
245
249
  * v3 only: a manifest-relative image of the pose to start from, instead
@@ -276,7 +280,7 @@ var CharacterAnimationSchema = z.object({
276
280
  if (animation[key] !== void 0 && mode2 !== "v3") {
277
281
  context.addIssue({
278
282
  code: z.ZodIssueCode.custom,
279
- message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
283
+ message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" || mode2 === "skeleton-v3" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
280
284
  path: [key]
281
285
  });
282
286
  }
@@ -286,10 +290,10 @@ var CharacterAnimationSchema = z.object({
286
290
  context.addIssue({ code: z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
287
291
  }
288
292
  }
289
- if (animation.template && animation.mode && animation.mode !== "template") {
293
+ if (animation.template && animation.mode && animation.mode !== "template" && animation.mode !== "skeleton-v3") {
290
294
  context.addIssue({
291
295
  code: z.ZodIssueCode.custom,
292
- message: `a template animation cannot use mode "${animation.mode}"`,
296
+ message: `a template animation cannot use mode "${animation.mode}"; use "template" or "skeleton-v3"`,
293
297
  path: ["mode"]
294
298
  });
295
299
  }
@@ -300,8 +304,8 @@ var CharacterAnimationSchema = z.object({
300
304
  path: ["frames"]
301
305
  });
302
306
  }
303
- if (!animation.template && animation.mode === "template") {
304
- context.addIssue({ code: z.ZodIssueCode.custom, message: "template mode needs a template", path: ["template"] });
307
+ if (!animation.template && (animation.mode === "template" || animation.mode === "skeleton-v3")) {
308
+ context.addIssue({ code: z.ZodIssueCode.custom, message: `${animation.mode} mode needs a template`, path: ["template"] });
305
309
  }
306
310
  });
307
311
  var RevisionDitheringSchema = z.enum(["none", "2x2", "4x4", "8x8"]);
@@ -2235,12 +2239,20 @@ var PixelLabClient = class {
2235
2239
  }
2236
2240
  /**
2237
2241
  * One animation of one character. Template mode costs a generation per
2238
- * direction and fixes the frame count; v3 draws `frameCount` frames from
2239
- * the action text; pro is the sequential engine. One job per direction.
2242
+ * direction and fixes the frame count; skeleton-v3 poses the same template
2243
+ * with the skeleton video model (2-4 generations per direction); v3 draws
2244
+ * `frameCount` frames from the action text; pro is the sequential engine.
2245
+ * One job per direction.
2246
+ *
2247
+ * Every mode but skeleton-v3 goes to `/animate-character`, the path whose
2248
+ * shapes were measured live. skeleton-v3 is documented only on
2249
+ * `/characters/animations`, which takes the same request and answers with
2250
+ * the same job list (plus an `animation_group_id`).
2240
2251
  */
2241
2252
  async animateCharacter(args) {
2242
2253
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
2243
- const raw = await this.request("/animate-character", {
2254
+ const endpoint = args.mode === "skeleton-v3" ? "/characters/animations" : "/animate-character";
2255
+ const raw = await this.request(endpoint, {
2244
2256
  method: "POST",
2245
2257
  body: JSON.stringify({
2246
2258
  character_id: args.characterId,
@@ -2264,7 +2276,7 @@ var PixelLabClient = class {
2264
2276
  ...args.seed != null ? { seed: args.seed } : {}
2265
2277
  })
2266
2278
  });
2267
- return validateResponse(AnimateSubmitSchema, raw, "animate-character");
2279
+ return validateResponse(AnimateSubmitSchema, raw, endpoint.slice(1));
2268
2280
  }
2269
2281
  /**
2270
2282
  * A full-body sprite in, a bust portrait out (or the reverse, unused
@@ -3557,6 +3569,7 @@ function characterCost(spec) {
3557
3569
  if (character.kind === "animation") {
3558
3570
  const animation = character.animation;
3559
3571
  if (animation.mode === "template") return 1;
3572
+ if (animation.mode === "skeleton-v3") return 4;
3560
3573
  if (animation.mode === "v3") return Math.max(1, Math.ceil(px * animation.frames / 65536));
3561
3574
  return generationCost(spec.width, spec.height, "1dir");
3562
3575
  }
@@ -4149,7 +4162,7 @@ var PixelLabProvider = class _PixelLabProvider {
4149
4162
  `${spec.styleId}/${spec.assetId} animates "${character.animation.direction}", but its character has ${character.directions} directions: ${allowed.join(", ")}`
4150
4163
  );
4151
4164
  }
4152
- if (character.animation.mode !== "template" && !spec.prompt.trim()) {
4165
+ if (character.animation.mode !== "template" && character.animation.mode !== "skeleton-v3" && !spec.prompt.trim()) {
4153
4166
  throw new Error(`${spec.styleId}/${spec.assetId} needs a prompt describing the motion, or a template`);
4154
4167
  }
4155
4168
  }
@@ -9077,8 +9090,8 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
9077
9090
  );
9078
9091
  }
9079
9092
  }
9080
- if (asset.animation.mode === "template") {
9081
- throw new Error(`assets.${assetId}: objectPro animations have no "template" mode; use "v3" or "pro"`);
9093
+ if (asset.animation.mode === "template" || asset.animation.mode === "skeleton-v3") {
9094
+ throw new Error(`assets.${assetId}: objectPro animations have no "${asset.animation.mode}" mode; use "v3" or "pro"`);
9082
9095
  }
9083
9096
  const pose = async (rel, what) => {
9084
9097
  const image = await files.load(rel, what);
@@ -15623,6 +15636,15 @@ var SkeletonAnimationRequestSchema = z10.object({
15623
15636
  /** Replace an existing `keypointsFile` with `set`. */
15624
15637
  overwrite: z10.boolean().optional()
15625
15638
  }).strict();
15639
+ var SkeletonKeypointsUpdateSchema = z10.object({
15640
+ action: z10.literal("update-skeleton-keypoints"),
15641
+ project: z10.string().min(1).optional(),
15642
+ styleId: z10.string().min(1),
15643
+ assetId: z10.string().min(1),
15644
+ /** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
15645
+ expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
15646
+ set: SkeletonSetSchema
15647
+ }).strict();
15626
15648
  async function createSkeletonAnimation(manifestPath, request) {
15627
15649
  const root = path28.dirname(path28.resolve(manifestPath));
15628
15650
  const file = path28.resolve(root, request.keypointsFile);
@@ -15668,6 +15690,26 @@ async function createSkeletonAnimation(manifestPath, request) {
15668
15690
  throw error;
15669
15691
  }
15670
15692
  }
15693
+ async function updateSkeletonKeypoints(ctx, request) {
15694
+ const key = lockKey(request.styleId, request.assetId);
15695
+ const spec = ctx.specs.find((candidate) => lockKey(candidate.styleId, candidate.assetId) === key);
15696
+ if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
15697
+ const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
15698
+ if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
15699
+ const relative = path28.relative(ctx.loaded.root, file);
15700
+ if (relative.startsWith("..") || path28.isAbsolute(relative)) {
15701
+ throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
15702
+ }
15703
+ const current = existsSync23(file) ? sha256(await readFile21(file)) : null;
15704
+ if (current !== request.expectedSha256) {
15705
+ throw Object.assign(
15706
+ new Error(`${relative.split(path28.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
15707
+ { status: 409 }
15708
+ );
15709
+ }
15710
+ await writeFile12(file, JSON.stringify(request.set, null, 2) + "\n");
15711
+ return relative.split(path28.sep).join("/");
15712
+ }
15671
15713
 
15672
15714
  // src/gallery/edit.ts
15673
15715
  var Base64 = z11.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
@@ -15744,6 +15786,18 @@ function createGalleryEditHandler(opts) {
15744
15786
  }
15745
15787
  return opts.reload();
15746
15788
  }
15789
+ const poses = SkeletonKeypointsUpdateSchema.safeParse(body);
15790
+ if (poses.success) {
15791
+ if (!opts.loadProject) throw new ManifestEditError("pose edits are not available in this gallery");
15792
+ const written = await updateSkeletonKeypoints(await opts.loadProject(poses.data.project), poses.data);
15793
+ log(` poses saved: ${written} (${poses.data.styleId}/${poses.data.assetId} is stale until generated again)`);
15794
+ return opts.reload();
15795
+ }
15796
+ if (body?.action === "update-skeleton-keypoints") {
15797
+ throw new ManifestEditError(
15798
+ "invalid pose edit: " + poses.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
15799
+ );
15800
+ }
15747
15801
  const skeleton = SkeletonAnimationRequestSchema.safeParse(body);
15748
15802
  if (skeleton.success) {
15749
15803
  let manifestPath2;
@@ -16117,7 +16171,8 @@ async function buildGallerySnapshot(opts) {
16117
16171
  mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
16118
16172
  skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
16119
16173
  keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
16120
- set: spec.revision.skeleton ?? null
16174
+ set: spec.revision.skeleton ?? null,
16175
+ sha256: spec.revision.keypointsSha256 ?? null
16121
16176
  } : null,
16122
16177
  character: describeCharacter(spec, entry),
16123
16178
  outputs,