@leviosa-ai/detail-page-editor 0.6.2 → 0.6.3
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.
|
@@ -45,7 +45,7 @@ export const MAX_GIF_WIDTH = 512;
|
|
|
45
45
|
/**
|
|
46
46
|
* MP4 gets a wider cap than GIF/WebP. That 512 is a *transfer* limit: GIF pays
|
|
47
47
|
* for every pixel in a palette-coded frame, and WebP frames travel to the server
|
|
48
|
-
*
|
|
48
|
+
* one by one under an upload cap. MP4 is encoded right here by a hardware H.264
|
|
49
49
|
* encoder that eats resolution cheaply, so holding it at 512 would only make the
|
|
50
50
|
* video soft on a ~860px-wide detail page for no gain.
|
|
51
51
|
*/
|
|
@@ -131,12 +131,35 @@ function buildGlobalPalette(frames: HTMLCanvasElement[]): number[][] {
|
|
|
131
131
|
/** Output format for an animated section. WebP is the default. */
|
|
132
132
|
export type AnimationFormat = "webp" | "gif" | "mp4";
|
|
133
133
|
|
|
134
|
-
/**
|
|
135
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Wire format for the frames that travel to the server for WebP encoding.
|
|
136
|
+
*
|
|
137
|
+
* They used to go as lossless PNG. That is what a section costs when nothing is
|
|
138
|
+
* thrown away: a 512×2644 composed frame is ~1.4MB as PNG, and a section is up
|
|
139
|
+
* to ``GIF_MAX_FRAMES`` of them — ~56MB of request body, which no edge in front
|
|
140
|
+
* of the API accepts (nginx answers 413 before the app sees a byte).
|
|
141
|
+
*
|
|
142
|
+
* The same frame is ~220KB as quality-92 WebP. Nothing visible is lost on the
|
|
143
|
+
* way: the server's animated WebP is itself encoded at quality 80, so a
|
|
144
|
+
* near-lossless intermediate is already below the floor the output sits on.
|
|
145
|
+
*/
|
|
146
|
+
const FRAME_WIRE_TYPE = "image/webp";
|
|
147
|
+
const FRAME_WIRE_QUALITY = 0.92;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Turn a canvas into the Blob that goes over the wire.
|
|
151
|
+
*
|
|
152
|
+
* A browser that cannot encode WebP falls back to PNG on its own — ``toBlob``
|
|
153
|
+
* is specified to use ``image/png`` for a type it does not support — and the
|
|
154
|
+
* server sniffs the bytes rather than trusting the name, so that path still
|
|
155
|
+
* encodes. It just pays the old size.
|
|
156
|
+
*/
|
|
157
|
+
export function canvasToFrameBlob(canvas: HTMLCanvasElement): Promise<Blob> {
|
|
136
158
|
return new Promise((resolve, reject) => {
|
|
137
159
|
canvas.toBlob(
|
|
138
160
|
(blob) => (blob ? resolve(blob) : reject(new Error("canvas toBlob failed"))),
|
|
139
|
-
|
|
161
|
+
FRAME_WIRE_TYPE,
|
|
162
|
+
FRAME_WIRE_QUALITY,
|
|
140
163
|
);
|
|
141
164
|
});
|
|
142
165
|
}
|
|
@@ -161,8 +184,8 @@ function encodeFramesAsGif(frames: HTMLCanvasElement[], delayMs: number): Blob {
|
|
|
161
184
|
* Encode composed frames as an animated WebP.
|
|
162
185
|
*
|
|
163
186
|
* The round trip is not an optimisation choice — no browser can encode animated
|
|
164
|
-
* WebP, so the frames have to leave the page.
|
|
165
|
-
*
|
|
187
|
+
* WebP, so the frames have to leave the page. See ``FRAME_WIRE_TYPE`` for why
|
|
188
|
+
* they leave as WebP rather than as the PNG they are composed in.
|
|
166
189
|
*/
|
|
167
190
|
async function encodeFramesAsWebp(
|
|
168
191
|
host: DetailPageHost,
|
|
@@ -170,7 +193,7 @@ async function encodeFramesAsWebp(
|
|
|
170
193
|
delayMs: number,
|
|
171
194
|
signal?: AbortSignal,
|
|
172
195
|
): Promise<Blob> {
|
|
173
|
-
const payload = await Promise.all(frames.map(
|
|
196
|
+
const payload = await Promise.all(frames.map(canvasToFrameBlob));
|
|
174
197
|
const fps = Math.max(1, Math.round(1000 / Math.max(1, delayMs)));
|
|
175
198
|
return host.api.encodeDetailPageAnimation(payload, { fps, format: "webp" }, signal);
|
|
176
199
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leviosa-ai/detail-page-editor",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|