ae-agent-setup 0.2.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/CSXS/manifest.xml +44 -0
- package/README.ja.md +249 -0
- package/README.md +249 -0
- package/bin/ae-agent-setup.mjs +337 -0
- package/client/CSInterface.js +1291 -0
- package/client/index.html +64 -0
- package/client/lib/bridge_utils.js +56 -0
- package/client/lib/logging.js +10 -0
- package/client/lib/request_handlers.js +468 -0
- package/client/lib/request_handlers_essential.js +35 -0
- package/client/lib/request_handlers_layer_structure.js +180 -0
- package/client/lib/request_handlers_scene.js +38 -0
- package/client/lib/request_handlers_shape.js +288 -0
- package/client/lib/request_handlers_timeline.js +115 -0
- package/client/lib/runtime.js +35 -0
- package/client/lib/server.js +33 -0
- package/client/main.js +1 -0
- package/host/index.jsx +11 -0
- package/host/json2.js +504 -0
- package/host/lib/common.jsx +128 -0
- package/host/lib/mutation_handlers.jsx +358 -0
- package/host/lib/mutation_keyframe_handlers.jsx +265 -0
- package/host/lib/mutation_layer_structure_handlers.jsx +235 -0
- package/host/lib/mutation_scene_handlers.jsx +1226 -0
- package/host/lib/mutation_shape_handlers.jsx +358 -0
- package/host/lib/mutation_timeline_handlers.jsx +137 -0
- package/host/lib/property_utils.jsx +105 -0
- package/host/lib/query_handlers.jsx +427 -0
- package/package.json +21 -0
- package/scripts/signing/build-zxp.sh +56 -0
- package/scripts/signing/create-dev-cert.sh +97 -0
- package/scripts/signing/install-zxp.sh +29 -0
- package/templates/skills/aftereffects-cli.SKILL.md +74 -0
- package/templates/skills/aftereffects-declarative.SKILL.md +112 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
function handleParentLayer(req, res) {
|
|
2
|
+
readJsonBody(req, res, ({ childLayerId, parentLayerId }) => {
|
|
3
|
+
if (!childLayerId) {
|
|
4
|
+
sendBadRequest(res, 'childLayerId is required');
|
|
5
|
+
log('parentLayer failed: missing childLayerId');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (parentLayerId !== undefined && parentLayerId !== null && typeof parentLayerId !== 'number') {
|
|
9
|
+
sendBadRequest(res, 'parentLayerId must be a number when specified');
|
|
10
|
+
log('parentLayer failed: invalid parentLayerId');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const parentLiteral = parentLayerId === undefined || parentLayerId === null
|
|
15
|
+
? 'null'
|
|
16
|
+
: String(parentLayerId);
|
|
17
|
+
const script = `parentLayer(${childLayerId}, ${parentLiteral})`;
|
|
18
|
+
handleBridgeMutationCall(script, res, 'parentLayer()', 'Failed to set parent layer');
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function handlePrecompose(req, res) {
|
|
23
|
+
readJsonBody(req, res, ({ layerIds, name, moveAllAttributes }) => {
|
|
24
|
+
if (!Array.isArray(layerIds) || layerIds.length === 0 || !name || typeof name !== 'string') {
|
|
25
|
+
sendBadRequest(res, 'layerIds (non-empty array) and name (string) are required');
|
|
26
|
+
log('precompose failed: invalid layerIds or name');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const allNumbers = layerIds.every((id) => typeof id === 'number');
|
|
30
|
+
if (!allNumbers) {
|
|
31
|
+
sendBadRequest(res, 'layerIds must be an array of numbers');
|
|
32
|
+
log('precompose failed: invalid layerIds values');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (moveAllAttributes !== undefined && typeof moveAllAttributes !== 'boolean') {
|
|
36
|
+
sendBadRequest(res, 'moveAllAttributes must be boolean when specified');
|
|
37
|
+
log('precompose failed: invalid moveAllAttributes');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const layerIdsLiteral = toExtendScriptStringLiteral(JSON.stringify(layerIds));
|
|
42
|
+
const nameLiteral = toExtendScriptStringLiteral(name);
|
|
43
|
+
const moveLiteral = moveAllAttributes === undefined ? 'false' : String(moveAllAttributes);
|
|
44
|
+
const script = `precomposeLayers(${layerIdsLiteral}, ${nameLiteral}, ${moveLiteral})`;
|
|
45
|
+
handleBridgeMutationCall(script, res, 'precomposeLayers()', 'Failed to precompose layers');
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function handleDuplicateLayer(req, res) {
|
|
50
|
+
readJsonBody(req, res, ({ layerId }) => {
|
|
51
|
+
if (!layerId) {
|
|
52
|
+
sendBadRequest(res, 'layerId is required');
|
|
53
|
+
log('duplicateLayer failed: missing layerId');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const script = `duplicateLayer(${layerId})`;
|
|
57
|
+
handleBridgeMutationCall(script, res, 'duplicateLayer()', 'Failed to duplicate layer');
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleMoveLayerOrder(req, res) {
|
|
62
|
+
readJsonBody(req, res, ({ layerId, beforeLayerId, afterLayerId, toTop, toBottom }) => {
|
|
63
|
+
if (!layerId) {
|
|
64
|
+
sendBadRequest(res, 'layerId is required');
|
|
65
|
+
log('moveLayerOrder failed: missing layerId');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let specified = 0;
|
|
70
|
+
if (beforeLayerId !== undefined) specified += 1;
|
|
71
|
+
if (afterLayerId !== undefined) specified += 1;
|
|
72
|
+
if (toTop === true) specified += 1;
|
|
73
|
+
if (toBottom === true) specified += 1;
|
|
74
|
+
if (specified !== 1) {
|
|
75
|
+
sendBadRequest(res, 'Specify exactly one of beforeLayerId, afterLayerId, toTop, toBottom');
|
|
76
|
+
log('moveLayerOrder failed: invalid target selector');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (beforeLayerId !== undefined && typeof beforeLayerId !== 'number') {
|
|
80
|
+
sendBadRequest(res, 'beforeLayerId must be a number');
|
|
81
|
+
log('moveLayerOrder failed: invalid beforeLayerId');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (afterLayerId !== undefined && typeof afterLayerId !== 'number') {
|
|
85
|
+
sendBadRequest(res, 'afterLayerId must be a number');
|
|
86
|
+
log('moveLayerOrder failed: invalid afterLayerId');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (toTop !== undefined && typeof toTop !== 'boolean') {
|
|
90
|
+
sendBadRequest(res, 'toTop must be boolean when specified');
|
|
91
|
+
log('moveLayerOrder failed: invalid toTop');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (toBottom !== undefined && typeof toBottom !== 'boolean') {
|
|
95
|
+
sendBadRequest(res, 'toBottom must be boolean when specified');
|
|
96
|
+
log('moveLayerOrder failed: invalid toBottom');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const beforeLiteral = beforeLayerId === undefined ? 'null' : String(beforeLayerId);
|
|
101
|
+
const afterLiteral = afterLayerId === undefined ? 'null' : String(afterLayerId);
|
|
102
|
+
const topLiteral = toTop === true ? 'true' : 'false';
|
|
103
|
+
const bottomLiteral = toBottom === true ? 'true' : 'false';
|
|
104
|
+
const script = `moveLayerOrder(${layerId}, ${beforeLiteral}, ${afterLiteral}, ${topLiteral}, ${bottomLiteral})`;
|
|
105
|
+
handleBridgeMutationCall(script, res, 'moveLayerOrder()', 'Failed to move layer order');
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleDeleteLayer(req, res) {
|
|
110
|
+
readJsonBody(req, res, ({ layerId }) => {
|
|
111
|
+
if (!layerId) {
|
|
112
|
+
sendBadRequest(res, 'layerId is required');
|
|
113
|
+
log('deleteLayer failed: missing layerId');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (typeof layerId !== 'number') {
|
|
117
|
+
sendBadRequest(res, 'layerId must be a number');
|
|
118
|
+
log('deleteLayer failed: invalid layerId');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const script = `deleteLayer(${layerId})`;
|
|
123
|
+
handleBridgeMutationCall(script, res, 'deleteLayer()', 'Failed to delete layer');
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleDeleteComp(req, res) {
|
|
128
|
+
readJsonBody(req, res, ({ compId, compName }) => {
|
|
129
|
+
const hasCompId = compId !== undefined;
|
|
130
|
+
const hasCompName = compName !== undefined && compName !== null && compName !== '';
|
|
131
|
+
if ((hasCompId && hasCompName) || (!hasCompId && !hasCompName)) {
|
|
132
|
+
sendBadRequest(res, 'Provide exactly one of compId or compName');
|
|
133
|
+
log('deleteComp failed: invalid selector');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (hasCompId && typeof compId !== 'number') {
|
|
137
|
+
sendBadRequest(res, 'compId must be a number');
|
|
138
|
+
log('deleteComp failed: compId must be number');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (hasCompName && typeof compName !== 'string') {
|
|
142
|
+
sendBadRequest(res, 'compName must be a string');
|
|
143
|
+
log('deleteComp failed: compName must be string');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const compIdLiteral = hasCompId ? String(compId) : 'null';
|
|
148
|
+
const compNameLiteral = hasCompName ? toExtendScriptStringLiteral(compName) : 'null';
|
|
149
|
+
const script = `deleteComp(${compIdLiteral}, ${compNameLiteral})`;
|
|
150
|
+
handleBridgeMutationCall(script, res, 'deleteComp()', 'Failed to delete comp');
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function routeLayerStructureRequest(pathname, method, req, res) {
|
|
155
|
+
if (pathname === '/layer-parent' && method === 'POST') {
|
|
156
|
+
handleParentLayer(req, res);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (pathname === '/precompose' && method === 'POST') {
|
|
160
|
+
handlePrecompose(req, res);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (pathname === '/duplicate-layer' && method === 'POST') {
|
|
164
|
+
handleDuplicateLayer(req, res);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (pathname === '/layer-order' && method === 'POST') {
|
|
168
|
+
handleMoveLayerOrder(req, res);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
if (pathname === '/delete-layer' && method === 'POST') {
|
|
172
|
+
handleDeleteLayer(req, res);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
if (pathname === '/delete-comp' && method === 'POST') {
|
|
176
|
+
handleDeleteComp(req, res);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function handleApplyScene(req, res) {
|
|
2
|
+
readJsonBody(req, res, ({ scene, validateOnly, mode }) => {
|
|
3
|
+
if (!scene || typeof scene !== 'object' || Array.isArray(scene)) {
|
|
4
|
+
sendBadRequest(res, 'scene is required and must be an object');
|
|
5
|
+
log('applyScene failed: invalid scene');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (validateOnly !== undefined && typeof validateOnly !== 'boolean') {
|
|
9
|
+
sendBadRequest(res, 'validateOnly must be a boolean when specified');
|
|
10
|
+
log('applyScene failed: invalid validateOnly');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const normalizedMode = mode === undefined ? 'merge' : String(mode);
|
|
14
|
+
if (!['merge', 'replace-managed', 'clear-all'].includes(normalizedMode)) {
|
|
15
|
+
sendBadRequest(res, 'mode must be one of: merge, replace-managed, clear-all');
|
|
16
|
+
log('applyScene failed: invalid mode');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const sceneLiteral = toExtendScriptStringLiteral(JSON.stringify(scene));
|
|
21
|
+
const optionsLiteral = toExtendScriptStringLiteral(
|
|
22
|
+
JSON.stringify({
|
|
23
|
+
validateOnly: validateOnly === true,
|
|
24
|
+
mode: normalizedMode,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
const script = `applyScene(${sceneLiteral}, ${optionsLiteral})`;
|
|
28
|
+
handleBridgeMutationCall(script, res, 'applyScene()', 'Failed to apply scene');
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function routeSceneRequest(pathname, method, req, res) {
|
|
33
|
+
if (pathname === '/scene' && method === 'POST') {
|
|
34
|
+
handleApplyScene(req, res);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
function handleAddShapeRepeater(req, res) {
|
|
2
|
+
readJsonBody(
|
|
3
|
+
req,
|
|
4
|
+
res,
|
|
5
|
+
({ layerId, layerName, groupIndex, name, copies, offset, position, scale, rotation, startOpacity, endOpacity }) => {
|
|
6
|
+
const selector = normalizeLayerSelector(layerId, layerName);
|
|
7
|
+
if (!selector.ok) {
|
|
8
|
+
sendBadRequest(res, selector.error);
|
|
9
|
+
log(`addShapeRepeater failed: ${selector.error}`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (groupIndex !== undefined && (!Number.isInteger(groupIndex) || groupIndex <= 0)) {
|
|
13
|
+
sendBadRequest(res, 'groupIndex must be a positive integer when specified');
|
|
14
|
+
log('addShapeRepeater failed: invalid groupIndex');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (name !== undefined && typeof name !== 'string') {
|
|
18
|
+
sendBadRequest(res, 'name must be a string when specified');
|
|
19
|
+
log('addShapeRepeater failed: invalid name');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (copies !== undefined && (typeof copies !== 'number' || !isFinite(copies))) {
|
|
23
|
+
sendBadRequest(res, 'copies must be a finite number when specified');
|
|
24
|
+
log('addShapeRepeater failed: invalid copies');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (offset !== undefined && (typeof offset !== 'number' || !isFinite(offset))) {
|
|
28
|
+
sendBadRequest(res, 'offset must be a finite number when specified');
|
|
29
|
+
log('addShapeRepeater failed: invalid offset');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (rotation !== undefined && (typeof rotation !== 'number' || !isFinite(rotation))) {
|
|
33
|
+
sendBadRequest(res, 'rotation must be a finite number when specified');
|
|
34
|
+
log('addShapeRepeater failed: invalid rotation');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (startOpacity !== undefined && (typeof startOpacity !== 'number' || !isFinite(startOpacity))) {
|
|
38
|
+
sendBadRequest(res, 'startOpacity must be a finite number when specified');
|
|
39
|
+
log('addShapeRepeater failed: invalid startOpacity');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (endOpacity !== undefined && (typeof endOpacity !== 'number' || !isFinite(endOpacity))) {
|
|
43
|
+
sendBadRequest(res, 'endOpacity must be a finite number when specified');
|
|
44
|
+
log('addShapeRepeater failed: invalid endOpacity');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isVec2(value) {
|
|
49
|
+
return Array.isArray(value)
|
|
50
|
+
&& value.length === 2
|
|
51
|
+
&& value.every((part) => typeof part === 'number' && isFinite(part));
|
|
52
|
+
}
|
|
53
|
+
if (position !== undefined && !isVec2(position)) {
|
|
54
|
+
sendBadRequest(res, 'position must be an array of 2 numbers when specified');
|
|
55
|
+
log('addShapeRepeater failed: invalid position');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (scale !== undefined && !isVec2(scale)) {
|
|
59
|
+
sendBadRequest(res, 'scale must be an array of 2 numbers when specified');
|
|
60
|
+
log('addShapeRepeater failed: invalid scale');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const options = {};
|
|
65
|
+
if (groupIndex !== undefined) options.groupIndex = groupIndex;
|
|
66
|
+
if (name !== undefined) options.name = name;
|
|
67
|
+
if (copies !== undefined) options.copies = copies;
|
|
68
|
+
if (offset !== undefined) options.offset = offset;
|
|
69
|
+
if (position !== undefined) options.position = position;
|
|
70
|
+
if (scale !== undefined) options.scale = scale;
|
|
71
|
+
if (rotation !== undefined) options.rotation = rotation;
|
|
72
|
+
if (startOpacity !== undefined) options.startOpacity = startOpacity;
|
|
73
|
+
if (endOpacity !== undefined) options.endOpacity = endOpacity;
|
|
74
|
+
|
|
75
|
+
const optionsLiteral = Object.keys(options).length === 0
|
|
76
|
+
? 'null'
|
|
77
|
+
: toExtendScriptStringLiteral(JSON.stringify(options));
|
|
78
|
+
const script = `addShapeRepeater(${selector.layerIdLiteral}, ${selector.layerNameLiteral}, ${optionsLiteral})`;
|
|
79
|
+
handleBridgeMutationCall(script, res, 'addShapeRepeater()', 'Failed to add shape repeater');
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleAddLayer(req, res) {
|
|
85
|
+
readJsonBody(
|
|
86
|
+
req,
|
|
87
|
+
res,
|
|
88
|
+
({
|
|
89
|
+
layerType,
|
|
90
|
+
name,
|
|
91
|
+
text,
|
|
92
|
+
width,
|
|
93
|
+
height,
|
|
94
|
+
color,
|
|
95
|
+
duration,
|
|
96
|
+
shapeType,
|
|
97
|
+
shapeSize,
|
|
98
|
+
shapePosition,
|
|
99
|
+
shapeFillColor,
|
|
100
|
+
shapeFillOpacity,
|
|
101
|
+
shapeStrokeColor,
|
|
102
|
+
shapeStrokeOpacity,
|
|
103
|
+
shapeStrokeWidth,
|
|
104
|
+
shapeStrokeLineCap,
|
|
105
|
+
shapeRoundness,
|
|
106
|
+
}) => {
|
|
107
|
+
if (!layerType || typeof layerType !== 'string') {
|
|
108
|
+
sendBadRequest(res, 'layerType is required and must be a string');
|
|
109
|
+
log('addLayer failed: invalid layerType');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const normalizedType = layerType.toLowerCase();
|
|
114
|
+
if (!['text', 'null', 'solid', 'shape'].includes(normalizedType)) {
|
|
115
|
+
sendBadRequest(res, 'Unsupported layerType. Use one of: text, null, solid, shape.');
|
|
116
|
+
log(`addLayer failed: unsupported layerType "${layerType}"`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (name !== undefined && typeof name !== 'string') {
|
|
120
|
+
sendBadRequest(res, 'name must be a string when specified');
|
|
121
|
+
log('addLayer failed: name must be string');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (text !== undefined && typeof text !== 'string') {
|
|
125
|
+
sendBadRequest(res, 'text must be a string when specified');
|
|
126
|
+
log('addLayer failed: text must be string');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (width !== undefined && typeof width !== 'number') {
|
|
130
|
+
sendBadRequest(res, 'width must be a number when specified');
|
|
131
|
+
log('addLayer failed: width must be number');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (height !== undefined && typeof height !== 'number') {
|
|
135
|
+
sendBadRequest(res, 'height must be a number when specified');
|
|
136
|
+
log('addLayer failed: height must be number');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (duration !== undefined && typeof duration !== 'number') {
|
|
140
|
+
sendBadRequest(res, 'duration must be a number when specified');
|
|
141
|
+
log('addLayer failed: duration must be number');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (color !== undefined) {
|
|
145
|
+
const validColor = Array.isArray(color)
|
|
146
|
+
&& color.length === 3
|
|
147
|
+
&& color.every((part) => typeof part === 'number');
|
|
148
|
+
if (!validColor) {
|
|
149
|
+
sendBadRequest(res, 'color must be an array of 3 numbers when specified');
|
|
150
|
+
log('addLayer failed: color must be [r, g, b]');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (shapeType !== undefined && !['ellipse', 'rect'].includes(shapeType)) {
|
|
155
|
+
sendBadRequest(res, 'shapeType must be one of: ellipse, rect');
|
|
156
|
+
log('addLayer failed: invalid shapeType');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (shapeSize !== undefined) {
|
|
160
|
+
const validShapeSize = Array.isArray(shapeSize)
|
|
161
|
+
&& shapeSize.length === 2
|
|
162
|
+
&& shapeSize.every((part) => typeof part === 'number' && isFinite(part));
|
|
163
|
+
if (!validShapeSize) {
|
|
164
|
+
sendBadRequest(res, 'shapeSize must be an array of 2 numbers when specified');
|
|
165
|
+
log('addLayer failed: invalid shapeSize');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (shapePosition !== undefined) {
|
|
170
|
+
const validShapePosition = Array.isArray(shapePosition)
|
|
171
|
+
&& shapePosition.length === 2
|
|
172
|
+
&& shapePosition.every((part) => typeof part === 'number' && isFinite(part));
|
|
173
|
+
if (!validShapePosition) {
|
|
174
|
+
sendBadRequest(res, 'shapePosition must be an array of 2 numbers when specified');
|
|
175
|
+
log('addLayer failed: invalid shapePosition');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (shapeFillColor !== undefined) {
|
|
180
|
+
const validShapeFillColor = Array.isArray(shapeFillColor)
|
|
181
|
+
&& shapeFillColor.length === 3
|
|
182
|
+
&& shapeFillColor.every((part) => typeof part === 'number' && isFinite(part));
|
|
183
|
+
if (!validShapeFillColor) {
|
|
184
|
+
sendBadRequest(res, 'shapeFillColor must be an array of 3 numbers when specified');
|
|
185
|
+
log('addLayer failed: invalid shapeFillColor');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (shapeFillOpacity !== undefined && (typeof shapeFillOpacity !== 'number' || !isFinite(shapeFillOpacity))) {
|
|
190
|
+
sendBadRequest(res, 'shapeFillOpacity must be a finite number when specified');
|
|
191
|
+
log('addLayer failed: invalid shapeFillOpacity');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (shapeStrokeColor !== undefined) {
|
|
195
|
+
const validShapeStrokeColor = Array.isArray(shapeStrokeColor)
|
|
196
|
+
&& shapeStrokeColor.length === 3
|
|
197
|
+
&& shapeStrokeColor.every((part) => typeof part === 'number' && isFinite(part));
|
|
198
|
+
if (!validShapeStrokeColor) {
|
|
199
|
+
sendBadRequest(res, 'shapeStrokeColor must be an array of 3 numbers when specified');
|
|
200
|
+
log('addLayer failed: invalid shapeStrokeColor');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
shapeStrokeOpacity !== undefined
|
|
206
|
+
&& (typeof shapeStrokeOpacity !== 'number' || !isFinite(shapeStrokeOpacity))
|
|
207
|
+
) {
|
|
208
|
+
sendBadRequest(res, 'shapeStrokeOpacity must be a finite number when specified');
|
|
209
|
+
log('addLayer failed: invalid shapeStrokeOpacity');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (shapeStrokeWidth !== undefined && (typeof shapeStrokeWidth !== 'number' || !isFinite(shapeStrokeWidth))) {
|
|
213
|
+
sendBadRequest(res, 'shapeStrokeWidth must be a finite number when specified');
|
|
214
|
+
log('addLayer failed: invalid shapeStrokeWidth');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (
|
|
218
|
+
shapeStrokeLineCap !== undefined
|
|
219
|
+
&& !['butt', 'round', 'projecting'].includes(shapeStrokeLineCap)
|
|
220
|
+
) {
|
|
221
|
+
sendBadRequest(res, 'shapeStrokeLineCap must be one of: butt, round, projecting');
|
|
222
|
+
log('addLayer failed: invalid shapeStrokeLineCap');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (shapeRoundness !== undefined && (typeof shapeRoundness !== 'number' || !isFinite(shapeRoundness))) {
|
|
226
|
+
sendBadRequest(res, 'shapeRoundness must be a finite number when specified');
|
|
227
|
+
log('addLayer failed: invalid shapeRoundness');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const options = {};
|
|
232
|
+
if (name !== undefined) options.name = name;
|
|
233
|
+
if (text !== undefined) options.text = text;
|
|
234
|
+
if (width !== undefined) options.width = width;
|
|
235
|
+
if (height !== undefined) options.height = height;
|
|
236
|
+
if (color !== undefined) options.color = color;
|
|
237
|
+
if (duration !== undefined) options.duration = duration;
|
|
238
|
+
if (shapeType !== undefined) options.shapeType = shapeType;
|
|
239
|
+
if (shapeSize !== undefined) options.shapeSize = shapeSize;
|
|
240
|
+
if (shapePosition !== undefined) options.shapePosition = shapePosition;
|
|
241
|
+
if (shapeFillColor !== undefined) options.shapeFillColor = shapeFillColor;
|
|
242
|
+
if (shapeFillOpacity !== undefined) options.shapeFillOpacity = shapeFillOpacity;
|
|
243
|
+
if (shapeStrokeColor !== undefined) options.shapeStrokeColor = shapeStrokeColor;
|
|
244
|
+
if (shapeStrokeOpacity !== undefined) options.shapeStrokeOpacity = shapeStrokeOpacity;
|
|
245
|
+
if (shapeStrokeWidth !== undefined) options.shapeStrokeWidth = shapeStrokeWidth;
|
|
246
|
+
if (shapeStrokeLineCap !== undefined) options.shapeStrokeLineCap = shapeStrokeLineCap;
|
|
247
|
+
if (shapeRoundness !== undefined) options.shapeRoundness = shapeRoundness;
|
|
248
|
+
|
|
249
|
+
const layerTypeLiteral = toExtendScriptStringLiteral(normalizedType);
|
|
250
|
+
const optionsLiteral = Object.keys(options).length === 0
|
|
251
|
+
? 'null'
|
|
252
|
+
: toExtendScriptStringLiteral(JSON.stringify(options));
|
|
253
|
+
const script = `addLayer(${layerTypeLiteral}, ${optionsLiteral})`;
|
|
254
|
+
|
|
255
|
+
log(`Calling ExtendScript: ${script}`);
|
|
256
|
+
evalHostScript(script, (result) => {
|
|
257
|
+
try {
|
|
258
|
+
const parsedResult = parseBridgeResult(result);
|
|
259
|
+
if (parsedResult && parsedResult.status === 'error') {
|
|
260
|
+
sendJson(res, 500, {
|
|
261
|
+
status: 'error',
|
|
262
|
+
message: parsedResult.message || 'Failed to add layer',
|
|
263
|
+
});
|
|
264
|
+
log(`addLayer failed: ${parsedResult.message || 'Unknown error'}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
sendJson(res, 200, { status: 'success', data: parsedResult });
|
|
268
|
+
log('addLayer successful.');
|
|
269
|
+
} catch (e) {
|
|
270
|
+
sendBridgeParseError(res, result, e);
|
|
271
|
+
log(`addLayer failed: ${e.toString()}`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function routeShapeRequest(pathname, method, req, res) {
|
|
279
|
+
if (pathname === '/shape-repeater' && method === 'POST') {
|
|
280
|
+
handleAddShapeRepeater(req, res);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
if (pathname === '/layers' && method === 'POST') {
|
|
284
|
+
handleAddLayer(req, res);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
function handleSetInOutPoint(req, res) {
|
|
2
|
+
readJsonBody(req, res, ({ layerId, layerName, inPoint, outPoint }) => {
|
|
3
|
+
if (inPoint === undefined && outPoint === undefined) {
|
|
4
|
+
sendBadRequest(res, 'At least one of inPoint/outPoint is required');
|
|
5
|
+
log('setInOutPoint failed: missing parameters');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const selector = normalizeLayerSelector(layerId, layerName);
|
|
9
|
+
if (!selector.ok) {
|
|
10
|
+
sendBadRequest(res, selector.error);
|
|
11
|
+
log(`setInOutPoint failed: ${selector.error}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (inPoint !== undefined && (typeof inPoint !== 'number' || !isFinite(inPoint))) {
|
|
15
|
+
sendBadRequest(res, 'inPoint must be a finite number when specified');
|
|
16
|
+
log('setInOutPoint failed: invalid inPoint');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (outPoint !== undefined && (typeof outPoint !== 'number' || !isFinite(outPoint))) {
|
|
20
|
+
sendBadRequest(res, 'outPoint must be a finite number when specified');
|
|
21
|
+
log('setInOutPoint failed: invalid outPoint');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const inPointLiteral = inPoint === undefined ? 'null' : String(inPoint);
|
|
26
|
+
const outPointLiteral = outPoint === undefined ? 'null' : String(outPoint);
|
|
27
|
+
const script = `setInOutPoint(${selector.layerIdLiteral}, ${selector.layerNameLiteral}, ${inPointLiteral}, ${outPointLiteral})`;
|
|
28
|
+
handleBridgeMutationCall(script, res, 'setInOutPoint()', 'Failed to set in/out point');
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleMoveLayerTime(req, res) {
|
|
33
|
+
readJsonBody(req, res, ({ layerId, layerName, delta }) => {
|
|
34
|
+
if (delta === undefined) {
|
|
35
|
+
sendBadRequest(res, 'delta is required');
|
|
36
|
+
log('moveLayerTime failed: missing parameters');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const selector = normalizeLayerSelector(layerId, layerName);
|
|
40
|
+
if (!selector.ok) {
|
|
41
|
+
sendBadRequest(res, selector.error);
|
|
42
|
+
log(`moveLayerTime failed: ${selector.error}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (typeof delta !== 'number' || !isFinite(delta)) {
|
|
46
|
+
sendBadRequest(res, 'delta must be a finite number');
|
|
47
|
+
log('moveLayerTime failed: invalid delta');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const script = `moveLayerTime(${selector.layerIdLiteral}, ${selector.layerNameLiteral}, ${delta})`;
|
|
52
|
+
handleBridgeMutationCall(script, res, 'moveLayerTime()', 'Failed to move layer time');
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleSetCti(req, res) {
|
|
57
|
+
readJsonBody(req, res, ({ time }) => {
|
|
58
|
+
if (time === undefined) {
|
|
59
|
+
sendBadRequest(res, 'time is required');
|
|
60
|
+
log('setCTI failed: missing time');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (typeof time !== 'number' || !isFinite(time)) {
|
|
64
|
+
sendBadRequest(res, 'time must be a finite number');
|
|
65
|
+
log('setCTI failed: invalid time');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const script = `setCTI(${time})`;
|
|
70
|
+
handleBridgeMutationCall(script, res, 'setCTI()', 'Failed to set CTI');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleSetWorkArea(req, res) {
|
|
75
|
+
readJsonBody(req, res, ({ start, duration }) => {
|
|
76
|
+
if (start === undefined || duration === undefined) {
|
|
77
|
+
sendBadRequest(res, 'start and duration are required');
|
|
78
|
+
log('setWorkArea failed: missing parameters');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (typeof start !== 'number' || !isFinite(start)) {
|
|
82
|
+
sendBadRequest(res, 'start must be a finite number');
|
|
83
|
+
log('setWorkArea failed: invalid start');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (typeof duration !== 'number' || !isFinite(duration)) {
|
|
87
|
+
sendBadRequest(res, 'duration must be a finite number');
|
|
88
|
+
log('setWorkArea failed: invalid duration');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const script = `setWorkArea(${start}, ${duration})`;
|
|
93
|
+
handleBridgeMutationCall(script, res, 'setWorkArea()', 'Failed to set work area');
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function routeTimelineRequest(pathname, method, req, res) {
|
|
98
|
+
if (pathname === '/layer-in-out' && method === 'POST') {
|
|
99
|
+
handleSetInOutPoint(req, res);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (pathname === '/layer-time' && method === 'POST') {
|
|
103
|
+
handleMoveLayerTime(req, res);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (pathname === '/cti' && method === 'POST') {
|
|
107
|
+
handleSetCti(req, res);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (pathname === '/work-area' && method === 'POST') {
|
|
111
|
+
handleSetWorkArea(req, res);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
let http = null;
|
|
2
|
+
let path = null;
|
|
3
|
+
let nodeReady = true;
|
|
4
|
+
let nodeInitError = null;
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
http = require('http');
|
|
8
|
+
path = require('path');
|
|
9
|
+
} catch (e) {
|
|
10
|
+
nodeReady = false;
|
|
11
|
+
nodeInitError = e;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const csInterface = new CSInterface();
|
|
15
|
+
const extensionRoot = csInterface.getSystemPath(SystemPath.EXTENSION);
|
|
16
|
+
const hostScriptPath = nodeReady
|
|
17
|
+
? escapeForExtendScript(path.join(extensionRoot, 'host', 'index.jsx'))
|
|
18
|
+
: null;
|
|
19
|
+
|
|
20
|
+
function escapeForExtendScript(str) {
|
|
21
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toExtendScriptStringLiteral(str) {
|
|
25
|
+
return JSON.stringify(str);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function evalHostScript(scriptSource, callback) {
|
|
29
|
+
if (!hostScriptPath) {
|
|
30
|
+
callback('{"status":"error","message":"Host script unavailable because CEP Node.js is disabled."}');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const fullScript = `$.evalFile("${hostScriptPath}");${scriptSource}`;
|
|
34
|
+
csInterface.evalScript(fullScript, callback);
|
|
35
|
+
}
|