shiveshnavin commited on
Commit
5a54bc2
·
1 Parent(s): 721d060

Add sync API

Browse files
Files changed (7) hide show
  1. app.js +31 -26
  2. common-utils +1 -1
  3. package-lock.json +0 -0
  4. package.json +4 -1
  5. renderer.js +84 -0
  6. routes.js +124 -0
  7. start.sh +1 -1
app.js CHANGED
@@ -1,9 +1,6 @@
1
  import express from 'express';
2
- import stream from 'stream';
3
- import bodyParser from 'body-parser';
4
  import fs, {
5
  existsSync,
6
- unlinkSync,
7
  readFileSync,
8
  writeFileSync,
9
  copyFileSync,
@@ -13,16 +10,23 @@ import fs, {
13
  createWriteStream,
14
  } from 'fs';
15
  import path, {resolve as _resolve, join} from 'path';
16
- import {Utils, FileUploader, PerformanceRecorder, Auditlog} from 'common-utils';
17
- import {ZipFiles, UnzipFiles} from 'common-utils';
18
- import OracleStorage from './common-creds/oracle/storage.json' assert {type: 'json'};
19
- import {spawn, exec, ChildProcess} from 'child_process';
20
- import {FireStoreDB} from 'multi-db-orm';
21
- import {syllableCount} from 'syllable-count-english';
22
  import kill from 'tree-kill';
23
  import {fileURLToPath} from 'url';
24
  import {dirname} from 'path';
25
-
 
 
 
 
 
 
 
 
 
 
 
 
26
  const __filename = fileURLToPath(import.meta.url);
27
  const __dirname = dirname(__filename);
28
  const axios = Utils.getAxios();
@@ -179,6 +183,7 @@ function clear() {
179
  Utils.clearFolder(join(__dirname, './uploads'));
180
  } catch (e) {}
181
  }
 
182
  app.all('/clear', async (req, res) => {
183
  clear();
184
  res.send('Cache Cleared');
@@ -584,22 +589,22 @@ function doRenderPoster(jobId, statusCb) {
584
 
585
  let indexFile = join(__dirname, 'index.html');
586
 
587
- app.get('/execute', (req, res) => {
588
- const command = decodeURIComponent(req.query.cmd);
589
- console.log(command);
590
- exec(command, (error, stdout, stderr) => {
591
- if (error) {
592
- console.error(`Error executing command: ${error.message}`);
593
- res.status(500).json({error: error.message});
594
- return;
595
- }
596
- if (stderr) {
597
- console.error(`Command stderr: ${stderr}`);
598
- }
599
- console.log(stdout);
600
- res.send(stdout);
601
- });
602
- });
603
 
604
  app.get('/', (req, res) => {
605
  res.sendFile(indexFile);
 
1
  import express from 'express';
 
 
2
  import fs, {
3
  existsSync,
 
4
  readFileSync,
5
  writeFileSync,
6
  copyFileSync,
 
10
  createWriteStream,
11
  } from 'fs';
12
  import path, {resolve as _resolve, join} from 'path';
13
+ import {exec} from 'child_process';
 
 
 
 
 
14
  import kill from 'tree-kill';
15
  import {fileURLToPath} from 'url';
16
  import {dirname} from 'path';
17
+ import RenderRouter from './routes.js';
18
+
19
+ import pkg from 'common-utils';
20
+ const {
21
+ UnzipFiles,
22
+ ZipFiles,
23
+ Utils,
24
+ FileUploader,
25
+ PerformanceRecorder,
26
+ Auditlog,
27
+ Vault,
28
+ } = pkg;
29
+ const OracleStorage = Vault.get().readCredSyncJson('oracle/storage.json');
30
  const __filename = fileURLToPath(import.meta.url);
31
  const __dirname = dirname(__filename);
32
  const axios = Utils.getAxios();
 
183
  Utils.clearFolder(join(__dirname, './uploads'));
184
  } catch (e) {}
185
  }
186
+ app.use(RenderRouter);
187
  app.all('/clear', async (req, res) => {
188
  clear();
189
  res.send('Cache Cleared');
 
589
 
590
  let indexFile = join(__dirname, 'index.html');
591
 
592
+ // app.get('/execute', (req, res) => {
593
+ // const command = decodeURIComponent(req.query.cmd);
594
+ // console.log(command);
595
+ // exec(command, (error, stdout, stderr) => {
596
+ // if (error) {
597
+ // console.error(`Error executing command: ${error.message}`);
598
+ // res.status(500).json({error: error.message});
599
+ // return;
600
+ // }
601
+ // if (stderr) {
602
+ // console.error(`Command stderr: ${stderr}`);
603
+ // }
604
+ // console.log(stdout);
605
+ // res.send(stdout);
606
+ // });
607
+ // });
608
 
609
  app.get('/', (req, res) => {
610
  res.sendFile(indexFile);
common-utils CHANGED
@@ -1 +1 @@
1
- Subproject commit c05940669cca9677af3634138c1af20e98a45da0
 
1
+ Subproject commit a07eb36a227970fc69f0cb70e00e8b8a2306697b
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -53,8 +53,10 @@
53
  "@remotion/zod-types": "4.0.19",
54
  "@types/react": "^18.0.26",
55
  "@types/web": "^0.0.86",
 
56
  "axios": "^1.4.0",
57
  "body-parser": "^1.20.2",
 
58
  "command-line-args": "^6.0.0",
59
  "common-utils": "file:common-utils",
60
  "express": "^4.18.2",
@@ -85,6 +87,7 @@
85
  "tree-kill": "^1.2.2",
86
  "ts-node-dev": "^2.0.0",
87
  "typescript": "^4.9.4",
 
88
  "which": "^3.0.1",
89
  "ws": "^8.13.0",
90
  "zod": "^3.21.4"
@@ -93,4 +96,4 @@
93
  "devDependencies": {
94
  "@types/lodash": "^4.14.199"
95
  }
96
- }
 
53
  "@remotion/zod-types": "4.0.19",
54
  "@types/react": "^18.0.26",
55
  "@types/web": "^0.0.86",
56
+ "archiver": "^7.0.1",
57
  "axios": "^1.4.0",
58
  "body-parser": "^1.20.2",
59
+ "bottleneck": "^2.19.5",
60
  "command-line-args": "^6.0.0",
61
  "common-utils": "file:common-utils",
62
  "express": "^4.18.2",
 
87
  "tree-kill": "^1.2.2",
88
  "ts-node-dev": "^2.0.0",
89
  "typescript": "^4.9.4",
90
+ "unzipper": "^0.12.3",
91
  "which": "^3.0.1",
92
  "ws": "^8.13.0",
93
  "zod": "^3.21.4"
 
96
  "devDependencies": {
97
  "@types/lodash": "^4.14.199"
98
  }
99
+ }
renderer.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {copyFileSync, existsSync, readdirSync, unlinkSync} from 'fs';
2
+ import {join} from 'path';
3
+ import pkg from 'common-utils';
4
+ import {exec} from 'child_process';
5
+ import {dirname} from 'path';
6
+ import {fileURLToPath} from 'url';
7
+ const {UnzipFiles, Utils, ZipFiles} = pkg;
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ export async function explodeUrl(fileUrl, jobId, dir) {
13
+ const zipFile = `${dir}/exported-${jobId}.zip`;
14
+ await Utils.downloadFile(fileUrl, zipFile, true);
15
+ await UnzipFiles(zipFile, dir);
16
+ }
17
+ export async function listOutputFiles(jobId) {
18
+ let outDir = join(__dirname, 'out');
19
+ let manuFile = join(__dirname, `public/original_manuscript.json`);
20
+ copyFileSync(manuFile, join(__dirname, 'out', `original_manuscript.json`));
21
+ let outputFiles = readdirSync(outDir).map((fname) => {
22
+ const filePath = join(outDir, fname);
23
+ return filePath;
24
+ });
25
+ return outputFiles;
26
+ }
27
+
28
+ export async function generateOutputBundle(jobId) {
29
+ let outFile = join(__dirname, 'out', `output-${jobId}.zip`);
30
+ let outputFiles = await listOutputFiles(jobId);
31
+ if (existsSync(outFile)) {
32
+ unlinkSync(outFile);
33
+ }
34
+ await ZipFiles(outputFiles, outFile);
35
+ return outFile;
36
+ }
37
+
38
+ export function doRender(jobId, composition, sendToObserver) {
39
+ const npmScript = 'render-build';
40
+ const renderComposition = composition || 'SemibitComposition';
41
+ let outFile = `out/${jobId}-video.mp4`;
42
+ let cmd = `npm run ${npmScript} -- ${renderComposition} `;
43
+ const childProcess = exec(cmd);
44
+
45
+ console.log('Starting video render. ' + cmd);
46
+ let updateCounter = 0;
47
+ childProcess.stdout.on('data', (data) => {
48
+ sendToObserver(jobId, data);
49
+ if (!process.env.is_pm2) console.log(data.toString());
50
+ if (updateCounter++ % 100 == 0 || updateCounter < 5) {
51
+ console.log(data.split('\n')[0]);
52
+ }
53
+ });
54
+
55
+ childProcess.stderr.on('data', (data) => {
56
+ sendToObserver(jobId, data);
57
+ console.error(data.toString());
58
+ });
59
+
60
+ return new Promise((resolve, reject) => {
61
+ childProcess.on('close', (code) => {
62
+ console.log('Render video finished');
63
+ sendToObserver(jobId, code === 0 ? 'completed' : 'failed');
64
+ if (code === 0) {
65
+ resolve(outFile);
66
+ console.log(`'${npmScript}' completed successfully.`);
67
+ } else {
68
+ reject({
69
+ message: `'${npmScript}' failed with code ${code}.`,
70
+ });
71
+ console.error(`'${npmScript}' failed with code ${code}.`);
72
+ }
73
+ });
74
+ });
75
+ }
76
+
77
+ export function clear(skipOutput, skipPublic, skipUploads) {
78
+ try {
79
+ const preserve = ['assets', 'mp3'];
80
+ if (!skipPublic) Utils.clearFolder(join(__dirname, './public'), preserve);
81
+ if (!skipOutput) Utils.clearFolder(join(__dirname, './out'));
82
+ if (!skipUploads) Utils.clearFolder(join(__dirname, './uploads'));
83
+ } catch (e) {}
84
+ }
routes.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {Router} from 'express';
2
+ import Bottleneck from 'bottleneck';
3
+ import {
4
+ clear,
5
+ doRender,
6
+ explodeUrl,
7
+ generateOutputBundle,
8
+ listOutputFiles,
9
+ } from './renderer.js';
10
+ import path, {dirname} from 'path';
11
+ import {fileURLToPath} from 'url';
12
+ import pkg from 'common-utils';
13
+ const {Utils, PerformanceRecorder, FileUploader, Vault} = pkg;
14
+ import bodyParser from 'body-parser';
15
+
16
+ const OracleStorage = Vault.get().readCredSyncJson('oracle/storage.json');
17
+ const RenderRouter = Router();
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+ const limiter = new Bottleneck({
21
+ maxConcurrent: 1,
22
+ });
23
+ RenderRouter.use(bodyParser.json());
24
+ RenderRouter.use(bodyParser.urlencoded());
25
+ RenderRouter.post('/api/render-sync', async (req, res) => {
26
+ let fileUrl = req.body.fileUrl;
27
+ let targetUrl = req.body.targetUrl;
28
+ let skipClear = req.body.skip_clear;
29
+ let skipRender = req.body.skip_render;
30
+ let zip = req.body.zip ?? true;
31
+
32
+ if (!fileUrl) {
33
+ return res.status(400).send({
34
+ message:
35
+ 'Missing `fileUrl` in body. Required params `fileUrl`, `targetUrl`. Optionally pass `zip` to skip zipping and directly upload the files to targetUrl/{fileName}, `skip_clear` if you dont want to clear the folders after render, `skip_render` is you want to skip rendering, useful with `skip_clear` ',
36
+ });
37
+ }
38
+
39
+ if (!targetUrl) {
40
+ return res.status(400).send({
41
+ message:
42
+ 'Missing `targetUrl` in body. The result will be uploaded to `targetUrl`',
43
+ });
44
+ }
45
+
46
+ // make sure only i request is being processed at a time
47
+ // set headers appoprately to hint that timeout must be large
48
+
49
+ let jobId = req.body.jobId || Utils.generateUID(fileUrl);
50
+ res.setHeader('X-Job-Id', jobId);
51
+ res.setTimeout(0);
52
+ res.setHeader('Connection', 'keep-alive');
53
+ if (!skipClear) {
54
+ clear();
55
+ }
56
+ let logs = [];
57
+ try {
58
+ async function run() {
59
+ let pref = new PerformanceRecorder();
60
+ await explodeUrl(fileUrl, jobId, path.join(__dirname, 'public'));
61
+ let originalManuscript = Utils.readFileToObject(
62
+ path.join(__dirname, `public/original_manuscript.json`)
63
+ );
64
+ if (!skipRender) {
65
+ await doRender(
66
+ jobId,
67
+ originalManuscript.meta.renderComposition,
68
+ (jobId, log) => {
69
+ logs.push(log);
70
+ }
71
+ );
72
+ }
73
+ const uploader = new FileUploader('oracle', {
74
+ url: targetUrl + jobId + '/',
75
+ });
76
+ uploader.creds.url = uploader.creds.url;
77
+ if (zip) {
78
+ let outFile = await generateOutputBundle(jobId);
79
+ let uploadResult = await uploader.upload(outFile);
80
+ if (!skipClear) {
81
+ clear();
82
+ }
83
+ return {
84
+ response_time: pref.elapsed(),
85
+ urls: [uploadResult.url],
86
+ url: uploadResult.url,
87
+ original_manuscript: originalManuscript,
88
+ };
89
+ } else {
90
+ let outFiles = await listOutputFiles(jobId);
91
+ let urls = await Promise.all(
92
+ outFiles.map((file) => {
93
+ return uploader.upload(file);
94
+ })
95
+ ).then((results) => {
96
+ return results.map((result) => result.url);
97
+ });
98
+ let url =
99
+ urls.find((u) => u.includes('.mp4')) ||
100
+ urls.find((u) => u.includes('.jpg')) ||
101
+ urls.find((u) => u.includes('.jpeg')) ||
102
+ urls.find((u) => u.includes('.png'));
103
+ return {
104
+ response_time: pref.elapsed(),
105
+ urls: urls,
106
+ url,
107
+ original_manuscript: originalManuscript,
108
+ };
109
+ }
110
+ }
111
+
112
+ const result = await limiter.schedule(run);
113
+ res.status(200).json({jobId, success: true, ...result});
114
+ } catch (err) {
115
+ console.error(`Render job ${jobId} failed:`, err.message);
116
+ res.status(500).json({
117
+ jobId,
118
+ success: false,
119
+ message: err.message + ' Details : ' + logs.join('\n'),
120
+ });
121
+ }
122
+ });
123
+
124
+ export default RenderRouter;
start.sh CHANGED
@@ -1,4 +1,4 @@
1
- export DISPLAY=:11.0 && is_pm2=1 node server.js
2
 
3
 
4
  # sudo nano /etc/systemd/system/media-render-farm.service
 
1
+ export DISPLAY=:10.0 && is_pm2=1 node server.js
2
 
3
 
4
  # sudo nano /etc/systemd/system/media-render-farm.service