pixelkiln 0.40.0 → 0.41.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 +102 -7
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +1 -0
- package/dist/client/gallery.js +39 -0
- package/dist/index.cjs +114 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +114 -7
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +11 -6
- package/docs/ENDPOINTS.md +7 -0
- package/docs/GENERATORS.md +4 -1
- package/docs/PIXELLAB.md +7 -0
- package/package.json +1 -1
package/dist/client/gallery.css
CHANGED
|
@@ -93,6 +93,7 @@ main { width:min(100%,var(--content)); margin:0 auto; border-inline:1px solid va
|
|
|
93
93
|
.aid { font:600 13px/1.3 var(--mono); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
94
94
|
.cmeta { display:flex; gap:8px; align-items:center; color:var(--dim); font-size:11.5px;
|
|
95
95
|
font-variant-numeric:tabular-nums; white-space:nowrap; overflow:hidden; }
|
|
96
|
+
.family { font-size:11px; color:var(--dim); margin-top:2px; }
|
|
96
97
|
.cmeta .st { display:inline-flex; align-items:center; gap:5px; }
|
|
97
98
|
.empty { padding:64px 22px; color:var(--dim); text-align:center; }
|
|
98
99
|
.empty p { margin:0 0 12px; }
|
package/dist/client/gallery.js
CHANGED
|
@@ -97,6 +97,14 @@ const ACTIVE_PHASES = new Set(['queued', 'submitting', 'polling', 'fetching', 'r
|
|
|
97
97
|
const PHASE_TONE = { queued: 'cool', submitting: 'cool', polling: 'cool', fetching: 'cool', review: 'warn', done: 'ok', failed: 'bad' };
|
|
98
98
|
// The job still working on this record, if any: what the drawer and its card
|
|
99
99
|
// show while a regeneration, restore, or pull is in flight.
|
|
100
|
+
/** "state of mira", "loop of mira.chair_spin, east": where a character family member sits. */
|
|
101
|
+
const familyLabel = (item) => {
|
|
102
|
+
const c = item.character;
|
|
103
|
+
if (!c || c.kind === 'base') return null;
|
|
104
|
+
const parent = c.parentKey ? c.parentKey.split('/').slice(1).join('/') : null;
|
|
105
|
+
if (c.kind === 'state') return parent ? 'state of ' + parent : 'state';
|
|
106
|
+
return (parent ? 'loop of ' + parent : 'loop') + (c.direction ? ', ' + c.direction : '');
|
|
107
|
+
};
|
|
100
108
|
/** The style's palette rule as the manifest has it now, or null. */
|
|
101
109
|
const paletteRuleOf = (item) => {
|
|
102
110
|
const style = snap.styles.find((s) => s.id === item.styleId && s.project === item.project);
|
|
@@ -819,6 +827,8 @@ function card(item) {
|
|
|
819
827
|
meta.append(st, el('span', null, item.width + '×' + item.height));
|
|
820
828
|
if (item.cost) meta.append(el('span', null, fmtCost(item.costUnit, item.cost)));
|
|
821
829
|
body.append(meta);
|
|
830
|
+
const family = familyLabel(item);
|
|
831
|
+
if (family) body.append(el('div', 'family', family));
|
|
822
832
|
const cell = thumb(item);
|
|
823
833
|
if (slot >= 0) cell.append(el('span', 'slot', String(slot + 1)));
|
|
824
834
|
if (item.editStatus === 'edited' || item.editStatus === 'regenerated-since') {
|
|
@@ -899,6 +909,13 @@ function renderMain(items) {
|
|
|
899
909
|
meta.append(sw);
|
|
900
910
|
}
|
|
901
911
|
if (s.quality) meta.append(el('span', null, 'quality profile'));
|
|
912
|
+
if (s.characters) {
|
|
913
|
+
const parts = [];
|
|
914
|
+
parts.push(s.characters.bases + (s.characters.bases === 1 ? ' character' : ' characters'));
|
|
915
|
+
if (s.characters.states) parts.push(s.characters.states + (s.characters.states === 1 ? ' state' : ' states'));
|
|
916
|
+
if (s.characters.animations) parts.push(s.characters.animations + (s.characters.animations === 1 ? ' loop' : ' loops'));
|
|
917
|
+
meta.append(el('span', null, parts.join(', ')));
|
|
918
|
+
}
|
|
902
919
|
meta.append(el('span', null, sec.items.length + ' of ' + s.items + (s.items === 1 ? ' asset' : ' assets')));
|
|
903
920
|
if (Object.keys(s.spendByUnit).length) meta.append(el('span', null, fmtSpend(s.spendByUnit)));
|
|
904
921
|
head.append(meta);
|
|
@@ -1910,6 +1927,28 @@ function renderDrawer() {
|
|
|
1910
1927
|
const versions = historySection(item);
|
|
1911
1928
|
if (versions) body.append(versions);
|
|
1912
1929
|
|
|
1930
|
+
if (item.character) {
|
|
1931
|
+
const c = item.character;
|
|
1932
|
+
const { s, dl } = section('Character');
|
|
1933
|
+
row(dl, 'kind', c.kind === 'animation' ? 'animation' : c.kind === 'state' ? 'state (a pose or outfit of its parent)' : 'base');
|
|
1934
|
+
if (c.parentKey) row(dl, 'parent', keyLink(item, c.parentKey));
|
|
1935
|
+
row(dl, 'engine', c.mode);
|
|
1936
|
+
if (c.kind === 'animation') row(dl, 'direction', c.direction || '—');
|
|
1937
|
+
else row(dl, 'directions', String(c.directions));
|
|
1938
|
+
if (c.characterId) row(dl, 'character id', c.characterId, { mono: true, copy: c.characterId });
|
|
1939
|
+
const family = snap.items.filter((i) => i.character && i.character.parentKey === item.key && i.project === item.project);
|
|
1940
|
+
if (family.length) {
|
|
1941
|
+
const list = el('div');
|
|
1942
|
+
for (const f of family) {
|
|
1943
|
+
const line = el('div');
|
|
1944
|
+
line.append(document.createTextNode((f.character.kind === 'state' ? 'state ' : 'loop ') + (f.character.direction ? f.character.direction + ' ' : '')), keyLink(item, f.key));
|
|
1945
|
+
list.append(line);
|
|
1946
|
+
}
|
|
1947
|
+
row(dl, family.length === 1 ? 'depends on this' : family.length + ' depend on this', list);
|
|
1948
|
+
}
|
|
1949
|
+
body.append(s);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1913
1952
|
if (item.revision || item.revisionParentKey) {
|
|
1914
1953
|
const { s, dl } = section('Lineage');
|
|
1915
1954
|
row(dl, 'mode', item.revision ? item.revision.mode : (item.asset && item.asset.revision && item.asset.revision.mode));
|
package/dist/index.cjs
CHANGED
|
@@ -2297,12 +2297,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2297
2297
|
const character = await this.client.getCharacter(jobId);
|
|
2298
2298
|
if (character.status === "failed") return { status: "failed", error: "character generation failed upstream" };
|
|
2299
2299
|
if (character.status !== "completed" || !character.rotation_urls) return { status: "processing" };
|
|
2300
|
-
const
|
|
2301
|
-
const sources = [];
|
|
2302
|
-
for (const direction of order) {
|
|
2303
|
-
const url = character.rotation_urls[direction];
|
|
2304
|
-
if (url) sources.push({ url, role: direction });
|
|
2305
|
-
}
|
|
2300
|
+
const sources = rotationSources(character);
|
|
2306
2301
|
if (!sources.length) return { status: "failed", error: "character completed with no rotation images" };
|
|
2307
2302
|
return {
|
|
2308
2303
|
status: "ready",
|
|
@@ -2594,6 +2589,27 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2594
2589
|
})))
|
|
2595
2590
|
};
|
|
2596
2591
|
}
|
|
2592
|
+
/**
|
|
2593
|
+
* A character's rotation and frame URLs carry its last edit time as a
|
|
2594
|
+
* cache-buster, so the record's URLs can name bytes a cache still holds
|
|
2595
|
+
* after an edit in PixelLab's editor. Objects keep stable URLs.
|
|
2596
|
+
*/
|
|
2597
|
+
async refreshSources(objectId, context) {
|
|
2598
|
+
if (context.generator !== "character") return void 0;
|
|
2599
|
+
const [characterId, groupId] = objectId.split("#");
|
|
2600
|
+
const character = await this.client.getCharacter(characterId).catch((err) => {
|
|
2601
|
+
if (err instanceof PixelLabError && err.status === 404) return null;
|
|
2602
|
+
throw err;
|
|
2603
|
+
});
|
|
2604
|
+
if (!character) return null;
|
|
2605
|
+
if (!groupId) return character.status === "completed" ? rotationSources(character) : void 0;
|
|
2606
|
+
const recorded = context.metadata?.character ?? {};
|
|
2607
|
+
const direction = recorded.direction ?? "south";
|
|
2608
|
+
const group = character.animations.find((g) => g.animation_group_id === groupId);
|
|
2609
|
+
const frames = group?.directions.find((d) => d.direction === direction && d.frames.length)?.frames ?? (recorded.animationName ? findAnimation(character, recorded.animationName, direction, recorded.animationId ?? null)?.frames : void 0);
|
|
2610
|
+
if (!frames) return null;
|
|
2611
|
+
return frames.map((url, index) => ({ url, role: `frame-${String(index).padStart(2, "0")}` }));
|
|
2612
|
+
}
|
|
2597
2613
|
};
|
|
2598
2614
|
function remoteCharacter(character) {
|
|
2599
2615
|
return {
|
|
@@ -2624,6 +2640,15 @@ function firstUrl(urls) {
|
|
|
2624
2640
|
if (!urls) return null;
|
|
2625
2641
|
return Object.values(urls).find((u) => typeof u === "string") ?? null;
|
|
2626
2642
|
}
|
|
2643
|
+
function rotationSources(character) {
|
|
2644
|
+
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2645
|
+
const sources = [];
|
|
2646
|
+
for (const direction of order) {
|
|
2647
|
+
const url = character.rotation_urls?.[direction];
|
|
2648
|
+
if (url) sources.push({ url, role: direction });
|
|
2649
|
+
}
|
|
2650
|
+
return sources;
|
|
2651
|
+
}
|
|
2627
2652
|
function findAnimation(character, name, direction, animationId) {
|
|
2628
2653
|
for (const group of character.animations) {
|
|
2629
2654
|
const match = group.directions.find((d) => d.direction === direction && d.frames.length > 0);
|
|
@@ -4554,6 +4579,18 @@ var FakeProvider = class {
|
|
|
4554
4579
|
if (!character) throw new Error(`fake provider has no character ${id}`);
|
|
4555
4580
|
return character;
|
|
4556
4581
|
}
|
|
4582
|
+
/** Characters answer with whatever the store holds now; other objects keep their URLs. */
|
|
4583
|
+
async refreshSources(objectId, context) {
|
|
4584
|
+
if (context.generator !== "character") return void 0;
|
|
4585
|
+
const [characterId, groupId] = objectId.split("#");
|
|
4586
|
+
const character = this.characters.get(characterId);
|
|
4587
|
+
if (!character) return null;
|
|
4588
|
+
if (!groupId) return character.rotations;
|
|
4589
|
+
const direction = context.metadata?.character?.direction ?? "south";
|
|
4590
|
+
const animation = character.animations.find((a) => a.groupId === groupId && a.direction === direction);
|
|
4591
|
+
if (!animation) return null;
|
|
4592
|
+
return animation.frames.map((url, index) => ({ url, role: `frame-${String(index).padStart(2, "0")}` }));
|
|
4593
|
+
}
|
|
4557
4594
|
async delete(assetId) {
|
|
4558
4595
|
this.deleted.push(assetId);
|
|
4559
4596
|
this.assets.delete(assetId);
|
|
@@ -8232,7 +8269,40 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8232
8269
|
result.skipped++;
|
|
8233
8270
|
continue;
|
|
8234
8271
|
}
|
|
8235
|
-
const
|
|
8272
|
+
const recordedSources = entry.sourceUrls?.length ? entry.sourceUrls : entry.sourceUrl ? [{ url: entry.sourceUrl }] : opts.repair && entry.outputs.length ? entry.outputs.map((o) => ({ url: "", role: o.role, mediaType: o.mediaType })) : [];
|
|
8273
|
+
let sources = recordedSources;
|
|
8274
|
+
let rotated = false;
|
|
8275
|
+
if (opts.refresh && entry.status === "downloaded" && entry.objectId && provider.refreshSources) {
|
|
8276
|
+
let current;
|
|
8277
|
+
try {
|
|
8278
|
+
current = await provider.refreshSources(entry.objectId, {
|
|
8279
|
+
generator: entry.generator,
|
|
8280
|
+
metadata: entry.providerMetadata?.[provider.id],
|
|
8281
|
+
sources: recordedSources
|
|
8282
|
+
});
|
|
8283
|
+
} catch (err) {
|
|
8284
|
+
const message6 = err instanceof Error ? err.message : String(err);
|
|
8285
|
+
upsert(lock, key, { status: "download-failed", error: `download failed: ${message6}` });
|
|
8286
|
+
result.failed++;
|
|
8287
|
+
log(` FAILED ${key}: ${message6}`);
|
|
8288
|
+
await saveLock(lockPath, lock);
|
|
8289
|
+
continue;
|
|
8290
|
+
}
|
|
8291
|
+
if (current === null) {
|
|
8292
|
+
upsert(lock, key, { status: "download-failed", error: `download failed: ${entry.objectId} no longer exists upstream` });
|
|
8293
|
+
result.failed++;
|
|
8294
|
+
log(` FAILED ${key}: ${entry.objectId} no longer exists upstream`);
|
|
8295
|
+
await saveLock(lockPath, lock);
|
|
8296
|
+
continue;
|
|
8297
|
+
}
|
|
8298
|
+
if (current) {
|
|
8299
|
+
sources = current.map((source) => ({
|
|
8300
|
+
...source,
|
|
8301
|
+
mediaType: source.mediaType ?? recordedSources.find((r) => r.role === source.role)?.mediaType
|
|
8302
|
+
}));
|
|
8303
|
+
rotated = sources.length !== recordedSources.length || sources.some((source, index) => source.url !== recordedSources[index]?.url || source.role !== recordedSources[index]?.role);
|
|
8304
|
+
}
|
|
8305
|
+
}
|
|
8236
8306
|
if (!sources.length) {
|
|
8237
8307
|
upsert(lock, key, {
|
|
8238
8308
|
status: "download-failed",
|
|
@@ -8361,6 +8431,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8361
8431
|
log(` wrote ${import_node_path17.default.relative(process.cwd(), target)}`);
|
|
8362
8432
|
}
|
|
8363
8433
|
if (opts.refresh && !wrote) {
|
|
8434
|
+
if (rotated) {
|
|
8435
|
+
const persistent = sources.filter((source) => shouldPersistSourceUrl(source.url));
|
|
8436
|
+
upsert(lock, key, { sourceUrl: persistent[0]?.url ?? null, sourceUrls: persistent });
|
|
8437
|
+
}
|
|
8364
8438
|
result.unchanged = (result.unchanged ?? 0) + 1;
|
|
8365
8439
|
continue;
|
|
8366
8440
|
}
|
|
@@ -11509,6 +11583,32 @@ async function samePixels(a, b) {
|
|
|
11509
11583
|
}
|
|
11510
11584
|
}
|
|
11511
11585
|
var metadataFps = (entry) => entry ? frameSetFps(entry) : null;
|
|
11586
|
+
function describeCharacter(spec, entry) {
|
|
11587
|
+
const recorded = entry?.providerMetadata?.[entry.provider]?.character;
|
|
11588
|
+
if (spec?.character) {
|
|
11589
|
+
const character = spec.character;
|
|
11590
|
+
return {
|
|
11591
|
+
kind: character.kind,
|
|
11592
|
+
parentKey: character.parentAssetId ? lockKey(spec.styleId, character.parentAssetId) : null,
|
|
11593
|
+
mode: character.kind === "animation" ? character.animation.mode : character.mode,
|
|
11594
|
+
directions: character.directions,
|
|
11595
|
+
direction: character.animation?.direction ?? null,
|
|
11596
|
+
characterId: recorded?.characterId ?? entry?.objectId?.split("#")[0] ?? null
|
|
11597
|
+
};
|
|
11598
|
+
}
|
|
11599
|
+
if (entry?.generator === "character") {
|
|
11600
|
+
const kind = recorded?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
11601
|
+
return {
|
|
11602
|
+
kind,
|
|
11603
|
+
parentKey: null,
|
|
11604
|
+
mode: recorded?.mode ?? "standard",
|
|
11605
|
+
directions: recorded?.directions ?? entry.outputs.length,
|
|
11606
|
+
direction: recorded?.direction ?? null,
|
|
11607
|
+
characterId: recorded?.characterId ?? entry.objectId?.split("#")[0] ?? null
|
|
11608
|
+
};
|
|
11609
|
+
}
|
|
11610
|
+
return null;
|
|
11611
|
+
}
|
|
11512
11612
|
async function fileInfo(absolutePath) {
|
|
11513
11613
|
try {
|
|
11514
11614
|
const info = await (0, import_promises20.stat)(absolutePath);
|
|
@@ -11717,6 +11817,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11717
11817
|
currentSpecHash: spec.specHash,
|
|
11718
11818
|
revision: entry?.revision ?? null,
|
|
11719
11819
|
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
11820
|
+
character: describeCharacter(spec, entry),
|
|
11720
11821
|
outputs,
|
|
11721
11822
|
quality,
|
|
11722
11823
|
providerMetadata: entry?.providerMetadata ?? {},
|
|
@@ -11778,6 +11879,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11778
11879
|
currentSpecHash: null,
|
|
11779
11880
|
revision: entry.revision,
|
|
11780
11881
|
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
11882
|
+
character: describeCharacter(void 0, entry),
|
|
11781
11883
|
outputs,
|
|
11782
11884
|
quality: null,
|
|
11783
11885
|
providerMetadata: entry.providerMetadata ?? {},
|
|
@@ -11834,6 +11936,11 @@ async function buildGallerySnapshot(opts) {
|
|
|
11834
11936
|
view: style?.view ?? null,
|
|
11835
11937
|
noBackground: style?.noBackground ?? true,
|
|
11836
11938
|
quality: Boolean(style?.quality),
|
|
11939
|
+
characters: style?.generator === "character" ? {
|
|
11940
|
+
bases: styleItems.filter((item) => item.character?.kind === "base").length,
|
|
11941
|
+
states: styleItems.filter((item) => item.character?.kind === "state").length,
|
|
11942
|
+
animations: styleItems.filter((item) => item.character?.kind === "animation").length
|
|
11943
|
+
} : null,
|
|
11837
11944
|
tags: style?.tags ?? [],
|
|
11838
11945
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
11839
11946
|
ownFields: rawStyle ? Object.keys(rawStyle).filter((key) => key !== "extends") : [],
|