polotno-node 2.9.10 → 2.9.12
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/dist/assets/{index-DsJ35GHi.js → index-BwgLDHgn.js} +41 -41
- package/dist/index.html +1 -1
- package/index.js +1 -0
- package/package.json +8 -8
- package/video-parallel.js +70 -67
package/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
}
|
|
9
9
|
</style>
|
|
10
10
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
11
|
-
<script type="module" crossorigin src="./assets/index-
|
|
11
|
+
<script type="module" crossorigin src="./assets/index-BwgLDHgn.js"></script>
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
package/index.js
CHANGED
|
@@ -48,6 +48,7 @@ const minimal_args = [
|
|
|
48
48
|
// this line breaks the rendering on most of OS
|
|
49
49
|
// it doesn't allow creating of pages
|
|
50
50
|
//'--use-gl=swiftshader',
|
|
51
|
+
'--disable-gpu',
|
|
51
52
|
'--use-mock-keychain',
|
|
52
53
|
'--font-render-hinting=none',
|
|
53
54
|
'--disable-font-subpixel-positioning',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polotno-node",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.12",
|
|
4
4
|
"description": "Polotno workflow from NodeJS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"browserslist": "> 0.5%, last 2 versions, not dead",
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"ava": "^6.1.
|
|
21
|
+
"ava": "^6.1.3",
|
|
22
22
|
"axios": "^1.6.8",
|
|
23
23
|
"canvas": "^2.11.2",
|
|
24
24
|
"dotenv": "^16.4.5",
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
"gifencoder": "^2.0.1",
|
|
27
27
|
"pixelmatch": "^5.3.0",
|
|
28
28
|
"pngjs": "^7.0.0",
|
|
29
|
-
"polotno": "^2.
|
|
30
|
-
"react": "^18.
|
|
31
|
-
"react-dom": "^18.
|
|
32
|
-
"vite": "^5.2.
|
|
29
|
+
"polotno": "^2.5.1",
|
|
30
|
+
"react": "^18.3.1",
|
|
31
|
+
"react-dom": "^18.3.1",
|
|
32
|
+
"vite": "^5.2.11"
|
|
33
33
|
},
|
|
34
34
|
"author": "Anton Lavrenov",
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@sparticuz/chromium": "123.0.1",
|
|
38
|
-
"puppeteer": "22.
|
|
39
|
-
"puppeteer-core": "22.
|
|
38
|
+
"puppeteer": "22.8.0",
|
|
39
|
+
"puppeteer-core": "22.8.0",
|
|
40
40
|
"tmp": "^0.2.3"
|
|
41
41
|
},
|
|
42
42
|
"bugs": {
|
package/video-parallel.js
CHANGED
|
@@ -4,7 +4,6 @@ const axios = require('axios');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
const tmp = require('tmp');
|
|
7
|
-
|
|
8
7
|
tmp.setGracefulCleanup();
|
|
9
8
|
|
|
10
9
|
const downloadVideo = async (url, destination) => {
|
|
@@ -50,24 +49,23 @@ const fileToDataUrl = (filename) => {
|
|
|
50
49
|
});
|
|
51
50
|
};
|
|
52
51
|
|
|
53
|
-
const videoToDataURL = async (url) => {
|
|
54
|
-
console.log('downloading', url);
|
|
52
|
+
const videoToDataURL = async (url, tempFolder) => {
|
|
55
53
|
const filename = Math.random().toString(36).substring(7);
|
|
56
|
-
const tempFolder = tmp.dirSync();
|
|
57
54
|
const destination = path.join(tempFolder.name, filename + '.mp4');
|
|
58
55
|
await downloadVideo(url, destination);
|
|
59
56
|
const webmFile = destination.replace('.mp4', '.webm');
|
|
60
|
-
console.log('converting to webm', url);
|
|
61
57
|
await convertToWebM(destination, webmFile);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
const dataURL = await fileToDataUrl(webmFile);
|
|
59
|
+
return {
|
|
60
|
+
dataURL,
|
|
61
|
+
file: webmFile,
|
|
62
|
+
};
|
|
65
63
|
};
|
|
66
64
|
|
|
67
65
|
const urlCache = {};
|
|
68
|
-
const cachedVideoToDataURL = async (url) => {
|
|
66
|
+
const cachedVideoToDataURL = async (url, tempFolder) => {
|
|
69
67
|
if (!urlCache[url]) {
|
|
70
|
-
urlCache[url] = videoToDataURL(url);
|
|
68
|
+
urlCache[url] = videoToDataURL(url, tempFolder);
|
|
71
69
|
}
|
|
72
70
|
return urlCache[url];
|
|
73
71
|
};
|
|
@@ -76,11 +74,7 @@ function printProgress(progress) {
|
|
|
76
74
|
console.log('Rendering frame: ' + progress + '%');
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
module.exports.jsonToVideo = async function jsonToVideo(
|
|
80
|
-
createInstance,
|
|
81
|
-
json,
|
|
82
|
-
attrs
|
|
83
|
-
) {
|
|
77
|
+
module.exports.jsonToVideo = async function jsonToVideo(inst, json, attrs) {
|
|
84
78
|
const tempFolder = tmp.dirSync();
|
|
85
79
|
|
|
86
80
|
const videoEls = [];
|
|
@@ -95,7 +89,9 @@ module.exports.jsonToVideo = async function jsonToVideo(
|
|
|
95
89
|
|
|
96
90
|
await Promise.all(
|
|
97
91
|
videoEls.map(async (el) => {
|
|
98
|
-
|
|
92
|
+
const { dataURL, file } = await cachedVideoToDataURL(el.src, tempFolder);
|
|
93
|
+
el.src = dataURL;
|
|
94
|
+
el.file = file;
|
|
99
95
|
})
|
|
100
96
|
);
|
|
101
97
|
|
|
@@ -134,7 +130,7 @@ module.exports.jsonToVideo = async function jsonToVideo(
|
|
|
134
130
|
|
|
135
131
|
await Promise.all(
|
|
136
132
|
chunks.map(async (chunk, chunkIndex) => {
|
|
137
|
-
const instance = await
|
|
133
|
+
const instance = typeof inst === 'function' ? await inst() : inst;
|
|
138
134
|
const page = await instance.createPage();
|
|
139
135
|
await page.evaluate(async (json) => {
|
|
140
136
|
store.loadJSON(json);
|
|
@@ -198,68 +194,75 @@ module.exports.jsonToVideo = async function jsonToVideo(
|
|
|
198
194
|
'base64'
|
|
199
195
|
);
|
|
200
196
|
finishedFramesNumber += 1;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
if (attrs.onProgress) {
|
|
198
|
+
const progress = finishedFramesNumber / framesNumber;
|
|
199
|
+
attrs.onProgress(progress);
|
|
200
|
+
}
|
|
205
201
|
}
|
|
206
|
-
// TODO: why can't we just close the one created page?
|
|
207
|
-
// const browserPages = await instance.browser.pages();
|
|
208
|
-
// for (let i = 0; i < browserPages.length; i++) {
|
|
209
|
-
// await browserPages[i].close();
|
|
210
|
-
// }
|
|
211
202
|
await page.close();
|
|
212
|
-
if (
|
|
203
|
+
if (typeof inst === 'function') {
|
|
213
204
|
await instance.close();
|
|
214
205
|
}
|
|
215
206
|
})
|
|
216
207
|
);
|
|
217
208
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// ...attrs,
|
|
241
|
-
// pageId: currentPage?.id,
|
|
242
|
-
// });
|
|
243
|
-
// return url;
|
|
244
|
-
// },
|
|
245
|
-
// json,
|
|
246
|
-
// attrs || {},
|
|
247
|
-
// currentTime
|
|
248
|
-
// );
|
|
249
|
-
// const progress = ((i / (frames - 1)) * 100).toFixed(1);
|
|
250
|
-
// printProgress(progress);
|
|
251
|
-
// fs.mkdirSync('./tmp', { recursive: true });
|
|
252
|
-
// fs.writeFileSync(`./tmp/${i}.png`, dataURL.split(',')[1], 'base64');
|
|
253
|
-
// }
|
|
209
|
+
const inputs = [];
|
|
210
|
+
let pageStartTime = 0;
|
|
211
|
+
for (const page of json.pages) {
|
|
212
|
+
for (const el of page.children) {
|
|
213
|
+
if (el.type === 'video') {
|
|
214
|
+
const elStartTime = el.startTime * el.duration;
|
|
215
|
+
const elDuration = Math.min(
|
|
216
|
+
page.duration,
|
|
217
|
+
el.duration * (el.endTime - el.startTime)
|
|
218
|
+
);
|
|
219
|
+
const elEndTime = elStartTime + elDuration;
|
|
220
|
+
inputs.push({
|
|
221
|
+
file: el.file,
|
|
222
|
+
inputStartTime: elStartTime,
|
|
223
|
+
inputEndTime: elEndTime,
|
|
224
|
+
outputStartTime: pageStartTime,
|
|
225
|
+
outputEndTime: pageStartTime + page.duration,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
pageStartTime += page.duration;
|
|
230
|
+
}
|
|
254
231
|
|
|
255
232
|
const format = attrs.out.split('.').pop() || 'mp4';
|
|
256
233
|
|
|
257
234
|
await new Promise((resolve, reject) => {
|
|
258
|
-
ffmpeg()
|
|
259
|
-
.input(`${tempFolder.name}/%d.png`)
|
|
235
|
+
const ffmpegCmd = ffmpeg()
|
|
236
|
+
.input(`${tempFolder.name}/%d.png`)
|
|
260
237
|
.inputFPS(fps)
|
|
261
|
-
|
|
262
|
-
.outputOptions('-pix_fmt
|
|
238
|
+
.videoCodec('libx264')
|
|
239
|
+
.outputOptions('-pix_fmt yuv420p');
|
|
240
|
+
|
|
241
|
+
// Add each audio segment as a separate input
|
|
242
|
+
inputs.forEach((input, index) => {
|
|
243
|
+
const inputStartSec = (input.inputStartTime / 1000).toFixed(3);
|
|
244
|
+
const inputDurationSec = (
|
|
245
|
+
(input.inputEndTime - input.inputStartTime) /
|
|
246
|
+
1000
|
|
247
|
+
).toFixed(3);
|
|
248
|
+
const outputOffsetSec = (input.outputStartTime / 1000).toFixed(3);
|
|
249
|
+
|
|
250
|
+
// Apply `-itsoffset` before each input file to shift its audio stream
|
|
251
|
+
ffmpegCmd
|
|
252
|
+
.inputOptions([`-itsoffset ${outputOffsetSec}`]) // Shift the input time
|
|
253
|
+
.input(input.file) // Add the actual input file
|
|
254
|
+
.inputOptions([`-ss ${inputStartSec}`, `-t ${inputDurationSec}`]); // Specify the timing to cut the audio
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Map the video stream from the image sequence
|
|
258
|
+
ffmpegCmd.outputOptions(['-map 0:v']);
|
|
259
|
+
|
|
260
|
+
// Map each audio stream
|
|
261
|
+
inputs.forEach((_, index) => {
|
|
262
|
+
ffmpegCmd.outputOptions([`-map ${index + 1}:a`]);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
ffmpegCmd
|
|
263
266
|
.format(format)
|
|
264
267
|
.on('end', () => resolve())
|
|
265
268
|
.on('error', (err) => reject(err))
|