feat(nitro): normalise hotel and uploaded bundles to WebP Lossless
Importing from a hotel wrote the downloaded .nitro to disk untouched, so official PNG textures stayed PNG and only SWF imports ended up as WebP. Every Studio import should produce the same format regardless of where the bytes came from, so both clone and upload paths now run the bundle through toWebpLosslessBundle. The helper decodes the texture and re-encodes it with the same VP8L options the SWF importer uses, so the artwork round-trips bit-for-bit, and lets createNitroBundle relabel the member and repair the meta.image pointer. A bundle that is already lossless WebP is returned untouched, making the operation idempotent and safe to run on re-import. A colour variant that shares a library keeps the member base name it arrived with. A texture that cannot be decoded keeps its original format with a warning instead of failing the import: the bundle is valid, and losing a furniture item over a codec edge case is worse than a slightly larger texture.
This commit is contained in:
1 parent
306e209e29
commit
b26e2e0de4
5 files changed
+174
-36
No files matched your search
@@ -503,3 +503,44 @@ function inflateJsonOrGzip(
|
||||
return gunzipSync(input, options);
|
||||
}
|
||||
}
|
||||
|
||||
// ── WebP Lossless Normalisation ────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Return the bundle with its texture stored as WebP Lossless (VP8L).
|
||||
*
|
||||
* Studio imports should all look the same regardless of where the bytes
|
||||
* came from: a hotel serves PNG, a hand-made bundle may carry anything, and
|
||||
* an SWF import already produces WebP. Re-encoding here means one code path
|
||||
* decides the format instead of each importer deciding on its own.
|
||||
*
|
||||
* The texture is decoded and re-encoded with `encodeWebpLossless`, so it
|
||||
* round-trips bit-for-bit, and the member keeps the base name it arrived
|
||||
* with — a `chair*2` colour variant sharing the `chair` library must not be
|
||||
* renamed. `meta.image` is corrected by `createNitroBundle`.
|
||||
*
|
||||
* A bundle that is already lossless WebP is returned untouched, which keeps
|
||||
* the operation idempotent and avoids a pointless re-encode.
|
||||
*
|
||||
* Throws if the texture claims to be PNG or WebP but cannot actually be
|
||||
* decoded; callers that would rather keep the original should catch.
|
||||
*/
|
||||
export async function toWebpLosslessBundle(bundle: Buffer): Promise<Buffer> {
|
||||
const parsed = parseNitroBundle(bundle);
|
||||
if (isLosslessWebp(parsed.texture)) return bundle;
|
||||
|
||||
const image = sharp(parsed.texture);
|
||||
const { width = 0, height = 0 } = await image.metadata();
|
||||
if (!width || !height)
|
||||
throw new Error(
|
||||
`Nitro texture "${parsed.textureFileName}" has no readable dimensions`,
|
||||
);
|
||||
|
||||
const webp = await encodeWebpLossless(
|
||||
width,
|
||||
height,
|
||||
await image.raw().ensureAlpha().toBuffer(),
|
||||
);
|
||||
const memberBase = parsed.textureFileName.replace(/\.(?:png|webp)$/i, "");
|
||||
return createNitroBundle(parsed.json, webp, memberBase);
|
||||
}
|
||||
Reference in new issue
Block a user