@nexrender/core 1.43.3 → 1.44.1

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 CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@nexrender/core",
3
- "version": "1.43.3",
3
+ "version": "1.44.1",
4
4
  "main": "src/index.js",
5
5
  "author": "Inlife",
6
+ "homepage": "https://github.com/inlife/nexrender",
7
+ "bugs": "https://github.com/inlife/nexrender/issues",
8
+ "repository": "https://github.com/inlife/nexrender.git",
6
9
  "scripts": {
7
10
  "pkg-prelink": "node ../../misc/prelink.js"
8
11
  },
9
12
  "dependencies": {
10
- "@nexrender/types": "^1.43.1",
13
+ "@nexrender/types": "^1.44.1",
11
14
  "data-uri-to-buffer": "^3.0.0",
12
15
  "file-uri-to-path": "^2.0.0",
13
16
  "is-wsl": "^2.2.0",
@@ -15,8 +18,11 @@
15
18
  "match-all": "^1.2.5",
16
19
  "mime-types": "^2.1.29",
17
20
  "mkdirp": "^1.0.4",
21
+ "nanoid": "^3.2.0",
22
+ "posthog-node": "^3.1.1",
18
23
  "requireg": "^0.2.1",
19
- "rimraf": "^3.0.2"
24
+ "rimraf": "^3.0.2",
25
+ "systeminformation": "^5.18.3"
20
26
  },
21
27
  "peerDependencies": {
22
28
  "@nexrender/action-copy": "^1.0.0",
@@ -30,5 +36,5 @@
30
36
  "publishConfig": {
31
37
  "access": "public"
32
38
  },
33
- "gitHead": "9f836119c0636690b8ab32f73e4fb6919ae484f5"
39
+ "gitHead": "db4b6d36c4f0daa85b022538ca654539d2ddce96"
34
40
  }
package/readme.md CHANGED
@@ -71,5 +71,6 @@ Second one is responsible for mainly job-related operations of the full cycle: d
71
71
  * `addLicense` - boolean, providing false will disable ae_render_only_node.txt license file auto-creation (true by default)
72
72
  * `forceCommandLinePatch` - boolean, providing true will force patch re-installation
73
73
  * `onInstanceSpawn` - a callback, if provided, gets called when **aerender** instance is getting spawned, with instance pointer. Can be later used to kill a hung aerender process. Callback signature: `function (instance, job, settings) {}`
74
+ * `noAnalytics` - boolean, enables or disables built-in fully-anonymous analytics, false by default
74
75
  * `actions` - an object with keys corresponding to the `module` field when defining an action, value should be a function matching expected signature of an action. Used for defining actions programmatically without needing to package the action as a separate package
75
76
  * `cache` - boolean or string. Set the cache folder used by HTTP assets. If `true` will use the default path of `${workpath}/http-cache`, if set to a string it will be interpreted as a filesystem path to the cache folder.
@@ -237,7 +237,7 @@ nexrender.changeValueForKeypath = function (layer, keys, val) {
237
237
  }
238
238
  return { "value": o, "changed": true };
239
239
  } else {
240
- throw new Error("nexrender: Can't find a property sequence " + keys.join('.') + " for key: " + key + "within layer: " + layer);
240
+ throw new Error("nexrender: Can't find a property sequence " + keys.join('.') + " for key: " + key + "within layer: " + layer.name);
241
241
  }
242
242
  }
243
243
  }
@@ -0,0 +1,132 @@
1
+ const fs = require('fs')
2
+ const os = require('os')
3
+ const path = require('path')
4
+ const crypto = require('crypto')
5
+
6
+ const si = require('systeminformation')
7
+ const { nanoid } = require('nanoid')
8
+ const {PostHog} = require('posthog-node')
9
+ const childProcess = require('child_process')
10
+
11
+ const { version } = require('../../package.json')
12
+
13
+ const hash = (data, salt) => crypto
14
+ .createHmac('md5', salt)
15
+ .update(data)
16
+ .digest('hex');
17
+
18
+ const analyticsPublicKey = 'phc_AWcZMlCOqHJiFyFKSoxT9WSrRkdKDFxpiFn8Ww0ZMHu';
19
+ const analytics = new PostHog(analyticsPublicKey, {
20
+ host: 'https://eu.posthog.com',
21
+ flushAt: 1,
22
+ flushInterval: 0,
23
+ disableGeoip: true,
24
+ });
25
+
26
+ /**
27
+ * A helper function to force syncronous tracking
28
+ *
29
+ * @param {*} settings
30
+ * @param {*} event
31
+ * @param {*} properties
32
+ */
33
+ const forceSyncRequest = (settings, event, properties) => {
34
+ const args = JSON.stringify({settings, event, properties})
35
+ const proc = childProcess.fork(__filename, ['child', args], {
36
+ stdio: 'ignore',
37
+ })
38
+ }
39
+
40
+ /**
41
+ * Tracking function for analytics
42
+ *
43
+ * @param {*} settings
44
+ * @param {*} event
45
+ * @param {*} properties
46
+ * @returns {Promise<void>}
47
+ */
48
+ const track = async (settings, event, properties = {}, isRemote) => {
49
+ // if (isRemote) console.log('tracking', event, properties, settings)
50
+
51
+ if (settings.noAnalytics === true) return;
52
+
53
+ if (!settings.session) {
54
+ settings.session = hash(nanoid(), 'nexrender-session')
55
+ }
56
+
57
+ // make sure we have a unique id for this user
58
+ if (!settings.analyticsId) {
59
+ let filepath = path.join(os.homedir(), '.nexrender', 'uuid')
60
+ if (fs.existsSync(filepath)) {
61
+ settings.analyticsId = fs.readFileSync(filepath, 'utf8')
62
+ } else {
63
+ settings.analyticsId = hash(nanoid(), 'nexrender-analytics')
64
+ if (!fs.existsSync(path.dirname(filepath)))
65
+ fs.mkdirSync(path.dirname(filepath), { recursive: true })
66
+
67
+ fs.writeFileSync(filepath, settings.analyticsId)
68
+ }
69
+ }
70
+
71
+ // make sure we wait to send the event if there is an error
72
+ if (properties.forced === true) {
73
+ delete properties.forced
74
+ properties.$timestamp = (new Date()).toISOString();
75
+ return forceSyncRequest(settings, event, properties)
76
+ }
77
+
78
+ // collect system info
79
+ if (!settings.systemInfo) {
80
+ if (isRemote) console.log('collectiing systeminfo')
81
+
82
+ settings.systemInfo = await si.get({
83
+ cpu: 'manufacturer,brand,cores',
84
+ mem: 'total',
85
+ graphics: '*',
86
+ osInfo: 'platform,arch,distro,release',
87
+ dockerInfo: 'id',
88
+ })
89
+
90
+ properties.$set_once = {
91
+ sys_cpu_manufacturer: settings.systemInfo.cpu.manufacturer,
92
+ sys_cpu_brand: settings.systemInfo.cpu.brand,
93
+ sys_cpu_cores: settings.systemInfo.cpu.cores,
94
+ sys_mem_total: settings.systemInfo.mem.total,
95
+ sys_graphics_vendor: (settings.systemInfo.graphics.controllers[0] || []).vendor,
96
+ sys_graphics_model: (settings.systemInfo.graphics.controllers[0] || []).model,
97
+ sys_os_platform: settings.systemInfo.osInfo.platform,
98
+ sys_os_arch: settings.systemInfo.osInfo.arch,
99
+ sys_os_distro: settings.systemInfo.osInfo.distro,
100
+ sys_os_release: settings.systemInfo.osInfo.release,
101
+ sys_docker: !!settings.systemInfo.dockerInfo.id,
102
+ }
103
+ }
104
+
105
+ // anonymize job_id
106
+ if (properties.job_id) {
107
+ properties.job_id = hash(properties.job_id, settings.analyticsId)
108
+ }
109
+
110
+ const params = {
111
+ distinctId: settings.analyticsId,
112
+ event,
113
+ properties: Object.assign({
114
+ process: settings.process,
115
+ version: version,
116
+ debug: settings.debug,
117
+ $timestamp: (new Date()).toISOString(),
118
+ $session_id: settings.session,
119
+ }, properties)
120
+ }
121
+
122
+ analytics.capture(params);
123
+ await analytics.flush();
124
+ }
125
+
126
+ if (process.argv[2] === 'child') {
127
+ const args = JSON.parse(process.argv[3])
128
+ track(args.settings, args.event, args.properties, true)
129
+ .then(() => {})
130
+ }
131
+
132
+ module.exports = track
@@ -13,15 +13,24 @@ module.exports = (settings) => {
13
13
  settings.logger.log(' - ' + nodefile1)
14
14
  settings.logger.log(' - ' + nodefile2)
15
15
 
16
+ let applied = false
17
+
16
18
  if (!fs.existsSync(adobe)) {
17
19
  fs.mkdirSync(adobe)
18
20
  }
19
21
 
20
22
  if (!fs.existsSync(nodefile1)) {
21
23
  fs.writeFileSync(nodefile1, '')
24
+ applied = true
22
25
  }
23
26
 
24
27
  if (!fs.existsSync(nodefile2)) {
25
28
  fs.writeFileSync(nodefile2, '')
29
+ applied = true
30
+ }
31
+
32
+ if (applied) {
33
+ settings.track('Init Render License Added')
34
+ settings.logger.log('added render-only-node licenses for After Effects')
26
35
  }
27
36
  }
@@ -39,8 +39,11 @@ module.exports = (settings) => {
39
39
  if (patchedMatch[1] !== existingMatch[1]) {
40
40
  try {
41
41
  settings.logger.log('out-of-date version of the commandLineRenderer.jsx patch is detected, attepmting to update')
42
+ settings.track('Init Patch Update Succeeded')
42
43
  writeTo(patched, originalFile)
43
44
  } catch (err) {
45
+ settings.trackSync('Init Patch Update Failed')
46
+
44
47
  if (err.code == 'EPERM') {
45
48
  settings.logger.log('\n\n -- E R R O R --\n');
46
49
  settings.logger.log('you need to run application with admin priviledges once');
@@ -62,6 +65,7 @@ module.exports = (settings) => {
62
65
 
63
66
  if (settings.forceCommandLinePatch) {
64
67
  settings.logger.log('forced rewrite of command line patch')
68
+ settings.track('Init Patch Forced')
65
69
  writeTo(patched, originalFile)
66
70
  }
67
71
  } else {
@@ -75,7 +79,11 @@ module.exports = (settings) => {
75
79
  settings.logger.log('patching the command line script')
76
80
  fs.chmodSync(originalFile, '755');
77
81
  writeTo(patched, originalFile)
82
+
83
+ settings.track('Init Patch Install Succeeded')
78
84
  } catch (err) {
85
+ settings.trackSync('Init Patch Install Failed')
86
+
79
87
  if (err.code == 'EPERM') {
80
88
  settings.logger.log('\n\n -- E R R O R --\n');
81
89
  settings.logger.log('you need to run application with admin priviledges once');
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ const license = require('./helpers/license')
10
10
  const autofind = require('./helpers/autofind')
11
11
  const patch = require('./helpers/patch')
12
12
  const state = require('./helpers/state')
13
+ const track = require('./helpers/analytics')
13
14
 
14
15
  const setup = require('./tasks/setup')
15
16
  const predownload = require('./tasks/actions')('predownload')
@@ -36,13 +37,18 @@ if (process.env.NEXRENDER_REQUIRE_PLUGINS) {
36
37
  require('@nexrender/provider-sftp');
37
38
  }
38
39
 
39
- //
40
- // https://video.stackexchange.com/questions/16706/rendered-file-with-after-effects-is-very-huge
41
- //
42
-
43
40
  const init = (settings) => {
44
41
  settings = Object.assign({}, settings);
45
42
  settings.logger = settings.logger || console;
43
+ settings.track = (...args) => track(settings, ...args);
44
+ settings.trackSync = (event, params) => track(settings, event, { forced: true, ...params })
45
+
46
+ // set default process name for analytics
47
+ if (!settings.process) {
48
+ settings.process = 'nexrender-core';
49
+ }
50
+
51
+ settings.trackSync('Init Started')
46
52
 
47
53
  // check for WSL
48
54
  settings.wsl = isWsl
@@ -51,12 +57,16 @@ const init = (settings) => {
51
57
  const binaryUser = settings.binary && fs.existsSync(settings.binary) ? settings.binary : null;
52
58
 
53
59
  if (!binaryUser && !binaryAuto) {
60
+ settings.trackSync('Init Failed', { error: 'no_aerender_binary_found' })
54
61
  throw new Error('you should provide a proper path to After Effects\' "aerender" binary')
55
62
  }
56
63
 
57
64
  if (binaryAuto && !binaryUser) {
58
65
  settings.logger.log('using automatically determined directory of After Effects installation:')
59
66
  settings.logger.log(' - ' + binaryAuto)
67
+ settings.aeBinaryStrategy = 'auto';
68
+ } else {
69
+ settings.aeBinaryStrategy = 'manual';
60
70
  }
61
71
 
62
72
  settings = Object.assign({
@@ -82,15 +92,19 @@ const init = (settings) => {
82
92
  binary: binaryUser || binaryAuto,
83
93
  })
84
94
 
95
+ // try to detect version
96
+ settings.aeBinaryVersion = (settings.binary.match(/([0-9]{4})\/Support Files/) || [])[1] || 'Unknown';
97
+
85
98
  // make sure we will have absolute path
86
99
  if (!path.isAbsolute(settings.workpath)) {
87
100
  settings.workpath = path.join(process.cwd(), settings.workpath);
88
101
  }
89
102
 
90
103
  // if WSL, ask user to define Mapping
91
- if (settings.wsl && !settings.wslMap)
104
+ if (settings.wsl && !settings.wslMap) {
105
+ settings.trackSync('Init Failed', { error: 'wsl_detected_no_map' });
92
106
  throw new Error('WSL detected: provide your WSL drive map; ie. "Z"')
93
-
107
+ }
94
108
 
95
109
  // add license helper
96
110
  if (settings.addLicense) {
@@ -101,6 +115,24 @@ const init = (settings) => {
101
115
  // Scripts/commandLineRenderer.jsx
102
116
  patch(settings);
103
117
 
118
+ settings.trackSync('Init Succeeded', {
119
+ ae_binary_strategy: settings.aeBinaryStrategy,
120
+ ae_binary_version: settings.aeBinaryVersion,
121
+ ae_multi_frames: settings.multiFrames,
122
+ ae_max_memory_percent: !!settings.maxMemoryPercent,
123
+ ae_image_cache_percent: !!settings.imageCachePercent,
124
+
125
+ wsl_is_detected: !!settings.wsl,
126
+ wsl_map: !!settings.wslMap,
127
+
128
+ set_add_license: settings.addLicense,
129
+ set_force_patch: settings.forceCommandLinePatch,
130
+ set_skip_cleanup: settings.skipCleanup,
131
+ set_skip_render: settings.skipRender,
132
+ set_stop_onerror: settings.stopOnError,
133
+ set_custom_workpath: settings.workpath !== path.join(os.tmpdir(), 'nexrender'),
134
+ })
135
+
104
136
  return settings;
105
137
  }
106
138
 
@@ -130,5 +162,5 @@ const render = (jobConfig, settings = {}) => {
130
162
 
131
163
  module.exports = {
132
164
  init,
133
- render
165
+ render,
134
166
  }
@@ -21,9 +21,15 @@ module.exports = actionType => (job, settings) => {
21
21
  settings.logger.log(`[${job.uid}] applying ${actionType} actions...`);
22
22
 
23
23
  return PromiseSerial((job.actions[actionType] || []).map(action => () => {
24
- if(settings.actions && settings.actions[action.module]){
24
+ settings.track(`Job Action Started`, {
25
+ job_id: job.uid, // anonymized internally
26
+ action_type: actionType,
27
+ action_module: action.module,
28
+ })
29
+
30
+ if (settings.actions && settings.actions[action.module]) {
25
31
  return settings.actions[action.module](job, settings, action, actionType);
26
- }else{
32
+ } else {
27
33
  return requireg(action.module)(job, settings, action, actionType).catch(err => {
28
34
  return Promise.reject(new Error(`Error loading ${actionType} module ${action.module}: ${err}`));
29
35
  });
@@ -1,15 +1,13 @@
1
1
  const fs = require('fs')
2
2
  const url = require('url')
3
3
  const path = require('path')
4
+ const requireg = require('requireg')
4
5
  const fetch = require('make-fetch-happen')
5
6
  const uri2path = require('file-uri-to-path')
6
7
  const data2buf = require('data-uri-to-buffer')
7
- const mime = require('mime-types')
8
+ const mime = require('mime-types')
8
9
  const {expandEnvironmentVariables} = require('../helpers/path')
9
10
 
10
- // TODO: redeuce dep size
11
- const requireg = require('requireg')
12
-
13
11
  const download = (job, settings, asset) => {
14
12
  if (asset.type == 'data') return Promise.resolve();
15
13
 
@@ -50,6 +48,13 @@ const download = (job, settings, asset) => {
50
48
 
51
49
  asset.dest = path.join(job.workpath, destName);
52
50
 
51
+ settings.track('Job Asset Download Started', {
52
+ job_id: job.uid, // anonymized internally
53
+ asset_type: asset.type,
54
+ asset_protocol: protocol,
55
+ asset_extension: asset.extension,
56
+ })
57
+
53
58
  switch (protocol) {
54
59
  /* built-in handlers */
55
60
  case 'data':
@@ -72,7 +77,7 @@ const download = (job, settings, asset) => {
72
77
  path.join(settings.workpath, "http-cache") :
73
78
  settings.cache;
74
79
 
75
- if(!asset.params) asset.params = {};
80
+ if (!asset.params) asset.params = {};
76
81
  // Asset's own `params.cachePath` takes precedence (including falsy values)
77
82
  asset.params.cachePath = Object.hasOwn(asset.params, 'cachePath') ?
78
83
  asset.params.cachePath :
@@ -150,6 +150,28 @@ Estimated date of change to the new behavior: 2023-06-01.\n`);
150
150
  return data;
151
151
  }
152
152
 
153
+ settings.track('Job Render Started', {
154
+ job_id: job.uid, // anonymized internally
155
+ job_output_module: job.template.outputModule,
156
+ job_settings_template: job.template.settingsTemplate,
157
+ job_output_settings: job.template.outputSettings,
158
+ job_render_settings: job.template.renderSettings,
159
+ job_frame_start_set: job.template.frameStart !== undefined,
160
+ job_frame_end_set: job.template.frameEnd !== undefined,
161
+ job_frame_increment_set: job.template.frameIncrement !== undefined,
162
+ job_continue_on_missing: job.template.continueOnMissing,
163
+ job_image_sequence: job.template.imageSequence,
164
+ job_multi_frames: settings.multiFrames,
165
+ job_settings_reuse: settings.reuse,
166
+ job_settings_skip_render: settings.skipRender,
167
+ job_settings_stop_on_error: settings.stopOnError,
168
+ job_settings_skip_cleanup: settings.skipCleanup,
169
+ job_settings_max_memory_percent: !!settings.maxMemoryPercent,
170
+ job_settings_image_cache_percent: !!settings.imageCachePercent,
171
+ job_settings_aeparams_set: !!settings['aeParams'],
172
+ job_settings_max_render_timeout: settings.maxRenderTimeout,
173
+ })
174
+
153
175
  // spawn process and begin rendering
154
176
  return new Promise((resolve, reject) => {
155
177
  renderStopwatch = Date.now();
@@ -170,6 +192,7 @@ Estimated date of change to the new behavior: 2023-06-01.\n`);
170
192
 
171
193
  instance.on('error', err => {
172
194
  clearTimeout(timeoutID);
195
+ settings.trackSync('Job Render Failed', { job_id: job.uid, error: 'aerender_spawn_error' });
173
196
  return reject(new Error(`Error starting aerender process: ${err}`));
174
197
  });
175
198
 
@@ -187,6 +210,7 @@ Estimated date of change to the new behavior: 2023-06-01.\n`);
187
210
  const timeout = 1000 * settings.maxRenderTimeout;
188
211
  timeoutID = setTimeout(
189
212
  () => {
213
+ settings.trackSync('Job Render Failed', { job_id: job.uid, error: 'aerender_timeout' });
190
214
  reject(new Error(`Maximum rendering time exceeded`));
191
215
  instance.kill('SIGINT');
192
216
  },
@@ -206,17 +230,30 @@ Estimated date of change to the new behavior: 2023-06-01.\n`);
206
230
  settings.logger.log(fs.readFileSync(logPath, 'utf8'))
207
231
  }
208
232
 
233
+ settings.trackSync('Job Render Failed', {
234
+ job_id: job.uid, // anonymized internally
235
+ exit_code: code,
236
+ error: 'aerender_exit_code',
237
+ });
238
+
209
239
  clearTimeout(timeoutID);
210
240
  return reject(new Error(outputStr || 'aerender.exe failed to render the output into the file due to an unknown reason'));
211
241
  }
212
242
 
213
- settings.logger.log(`[${job.uid}] rendering took ~${(Date.now() - renderStopwatch) / 1000} sec.`);
243
+ const renderTime = (Date.now() - renderStopwatch) / 1000
244
+ settings.logger.log(`[${job.uid}] rendering took ~${renderTime} sec.`);
214
245
  settings.logger.log(`[${job.uid}] writing aerender job log to: ${logPath}`);
215
246
 
216
247
  fs.writeFileSync(logPath, outputStr);
217
248
 
218
249
  /* resolve job without checking if file exists, or its size for image sequences */
219
250
  if (settings.skipRender || job.template.imageSequence || ['jpeg', 'jpg', 'png'].indexOf(outputFile) !== -1) {
251
+ settings.track('Job Render Finished', {
252
+ job_id: job.uid, // anonymized internally
253
+ job_finish_reason: 'skipped_check',
254
+ job_render_time: renderTime,
255
+ })
256
+
220
257
  clearTimeout(timeoutID);
221
258
  return resolve(job)
222
259
  }
@@ -244,6 +281,7 @@ Estimated date of change to the new behavior: 2023-06-01.\n`);
244
281
  settings.logger.log(fs.readFileSync(logPath, 'utf8'))
245
282
  }
246
283
 
284
+ settings.trackSync('Job Render Failed', { job_id: job.uid, error: 'aerender_output_not_found' });
247
285
  clearTimeout(timeoutID);
248
286
  return reject(new Error(`Couldn't find a result file: ${outputFile}`))
249
287
  }
@@ -256,6 +294,12 @@ Estimated date of change to the new behavior: 2023-06-01.\n`);
256
294
  settings.logger.log(`[${job.uid}] Warning: output file size is less than 1000 bytes (${stats.size} bytes), be advised that file is corrupted, or rendering is still being finished`)
257
295
  }
258
296
 
297
+ settings.track('Job Render Finished', {
298
+ job_id: job.uid, // anonymized internally
299
+ job_finish_reason: 'success',
300
+ job_render_time: renderTime,
301
+ });
302
+
259
303
  clearTimeout(timeoutID);
260
304
  resolve(job)
261
305
  });
@@ -432,6 +432,17 @@ module.exports = (job, settings) => {
432
432
  const base = job.workpath;
433
433
 
434
434
  job.assets.map(asset => {
435
+ settings.track('Job Script Asset Wrap', {
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
+
435
446
  switch (asset.type) {
436
447
  case 'video':
437
448
  case 'audio':
@@ -56,10 +56,17 @@ P.S. to prevent nexrender from removing temp file data, you also can please prov
56
56
 
57
57
  // setup paths
58
58
  job.workpath = path.join(settings.workpath, job.uid);
59
- job.output = job.output || path.join(job.workpath, job.resultname);
59
+ job.output = job.template.output || job.output || path.join(job.workpath, job.resultname);
60
60
  mkdirp.sync(job.workpath);
61
61
 
62
62
  settings.logger.log(`[${job.uid}] working directory is: ${job.workpath}`);
63
63
 
64
+ settings.track('Job Setup Succeeded', {
65
+ job_id: job.uid, // anonymized internally
66
+ job_set_output_ext: !!job.template.outputExt,
67
+ job_set_output_module: !!job.template.outputModule,
68
+ job_set_output_image_sequence: !!job.template.imageSequence,
69
+ })
70
+
64
71
  return Promise.resolve(job)
65
72
  };
@@ -0,0 +1 @@
1
+ // Command line renderer for After Effects. (nexrender-patch-v1.0.3)
package/test/aerender ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ console.log('imitating rendering with aerender :p')
3
+
4
+ console.log('rendering... 0%')
5
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000)
6
+ console.log('rendering... 50%')
7
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000)
8
+ console.log('rendering... 100%')
9
+
10
+ process.exit(0)
@@ -0,0 +1,13 @@
1
+ {
2
+ "template": {
3
+ "src": "data:text/plain,Hello, World!",
4
+ "composition": "test"
5
+ },
6
+ "assets": [
7
+ {
8
+ "type": "image",
9
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",
10
+ "layerName": "test"
11
+ }
12
+ ]
13
+ }
package/test/index.js CHANGED
@@ -1,2 +1,28 @@
1
- // simple test of code syntax
2
- require('../src')
1
+ // simple test code
2
+ const {render} = require('../src')
3
+
4
+ process.env.NEXRENDER_ENABLE_AELOG_PROJECT_FOLDER = true
5
+
6
+ const job = {
7
+ template: {
8
+ src: 'data:text/plain,Hello, World!',
9
+ composition: 'test',
10
+ output: __dirname + '/index.js',
11
+ },
12
+ assets: [
13
+ {
14
+ type: 'image',
15
+ src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
16
+ layerName: 'test',
17
+ }
18
+ ]
19
+ }
20
+
21
+ const settings = {
22
+ binary: __dirname + '/aerender',
23
+ workpath: __dirname + '/workpath',
24
+ }
25
+
26
+ render(job, settings)
27
+ .then(() => console.log('rendered successfully'))
28
+ .catch(console.error)