polotno-node 2.9.18 → 2.9.19

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/video-parallel.js +192 -178
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polotno-node",
3
- "version": "2.9.18",
3
+ "version": "2.9.19",
4
4
  "description": "Polotno workflow from NodeJS",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/video-parallel.js CHANGED
@@ -50,12 +50,15 @@ const fileToDataUrl = (filename) => {
50
50
  };
51
51
 
52
52
  const videoToDataURL = async (url, tempFolder) => {
53
- console.log('Downloading video: ' + url.slice(0, 50) + '...');
53
+ const shortUrl = url.slice(0, 50) + '...';
54
+ console.log('Downloading video: ' + shortUrl);
54
55
  const filename = Math.random().toString(36).substring(7);
55
56
  const destination = path.join(tempFolder.name, filename + '.mp4');
56
57
  await downloadVideo(url, destination);
58
+ console.log('Converting video to webm: ' + shortUrl);
57
59
  const webmFile = destination.replace('.mp4', '.webm');
58
60
  await convertToWebM(destination, webmFile);
61
+ fs.unlinkSync(destination);
59
62
  const dataURL = await fileToDataUrl(webmFile);
60
63
  console.log('Downloading video finished: ' + url.slice(0, 50) + '...');
61
64
  return {
@@ -74,208 +77,219 @@ const cachedVideoToDataURL = async (url, tempFolder) => {
74
77
 
75
78
  module.exports.jsonToVideo = async function jsonToVideo(inst, json, attrs) {
76
79
  const tempFolder = tmp.dirSync();
80
+ // const tempFolder = {
81
+ // name: './temp',
82
+ // };
77
83
 
78
- const videoEls = [];
84
+ try {
85
+ const videoEls = [];
79
86
 
80
- for (const page of json.pages) {
81
- for (const el of page.children) {
82
- if (el.type === 'video') {
83
- videoEls.push(el);
87
+ for (const page of json.pages) {
88
+ for (const el of page.children) {
89
+ if (el.type === 'video') {
90
+ videoEls.push(el);
91
+ }
84
92
  }
85
93
  }
86
- }
87
94
 
88
- await Promise.all(
89
- videoEls.map(async (el) => {
90
- const { dataURL, file } = await cachedVideoToDataURL(el.src, tempFolder);
91
- el.src = dataURL;
92
- el.file = file;
93
- })
94
- );
95
+ await Promise.all(
96
+ videoEls.map(async (el) => {
97
+ const { dataURL, file } = await cachedVideoToDataURL(
98
+ el.src,
99
+ tempFolder
100
+ );
101
+ el.src = dataURL;
102
+ el.file = file;
103
+ })
104
+ );
95
105
 
96
- const duration = json.pages.reduce((acc, page) => {
97
- return acc + (page.duration ?? 5000);
98
- }, 0);
99
- const parallel = attrs.parallel || 5;
106
+ const duration = json.pages.reduce((acc, page) => {
107
+ return acc + (page.duration ?? 5000);
108
+ }, 0);
109
+ const parallel = attrs.parallel || 5;
100
110
 
101
- const fps = attrs.fps || 15;
102
- const timePerFrame = 1000 / fps;
111
+ const fps = attrs.fps || 15;
112
+ const timePerFrame = 1000 / fps;
103
113
 
104
- // loop through the images and add each to the animation
105
- const framesNumber = Math.floor((duration / 1000) * fps);
106
- const frames = Array.from(Array(framesNumber).keys());
114
+ // loop through the images and add each to the animation
115
+ const framesNumber = Math.floor((duration / 1000) * fps);
116
+ const frames = Array.from(Array(framesNumber).keys());
107
117
 
108
- // split frames into parallel chunks
109
- const chunkSize = Math.ceil(framesNumber / parallel);
110
- const chunks = [];
111
- for (let i = 0; i < framesNumber; i += chunkSize) {
112
- chunks.push(frames.slice(i, i + chunkSize));
113
- }
118
+ // split frames into parallel chunks
119
+ const chunkSize = Math.ceil(framesNumber / parallel);
120
+ const chunks = [];
121
+ for (let i = 0; i < framesNumber; i += chunkSize) {
122
+ chunks.push(frames.slice(i, i + chunkSize));
123
+ }
114
124
 
115
- const pages = json.pages.map((p, index) => {
116
- const previousPage = json.pages[index - 1];
117
- const startTime = previousPage
118
- ? previousPage.startTime + previousPage.duration
119
- : 0;
120
- return {
121
- id: p.id,
122
- startTime,
123
- duration: p.duration,
124
- };
125
- });
125
+ const pages = json.pages.map((p, index) => {
126
+ const previousPage = json.pages[index - 1];
127
+ const startTime = previousPage
128
+ ? previousPage.startTime + previousPage.duration
129
+ : 0;
130
+ return {
131
+ id: p.id,
132
+ startTime,
133
+ duration: p.duration,
134
+ };
135
+ });
126
136
 
127
- let finishedFramesNumber = 0;
137
+ let finishedFramesNumber = 0;
128
138
 
129
- await Promise.all(
130
- chunks.map(async (chunk, chunkIndex) => {
131
- const instance = typeof inst === 'function' ? await inst() : inst;
132
- const page = await instance.createPage();
133
- await page.evaluate(async (json) => {
134
- store.loadJSON(json);
135
- await store.waitLoading();
136
- await new Promise((resolve) => {
137
- setTimeout(resolve, 100);
138
- });
139
- }, json);
139
+ await Promise.all(
140
+ chunks.map(async (chunk, chunkIndex) => {
141
+ const instance = typeof inst === 'function' ? await inst() : inst;
142
+ const page = await instance.createPage();
143
+ await page.evaluate(async (json) => {
144
+ store.loadJSON(json);
145
+ await store.waitLoading();
146
+ await new Promise((resolve) => {
147
+ setTimeout(resolve, 100);
148
+ });
149
+ }, json);
140
150
 
141
- let lastPageIndex = -1;
142
- for (const frameIndex of chunk) {
143
- let currentTime = frameIndex * timePerFrame;
144
- if (frameIndex === 0) {
145
- // offset the very first frame to enable animation start
146
- currentTime = 1;
151
+ let lastPageIndex = -1;
152
+ for (const frameIndex of chunk) {
153
+ let currentTime = frameIndex * timePerFrame;
154
+ if (frameIndex === 0) {
155
+ // offset the very first frame to enable animation start
156
+ currentTime = 1;
157
+ }
158
+ if (frameIndex === framesNumber - 1) {
159
+ // offset the very last frame to enable animation end
160
+ currentTime = duration - 1;
161
+ }
162
+ const storePageIndex = pages.findIndex((p) => {
163
+ return (
164
+ currentTime >= p.startTime &&
165
+ currentTime < p.startTime + p.duration
166
+ );
167
+ });
168
+ if (storePageIndex !== lastPageIndex) {
169
+ await page.evaluate(async (currentTime) => {
170
+ store.setCurrentTime(currentTime);
171
+ await store.waitLoading();
172
+ if (window.config.unstable_setTextOverflow) {
173
+ window.config.unstable_setTextOverflow('resize');
174
+ } else {
175
+ // console.error(
176
+ // 'Can not set text overflow mode. Define window.config.unstable_setTextOverflow function'
177
+ // );
178
+ }
179
+ });
180
+ }
181
+ const startTime = Date.now();
182
+ const dataURL = await page.evaluate(
183
+ async (json, attrs, currentTime) => {
184
+ store.setCurrentTime(currentTime);
185
+ const currentPage = store.pages.find((p) => {
186
+ return (
187
+ store.currentTime >= p.startTime &&
188
+ store.currentTime < p.startTime + p.duration
189
+ );
190
+ });
191
+ const url = await store.toDataURL({
192
+ ...attrs,
193
+ _skipTimeout: true,
194
+ mimeType: 'image/jpeg',
195
+ pageId: currentPage?.id,
196
+ });
197
+ return url;
198
+ },
199
+ json,
200
+ attrs || {},
201
+ currentTime
202
+ );
203
+ fs.writeFileSync(
204
+ `${tempFolder.name}/${frameIndex}.jpeg`,
205
+ dataURL.split(',')[1],
206
+ 'base64'
207
+ );
208
+ finishedFramesNumber += 1;
209
+ if (attrs.onProgress) {
210
+ const frameTime = Date.now() - startTime;
211
+ const progress = finishedFramesNumber / framesNumber;
212
+ attrs.onProgress(progress, frameTime);
213
+ }
147
214
  }
148
- if (frameIndex === framesNumber - 1) {
149
- // offset the very last frame to enable animation end
150
- currentTime = duration - 1;
215
+ await page.close();
216
+ if (typeof inst === 'function') {
217
+ await instance.close();
151
218
  }
152
- const storePageIndex = pages.findIndex((p) => {
153
- return (
154
- currentTime >= p.startTime && currentTime < p.startTime + p.duration
219
+ })
220
+ );
221
+
222
+ const inputs = [];
223
+ let pageStartTime = 0;
224
+ for (const page of json.pages) {
225
+ for (const el of page.children) {
226
+ if (el.type === 'video') {
227
+ const elStartTime = el.startTime * el.duration;
228
+ const elDuration = Math.min(
229
+ page.duration,
230
+ el.duration * (el.endTime - el.startTime)
155
231
  );
156
- });
157
- if (storePageIndex !== lastPageIndex) {
158
- await page.evaluate(async (currentTime) => {
159
- store.setCurrentTime(currentTime);
160
- await store.waitLoading();
161
- if (window.config.unstable_setTextOverflow) {
162
- window.config.unstable_setTextOverflow('resize');
163
- } else {
164
- // console.error(
165
- // 'Can not set text overflow mode. Define window.config.unstable_setTextOverflow function'
166
- // );
167
- }
232
+ const elEndTime = elStartTime + elDuration;
233
+ inputs.push({
234
+ file: el.file,
235
+ inputStartTime: elStartTime,
236
+ inputEndTime: elEndTime,
237
+ outputStartTime: pageStartTime,
238
+ outputEndTime: pageStartTime + page.duration,
168
239
  });
169
240
  }
170
- const startTime = Date.now();
171
- const dataURL = await page.evaluate(
172
- async (json, attrs, currentTime) => {
173
- store.setCurrentTime(currentTime);
174
- const currentPage = store.pages.find((p) => {
175
- return (
176
- store.currentTime >= p.startTime &&
177
- store.currentTime < p.startTime + p.duration
178
- );
179
- });
180
- const url = await store.toDataURL({
181
- ...attrs,
182
- _skipTimeout: true,
183
- mimeType: 'image/jpeg',
184
- pageId: currentPage?.id,
185
- });
186
- return url;
187
- },
188
- json,
189
- attrs || {},
190
- currentTime
191
- );
192
- fs.writeFileSync(
193
- `${tempFolder.name}/${frameIndex}.jpeg`,
194
- dataURL.split(',')[1],
195
- 'base64'
196
- );
197
- finishedFramesNumber += 1;
198
- if (attrs.onProgress) {
199
- const frameTime = Date.now() - startTime;
200
- const progress = finishedFramesNumber / framesNumber;
201
- attrs.onProgress(progress, frameTime);
202
- }
203
- }
204
- await page.close();
205
- if (typeof inst === 'function') {
206
- await instance.close();
207
- }
208
- })
209
- );
210
-
211
- const inputs = [];
212
- let pageStartTime = 0;
213
- for (const page of json.pages) {
214
- for (const el of page.children) {
215
- if (el.type === 'video') {
216
- const elStartTime = el.startTime * el.duration;
217
- const elDuration = Math.min(
218
- page.duration,
219
- el.duration * (el.endTime - el.startTime)
220
- );
221
- const elEndTime = elStartTime + elDuration;
222
- inputs.push({
223
- file: el.file,
224
- inputStartTime: elStartTime,
225
- inputEndTime: elEndTime,
226
- outputStartTime: pageStartTime,
227
- outputEndTime: pageStartTime + page.duration,
228
- });
229
241
  }
242
+ pageStartTime += page.duration;
230
243
  }
231
- pageStartTime += page.duration;
232
- }
233
244
 
234
- const format = attrs.out.split('.').pop() || 'mp4';
245
+ const format = attrs.out.split('.').pop() || 'mp4';
235
246
 
236
- await new Promise((resolve, reject) => {
237
- const ffmpegCmd = ffmpeg()
238
- .input(`${tempFolder.name}/%d.jpeg`)
239
- .inputFPS(fps)
240
- .videoCodec('libx264')
241
- .outputOptions('-pix_fmt yuv420p')
242
- .videoFilters('scale=trunc(iw/2)*2:trunc(ih/2)*2'); // Ensure dimensions are divisible by 2
247
+ await new Promise((resolve, reject) => {
248
+ const ffmpegCmd = ffmpeg()
249
+ .input(`${tempFolder.name}/%d.jpeg`)
250
+ .inputFPS(fps)
251
+ .videoCodec('libx264')
252
+ .outputOptions('-pix_fmt yuv420p')
253
+ .videoFilters('scale=trunc(iw/2)*2:trunc(ih/2)*2'); // Ensure dimensions are divisible by 2
243
254
 
244
- // Add each audio segment as a separate input
245
- inputs.forEach((input, index) => {
246
- const inputStartSec = (input.inputStartTime / 1000).toFixed(3);
247
- const inputDurationSec = (
248
- (input.inputEndTime - input.inputStartTime) /
249
- 1000
250
- ).toFixed(3);
251
- const outputOffsetSec = (input.outputStartTime / 1000).toFixed(3);
255
+ // Add each audio segment as a separate input
256
+ inputs.forEach((input, index) => {
257
+ const inputStartSec = (input.inputStartTime / 1000).toFixed(3);
258
+ const inputDurationSec = (
259
+ (input.inputEndTime - input.inputStartTime) /
260
+ 1000
261
+ ).toFixed(3);
262
+ const outputOffsetSec = (input.outputStartTime / 1000).toFixed(3);
252
263
 
253
- // Apply `-itsoffset` before each input file to shift its audio stream
254
- ffmpegCmd
255
- .inputOptions([`-itsoffset ${outputOffsetSec}`]) // Shift the input time
256
- .input(input.file) // Add the actual input file
257
- .inputOptions([`-ss ${inputStartSec}`, `-t ${inputDurationSec}`]); // Specify the timing to cut the audio
258
- });
264
+ // Apply `-itsoffset` before each input file to shift its audio stream
265
+ ffmpegCmd
266
+ .inputOptions([`-itsoffset ${outputOffsetSec}`]) // Shift the input time
267
+ .input(input.file) // Add the actual input file
268
+ .inputOptions([`-ss ${inputStartSec}`, `-t ${inputDurationSec}`]); // Specify the timing to cut the audio
269
+ });
259
270
 
260
- // Map the video stream from the image sequence
261
- ffmpegCmd.outputOptions(['-map 0:v']);
271
+ // Map the video stream from the image sequence
272
+ ffmpegCmd.outputOptions(['-map 0:v']);
262
273
 
263
- // Map each audio stream
264
- inputs.forEach((_, index) => {
265
- ffmpegCmd.outputOptions([`-map ${index + 1}:a?`]);
266
- });
267
-
268
- ffmpegCmd
269
- .format(format)
270
- .on('end', () => resolve())
271
- .on('error', (err, stdout, stderr) => {
272
- console.log(err.message);
273
- console.log('stdout:\n' + stdout);
274
- console.log('stderr:\n' + stderr);
275
- reject(err);
276
- })
277
- .save(attrs.out);
278
- });
274
+ // Map each audio stream
275
+ inputs.forEach((_, index) => {
276
+ ffmpegCmd.outputOptions([`-map ${index + 1}:a?`]);
277
+ });
279
278
 
280
- fs.rmSync(tempFolder.name, { recursive: true });
279
+ ffmpegCmd
280
+ .format(format)
281
+ .on('end', () => resolve())
282
+ .on('error', (err, stdout, stderr) => {
283
+ console.log(err.message);
284
+ console.log('stdout:\n' + stdout);
285
+ console.log('stderr:\n' + stderr);
286
+ reject(err);
287
+ })
288
+ .save(attrs.out);
289
+ });
290
+ } catch (e) {
291
+ throw e;
292
+ } finally {
293
+ fs.rmSync(tempFolder.name, { recursive: true });
294
+ }
281
295
  };