pixelkiln 0.47.0 → 0.48.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 +131 -46
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +6 -1
- package/dist/index.cjs +97 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +203 -6
- package/dist/index.d.ts +203 -6
- package/dist/index.js +97 -27
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +12 -6
- package/docs/ARTIFACTS.md +8 -1
- package/docs/CHARACTERS.md +5 -1
- package/docs/CLI.md +10 -1
- package/docs/ENDPOINTS.md +11 -1
- package/docs/GENERATORS.md +4 -0
- package/docs/PIXELLAB.md +15 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1374,6 +1374,8 @@ var LockHistoryEntrySchema = z.object({
|
|
|
1374
1374
|
downloadedAt: z.string().nullable().default(null),
|
|
1375
1375
|
cost: z.number().finite().nonnegative().default(0),
|
|
1376
1376
|
costUnit: z.string().min(1).default("generations"),
|
|
1377
|
+
/** What the provider actually billed, read from the completed job; null when never read. */
|
|
1378
|
+
billed: z.object({ amount: z.number(), unit: z.string() }).nullable().default(null),
|
|
1377
1379
|
provider: z.string().default("pixellab"),
|
|
1378
1380
|
postprocess: PostprocessSchema.optional(),
|
|
1379
1381
|
/** When the replacement was submitted. */
|
|
@@ -1465,6 +1467,14 @@ var LockEntrySchema = z.object({
|
|
|
1465
1467
|
cost: z.number().finite().nonnegative().default(0),
|
|
1466
1468
|
/** Unit for `cost`. Defaults preserve pre-unit PixelLab lockfiles. */
|
|
1467
1469
|
costUnit: z.string().min(1).default("generations"),
|
|
1470
|
+
/**
|
|
1471
|
+
* What the provider actually billed, read from the completed job rather
|
|
1472
|
+
* than estimated at submit time. Null until a poll reads it, and for a
|
|
1473
|
+
* provider or generator `poll` does not read it from at all; `cost` above
|
|
1474
|
+
* is still what a wave budget spends against, since the real number is
|
|
1475
|
+
* only known after the fact.
|
|
1476
|
+
*/
|
|
1477
|
+
billed: z.object({ amount: z.number(), unit: z.string() }).nullable().default(null),
|
|
1468
1478
|
/** Which provider produced this. Absent on entries written before providers. */
|
|
1469
1479
|
provider: z.string().default("pixellab"),
|
|
1470
1480
|
/** Generations this entry replaced, newest first; see LockHistoryEntrySchema. */
|
|
@@ -5193,7 +5203,13 @@ var CharacterListSchema = z2.object({ characters: z2.array(CharacterSchema), tot
|
|
|
5193
5203
|
var BackgroundJobSchema = z2.object({
|
|
5194
5204
|
id: z2.string(),
|
|
5195
5205
|
status: z2.string(),
|
|
5196
|
-
last_response: z2.record(z2.unknown()).nullable().optional()
|
|
5206
|
+
last_response: z2.record(z2.unknown()).nullable().optional(),
|
|
5207
|
+
/**
|
|
5208
|
+
* The billed amount, confirmed live for a `map` object and a standard
|
|
5209
|
+
* character base (docs/ENDPOINTS.md, "Limits and billing"); duplicated at
|
|
5210
|
+
* `last_response.billing_usage` on the jobs observed so far.
|
|
5211
|
+
*/
|
|
5212
|
+
usage: UsageSchema
|
|
5197
5213
|
}).passthrough();
|
|
5198
5214
|
var DeleteAnimationsSchema = z2.object({ success: z2.boolean(), deleted_count: z2.number().int().default(0), error: z2.string().nullable().optional() }).passthrough();
|
|
5199
5215
|
function validateResponse(schema, raw, operation) {
|
|
@@ -5647,6 +5663,14 @@ function pixelLabObjectUrl(generator, objectId) {
|
|
|
5647
5663
|
}
|
|
5648
5664
|
return null;
|
|
5649
5665
|
}
|
|
5666
|
+
function billedFromUsage(usage) {
|
|
5667
|
+
if (!usage) return null;
|
|
5668
|
+
if (usage.type === "usd" || usage.type === void 0 && typeof usage.usd === "number") {
|
|
5669
|
+
return typeof usage.usd === "number" ? { amount: usage.usd, unit: "usd" } : null;
|
|
5670
|
+
}
|
|
5671
|
+
if (typeof usage.generations === "number") return { amount: usage.generations, unit: "generations" };
|
|
5672
|
+
return null;
|
|
5673
|
+
}
|
|
5650
5674
|
function characterCost(spec) {
|
|
5651
5675
|
const character = spec.character;
|
|
5652
5676
|
if (!character) return generationCost(spec.width, spec.height, "1dir");
|
|
@@ -6017,6 +6041,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6017
6041
|
if (character.status !== "completed" || !character.rotation_urls) return { status: "processing" };
|
|
6018
6042
|
const sources = rotationSources(character);
|
|
6019
6043
|
if (!sources.length) return { status: "failed", error: "character completed with no rotation images" };
|
|
6044
|
+
const backgroundJobId = context?.metadata?.character?.backgroundJobId;
|
|
6020
6045
|
return {
|
|
6021
6046
|
status: "ready",
|
|
6022
6047
|
objectId: character.id,
|
|
@@ -6032,7 +6057,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6032
6057
|
size: character.size,
|
|
6033
6058
|
updatedAt: character.updated_at ?? null
|
|
6034
6059
|
}
|
|
6035
|
-
}
|
|
6060
|
+
},
|
|
6061
|
+
billed: await this.billedForJob(backgroundJobId)
|
|
6036
6062
|
};
|
|
6037
6063
|
}
|
|
6038
6064
|
/**
|
|
@@ -6044,7 +6070,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6044
6070
|
*/
|
|
6045
6071
|
async pollCharacterAnimation(job, context) {
|
|
6046
6072
|
const fps = context?.spec?.character?.animation?.fps ?? 8;
|
|
6047
|
-
const review = (frames, animationId, groupId) => ({
|
|
6073
|
+
const review = (frames, animationId, groupId, billed) => ({
|
|
6048
6074
|
status: "review-set",
|
|
6049
6075
|
objectId: `${job.characterId}#${groupId ?? animationId ?? job.name}`,
|
|
6050
6076
|
frameUrls: frames,
|
|
@@ -6060,7 +6086,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6060
6086
|
animationName: job.name,
|
|
6061
6087
|
direction: job.direction
|
|
6062
6088
|
}
|
|
6063
|
-
}
|
|
6089
|
+
},
|
|
6090
|
+
billed
|
|
6064
6091
|
});
|
|
6065
6092
|
let cleanedUp = job.jobIds.length === 0;
|
|
6066
6093
|
for (const id of job.jobIds) {
|
|
@@ -6080,13 +6107,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6080
6107
|
if (Array.isArray(frames) && frames.length && frames.every((f) => typeof f === "string")) {
|
|
6081
6108
|
const character2 = await this.client.getCharacter(job.characterId);
|
|
6082
6109
|
const group = findAnimation(character2, job.name, job.direction, animationId);
|
|
6083
|
-
return review(frames, animationId, group?.groupId ?? null);
|
|
6110
|
+
return review(frames, animationId, group?.groupId ?? null, billedFromUsage(status.usage));
|
|
6084
6111
|
}
|
|
6085
6112
|
}
|
|
6086
6113
|
const recorded = context?.metadata?.character?.animationId ?? null;
|
|
6087
6114
|
const character = await this.client.getCharacter(job.characterId);
|
|
6088
6115
|
const found = findAnimation(character, job.name, job.direction, recorded);
|
|
6089
|
-
if (found) return review(found.frames, found.animationId, found.groupId);
|
|
6116
|
+
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
6090
6117
|
return cleanedUp ? { status: "failed", error: `animation job is gone upstream and no "${job.name}" ${job.direction} animation exists on the character` } : { status: "processing" };
|
|
6091
6118
|
}
|
|
6092
6119
|
async submit(spec, styleImages, context) {
|
|
@@ -6119,7 +6146,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6119
6146
|
// unlike 1dir's {type, base64, format} payload.
|
|
6120
6147
|
styleImages: styleImages.map(({ base64, width, height }) => ({ base64, width, height }))
|
|
6121
6148
|
});
|
|
6122
|
-
return { jobId: res2.tile_id };
|
|
6149
|
+
return { jobId: res2.tile_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
6123
6150
|
}
|
|
6124
6151
|
if (spec.generator === "1dir") {
|
|
6125
6152
|
const res2 = await this.client.create1Direction({
|
|
@@ -6128,7 +6155,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6128
6155
|
view: spec.view === "sidescroller" ? "sidescroller" : "top-down",
|
|
6129
6156
|
styleImages
|
|
6130
6157
|
});
|
|
6131
|
-
return { jobId: res2.object_id };
|
|
6158
|
+
return { jobId: res2.object_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
6132
6159
|
}
|
|
6133
6160
|
const res = await this.client.createMapObject({
|
|
6134
6161
|
description: spec.prompt,
|
|
@@ -6140,7 +6167,22 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6140
6167
|
detail: spec.detail,
|
|
6141
6168
|
seed: spec.seed
|
|
6142
6169
|
});
|
|
6143
|
-
return { jobId: res.object_id };
|
|
6170
|
+
return { jobId: res.object_id, metadata: { backgroundJobId: res.background_job_id } };
|
|
6171
|
+
}
|
|
6172
|
+
/**
|
|
6173
|
+
* What a background job actually billed, once its own record is still
|
|
6174
|
+
* around to ask; see `billedFromUsage`. A stale or already-cleaned-up job
|
|
6175
|
+
* (a map object's record expires in hours; see `pollMap`) means "not
|
|
6176
|
+
* known", not a failure worth surfacing.
|
|
6177
|
+
*/
|
|
6178
|
+
async billedForJob(backgroundJobId) {
|
|
6179
|
+
if (!backgroundJobId) return null;
|
|
6180
|
+
try {
|
|
6181
|
+
const job = await this.client.getBackgroundJob(backgroundJobId);
|
|
6182
|
+
return billedFromUsage(job.usage);
|
|
6183
|
+
} catch {
|
|
6184
|
+
return null;
|
|
6185
|
+
}
|
|
6144
6186
|
}
|
|
6145
6187
|
async poll(jobId, generator, context) {
|
|
6146
6188
|
if (generator === "pixflux") {
|
|
@@ -6154,16 +6196,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6154
6196
|
error: "pixflux result is no longer cached locally; re-run submit for this asset"
|
|
6155
6197
|
};
|
|
6156
6198
|
}
|
|
6157
|
-
if (generator === "map") return this.pollMap(jobId);
|
|
6158
|
-
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature));
|
|
6199
|
+
if (generator === "map") return this.pollMap(jobId, context);
|
|
6200
|
+
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
|
|
6159
6201
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
6202
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6160
6203
|
const obj = await this.client.getObject(jobId);
|
|
6161
6204
|
if (obj.status === "review") {
|
|
6162
|
-
return { status: "review", candidateUrls: obj.frame_urls ?? [] };
|
|
6205
|
+
return { status: "review", candidateUrls: obj.frame_urls ?? [], billed: await this.billedForJob(backgroundJobId) };
|
|
6163
6206
|
}
|
|
6164
6207
|
if (obj.status === "completed") {
|
|
6165
6208
|
const url = firstUrl(obj.rotation_urls) ?? obj.preview_url ?? null;
|
|
6166
|
-
return { status: "ready", objectId: obj.id, sourceUrl: url, sources: url ? [{ url }] : [] };
|
|
6209
|
+
return { status: "ready", objectId: obj.id, sourceUrl: url, sources: url ? [{ url }] : [], billed: await this.billedForJob(backgroundJobId) };
|
|
6167
6210
|
}
|
|
6168
6211
|
if (obj.status === "failed") return { status: "failed", error: "generation failed upstream" };
|
|
6169
6212
|
return {
|
|
@@ -6179,7 +6222,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6179
6222
|
* `/objects` four months on, with `/map-objects` returning 404 for the same
|
|
6180
6223
|
* id. So a 404 here is not evidence the work is lost.
|
|
6181
6224
|
*/
|
|
6182
|
-
async pollMap(jobId) {
|
|
6225
|
+
async pollMap(jobId, context) {
|
|
6226
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6183
6227
|
try {
|
|
6184
6228
|
const obj = await this.client.getMapObject(jobId);
|
|
6185
6229
|
if (obj.status === "completed" && obj.download_url) {
|
|
@@ -6187,7 +6231,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6187
6231
|
status: "ready",
|
|
6188
6232
|
objectId: jobId,
|
|
6189
6233
|
sourceUrl: obj.download_url,
|
|
6190
|
-
sources: [{ url: obj.download_url }]
|
|
6234
|
+
sources: [{ url: obj.download_url }],
|
|
6235
|
+
billed: await this.billedForJob(backgroundJobId)
|
|
6191
6236
|
};
|
|
6192
6237
|
}
|
|
6193
6238
|
if (obj.status === "failed") return { status: "failed", error: "generation failed upstream" };
|
|
@@ -6198,7 +6243,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6198
6243
|
const survivor = await this.client.getObject(jobId).catch(() => null);
|
|
6199
6244
|
const url = firstUrl(survivor?.rotation_urls) ?? survivor?.preview_url ?? null;
|
|
6200
6245
|
if (survivor?.status === "completed" && url) {
|
|
6201
|
-
return { status: "ready", objectId: survivor.id, sourceUrl: url, sources: [{ url }] };
|
|
6246
|
+
return { status: "ready", objectId: survivor.id, sourceUrl: url, sources: [{ url }], billed: null };
|
|
6202
6247
|
}
|
|
6203
6248
|
return {
|
|
6204
6249
|
status: "failed",
|
|
@@ -6211,12 +6256,14 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6211
6256
|
* `storage_urls` once finished. There is no `status` field to read and no
|
|
6212
6257
|
* progress percentage on offer, so "processing" here carries no ETA.
|
|
6213
6258
|
*/
|
|
6214
|
-
async pollTiles(tileId, connectable) {
|
|
6259
|
+
async pollTiles(tileId, connectable, context) {
|
|
6215
6260
|
try {
|
|
6216
6261
|
const set = await this.client.getTilesPro(tileId);
|
|
6217
6262
|
const tiles = tilesInIndexOrder(set.storage_urls);
|
|
6218
6263
|
if (!tiles.length) return { status: "failed", error: "tiles job returned no storage urls" };
|
|
6219
|
-
|
|
6264
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6265
|
+
const billed = await this.billedForJob(backgroundJobId);
|
|
6266
|
+
if (!connectable) return { status: "review", candidateUrls: tiles.map((tile) => tile.url), billed };
|
|
6220
6267
|
return {
|
|
6221
6268
|
status: "ready",
|
|
6222
6269
|
objectId: tileId,
|
|
@@ -6228,7 +6275,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6228
6275
|
metadata: {
|
|
6229
6276
|
tileKind: set.kind,
|
|
6230
6277
|
...set.tile_rules ? { tileRules: set.tile_rules } : {}
|
|
6231
|
-
}
|
|
6278
|
+
},
|
|
6279
|
+
billed
|
|
6232
6280
|
};
|
|
6233
6281
|
} catch (err) {
|
|
6234
6282
|
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
@@ -10722,6 +10770,7 @@ function retireGeneration(entry, retiredAt) {
|
|
|
10722
10770
|
downloadedAt: entry.downloadedAt,
|
|
10723
10771
|
cost: entry.cost,
|
|
10724
10772
|
costUnit: entry.costUnit,
|
|
10773
|
+
billed: entry.billed,
|
|
10725
10774
|
provider: entry.provider,
|
|
10726
10775
|
postprocess: entry.postprocess,
|
|
10727
10776
|
retiredAt
|
|
@@ -10812,6 +10861,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
10812
10861
|
downloadedAt: generation.downloadedAt,
|
|
10813
10862
|
cost: generation.cost,
|
|
10814
10863
|
costUnit: generation.costUnit,
|
|
10864
|
+
billed: generation.billed,
|
|
10815
10865
|
provider: generation.provider,
|
|
10816
10866
|
postprocess: generation.postprocess,
|
|
10817
10867
|
history
|
|
@@ -10870,6 +10920,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
10870
10920
|
height: generation.height,
|
|
10871
10921
|
cost: generation.cost,
|
|
10872
10922
|
costUnit: generation.costUnit,
|
|
10923
|
+
billed: generation.billed,
|
|
10873
10924
|
submittedAt: generation.submittedAt,
|
|
10874
10925
|
downloadedAt: generation.downloadedAt,
|
|
10875
10926
|
retiredAt: generation.retiredAt,
|
|
@@ -11113,6 +11164,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11113
11164
|
fps: quality?.frameSet?.fps ?? metadataFps(entry) ?? spec.quality?.fps ?? null,
|
|
11114
11165
|
cost: entry?.cost ?? 0,
|
|
11115
11166
|
costUnit: entry?.costUnit ?? spec.costUnit,
|
|
11167
|
+
billed: entry?.billed ?? null,
|
|
11116
11168
|
estimatedCost: spec.cost,
|
|
11117
11169
|
candidates: spec.candidates,
|
|
11118
11170
|
submittedAt: entry?.submittedAt ?? null,
|
|
@@ -11176,6 +11228,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11176
11228
|
fps: metadataFps(entry),
|
|
11177
11229
|
cost: entry.cost,
|
|
11178
11230
|
costUnit: entry.costUnit,
|
|
11231
|
+
billed: entry.billed,
|
|
11179
11232
|
estimatedCost: null,
|
|
11180
11233
|
candidates: null,
|
|
11181
11234
|
submittedAt: entry.submittedAt,
|
|
@@ -11818,7 +11871,8 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
11818
11871
|
...entry.providerMetadata[provider.id],
|
|
11819
11872
|
...state.metadata
|
|
11820
11873
|
}
|
|
11821
|
-
} : entry.providerMetadata
|
|
11874
|
+
} : entry.providerMetadata,
|
|
11875
|
+
billed: state.billed ?? entry.billed
|
|
11822
11876
|
});
|
|
11823
11877
|
result.review++;
|
|
11824
11878
|
log2(
|
|
@@ -11838,7 +11892,8 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
11838
11892
|
...state.metadata
|
|
11839
11893
|
}
|
|
11840
11894
|
} : entry.providerMetadata,
|
|
11841
|
-
error: null
|
|
11895
|
+
error: null,
|
|
11896
|
+
billed: state.billed ?? entry.billed
|
|
11842
11897
|
});
|
|
11843
11898
|
result.completed++;
|
|
11844
11899
|
log2(` ready ${key}`);
|
|
@@ -13746,6 +13801,10 @@ async function runLifecycle(args, wave, spent) {
|
|
|
13746
13801
|
}
|
|
13747
13802
|
|
|
13748
13803
|
// src/cli/commands/history.ts
|
|
13804
|
+
function billedNote(cost, costUnit, billed) {
|
|
13805
|
+
if (!billed || billed.unit === costUnit && billed.amount === cost) return "";
|
|
13806
|
+
return ` (billed ${formatCost(billed.unit, billed.amount)})`;
|
|
13807
|
+
}
|
|
13749
13808
|
async function runHistory(args) {
|
|
13750
13809
|
const { loaded, specs, lock } = await openProject2(args);
|
|
13751
13810
|
const limit = historyLimit(loaded.manifest);
|
|
@@ -13756,7 +13815,7 @@ async function runHistory(args) {
|
|
|
13756
13815
|
limit,
|
|
13757
13816
|
assets: selected.map(({ spec, entry }) => ({
|
|
13758
13817
|
key: lockKey(spec.styleId, spec.assetId),
|
|
13759
|
-
current: entry ? { objectId: entry.objectId, outputs: entry.outputs, downloadedAt: entry.downloadedAt, cost: entry.cost, costUnit: entry.costUnit } : null,
|
|
13818
|
+
current: entry ? { objectId: entry.objectId, outputs: entry.outputs, downloadedAt: entry.downloadedAt, cost: entry.cost, costUnit: entry.costUnit, billed: entry.billed } : null,
|
|
13760
13819
|
history: entry?.history ?? []
|
|
13761
13820
|
}))
|
|
13762
13821
|
}, null, 2));
|
|
@@ -13770,10 +13829,10 @@ async function runHistory(args) {
|
|
|
13770
13829
|
const key = lockKey(spec.styleId, spec.assetId);
|
|
13771
13830
|
log(`
|
|
13772
13831
|
${key}`);
|
|
13773
|
-
log(` current ${entry.outputs[0]?.sha256.slice(0, 12) ?? "\u2014"} ${entry.downloadedAt ?? ""} ${entry.objectId ?? ""}`);
|
|
13832
|
+
log(` current ${entry.outputs[0]?.sha256.slice(0, 12) ?? "\u2014"} ${entry.downloadedAt ?? ""} ${entry.objectId ?? ""}${billedNote(entry.cost, entry.costUnit, entry.billed)}`);
|
|
13774
13833
|
for (const [i, generation] of entry.history.entries()) {
|
|
13775
13834
|
const changed = generation.prompt !== entry.prompt ? " (different prompt)" : "";
|
|
13776
|
-
log(` #${String(i + 1).padEnd(2)} ${generation.outputs[0]?.sha256.slice(0, 12) ?? "\u2014"} ${generation.downloadedAt ?? ""} ${generation.objectId ?? ""}${changed}`);
|
|
13835
|
+
log(` #${String(i + 1).padEnd(2)} ${generation.outputs[0]?.sha256.slice(0, 12) ?? "\u2014"} ${generation.downloadedAt ?? ""} ${generation.objectId ?? ""}${changed}${billedNote(generation.cost, generation.costUnit, generation.billed)}`);
|
|
13777
13836
|
}
|
|
13778
13837
|
}
|
|
13779
13838
|
if (!shown) log(` no asset has a previous generation recorded${selected.length ? "" : " (nothing selected)"}`);
|
|
@@ -14468,21 +14527,29 @@ async function runPlan(args) {
|
|
|
14468
14527
|
actionable: group.actionable.map((item) => item.key)
|
|
14469
14528
|
})),
|
|
14470
14529
|
actionable: plan.actionable.map((i) => i.key),
|
|
14471
|
-
items: plan.items.map(({ key, state, reason, quality, spec }) =>
|
|
14472
|
-
key
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
|
|
14530
|
+
items: plan.items.map(({ key, state, reason, quality, spec }) => {
|
|
14531
|
+
const entry = lock.entries[key];
|
|
14532
|
+
const billedDiffers = entry?.billed && (entry.billed.unit !== entry.costUnit || entry.billed.amount !== entry.cost);
|
|
14533
|
+
return {
|
|
14534
|
+
key,
|
|
14535
|
+
state,
|
|
14536
|
+
reason,
|
|
14537
|
+
...spec.revision ? {
|
|
14538
|
+
revision: {
|
|
14539
|
+
mode: spec.revision.mode,
|
|
14540
|
+
from: spec.revision.sourceAssetId,
|
|
14541
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
14542
|
+
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
|
|
14543
|
+
...spec.revision.strength == null ? {} : { strength: spec.revision.strength }
|
|
14544
|
+
}
|
|
14545
|
+
} : {},
|
|
14546
|
+
...quality ? { quality: { state: quality.state, reason: quality.reason } } : {},
|
|
14547
|
+
// The estimate a wave budget spent against; present alongside the
|
|
14548
|
+
// provider's actual charge only when `poll` has read one and it
|
|
14549
|
+
// differs, since that is the only time the gap is worth a look.
|
|
14550
|
+
...billedDiffers ? { cost: entry.cost, costUnit: entry.costUnit, billed: entry.billed } : {}
|
|
14551
|
+
};
|
|
14552
|
+
})
|
|
14486
14553
|
}, null, 2));
|
|
14487
14554
|
} else {
|
|
14488
14555
|
printPlan(plan);
|
|
@@ -14601,15 +14668,27 @@ function packSprites(inputs, options = {}) {
|
|
|
14601
14668
|
const sheetH = rows * cellH;
|
|
14602
14669
|
const rgba = Buffer.alloc(sheetW * sheetH * 4);
|
|
14603
14670
|
const frames = [];
|
|
14671
|
+
const pivot = options.pivot ?? "top-left";
|
|
14604
14672
|
sprites.forEach((sprite, i) => {
|
|
14605
|
-
const
|
|
14606
|
-
const
|
|
14673
|
+
const cellOx = i % columns * cellW;
|
|
14674
|
+
const cellOy = Math.floor(i / columns) * cellH;
|
|
14675
|
+
const offsetX = pivot === "bottom-center" ? Math.floor((cellW - sprite.width) / 2) : 0;
|
|
14676
|
+
const offsetY = pivot === "bottom-center" ? cellH - sprite.height : 0;
|
|
14677
|
+
const ox = cellOx + offsetX;
|
|
14678
|
+
const oy = cellOy + offsetY;
|
|
14607
14679
|
for (let y = 0; y < sprite.height; y++) {
|
|
14608
14680
|
const src = y * sprite.width * 4;
|
|
14609
14681
|
const dst = ((oy + y) * sheetW + ox) * 4;
|
|
14610
14682
|
sprite.pixels.copy(rgba, dst, src, src + sprite.width * 4);
|
|
14611
14683
|
}
|
|
14612
|
-
frames.push({
|
|
14684
|
+
frames.push({
|
|
14685
|
+
id: sprite.id,
|
|
14686
|
+
x: ox,
|
|
14687
|
+
y: oy,
|
|
14688
|
+
width: sprite.width,
|
|
14689
|
+
height: sprite.height,
|
|
14690
|
+
...offsetX || offsetY ? { offsetX, offsetY } : {}
|
|
14691
|
+
});
|
|
14613
14692
|
});
|
|
14614
14693
|
return {
|
|
14615
14694
|
png: encodeRgbaPng(sheetW, sheetH, rgba),
|
|
@@ -15126,12 +15205,15 @@ function renderAsepriteSheet(atlas, opts) {
|
|
|
15126
15205
|
}
|
|
15127
15206
|
const frames = {};
|
|
15128
15207
|
for (const frame of atlas.frames) {
|
|
15208
|
+
const offsetX = frame.offsetX ?? 0;
|
|
15209
|
+
const offsetY = frame.offsetY ?? 0;
|
|
15210
|
+
const pivoted = offsetX !== 0 || offsetY !== 0;
|
|
15129
15211
|
frames[frame.id] = {
|
|
15130
15212
|
frame: { x: frame.x, y: frame.y, w: frame.width, h: frame.height },
|
|
15131
15213
|
rotated: false,
|
|
15132
|
-
trimmed:
|
|
15133
|
-
spriteSourceSize: { x: 0, y: 0, w: frame.width, h: frame.height },
|
|
15134
|
-
sourceSize: { w: frame.width, h: frame.height },
|
|
15214
|
+
trimmed: pivoted,
|
|
15215
|
+
spriteSourceSize: pivoted ? { x: offsetX, y: offsetY, w: frame.width, h: frame.height } : { x: 0, y: 0, w: frame.width, h: frame.height },
|
|
15216
|
+
sourceSize: pivoted ? { w: atlas.cell.width, h: atlas.cell.height } : { w: frame.width, h: frame.height },
|
|
15135
15217
|
duration: durations.get(frame.id) ?? ASEPRITE_DEFAULT_DURATION_MS
|
|
15136
15218
|
};
|
|
15137
15219
|
}
|
|
@@ -15236,16 +15318,18 @@ async function runPack(args) {
|
|
|
15236
15318
|
const manifestDir = path38.dirname(path38.resolve(args.manifest));
|
|
15237
15319
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
15238
15320
|
for (const styleId of styleIds) {
|
|
15321
|
+
const style = loaded.manifest.styles[styleId];
|
|
15322
|
+
const pivot = style?.generator === "character" ? "bottom-center" : "top-left";
|
|
15239
15323
|
const packagingSpecs = await resolveSpecs(loaded, { styles: [styleId] });
|
|
15240
15324
|
const qualitySources = await requireApprovedQualitySources(packagingSpecs, lock);
|
|
15241
15325
|
const { png, atlas, skipped, sources } = packStyle(lock, styleId, manifestDir, {
|
|
15242
15326
|
columns: args.columns,
|
|
15243
15327
|
outputRoles: args.outputRoles,
|
|
15244
15328
|
primaryOnly: args.primaryOnly,
|
|
15329
|
+
pivot,
|
|
15245
15330
|
sourceOverrides: qualitySources,
|
|
15246
15331
|
sources: manifestSources(loaded.manifest, styleId)
|
|
15247
15332
|
});
|
|
15248
|
-
const style = loaded.manifest.styles[styleId];
|
|
15249
15333
|
const base = args.out ? path38.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path38.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
|
|
15250
15334
|
const format = sheetFormat(args);
|
|
15251
15335
|
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path38.basename(`${base}.png`) });
|
|
@@ -15272,6 +15356,7 @@ async function runPack(args) {
|
|
|
15272
15356
|
format,
|
|
15273
15357
|
order: "id",
|
|
15274
15358
|
outputRoles: [...args.outputRoles].sort(),
|
|
15359
|
+
pivot,
|
|
15275
15360
|
primaryOnly: args.primaryOnly,
|
|
15276
15361
|
style: styleId
|
|
15277
15362
|
}
|