polotno-node 2.9.39 → 2.10.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/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-BWNdW2kw.js"></script>
11
+ <script type="module" crossorigin src="./assets/index-D56TTNQ-.js"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polotno-node",
3
- "version": "2.9.39",
3
+ "version": "2.10.1",
4
4
  "description": "Polotno workflow from NodeJS",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -26,7 +26,7 @@
26
26
  "gifencoder": "^2.0.1",
27
27
  "pixelmatch": "^6.0.0",
28
28
  "pngjs": "^7.0.0",
29
- "polotno": "^2.11.5",
29
+ "polotno": "^2.12.2",
30
30
  "react": "^18.3.1",
31
31
  "react-dom": "^18.3.1",
32
32
  "vite": "^5.3.4"
package/video-parallel.js CHANGED
@@ -116,6 +116,20 @@ module.exports.jsonToVideo = async function jsonToVideo(
116
116
  })
117
117
  );
118
118
 
119
+ const audios = [];
120
+ // download all audio tracks
121
+ await Promise.all(
122
+ (json.audios || []).map(async (audio) => {
123
+ const filename = Math.random().toString(36).substring(7);
124
+ const destination = path.join(tempFolder.name, filename);
125
+ await downloadVideo(audio.src, destination);
126
+ audios.push({
127
+ ...audio,
128
+ file: destination,
129
+ });
130
+ })
131
+ );
132
+
119
133
  const duration = json.pages.reduce((acc, page) => {
120
134
  return acc + (page.duration ?? 5000);
121
135
  }, 0);
@@ -139,11 +153,10 @@ module.exports.jsonToVideo = async function jsonToVideo(
139
153
  chunks.push(frames.slice(i, i + chunkSize));
140
154
  }
141
155
 
156
+ let lastTime = 0;
142
157
  const pages = json.pages.map((p, index) => {
143
- const previousPage = json.pages[index - 1];
144
- const startTime = previousPage
145
- ? previousPage.startTime + previousPage.duration
146
- : 0;
158
+ const startTime = lastTime;
159
+ lastTime = startTime + p.duration;
147
160
  return {
148
161
  id: p.id,
149
162
  startTime,
@@ -157,16 +170,19 @@ module.exports.jsonToVideo = async function jsonToVideo(
157
170
  chunks.map(async (chunk, chunkIndex) => {
158
171
  const instance = typeof inst === 'function' ? await inst() : inst;
159
172
  const page = await instance.createPage();
173
+ await page.evaluate(() => {
174
+ if (window.config?.onLoadError) {
175
+ window.config.onLoadError((error) => {
176
+ window._polotnoError = error;
177
+ });
178
+ } else {
179
+ console.error(
180
+ 'onLoadError function is not defined in the client. Error handling will not work.'
181
+ );
182
+ }
183
+ });
160
184
  page.setDefaultNavigationTimeout(60000); // Increase timeout to 60 seconds
161
185
 
162
- await page.evaluate(async (json) => {
163
- store.loadJSON(json);
164
- await store.waitLoading();
165
- await new Promise((resolve) => {
166
- setTimeout(resolve, 100);
167
- });
168
- }, json);
169
-
170
186
  let lastPageIndex = -1;
171
187
  for (const frameIndex of chunk) {
172
188
  let currentTime = frameIndex * timePerFrame;
@@ -185,9 +201,23 @@ module.exports.jsonToVideo = async function jsonToVideo(
185
201
  );
186
202
  });
187
203
  if (storePageIndex !== lastPageIndex) {
204
+ await page.evaluate(async (json) => {
205
+ store.loadJSON(json);
206
+ await store.waitLoading();
207
+ await new Promise((resolve) => {
208
+ setTimeout(resolve, 100);
209
+ });
210
+ }, json);
188
211
  await page.evaluate(async (currentTime) => {
189
212
  store.setCurrentTime(currentTime);
213
+ store.checkActivePage();
214
+ store.activePage.set({
215
+ _rendering: true,
216
+ });
190
217
  await store.waitLoading();
218
+ await new Promise((resolve) => {
219
+ setTimeout(resolve, 100);
220
+ });
191
221
  if (window.config.unstable_setTextOverflow) {
192
222
  window.config.unstable_setTextOverflow('resize');
193
223
  } else {
@@ -195,7 +225,8 @@ module.exports.jsonToVideo = async function jsonToVideo(
195
225
  // 'Can not set text overflow mode. Define window.config.unstable_setTextOverflow function'
196
226
  // );
197
227
  }
198
- });
228
+ }, currentTime);
229
+ lastPageIndex = storePageIndex;
199
230
  }
200
231
  const startTime = Date.now();
201
232
  const dataURL = await page.evaluate(
@@ -219,6 +250,25 @@ module.exports.jsonToVideo = async function jsonToVideo(
219
250
  attrs || {},
220
251
  currentTime
221
252
  );
253
+ const error = await page.evaluate(() => window._polotnoError);
254
+ if (error) {
255
+ // clear error
256
+ await page.evaluate(() => {
257
+ window._polotnoError = null;
258
+ });
259
+ const message = error.toString();
260
+ const isFontError =
261
+ message.indexOf('Timeout for loading font') >= 0;
262
+ const skipError = isFontError && attrs.skipFontError;
263
+
264
+ const isImageError = message.indexOf('image ') >= 0;
265
+ const skipImageError = isImageError && attrs.skipImageError;
266
+
267
+ if (!skipError && !skipImageError) {
268
+ throw new Error(error);
269
+ }
270
+ }
271
+ // TODO: add error handling!!!!
222
272
  fs.writeFileSync(
223
273
  `${tempFolder.name}/${frameIndex}.jpeg`,
224
274
  dataURL.split(',')[1],
@@ -315,21 +365,31 @@ module.exports.jsonToVideo = async function jsonToVideo(
315
365
  // Map the video stream from the image sequence
316
366
  ffmpegCmd.outputOptions(['-map 0:v']);
317
367
 
318
- // Prepare audio filter complex only if there are audio inputs
319
- if (audioInputs.length > 0) {
320
- const audioFilters = audioInputs.map(
321
- (input, i) =>
322
- `[${input.index}:a]adelay=${input.delay}|${input.delay},volume=${input.volume}[a${i}]`
323
- );
324
- const audioMerge = audioInputs.map((_, i) => `[a${i}]`).join('');
368
+ // Prepare audio filter complex for both video and pure audio inputs
369
+ const allAudioInputs = [...audioInputs, ...audios];
370
+ if (allAudioInputs.length > 0) {
371
+ const audioFilters = allAudioInputs.map((input, i) => {
372
+ const inputIndex = audioInputs.includes(input)
373
+ ? input.index
374
+ : audioInputs.length + i + 1;
375
+ return `[${inputIndex}:a]adelay=${input.delay}|${input.delay},volume=${input.volume},apad[a${i}]`;
376
+ });
377
+ const audioMerge = allAudioInputs.map((_, i) => `[a${i}]`).join('');
325
378
  const filterComplex = `${audioFilters.join(
326
379
  ';'
327
380
  )};${audioMerge}amix=inputs=${
328
- audioInputs.length
329
- }:duration=longest[aout]`;
381
+ allAudioInputs.length
382
+ }:dropout_transition=0,aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,apad,atrim=0:${
383
+ duration / 1000
384
+ }[aout]`;
330
385
 
331
386
  ffmpegCmd.complexFilter(filterComplex);
332
387
  ffmpegCmd.outputOptions(['-map [aout]']);
388
+
389
+ // Add pure audio inputs
390
+ audios.forEach((audio) => {
391
+ ffmpegCmd.input(audio.file);
392
+ });
333
393
  }
334
394
 
335
395
  ffmpegCmd