@nexrender/core 1.46.3 → 1.46.4
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/package.json +5 -4
- package/src/tasks/script/EnhancedScript.js +280 -0
- package/src/tasks/script/EnhancedScript.spec.js +115 -0
- package/src/tasks/script/helpers.js +18 -0
- package/src/tasks/script/index.js +52 -0
- package/src/tasks/script/wrap-data.js +22 -0
- package/src/tasks/script/wrap-enhanced-script.js +25 -0
- package/src/tasks/script/wrap-footage.js +10 -0
- package/test/index.js +1 -1
- package/src/tasks/script.js +0 -471
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexrender/core",
|
|
3
|
-
"version": "1.46.
|
|
3
|
+
"version": "1.46.4",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"author": "Inlife",
|
|
6
6
|
"homepage": "https://www.nexrender.com",
|
|
7
7
|
"bugs": "https://github.com/inlife/nexrender/issues",
|
|
8
8
|
"repository": "https://github.com/inlife/nexrender.git",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"pkg-prelink": "node ../../misc/prelink.js"
|
|
10
|
+
"pkg-prelink": "node ../../misc/prelink.js",
|
|
11
|
+
"test": "mocha"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
14
|
"@nexrender/types": "^1.45.6",
|
|
@@ -15,12 +16,12 @@
|
|
|
15
16
|
"file-uri-to-path": "^2.0.0",
|
|
16
17
|
"is-wsl": "^2.2.0",
|
|
17
18
|
"make-fetch-happen": "^11.0.2",
|
|
18
|
-
"match-all": "^1.2.5",
|
|
19
19
|
"mime-types": "^2.1.29",
|
|
20
20
|
"mkdirp": "^1.0.4",
|
|
21
21
|
"nanoid": "^3.2.0",
|
|
22
22
|
"requireg": "^0.2.1",
|
|
23
23
|
"rimraf": "^3.0.2",
|
|
24
|
+
"strip-comments": "^2.0.1",
|
|
24
25
|
"systeminformation": "^5.18.3"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
@@ -35,5 +36,5 @@
|
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
38
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "e997bcd889a5d6ea61f2dd30cfd1fd382dcaf1e5"
|
|
39
40
|
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const strip = require('strip-comments');
|
|
3
|
+
|
|
4
|
+
const escapeForRegex = (str) => {
|
|
5
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Find instances of a function inside a single-line string.
|
|
9
|
+
const REGEX_FN_DETECT = new RegExp(/(?:(?:(?:function *(?:[a-zA-Z0-9_]+)?) *(?:\((?:.*)\)) *(?:\{(?:.*)\}))|(?:(?:.*) *(?:=>) *(?:\{)(?:.*?)?\}))/);
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// This regex will detect a self-invoking function like (function(){})() and will catch the invoking parameters in a single string for further inspection.
|
|
13
|
+
const REGEX_SELF_INVOKING_FN = new RegExp(/(?:(?:^\() *(?:.*?)(?:} *\)))(?: *(?:\() *(.*?) *(?:\) *$))/, "gm");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse method from parameter
|
|
17
|
+
* @description Given a parameter detect whether it should be casted as a function in the final argument injection.
|
|
18
|
+
* @param param (string|number|boolean|null|object|array) The parameter to parse a method from.
|
|
19
|
+
* @return bool (Boolean) If a method is detected then it's stripped from String quotes, else it's returned in it's original type.
|
|
20
|
+
*/
|
|
21
|
+
const isMethod = (param) => {
|
|
22
|
+
if (typeof param == "string") {
|
|
23
|
+
return (param.match(REGEX_FN_DETECT) ? true : false);
|
|
24
|
+
}
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const getSearchUsageByMethodRegex = (keyword = 'NX', method = "set", flags = "g") => {
|
|
29
|
+
const str = `(?<!(?:[\\/\\s]))(?<!(?:[\\*\\s]))(?:\\s*${keyword}\\s*\\.)\\s*(${method})\\s*(?:\\()(?:["']{1})([a-zA-Z0-9._-]+)(?:["']{1})(?:\\))`;
|
|
30
|
+
return new RegExp(str, flags);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function EnhancedScript (
|
|
34
|
+
dest,
|
|
35
|
+
src,
|
|
36
|
+
parameters = [],
|
|
37
|
+
keyword = "NX",
|
|
38
|
+
defaults = {
|
|
39
|
+
global: "null",
|
|
40
|
+
string: "",
|
|
41
|
+
number: 0,
|
|
42
|
+
array: [],
|
|
43
|
+
boolean: false,
|
|
44
|
+
object: {},
|
|
45
|
+
function: function (){},
|
|
46
|
+
null: null
|
|
47
|
+
},
|
|
48
|
+
jobID,
|
|
49
|
+
logger
|
|
50
|
+
) {
|
|
51
|
+
this.scriptPath = src;
|
|
52
|
+
this.script = fs.readFileSync(dest, 'utf8');
|
|
53
|
+
this.keyword = keyword;
|
|
54
|
+
this.defaults = defaults;
|
|
55
|
+
this.jobID = jobID;
|
|
56
|
+
this.logger = logger;
|
|
57
|
+
this.jsonParameters = parameters;
|
|
58
|
+
|
|
59
|
+
/** @type { key: string
|
|
60
|
+
* isVar: boolean
|
|
61
|
+
* isFn: boolean
|
|
62
|
+
* needsDefault: boolean.
|
|
63
|
+
* }
|
|
64
|
+
*/
|
|
65
|
+
this.missingJSONParams = [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get Default Value
|
|
70
|
+
* @description Retrieves a default value parameter based on a key.
|
|
71
|
+
* @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
|
|
72
|
+
* @returns default The value from this.defaults array.
|
|
73
|
+
*/
|
|
74
|
+
EnhancedScript.prototype.getDefaultValue = function(key) { return key in this.defaults ? this.defaults[key] : this.defaults[this.defaults.global]; }
|
|
75
|
+
|
|
76
|
+
EnhancedScript.prototype.parseMethod = function (parameter) {
|
|
77
|
+
const selfInvokingFn = [...parameter.value.matchAll(REGEX_SELF_INVOKING_FN)];
|
|
78
|
+
if (selfInvokingFn ) {
|
|
79
|
+
return this.parseMethodWithArgs(parameter);
|
|
80
|
+
}
|
|
81
|
+
return parameter.value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
EnhancedScript.prototype.matchAsJSONParameterKey = function( key ) {
|
|
85
|
+
const parameterMatch = this.jsonParameters.find(o => o.key == key);
|
|
86
|
+
return parameterMatch ? parameterMatch.value : key;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
EnhancedScript.prototype.parseMethodWithArgs = function (parameter) {
|
|
90
|
+
let value = parameter.value;
|
|
91
|
+
const methodArgs = [...parameter.value.matchAll(getSearchUsageByMethodRegex(this.keyword, 'arg', "gm"))];
|
|
92
|
+
|
|
93
|
+
if (methodArgs.length > 0 ) {
|
|
94
|
+
this.logger.log("We found a self-invoking method with arguments!");
|
|
95
|
+
this.logger.log(JSON.stringify(methodArgs));
|
|
96
|
+
|
|
97
|
+
let arg, fullMatch;
|
|
98
|
+
const foundArgument = methodArgs.filter( argMatch => {
|
|
99
|
+
fullMatch = argMatch[0]
|
|
100
|
+
arg = argMatch[2];
|
|
101
|
+
|
|
102
|
+
return parameter.arguments && parameter.arguments.find(o => o.key == arg);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (foundArgument) {
|
|
106
|
+
// Search if argument is present in JSON and has `arguments` array to match against the results.
|
|
107
|
+
// And do a replacement with either the found argument on the array or a global default value.
|
|
108
|
+
let argReplacement = parameter.arguments && parameter.arguments.find(o => o.key == arg).value || this.getStringifiedDefaultValue(this.defaults.global);
|
|
109
|
+
const fullMatchRegex = new RegExp(escapeForRegex(fullMatch), "gm");
|
|
110
|
+
value = parameter.value.replace(fullMatchRegex, argReplacement);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
EnhancedScript.prototype.detectValueType = function (parameter) {
|
|
118
|
+
return isMethod(parameter.value) ? this.parseMethod(parameter) : JSON.stringify(parameter.value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get Default Value as String
|
|
123
|
+
* @description Retrieves a default value parameter based on a key.
|
|
124
|
+
* @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
|
|
125
|
+
* @returns default (String) A template literal string with the embedded default parameter. If it's a string then it's wrapped with quotes.
|
|
126
|
+
*/
|
|
127
|
+
EnhancedScript.prototype.getStringifiedDefaultValue = function (key) {
|
|
128
|
+
return JSON.stringify(this.getDefaultValue(key))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Find Missing Matches in JSX Script
|
|
133
|
+
* ====================
|
|
134
|
+
* @description RegEx Searches a given JSX script to find occurences and saves an object with keys
|
|
135
|
+
* @return bool (Boolean) Whether or not there are any variables to inject. Defaults to false.
|
|
136
|
+
*/
|
|
137
|
+
EnhancedScript.prototype.findMissingMatchesInJSX = function () {
|
|
138
|
+
const script = strip(this.script);
|
|
139
|
+
|
|
140
|
+
// Parse all occurrences of the usage of NX on the provided script.
|
|
141
|
+
const nxMatches = [...script.matchAll(getSearchUsageByMethodRegex(this.keyword, "get", "gm"))];
|
|
142
|
+
|
|
143
|
+
if (nxMatches && nxMatches.length > 0 ) {
|
|
144
|
+
nxMatches.forEach( match => {
|
|
145
|
+
const keyword = match[2];
|
|
146
|
+
|
|
147
|
+
var nxMatch = {
|
|
148
|
+
key: keyword.replace(/\s/g, ''),
|
|
149
|
+
isVar: false,
|
|
150
|
+
isFn: false,
|
|
151
|
+
value: this.getDefaultValue(match.default ? match.default : this.defaults.global)
|
|
152
|
+
};
|
|
153
|
+
if (this.jsonParameters.filter( o => o.key == nxMatch.key ).length == 0) { // If the parameter doesn't have a value defined in JSON
|
|
154
|
+
this.missingJSONParams.push(nxMatch);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const numMissing = this.missingJSONParams.length;
|
|
160
|
+
if (numMissing > 0) {
|
|
161
|
+
this.logger.log(`[${this.jobID}] ${this.displayAlert()}`);
|
|
162
|
+
}
|
|
163
|
+
return numMissing > 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generated Placeholder Parameters
|
|
169
|
+
* ================================
|
|
170
|
+
* @description Generates placeholder for "parameters" JSON Object based on keys from an array.
|
|
171
|
+
* @return string (String) JSON "parameters" object.
|
|
172
|
+
*/
|
|
173
|
+
EnhancedScript.prototype.generatePlaceholderParameters = function ( ) {
|
|
174
|
+
const template = (key) => `
|
|
175
|
+
{
|
|
176
|
+
"key" : "${key}",
|
|
177
|
+
"value" : ${this.getStringifiedDefaultValue(this.defaults.global)}
|
|
178
|
+
}\n
|
|
179
|
+
`;
|
|
180
|
+
return `
|
|
181
|
+
"parameters" : [
|
|
182
|
+
${this.missingJSONParams.map((k) => template(k.key)).join("\n")}
|
|
183
|
+
]
|
|
184
|
+
`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Display Missing Alert
|
|
189
|
+
* =====================
|
|
190
|
+
* @description Display a log message if theres any missing parameter set on the JSON configuration but is being referred in the script.
|
|
191
|
+
* @return string (String) The template literal string displaying all the occurences if any.
|
|
192
|
+
*/
|
|
193
|
+
EnhancedScript.prototype.displayAlert = function () {
|
|
194
|
+
const keyword = this.keyword;
|
|
195
|
+
|
|
196
|
+
return ` -- W A R N I N G --
|
|
197
|
+
The following parameters used in the script were NOT found on the JSON "parameters" object of your script asset ${this.scriptPath }
|
|
198
|
+
|
|
199
|
+
${this.missingJSONParams.map(o => o.key).join(", ")}
|
|
200
|
+
|
|
201
|
+
Please set defaults in your JSX script (see documentation) or copy the following placeholder JSON code snippet and replace the values with your own:
|
|
202
|
+
|
|
203
|
+
${this.generatePlaceholderParameters()}
|
|
204
|
+
|
|
205
|
+
Remember to always use a fallback default value for any use of the ${keyword} object to have the ability to run this script on After Effects directly.
|
|
206
|
+
Example:
|
|
207
|
+
const dogName = ${keyword} && ${keyword}.get("doggo") || "Doggo";
|
|
208
|
+
`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
EnhancedScript.prototype.injectParameters = function () {
|
|
212
|
+
return [...this.jsonParameters, ...this.missingJSONParams].map( param => {
|
|
213
|
+
let value = param.type ? this.getStringifiedDefaultValue(param.type) : this.getDefaultValue(this.defaults.global);
|
|
214
|
+
|
|
215
|
+
if (param.value ) {
|
|
216
|
+
value = this.detectValueType(param);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return `${this.keyword}.set('${param.key}', ${value});`
|
|
220
|
+
}).join("\n");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
EnhancedScript.prototype.buildParameterConfigurator = function () {
|
|
224
|
+
const defaultGlobalValue = this.getStringifiedDefaultValue( this.defaults.global );
|
|
225
|
+
// const defaultFnValue = this.getDefaultValue( this.defaults.function );
|
|
226
|
+
const createParameterConfigurator = () => `
|
|
227
|
+
function ParameterConfigurator () {
|
|
228
|
+
this.params = [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
ParameterConfigurator.prototype.set = function (k, v) {
|
|
232
|
+
this.params.push({
|
|
233
|
+
key: k,
|
|
234
|
+
value: v
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
ParameterConfigurator.prototype.call = function ( key, args ) {
|
|
239
|
+
for (var i = 0; i < this.params.length; i++) {
|
|
240
|
+
if (this.params[i].key == key) {
|
|
241
|
+
if (typeof this.params[i].value == "function") return this.params[i].value.apply(this, args && args.length > 0 ? args : []);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
ParameterConfigurator.prototype.get = function ( key, args ) {
|
|
248
|
+
for (var i = 0; i < this.params.length; i++) {
|
|
249
|
+
if (this.params[i].key == key) {
|
|
250
|
+
if (typeof this.params[i].value == "function") return this.call(key, args || []);
|
|
251
|
+
return this.params[i].value;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return ${ defaultGlobalValue }
|
|
255
|
+
};
|
|
256
|
+
var ${ this.keyword } = new ParameterConfigurator();
|
|
257
|
+
|
|
258
|
+
// Parameter injection from job configuration
|
|
259
|
+
${ this.injectParameters() }
|
|
260
|
+
`;
|
|
261
|
+
|
|
262
|
+
return createParameterConfigurator();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
EnhancedScript.prototype.build = function () {
|
|
266
|
+
this.findMissingMatchesInJSX();
|
|
267
|
+
|
|
268
|
+
// Et voilà!
|
|
269
|
+
const enhancedScript = `(function() {
|
|
270
|
+
${this.buildParameterConfigurator()}
|
|
271
|
+
${this.script}
|
|
272
|
+
})();\n`;
|
|
273
|
+
|
|
274
|
+
// do not log the script (can be uncommented for debugging)
|
|
275
|
+
// this.logger.log(enhancedScript);
|
|
276
|
+
return enhancedScript;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
module.exports = EnhancedScript
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// simple syntax test
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const chai = require('chai')
|
|
6
|
+
const expect = chai.expect
|
|
7
|
+
|
|
8
|
+
describe('tasks/script/EnhancedScript', () => {
|
|
9
|
+
const EnhancedScript = require('./EnhancedScript')
|
|
10
|
+
|
|
11
|
+
const testJsxFilePath = path.join(os.tmpdir(), 'unittest.jsx')
|
|
12
|
+
|
|
13
|
+
it("Finds parameters in jsx with missing values in json and injects", () => {
|
|
14
|
+
fs.writeFileSync(testJsxFilePath,`
|
|
15
|
+
/* var c = NX.get('unittest_undefined_parameter_multi_line_comment')
|
|
16
|
+
*/
|
|
17
|
+
// var d = NX.get('unittest_undefined_parameter_single_line_comment')
|
|
18
|
+
var a = NX.get('unittest_undefined_parameter')
|
|
19
|
+
var b = NX.get('unittest_defined_parameter')
|
|
20
|
+
`);
|
|
21
|
+
|
|
22
|
+
const enhancedScript = new EnhancedScript(testJsxFilePath, 'src', [
|
|
23
|
+
{
|
|
24
|
+
key: 'unittest_defined_parameter',
|
|
25
|
+
value: 'VALUE'
|
|
26
|
+
},
|
|
27
|
+
], 'NX', {}, 'unittest', console);
|
|
28
|
+
|
|
29
|
+
const anyMissing = enhancedScript.findMissingMatchesInJSX();
|
|
30
|
+
expect(anyMissing).true;
|
|
31
|
+
expect(enhancedScript.missingJSONParams.map((o) => { return o.key})).to.have.members(['unittest_undefined_parameter'])
|
|
32
|
+
|
|
33
|
+
expect(enhancedScript.injectParameters()).to.equal(
|
|
34
|
+
[`NX.set('unittest_defined_parameter', "VALUE");`,
|
|
35
|
+
`NX.set('unittest_undefined_parameter', undefined);`].join('\n'));
|
|
36
|
+
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("injects functions and functions with arguments", () => {
|
|
40
|
+
|
|
41
|
+
//FIXME: NX.get('unittest_undefined_function', 'arg1') is currently not found and no warning is given
|
|
42
|
+
fs.writeFileSync(testJsxFilePath,`
|
|
43
|
+
var a = NX.get('unittest_undefined_function', 'arg1')
|
|
44
|
+
var b = NX.get('eventInvitation')
|
|
45
|
+
`);
|
|
46
|
+
|
|
47
|
+
const enhancedScript = new EnhancedScript(testJsxFilePath, 'src', [
|
|
48
|
+
{
|
|
49
|
+
"key" : "invitees",
|
|
50
|
+
"value": ["Steve", "Natasha", "Tony", "Bruce", "Wanda", "Thor", "Peter", "Clint" ]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"key" : "eventInvitation",
|
|
54
|
+
"value": `(function (venue) { alert( 'This years' Avengers Gala is on the prestigious ' + venue.name + ' located at ' + venue.location + '. Our special guests ' + NX.get('invitees').value.map(function (a, i) { return (i == NX.get('invitees').value.length - 1) ? ' and ' + a + ' (whoever that is)' : a + ', '; }).join('') + ' going to be present for the ceremony!');
|
|
55
|
+
})({ name: NX.arg('venue'), location: NX.arg('location') })`,
|
|
56
|
+
"arguments": [
|
|
57
|
+
{
|
|
58
|
+
"key" : "venue",
|
|
59
|
+
"value" : "Smithsonian Museum of Natural History"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"key" : "location",
|
|
63
|
+
"value": "10th St. & Constitution Ave."
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"type": "array",
|
|
69
|
+
"key" : "dogs",
|
|
70
|
+
"value": [ "Captain Sparkles", "Summer", "Neptune"]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type" : "number",
|
|
74
|
+
"key" : "anAmount"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "function",
|
|
78
|
+
"key": "getDogsCount",
|
|
79
|
+
"value" : "function() { return NX.get('dogs').length; }"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "function",
|
|
83
|
+
"key": "exampleFn",
|
|
84
|
+
"value": "function ( parameter ) { return parameter; }"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type" : "function",
|
|
88
|
+
"key" : "dogCount",
|
|
89
|
+
"value" : "(function(length) { return length })(NX.arg('dogCount'))",
|
|
90
|
+
"arguments": [
|
|
91
|
+
{
|
|
92
|
+
"key" : "dogCount",
|
|
93
|
+
"value": ["NX.call('exampleFn', [NX.call('getDogsCount') + NX.get('anAmount')])"]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
], 'NX', {}, 'unittest', console);
|
|
98
|
+
|
|
99
|
+
const anyMissing = enhancedScript.findMissingMatchesInJSX();
|
|
100
|
+
expect(anyMissing).false;
|
|
101
|
+
|
|
102
|
+
const injected = enhancedScript.injectParameters();
|
|
103
|
+
|
|
104
|
+
expect(injected).to.equal([
|
|
105
|
+
`NX.set('invitees', ["Steve","Natasha","Tony","Bruce","Wanda","Thor","Peter","Clint"]);`,
|
|
106
|
+
`NX.set('eventInvitation', (function (venue) { alert( 'This years' Avengers Gala is on the prestigious ' + venue.name + ' located at ' + venue.location + '. Our special guests ' + NX.get('invitees').value.map(function (a, i) { return (i == NX.get('invitees').value.length - 1) ? ' and ' + a + ' (whoever that is)' : a + ', '; }).join('') + ' going to be present for the ceremony!');
|
|
107
|
+
})({ name: NX.arg('venue'), location:10th St. & Constitution Ave. }));`,
|
|
108
|
+
`NX.set('dogs', ["Captain Sparkles","Summer","Neptune"]);`,
|
|
109
|
+
`NX.set('anAmount', undefined);`,
|
|
110
|
+
`NX.set('getDogsCount', function() { return NX.get('dogs').length; });`,
|
|
111
|
+
`NX.set('exampleFn', function ( parameter ) { return parameter; });`,
|
|
112
|
+
`NX.set('dogCount', (function(length) { return length })(NX.call('exampleFn', [NX.call('getDogsCount') + NX.get('anAmount')])));`
|
|
113
|
+
].join('\n'))
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const escape = str => {
|
|
2
|
+
str = JSON.stringify(str)
|
|
3
|
+
str = str.substring(1, str.length-1)
|
|
4
|
+
str = `'${str.replace(/'/g, '\\\'')}'`
|
|
5
|
+
return str
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const selectLayers = (job, { composition, layerName, layerIndex }, callbackString) => {
|
|
9
|
+
const method = layerName ? 'selectLayersByName' : 'selectLayersByIndex';
|
|
10
|
+
const compo = composition === undefined ? 'null' : escape(composition);
|
|
11
|
+
const value = layerName ? escape(layerName) : layerIndex;
|
|
12
|
+
return (`nexrender.${method}(${compo}, ${value}, ${callbackString}, null, ${job.template.continueOnMissing});`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
escape,
|
|
17
|
+
selectLayers,
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const script = require('../../assets/nexrender.jsx')
|
|
4
|
+
|
|
5
|
+
const wrapFootage = require('./wrap-footage')
|
|
6
|
+
const wrapData = require('./wrap-data')
|
|
7
|
+
const wrapEnhancedScript = require('./wrap-enhanced-script')
|
|
8
|
+
|
|
9
|
+
module.exports = (job, settings) => {
|
|
10
|
+
settings.logger.log(`[${job.uid}] running script assemble...`);
|
|
11
|
+
|
|
12
|
+
const data = [];
|
|
13
|
+
const base = job.workpath;
|
|
14
|
+
|
|
15
|
+
job.assets.map(asset => {
|
|
16
|
+
settings.trackCombined('Asset Script Wraps', {
|
|
17
|
+
job_id: job.uid, // anonymized internally
|
|
18
|
+
script_type: asset.type,
|
|
19
|
+
script_compostion_set: asset.composition !== undefined,
|
|
20
|
+
script_layer_strat: asset.layerName ? 'name' : 'index',
|
|
21
|
+
script_value_strat:
|
|
22
|
+
asset.value !== undefined ? 'value' : // eslint-disable-line no-nested-ternary, multiline-ternary
|
|
23
|
+
asset.expression !== undefined ? 'expression' : // eslint-disable-line multiline-ternary
|
|
24
|
+
undefined,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
switch (asset.type) {
|
|
28
|
+
case 'video':
|
|
29
|
+
case 'audio':
|
|
30
|
+
case 'image':
|
|
31
|
+
data.push(wrapFootage(job, settings, asset));
|
|
32
|
+
break;
|
|
33
|
+
|
|
34
|
+
case 'data':
|
|
35
|
+
data.push(wrapData(job, settings, asset));
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
case 'script':
|
|
39
|
+
data.push(wrapEnhancedScript(job, settings, asset));
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/* write out assembled custom script file in the workpath */
|
|
45
|
+
job.scriptfile = path.join(base, `nexrender-${job.uid}-script.jsx`);
|
|
46
|
+
fs.writeFileSync(job.scriptfile, script
|
|
47
|
+
.replace('/*COMPOSITION*/', job.template.composition)
|
|
48
|
+
.replace('/*USERSCRIPT*/', () => data.join('\n'))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return Promise.resolve(job)
|
|
52
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { selectLayers } = require('./helpers')
|
|
2
|
+
|
|
3
|
+
const renderIf = (value, string) => {
|
|
4
|
+
const encoded = typeof value == 'string' ? escape(value) : JSON.stringify(value);
|
|
5
|
+
return value === undefined ? '' : string.replace('$value', () => encoded);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const partsOfKeypath = (keypath) => {
|
|
9
|
+
var parts = keypath.split('->');
|
|
10
|
+
return (parts.length === 1) ? keypath.split('.') : parts
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const wrapData = (job, settings, { property, value, expression, ...asset }) => (`(function() {
|
|
14
|
+
${selectLayers(job, asset, `function(layer) {
|
|
15
|
+
var parts = ${JSON.stringify(partsOfKeypath(property))};
|
|
16
|
+
${renderIf(value, `var value = { "value": $value }`)}
|
|
17
|
+
${renderIf(expression, `var value = { "expression": $value }`)}
|
|
18
|
+
nexrender.changeValueForKeypath(layer, parts, value);
|
|
19
|
+
}`)}
|
|
20
|
+
})();\n`)
|
|
21
|
+
|
|
22
|
+
module.exports = wrapData
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const EnhancedScript = require('./EnhancedScript')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrap Enhanced Script
|
|
5
|
+
* ====================
|
|
6
|
+
* @author Dilip Ramírez (https://github.com/dukuo | https://notimetoexplain.co)
|
|
7
|
+
* @autor Based on the work of Potat (https://github.com/dukuo/potat)
|
|
8
|
+
* @description Parse a script from a source, and injects a configuration object named ${keyword} based on the "parameters" array of the script asset if any.
|
|
9
|
+
*
|
|
10
|
+
* If parameters or functions deriving from the configuration object are being used in the script, but no parameters are set, then it succeeds but
|
|
11
|
+
* displays a warning with the missing JSX/JSON matches, and sets all the missing ones to null for a soft fault tolerance at runtime.
|
|
12
|
+
*
|
|
13
|
+
* @param src The JSX script
|
|
14
|
+
* @param parameters (Array<Object>) Argument array described in the Asset JSON object inside the Job description
|
|
15
|
+
* @param keyword (String) Name for the exported variable holding configuration parameters. Defaults to NX as in NeXrender.
|
|
16
|
+
* @param defaults.null (Any) The default value in case the user setted key name on any given `parameter` child object. Defaults to `null`
|
|
17
|
+
*
|
|
18
|
+
* @return string (String) The compiled script with parameter injection outside its original scope to avoid user-defined defaults collision.
|
|
19
|
+
*/
|
|
20
|
+
const wrapEnhancedScript = (job, settings, { dest, src, parameters = [], keyword, defaults, /* ...asset */ }) => {
|
|
21
|
+
const enhancedScript = new EnhancedScript(dest, src, parameters, keyword, defaults, job.uid, settings.logger);
|
|
22
|
+
return enhancedScript.build();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = wrapEnhancedScript
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { selectLayers } = require('./helpers')
|
|
2
|
+
const { checkForWSL } = require('../../helpers/path')
|
|
3
|
+
|
|
4
|
+
const wrapFootage = (job, settings, { dest, ...asset }) => (`(function() {
|
|
5
|
+
${selectLayers(job, asset, `function(layer) {
|
|
6
|
+
nexrender.replaceFootage(layer, '${checkForWSL(dest.replace(/\\/g, "\\\\"), settings)}')
|
|
7
|
+
}`)}
|
|
8
|
+
})();\n`)
|
|
9
|
+
|
|
10
|
+
module.exports = wrapFootage
|
package/test/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// simple syntax test
|
|
2
|
-
require('../src')
|
|
2
|
+
require('../src')
|
package/src/tasks/script.js
DELETED
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const script = require('../assets/nexrender.jsx')
|
|
4
|
-
const matchAll = require('match-all')
|
|
5
|
-
const { checkForWSL } = require('../helpers/path')
|
|
6
|
-
|
|
7
|
-
/* helpers */
|
|
8
|
-
const escape = str => {
|
|
9
|
-
str = JSON.stringify(str)
|
|
10
|
-
str = str.substring(1, str.length-1)
|
|
11
|
-
str = `'${str.replace(/'/g, '\\\'')}'`
|
|
12
|
-
return str
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const selectLayers = (job, settings, { composition, layerName, layerIndex }, callbackString) => {
|
|
16
|
-
const method = layerName ? 'selectLayersByName' : 'selectLayersByIndex';
|
|
17
|
-
const compo = composition === undefined ? 'null' : escape(composition);
|
|
18
|
-
const value = layerName ? escape(layerName) : layerIndex;
|
|
19
|
-
return (`nexrender.${method}(${compo}, ${value}, ${callbackString}, null, ${job.template.continueOnMissing});`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const renderIf = (value, string) => {
|
|
23
|
-
const encoded = typeof value == 'string' ? escape(value) : JSON.stringify(value);
|
|
24
|
-
return value === undefined ? '' : string.replace('$value', () => encoded);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const partsOfKeypath = (keypath) => {
|
|
28
|
-
var parts = keypath.split('->');
|
|
29
|
-
return (parts.length === 1) ? keypath.split('.') : parts
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/* scripting wrappers */
|
|
33
|
-
const wrapFootage = (job, settings, { dest, ...asset }) => (`(function() {
|
|
34
|
-
${selectLayers(job, settings, asset, `function(layer) {
|
|
35
|
-
nexrender.replaceFootage(layer, '${checkForWSL(dest.replace(/\\/g, "\\\\"), settings)}')
|
|
36
|
-
}`)}
|
|
37
|
-
})();\n`)
|
|
38
|
-
|
|
39
|
-
const wrapData = (job, settings, { property, value, expression, ...asset }) => (`(function() {
|
|
40
|
-
${selectLayers(job, settings, asset, `function(layer) {
|
|
41
|
-
var parts = ${JSON.stringify(partsOfKeypath(property))};
|
|
42
|
-
${renderIf(value, `var value = { "value": $value }`)}
|
|
43
|
-
${renderIf(expression, `var value = { "expression": $value }`)}
|
|
44
|
-
nexrender.changeValueForKeypath(layer, parts, value);
|
|
45
|
-
}`)}
|
|
46
|
-
})();\n`)
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Wrap Enhanced Script
|
|
50
|
-
* ====================
|
|
51
|
-
* @author Dilip Ramírez (https://github.com/dukuo | https://notimetoexplain.co)
|
|
52
|
-
* @autor Based on the work of Potat (https://github.com/dukuo/potat)
|
|
53
|
-
* @description Parse a script from a source, and injects a configuration object named ${keyword} based on the "parameters" array of the script asset if any.
|
|
54
|
-
*
|
|
55
|
-
* If parameters or functions deriving from the configuration object are being used in the script, but no parameters are set, then it succeeds but
|
|
56
|
-
* displays a warning with the missing JSX/JSON matches, and sets all the missing ones to null for a soft fault tolerance at runtime.
|
|
57
|
-
*
|
|
58
|
-
* @param src The JSX script
|
|
59
|
-
* @param parameters (Array<Object>) Argument array described in the Asset JSON object inside the Job description
|
|
60
|
-
* @param keyword (String) Name for the exported variable holding configuration parameters. Defaults to NX as in NeXrender.
|
|
61
|
-
* @param defaults.null (Any) The default value in case the user setted key name on any given `parameter` child object. Defaults to `null`
|
|
62
|
-
*
|
|
63
|
-
* @return string (String) The compiled script with parameter injection outside its original scope to avoid user-defined defaults collision.
|
|
64
|
-
*/
|
|
65
|
-
const wrapEnhancedScript = (job, settings, { dest, src, parameters = [], keyword, defaults, /* ...asset */ }) => {
|
|
66
|
-
const jobID = job.uid;
|
|
67
|
-
let arg, fullMatch;
|
|
68
|
-
|
|
69
|
-
function EnhancedScript (
|
|
70
|
-
dest,
|
|
71
|
-
src,
|
|
72
|
-
parameters = [],
|
|
73
|
-
keyword = "NX",
|
|
74
|
-
defaults = {
|
|
75
|
-
global: "null",
|
|
76
|
-
string: "",
|
|
77
|
-
number: 0,
|
|
78
|
-
array: [],
|
|
79
|
-
boolean: false,
|
|
80
|
-
object: {},
|
|
81
|
-
function: function (){},
|
|
82
|
-
null: null
|
|
83
|
-
},
|
|
84
|
-
jobID,
|
|
85
|
-
logger
|
|
86
|
-
) {
|
|
87
|
-
this.scriptPath = src;
|
|
88
|
-
this.script = fs.readFileSync(dest, 'utf8');
|
|
89
|
-
this.keyword = keyword;
|
|
90
|
-
this.defaults = defaults;
|
|
91
|
-
this.jobID = jobID;
|
|
92
|
-
this.logger = logger;
|
|
93
|
-
this.jsonParameters = parameters;
|
|
94
|
-
|
|
95
|
-
this.regexes = {
|
|
96
|
-
keywordUsage: null,
|
|
97
|
-
keywordInit: null,
|
|
98
|
-
fnDetect: null
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
this.missingJSONParams = [];
|
|
102
|
-
|
|
103
|
-
// Setup
|
|
104
|
-
this.setupRegexes();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Utilities, one-liners
|
|
109
|
-
*/
|
|
110
|
-
EnhancedScript.prototype.getScriptPath = function() { return this.scriptPath; }
|
|
111
|
-
EnhancedScript.prototype.getGetterMethod = function () { return "get"; }
|
|
112
|
-
EnhancedScript.prototype.getSetterMethod = function () { return "set"; }
|
|
113
|
-
EnhancedScript.prototype.getLogger = function () { return this.logger; }
|
|
114
|
-
EnhancedScript.prototype.getJSXScript = function () { return this.script; }
|
|
115
|
-
EnhancedScript.prototype.getJSONParams = function () { return this.jsonParameters; }
|
|
116
|
-
EnhancedScript.prototype.jsonParametersCount = function () { return this.jsonParameters.length; }
|
|
117
|
-
EnhancedScript.prototype.getMissingJSONParams = function() { return this.missingJSONParams; }
|
|
118
|
-
EnhancedScript.prototype.countMissingJSONParams = function() { return this.getMissingJSONParams().length; }
|
|
119
|
-
EnhancedScript.prototype.getKeyword = function () { return this.keyword; }
|
|
120
|
-
EnhancedScript.prototype.getFnArgsKeyword = function () { return this.fnArgsKeyword; }
|
|
121
|
-
EnhancedScript.prototype.getRegex = function (key) { return this.regexes[key]; }
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get Default Value
|
|
125
|
-
* @description Retrieves a default value parameter based on a key.
|
|
126
|
-
* @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
|
|
127
|
-
* @returns default The value from this.defaults array.
|
|
128
|
-
*/
|
|
129
|
-
EnhancedScript.prototype.getDefaultValue = function(key) { return key in this.defaults ? this.defaults[key] : this.defaults[this.defaults.global]; }
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Add Missing JSON Parameter
|
|
133
|
-
* =====================
|
|
134
|
-
* @description Adds an object with the key of a parameter being used in JSX code but with no default in the JSON Asset.
|
|
135
|
-
* @param nxMatch (Object) The object to add to the missing array.
|
|
136
|
-
* Required format:
|
|
137
|
-
* {
|
|
138
|
-
* key: string
|
|
139
|
-
* isVar: boolean
|
|
140
|
-
* isFn: boolean
|
|
141
|
-
* needsDefault: boolean.
|
|
142
|
-
* }
|
|
143
|
-
* @returns object The recently added object.
|
|
144
|
-
*/
|
|
145
|
-
EnhancedScript.prototype.addToMissingJsonParameter = function(nxMatch) { return this.missingJSONParams.push(nxMatch); }
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* End one-liners
|
|
149
|
-
*/
|
|
150
|
-
EnhancedScript.prototype.setupRegexes = function() {
|
|
151
|
-
this.regexes.searchUsageByMethod = (method = "set", flags = "g") => {
|
|
152
|
-
const str = `(?<!(?:[\\/\\s]))(?<!(?:[\\*\\s]))(?:\\s*${this.getKeyword()}\\s*\\.)\\s*(${method})\\s*(?:\\()(?:["']{1})([a-zA-Z0-9._-]+)(?:["']{1})(?:\\))`;
|
|
153
|
-
return this.buildRegex(str, flags);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Find instances of a function inside a single-line string.
|
|
157
|
-
this.regexes.fnDetect = new RegExp(/(?:(?:(?:function *(?:[a-zA-Z0-9_]+)?) *(?:\((?:.*)\)) *(?:\{(?:.*)\}))|(?:(?:.*) *(?:=>) *(?:\{)(?:.*?)?\}))/);
|
|
158
|
-
|
|
159
|
-
// This regex will detect a self-invoking function like (function(){})() and will catch the invoking parameters in a single string for further inspection.
|
|
160
|
-
this.regexes.selfInvokingFn = new RegExp(/(?:(?:^\() *(?:.*?)(?:} *\)))(?: *(?:\() *(.*?) *(?:\) *$))/);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
EnhancedScript.prototype.escapeForRegex = function(str) {
|
|
164
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
EnhancedScript.prototype.stripComments = function (templateLiteral) {
|
|
168
|
-
return templateLiteral.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, '');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
EnhancedScript.prototype.buildRegex = function(templateLiteral, flags) {
|
|
172
|
-
return new RegExp(this.stripComments(templateLiteral).replace(/(\r\n|r\|\n|\s)/gm, ''), flags);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Parse method from parameter
|
|
177
|
-
* @description Given a parameter detect whether it should be casted as a function in the final argument injection.
|
|
178
|
-
* @param param (string|number|boolean|null|object|array) The parameter to parse a method from.
|
|
179
|
-
* @return bool (Boolean) If a method is detected then it's stripped from String quotes, else it's returned in it's original type.
|
|
180
|
-
*/
|
|
181
|
-
EnhancedScript.prototype.isMethod = function (param) {
|
|
182
|
-
if (typeof param == "string") {
|
|
183
|
-
return (param.match(this.getRegex("fnDetect")) ? true : false);
|
|
184
|
-
}
|
|
185
|
-
return false
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
EnhancedScript.prototype.parseMethod = function (parameter) {
|
|
189
|
-
const selfInvokingFn = matchAll(parameter.value, this.getRegex('selfInvokingFn'));
|
|
190
|
-
if (selfInvokingFn ) {
|
|
191
|
-
this.getLogger().log(Array.from(selfInvokingFn));
|
|
192
|
-
return this.parseMethodWithArgs(parameter);
|
|
193
|
-
}
|
|
194
|
-
return parameter.value;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
EnhancedScript.prototype.matchAsJSONParameterKey = function( key ) {
|
|
198
|
-
const parameterMatch = this.getJSONParams().find(o => o.key == key);
|
|
199
|
-
return parameterMatch ? parameterMatch.value : key;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
EnhancedScript.prototype.parseMethodWithArgs = function (parameter) {
|
|
203
|
-
let value = parameter.value;
|
|
204
|
-
const methodArgs = matchAll(parameter.value, this.getRegex('searchUsageByMethod')('arg', "gm")).toArray();
|
|
205
|
-
|
|
206
|
-
if (methodArgs.length > 0 ) {
|
|
207
|
-
this.getLogger().log("We found a self-invoking method with arguments!");
|
|
208
|
-
this.getLogger().log(JSON.stringify(methodArgs));
|
|
209
|
-
const foundArgument = methodArgs.filter( argMatch => {
|
|
210
|
-
fullMatch = argMatch[0]
|
|
211
|
-
arg = argMatch[2]
|
|
212
|
-
|
|
213
|
-
return parameter.arguments && parameter.arguments.find(o => o.key == arg);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (foundArgument) {
|
|
218
|
-
// Search if argument is present in JSON and has `arguments` array to match against the results.
|
|
219
|
-
// And do a replacement with either the found argument on the array or a global default value.
|
|
220
|
-
let argReplacement = parameter.arguments && parameter.arguments.find(o => o.key == arg).value || this.getStringifiedDefaultValue(this.defaults.global);
|
|
221
|
-
const fullMatchRegex = this.buildRegex(this.escapeForRegex(fullMatch), "gm");
|
|
222
|
-
value = parameter.value.replace(fullMatchRegex, argReplacement);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return value;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
EnhancedScript.prototype.detectValueType = function (parameter) {
|
|
230
|
-
return this.isMethod(parameter.value) ? this.parseMethod(parameter) : JSON.stringify(parameter.value);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get Default Value as String
|
|
235
|
-
* @description Retrieves a default value parameter based on a key.
|
|
236
|
-
* @param key (String)("string"|"number"|"array"|"object"|"null"|"function") The key to the required default parameter. Defaults to "null".
|
|
237
|
-
* @returns default (String) A template literal string with the embedded default parameter. If it's a string then it's wrapped with quotes.
|
|
238
|
-
*/
|
|
239
|
-
EnhancedScript.prototype.getStringifiedDefaultValue = function (key) {
|
|
240
|
-
return JSON.stringify(this.getDefaultValue(key))
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Strip comment blocks from Script [EXPERIMENTAL]
|
|
245
|
-
* ================================
|
|
246
|
-
* @description Removes /* * / comments from script to avoid mismatching occurrences.
|
|
247
|
-
* @param script (String) The target script to strip.
|
|
248
|
-
* @returns string (String) A one-line version of the original script without comment blocks.
|
|
249
|
-
*/
|
|
250
|
-
EnhancedScript.prototype.stripCommentsFromScript = function (script) {
|
|
251
|
-
// https://levelup.gitconnected.com/advanced-regex-find-and-remove-multi-line-comments-in-your-code-c162ba6e5811
|
|
252
|
-
return script
|
|
253
|
-
.replace(/\n/g, " ")
|
|
254
|
-
.replace(/\/\*.*\*\//g, " ")
|
|
255
|
-
.replace(/\s+/g, " ")
|
|
256
|
-
.trim();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/*
|
|
260
|
-
* End Utilities
|
|
261
|
-
*/
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Find Missing Matches in JSX Script
|
|
265
|
-
* ====================
|
|
266
|
-
* @description RegEx Searches a given JSX script to find occurences and saves an object with keys
|
|
267
|
-
* @param script (String) JSX script to find occurences in.
|
|
268
|
-
* @param regex (Object) RegEx object to match against JSX script.
|
|
269
|
-
* @param parameters (Array<Object>) Array with the parameters to compare against the matches.
|
|
270
|
-
* @return bool (Boolean) Whether or not there are any variables to inject. Defaults to false.
|
|
271
|
-
*/
|
|
272
|
-
EnhancedScript.prototype.findMissingMatchesInJSX = function () {
|
|
273
|
-
const script = this.stripCommentsFromScript(this.getJSXScript());
|
|
274
|
-
|
|
275
|
-
// Parse all occurrences of the usage of NX on the provided script.
|
|
276
|
-
const nxMatches = matchAll(script, this.getRegex("searchUsageByMethod")("get", "gm")).toArray();
|
|
277
|
-
|
|
278
|
-
if (nxMatches && nxMatches.length > 0 ) {
|
|
279
|
-
nxMatches.forEach( match => {
|
|
280
|
-
const keyword = match[2];
|
|
281
|
-
|
|
282
|
-
var nxMatch = {
|
|
283
|
-
key: keyword.replace(/\s/g, ''),
|
|
284
|
-
isVar: false,
|
|
285
|
-
isFn: false,
|
|
286
|
-
value: this.getDefaultValue(match.default ? match.default : this.defaults.global)
|
|
287
|
-
};
|
|
288
|
-
if (this.getJSONParams().filter( o => o.key == nxMatch.key ).length == 0) { // If the parameter doesn't have a value defined in JSON
|
|
289
|
-
this.addToMissingJsonParameter(nxMatch);
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const missingJSONParameters = this.countMissingJSONParams();
|
|
295
|
-
if (missingJSONParameters > 0) {
|
|
296
|
-
this.getLogger().log(`[${jobID}] ${this.displayAlert()}`);
|
|
297
|
-
}
|
|
298
|
-
return missingJSONParameters > 0;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Generated Placeholder Parameters
|
|
304
|
-
* ================================
|
|
305
|
-
* @description Generates placeholder for "parameters" JSON Object based on keys from an array.
|
|
306
|
-
* @param keys (Array) Array of strings. These should be the occurences of `keyword` variable use on the JSX script.
|
|
307
|
-
*
|
|
308
|
-
* @return string (String) JSON "parameters" object.
|
|
309
|
-
*/
|
|
310
|
-
EnhancedScript.prototype.generatePlaceholderParameters = function ( ) {
|
|
311
|
-
const missingParams = this.getMissingJSONParams();
|
|
312
|
-
|
|
313
|
-
const template = (key) => `
|
|
314
|
-
{
|
|
315
|
-
"key" : "${key}",
|
|
316
|
-
"value" : ${this.getStringifiedDefaultValue(this.defaults.global)}
|
|
317
|
-
}\n
|
|
318
|
-
`;
|
|
319
|
-
|
|
320
|
-
return `
|
|
321
|
-
"parameters" : [
|
|
322
|
-
${missingParams.map((k) => template(k.key)).join("\n")}
|
|
323
|
-
]
|
|
324
|
-
`
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Display Missing Alert
|
|
329
|
-
* =====================
|
|
330
|
-
* @description Display a log message if theres any missing parameter set on the JSON configuration but is being referred in the script.
|
|
331
|
-
*
|
|
332
|
-
* @param missingParam (Object) Missing Parameters object. See below for its construction. Must have child objects `fn` and `vars`
|
|
333
|
-
* @param showJSXWarning (Boolean) Flag for whether or not to display warning about not initializing variable in JSX script. Defaults to false.
|
|
334
|
-
* @param injectionVar (String) Variable initialized with placeholder values. Defaults to "".
|
|
335
|
-
*
|
|
336
|
-
* @return string (String) The template literal string displaying all the occurences if any.
|
|
337
|
-
*/
|
|
338
|
-
EnhancedScript.prototype.displayAlert = function () {
|
|
339
|
-
const keyword = this.getKeyword();
|
|
340
|
-
|
|
341
|
-
return ` -- W A R N I N G --
|
|
342
|
-
The following parameters used in the script were NOT found on the JSON "parameters" object of your script asset ${this.getScriptPath() }
|
|
343
|
-
|
|
344
|
-
${this.getMissingJSONParams().map(o => o.key).join(", ")}
|
|
345
|
-
|
|
346
|
-
Please set defaults in your JSX script (see documentation) or copy the following placeholder JSON code snippet and replace the values with your own:
|
|
347
|
-
|
|
348
|
-
${this.generatePlaceholderParameters()}
|
|
349
|
-
|
|
350
|
-
Remember to always use a fallback default value for any use of the ${keyword} object to have the ability to run this script on After Effects directly.
|
|
351
|
-
Example:
|
|
352
|
-
const dogName = ${keyword} && ${keyword}.${this.getGetterMethod()}("doggo") || "Doggo";
|
|
353
|
-
`
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
EnhancedScript.prototype.injectParameters = function () {
|
|
357
|
-
return [...this.getJSONParams(), ...this.getMissingJSONParams()].map( param => {
|
|
358
|
-
let value = param.type ? this.getStringifiedDefaultValue(param.type) : this.getDefaultValue(this.defaults.global);
|
|
359
|
-
|
|
360
|
-
if (param.value ) {
|
|
361
|
-
value = this.detectValueType(param);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return `${this.getKeyword()}.${this.getSetterMethod()}('${param.key}', ${value});`
|
|
365
|
-
}).join("\n");
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
EnhancedScript.prototype.buildParameterConfigurator = function () {
|
|
369
|
-
const defaultGlobalValue = this.getStringifiedDefaultValue( this.defaults.global );
|
|
370
|
-
// const defaultFnValue = this.getDefaultValue( this.defaults.function );
|
|
371
|
-
const createParameterConfigurator = () => `
|
|
372
|
-
function ParameterConfigurator () {
|
|
373
|
-
this.params = [];
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
ParameterConfigurator.prototype.set = function (k, v) {
|
|
377
|
-
this.params.push({
|
|
378
|
-
key: k,
|
|
379
|
-
value: v
|
|
380
|
-
});
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
ParameterConfigurator.prototype.call = function ( key, args ) {
|
|
384
|
-
for (var i = 0; i < this.params.length; i++) {
|
|
385
|
-
if (this.params[i].key == key) {
|
|
386
|
-
if (typeof this.params[i].value == "function") return this.params[i].value.apply(this, args && args.length > 0 ? args : []);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
ParameterConfigurator.prototype.get = function ( key, args ) {
|
|
393
|
-
for (var i = 0; i < this.params.length; i++) {
|
|
394
|
-
if (this.params[i].key == key) {
|
|
395
|
-
if (typeof this.params[i].value == "function") return this.call(key, args || []);
|
|
396
|
-
return this.params[i].value;
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
return ${ defaultGlobalValue }
|
|
400
|
-
};
|
|
401
|
-
var ${ this.getKeyword() } = new ParameterConfigurator();
|
|
402
|
-
|
|
403
|
-
// Parameter injection from job configuration
|
|
404
|
-
${ this.injectParameters() }
|
|
405
|
-
`;
|
|
406
|
-
|
|
407
|
-
return createParameterConfigurator();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
EnhancedScript.prototype.build = function () {
|
|
411
|
-
this.findMissingMatchesInJSX();
|
|
412
|
-
|
|
413
|
-
// Et voilà!
|
|
414
|
-
const enhancedScript = `(function() {
|
|
415
|
-
${this.buildParameterConfigurator()}
|
|
416
|
-
${this.getJSXScript()}
|
|
417
|
-
})();\n`;
|
|
418
|
-
|
|
419
|
-
// do not log the script (can be uncommented for debugging)
|
|
420
|
-
// this.getLogger().log(enhancedScript);
|
|
421
|
-
return enhancedScript;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const enhancedScript = new EnhancedScript(dest, src, parameters, keyword, defaults, jobID, settings.logger);
|
|
425
|
-
return enhancedScript.build();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
module.exports = (job, settings) => {
|
|
429
|
-
settings.logger.log(`[${job.uid}] running script assemble...`);
|
|
430
|
-
|
|
431
|
-
const data = [];
|
|
432
|
-
const base = job.workpath;
|
|
433
|
-
|
|
434
|
-
job.assets.map(asset => {
|
|
435
|
-
settings.trackCombined('Asset Script Wraps', {
|
|
436
|
-
job_id: job.uid, // anonymized internally
|
|
437
|
-
script_type: asset.type,
|
|
438
|
-
script_compostion_set: asset.composition !== undefined,
|
|
439
|
-
script_layer_strat: asset.layerName ? 'name' : 'index',
|
|
440
|
-
script_value_strat:
|
|
441
|
-
asset.value !== undefined ? 'value' : // eslint-disable-line no-nested-ternary, multiline-ternary
|
|
442
|
-
asset.expression !== undefined ? 'expression' : // eslint-disable-line multiline-ternary
|
|
443
|
-
undefined,
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
switch (asset.type) {
|
|
447
|
-
case 'video':
|
|
448
|
-
case 'audio':
|
|
449
|
-
case 'image':
|
|
450
|
-
data.push(wrapFootage(job, settings, asset));
|
|
451
|
-
break;
|
|
452
|
-
|
|
453
|
-
case 'data':
|
|
454
|
-
data.push(wrapData(job, settings, asset));
|
|
455
|
-
break;
|
|
456
|
-
|
|
457
|
-
case 'script':
|
|
458
|
-
data.push(wrapEnhancedScript(job, settings, asset));
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
/* write out assembled custom script file in the workpath */
|
|
464
|
-
job.scriptfile = path.join(base, `nexrender-${job.uid}-script.jsx`);
|
|
465
|
-
fs.writeFileSync(job.scriptfile, script
|
|
466
|
-
.replace('/*COMPOSITION*/', job.template.composition)
|
|
467
|
-
.replace('/*USERSCRIPT*/', () => data.join('\n'))
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
return Promise.resolve(job)
|
|
471
|
-
}
|