@engine-room/after-effects-mcp 0.3.1 → 0.4.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/README.md +7 -5
- package/bin/server.js +1379 -196
- package/package.json +1 -1
- package/panel/CSXS/manifest.xml +2 -2
- package/panel/client/contactsheet.js +347 -0
- package/panel/client/framereader.js +248 -0
- package/panel/client/main.js +400 -95
- package/panel/client/pngcodec.js +122 -0
- package/panel/jsx/bundle.jsx +2444 -116
- package/panel/package.json +1 -1
package/panel/client/pngcodec.js
CHANGED
|
@@ -77,6 +77,120 @@ function crc32(bytes) {
|
|
|
77
77
|
return (c ^ 0xffffffff) >>> 0;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// ---------- Structural completeness ----------
|
|
81
|
+
/**
|
|
82
|
+
* Is this byte range a whole PNG file yet?
|
|
83
|
+
*
|
|
84
|
+
* Answered by walking the chunk table alone — no inflate, no pixels — because
|
|
85
|
+
* the caller is asking about a file After Effects may still be writing, and the
|
|
86
|
+
* question has to be cheap enough to ask repeatedly.
|
|
87
|
+
*
|
|
88
|
+
* It exists because "the file stopped growing" was never the same statement as
|
|
89
|
+
* "the file is finished". `saveFrameToPng` returns before the bytes are on disk,
|
|
90
|
+
* and the panel used to accept a frame the moment two `stat` calls 30ms apart
|
|
91
|
+
* reported the same size. A writer that pauses for 30ms — which on a heavy comp
|
|
92
|
+
* it routinely does — was read mid-flight and shipped, and the agent got
|
|
93
|
+
* `truncated PNG: chunk IDAT runs past the end of the file` for a render that
|
|
94
|
+
* was merely still happening. Issue #45.
|
|
95
|
+
*
|
|
96
|
+
* `growable` matters as much as `complete`: it separates "not finished yet,
|
|
97
|
+
* keep waiting" from "no number of further bytes can make this a PNG, stop
|
|
98
|
+
* now". A truncated chunk is the first; a wrong signature or a missing IHDR is
|
|
99
|
+
* the second, and waiting out a 120s render budget for one of those only turns
|
|
100
|
+
* a precise diagnosis into a timeout.
|
|
101
|
+
*
|
|
102
|
+
* Returns { complete, growable, reason, bytes, lastChunk, trailingBytes }.
|
|
103
|
+
*/
|
|
104
|
+
function inspectPngStructure(buf) {
|
|
105
|
+
var n = buf.length;
|
|
106
|
+
var prefix = n < SIGNATURE.length ? n : SIGNATURE.length;
|
|
107
|
+
for (var i = 0; i < prefix; i++) {
|
|
108
|
+
if (buf[i] !== SIGNATURE[i]) {
|
|
109
|
+
return {
|
|
110
|
+
complete: false,
|
|
111
|
+
growable: false,
|
|
112
|
+
bytes: n,
|
|
113
|
+
reason: "not a PNG: signature mismatch at byte " + i,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (n < SIGNATURE.length) {
|
|
118
|
+
return {
|
|
119
|
+
complete: false,
|
|
120
|
+
growable: true,
|
|
121
|
+
bytes: n,
|
|
122
|
+
reason: "only " + n + " of the 8 signature bytes are on disk",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var pos = SIGNATURE.length;
|
|
127
|
+
var first = true;
|
|
128
|
+
while (true) {
|
|
129
|
+
if (pos + 8 > n) {
|
|
130
|
+
return {
|
|
131
|
+
complete: false,
|
|
132
|
+
growable: true,
|
|
133
|
+
bytes: n,
|
|
134
|
+
reason: "the chunk header at byte " + pos + " is incomplete",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
var len = buf.readUInt32BE(pos);
|
|
138
|
+
var type = buf.toString("ascii", pos + 4, pos + 8);
|
|
139
|
+
if (!/^[A-Za-z]{4}$/.test(type)) {
|
|
140
|
+
return {
|
|
141
|
+
complete: false,
|
|
142
|
+
growable: false,
|
|
143
|
+
bytes: n,
|
|
144
|
+
reason: "the chunk type at byte " + pos + " is not four letters",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// The format caps a chunk at 2^31-1. A larger figure is garbage, not a
|
|
148
|
+
// chunk still arriving, and calling it growable would spend the whole
|
|
149
|
+
// render budget waiting for a file that is already wrong.
|
|
150
|
+
if (len > 0x7fffffff) {
|
|
151
|
+
return {
|
|
152
|
+
complete: false,
|
|
153
|
+
growable: false,
|
|
154
|
+
bytes: n,
|
|
155
|
+
lastChunk: type,
|
|
156
|
+
reason: "chunk " + type + " declares " + len + " bytes, past the format's limit",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (first && type !== "IHDR") {
|
|
160
|
+
return {
|
|
161
|
+
complete: false,
|
|
162
|
+
growable: false,
|
|
163
|
+
bytes: n,
|
|
164
|
+
lastChunk: type,
|
|
165
|
+
reason: "malformed PNG: the first chunk is " + type + ", not IHDR",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
first = false;
|
|
169
|
+
var end = pos + 8 + len + 4;
|
|
170
|
+
if (end > n) {
|
|
171
|
+
return {
|
|
172
|
+
complete: false,
|
|
173
|
+
growable: true,
|
|
174
|
+
bytes: n,
|
|
175
|
+
lastChunk: type,
|
|
176
|
+
reason: "chunk " + type + " declares " + len + " bytes and " +
|
|
177
|
+
Math.max(0, n - pos - 8) + " of them are on disk",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (type === "IEND") {
|
|
181
|
+
return {
|
|
182
|
+
complete: true,
|
|
183
|
+
growable: false,
|
|
184
|
+
bytes: n,
|
|
185
|
+
lastChunk: "IEND",
|
|
186
|
+
reason: null,
|
|
187
|
+
trailingBytes: n - end,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
pos = end;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
80
194
|
// ---------- Reading ----------
|
|
81
195
|
function readChunks(buf) {
|
|
82
196
|
if (buf.length < 8) throw new Error("not a PNG: only " + buf.length + " bytes");
|
|
@@ -260,6 +374,7 @@ function everyPixelTransparent(data8, channels, alphaIndex, pixelCount) {
|
|
|
260
374
|
* width/height from IHDR, so they can never disagree with the pixels
|
|
261
375
|
* bitDepth of the *source*, before any conversion
|
|
262
376
|
* converted true when the bytes were re-encoded from 16-bit
|
|
377
|
+
* channels samples per pixel, for a caller that composites hashInput
|
|
263
378
|
* decoded true when the pixels were actually interpreted
|
|
264
379
|
* empty true when every pixel is fully transparent
|
|
265
380
|
* hashInput bytes to hash for the stale-render check
|
|
@@ -291,6 +406,9 @@ function normalizePng(buf) {
|
|
|
291
406
|
height: h.height,
|
|
292
407
|
bitDepth: h.bitDepth,
|
|
293
408
|
colorType: h.colorType,
|
|
409
|
+
// Samples per pixel, so a caller compositing `hashInput` into a contact
|
|
410
|
+
// sheet does not have to re-derive it from the colour type.
|
|
411
|
+
channels: channels,
|
|
294
412
|
converted: false,
|
|
295
413
|
decoded: false,
|
|
296
414
|
empty: false,
|
|
@@ -393,6 +511,10 @@ function decodePng8(buf) {
|
|
|
393
511
|
|
|
394
512
|
module.exports = {
|
|
395
513
|
normalizePng: normalizePng,
|
|
514
|
+
// For main.js, which has to know whether the file After Effects is writing is
|
|
515
|
+
// finished before it reads it. It lives here because the chunk walk and the
|
|
516
|
+
// signature it walks from are already defined once, above.
|
|
517
|
+
inspectPngStructure: inspectPngStructure,
|
|
396
518
|
// For mogrt.js, which resamples a rendered frame into a template's thumbnail
|
|
397
519
|
// and needs the same decoder, encoder and CRC rather than a second copy of
|
|
398
520
|
// each. The zip format's CRC-32 is the one PNG uses, bit for bit.
|