polotno-node 2.9.37 → 2.9.39

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 +67 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polotno-node",
3
- "version": "2.9.37",
3
+ "version": "2.9.39",
4
4
  "description": "Polotno workflow from NodeJS",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/video-parallel.js CHANGED
@@ -276,47 +276,75 @@ module.exports.jsonToVideo = async function jsonToVideo(
276
276
  .inputFPS(fps)
277
277
  .videoCodec('libx264')
278
278
  .outputOptions('-pix_fmt yuv420p')
279
- .videoFilters('scale=trunc(iw/2)*2:trunc(ih/2)*2'); // Ensure dimensions are divisible by 2
280
-
281
- // Add each audio segment as a separate input
282
- inputs.forEach((input, index) => {
283
- const inputStartSec = (input.inputStartTime / 1000).toFixed(3);
284
- const inputDurationSec = (
285
- (input.inputEndTime - input.inputStartTime) /
286
- 1000
287
- ).toFixed(3);
288
- const outputOffsetSec = (input.outputStartTime / 1000).toFixed(3);
289
- const volume = input.volume !== undefined ? input.volume : 1;
290
-
291
- // Apply `-itsoffset` before each input file to shift its audio stream
292
- ffmpegCmd
293
- .inputOptions([`-itsoffset ${outputOffsetSec}`]) // Shift the input time
294
- .input(input.mp4File) // Add the actual input file
295
- .inputOptions([`-ss ${inputStartSec}`, `-t ${inputDurationSec}`]); // Specify the timing to cut the audio
296
-
297
- // Apply volume filter to respect the input volume
298
- ffmpegCmd.audioFilters(`volume=${volume}`);
299
- });
300
-
301
- // Map the video stream from the image sequence
302
- ffmpegCmd.outputOptions(['-map 0:v']);
279
+ .videoFilters('scale=trunc(iw/2)*2:trunc(ih/2)*2');
280
+
281
+ const audioInputs = [];
282
+
283
+ // Use Promise.all to handle async ffprobe calls
284
+ Promise.all(
285
+ inputs.map((input, index) => {
286
+ return new Promise((resolveProbe) => {
287
+ const inputStartSec = (input.inputStartTime / 1000).toFixed(3);
288
+ const inputDurationSec = (
289
+ (input.inputEndTime - input.inputStartTime) /
290
+ 1000
291
+ ).toFixed(3);
292
+ const outputOffsetMs = Math.round(input.outputStartTime);
293
+ const volume = input.volume !== undefined ? input.volume : 1;
294
+
295
+ ffmpegCmd
296
+ .input(input.mp4File)
297
+ .inputOptions([`-ss ${inputStartSec}`, `-t ${inputDurationSec}`]);
298
+
299
+ ffmpeg.ffprobe(input.mp4File, (err, metadata) => {
300
+ if (
301
+ !err &&
302
+ metadata.streams.some((stream) => stream.codec_type === 'audio')
303
+ ) {
304
+ audioInputs.push({
305
+ index: index + 1,
306
+ delay: outputOffsetMs,
307
+ volume: volume,
308
+ });
309
+ }
310
+ resolveProbe();
311
+ });
312
+ });
313
+ })
314
+ ).then(() => {
315
+ // Map the video stream from the image sequence
316
+ ffmpegCmd.outputOptions(['-map 0:v']);
317
+
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('');
325
+ const filterComplex = `${audioFilters.join(
326
+ ';'
327
+ )};${audioMerge}amix=inputs=${
328
+ audioInputs.length
329
+ }:duration=longest[aout]`;
330
+
331
+ ffmpegCmd.complexFilter(filterComplex);
332
+ ffmpegCmd.outputOptions(['-map [aout]']);
333
+ }
303
334
 
304
- // Map each audio stream
305
- inputs.forEach((_, index) => {
306
- ffmpegCmd.outputOptions([`-map ${index + 1}:a?`]);
335
+ ffmpegCmd
336
+ .format(format)
337
+ .audioCodec('aac')
338
+ .on('start', (command) => console.log('FFmpeg command:', command))
339
+ .on('end', () => resolve())
340
+ .on('error', (err, stdout, stderr) => {
341
+ console.error('Error:', err.message);
342
+ console.error('stdout:', stdout);
343
+ console.error('stderr:', stderr);
344
+ reject(err);
345
+ })
346
+ .save(attrs.out);
307
347
  });
308
-
309
- ffmpegCmd
310
- .format(format)
311
- .audioCodec('aac')
312
- .on('end', () => resolve())
313
- .on('error', (err, stdout, stderr) => {
314
- console.log(err.message);
315
- console.log('stdout:\n' + stdout);
316
- console.log('stderr:\n' + stderr);
317
- reject(err);
318
- })
319
- .save(attrs.out);
320
348
  });
321
349
  } catch (e) {
322
350
  throw e;