sfxmix 1.4.2 → 1.4.6

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/index.js +52 -36
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -232,14 +232,18 @@ class SfxMix {
232
232
  }
233
233
  } else {
234
234
  // Save as MP3 or original format (no conversion needed)
235
- try {
236
- fs.renameSync(this.currentFile, absoluteOutput);
237
- } catch (renameErr) {
238
- // If rename fails, try to copy the file instead
239
- fs.copyFileSync(this.currentFile, absoluteOutput);
240
- if (this.isTempFile(this.currentFile)) {
235
+ if (this.isTempFile(this.currentFile)) {
236
+ // Temp file: move it (rename) to the output path
237
+ try {
238
+ fs.renameSync(this.currentFile, absoluteOutput);
239
+ } catch (renameErr) {
240
+ // Cross-device rename fails: copy + delete
241
+ fs.copyFileSync(this.currentFile, absoluteOutput);
241
242
  this.safeDeleteFile(this.currentFile);
242
243
  }
244
+ } else {
245
+ // Original user file: always copy, never move
246
+ fs.copyFileSync(this.currentFile, absoluteOutput);
243
247
  }
244
248
  }
245
249
 
@@ -481,35 +485,52 @@ class SfxMix {
481
485
  return new Promise(async (resolve, reject) => {
482
486
  try {
483
487
  // Default options for silenceremove filter
484
- // startDuration: minimum duration of silence to detect at start (in seconds)
485
- // startThreshold: noise tolerance for start (in dB, e.g., -50dB)
486
- // stopDuration: minimum duration of silence to detect at end (in seconds)
487
- // stopThreshold: noise tolerance for end (in dB)
488
+ // startDuration: min duration of non-silence to stop trimming at start (in seconds). 0 = stop at first non-silent sample.
489
+ // startThreshold: noise tolerance for start (in dB, e.g., -50dB). Higher values (e.g. -20) are more aggressive.
490
+ // stopDuration: min duration of non-silence to stop trimming at end (in seconds).
491
+ // Default 0.05s (50ms) to skip short noise artifacts/blips at the end that would prevent proper trimming.
492
+ // stopThreshold: noise tolerance for end (in dB, e.g., -50dB). Higher values (e.g. -20) are more aggressive.
488
493
  // paddingStart: milliseconds of silence to add at the start (after removing silence)
489
494
  // paddingEnd: milliseconds of silence to add at the end (after removing silence)
490
495
 
491
496
  const startDuration = options.startDuration !== undefined ? options.startDuration : 0;
492
- const startThreshold = options.startThreshold !== undefined ? options.startThreshold : -40;
493
- const stopDuration = options.stopDuration !== undefined ? options.stopDuration : 0;
494
- const stopThreshold = options.stopThreshold !== undefined ? options.stopThreshold : -40;
497
+ const startThreshold = options.startThreshold !== undefined ? options.startThreshold : -50;
498
+ const stopDuration = options.stopDuration !== undefined ? options.stopDuration : 0.05;
499
+ const stopThreshold = options.stopThreshold !== undefined ? options.stopThreshold : -50;
495
500
  const paddingStart = options.paddingStart || 0; // in milliseconds
496
501
  const paddingEnd = options.paddingEnd || 0; // in milliseconds
497
502
 
503
+ let bitrateKbps;
504
+ let audioInfo = null;
505
+
506
+ // If bitrate is null, auto-detect from input file
507
+ if (this.bitrate === null) {
508
+ try {
509
+ audioInfo = await this.getAudioInfo(inputFile);
510
+ } catch (err) {
511
+ console.warn('Could not get audio info for trim, using 128k default:', err.message);
512
+ }
513
+ bitrateKbps = audioInfo ? Math.floor(audioInfo.bitrate / 1000) + 'k' : '128k';
514
+ } else {
515
+ // Use configured default bitrate
516
+ bitrateKbps = Math.floor(this.bitrate / 1000) + 'k';
517
+ }
518
+
498
519
  // Build filter chain
499
520
  // Strategy: Remove silence from start, then reverse audio, remove silence from new start (old end), then reverse back
500
521
  // This ensures we ONLY remove silence from the beginning and end, preserving intermediate silences
501
522
  const filters = [];
502
523
 
503
- // Step 1: Remove silence from the start
524
+ // Remove silence from the start
504
525
  filters.push(`silenceremove=start_periods=1:start_duration=${startDuration}:start_threshold=${startThreshold}dB:detection=peak`);
505
526
 
506
- // Step 2: Reverse the audio
527
+ // Reverse the audio
507
528
  filters.push('areverse');
508
529
 
509
- // Step 3: Remove silence from what is now the start (but was the end)
530
+ // Remove silence from what is now the start (but was the end)
510
531
  filters.push(`silenceremove=start_periods=1:start_duration=${stopDuration}:start_threshold=${stopThreshold}dB:detection=peak`);
511
532
 
512
- // Step 4: Reverse back to original direction
533
+ // Reverse back to original direction
513
534
  filters.push('areverse');
514
535
 
515
536
  // Add padding at start if specified
@@ -523,24 +544,13 @@ class SfxMix {
523
544
  filters.push(`apad=pad_dur=${paddingSec}`);
524
545
  }
525
546
 
526
- const filterChain = filters.join(',');
547
+ // CRITICAL: Resample at the end to fix "inadequate AVFrame plane padding" error
548
+ // This regenerates the audio frames with proper padding for the encoder
549
+ filters.push('aresample=async=1:min_hard_comp=0.100000:first_pts=0');
527
550
 
528
- let bitrateKbps;
529
-
530
- // If bitrate is null, auto-detect from input file
531
- if (this.bitrate === null) {
532
- let audioInfo = null;
533
- try {
534
- audioInfo = await this.getAudioInfo(inputFile);
535
- } catch (err) {
536
- console.warn('Could not get audio info for trim, using 128k default:', err.message);
537
- }
538
- bitrateKbps = audioInfo ? Math.floor(audioInfo.bitrate / 1000) + 'k' : '128k';
539
- } else {
540
- // Use configured default bitrate
541
- bitrateKbps = Math.floor(this.bitrate / 1000) + 'k';
542
- }
551
+ const filterChain = filters.join(',');
543
552
 
553
+ // Apply filters directly on input file
544
554
  const command = ffmpeg()
545
555
  .input(inputFile)
546
556
  .audioFilters(filterChain)
@@ -551,7 +561,13 @@ class SfxMix {
551
561
 
552
562
  command
553
563
  .on('end', () => resolve())
554
- .on('error', (err) => reject(err))
564
+ .on('error', (err, stdout, stderr) => {
565
+ console.error('FFmpeg trim error:', err.message);
566
+ if (stderr) {
567
+ console.error('FFmpeg stderr:', stderr);
568
+ }
569
+ reject(err);
570
+ })
555
571
  .run();
556
572
  } catch (err) {
557
573
  reject(err);
@@ -563,10 +579,10 @@ class SfxMix {
563
579
  switch (filterName) {
564
580
  case 'normalize':
565
581
  // Normalize audio using loudnorm filter (EBU R128)
566
- // i: integrated loudness target in LUFS (default: -16)
582
+ // i: integrated loudness target in LUFS (default: -18)
567
583
  // lra: loudness range target in LU (default: 11)
568
584
  // tp: true peak in dBTP (configurable, typically -3dB or -0.1dB)
569
- const i = options.i !== undefined ? options.i : -16; // Target loudness
585
+ const i = options.i !== undefined ? options.i : -18; // Target loudness
570
586
  const lra = options.lra !== undefined ? options.lra : 11; // Loudness range
571
587
  const tp = options.tp !== undefined ? options.tp : -3; // True peak
572
588
  return `loudnorm=I=${i}:LRA=${lra}:TP=${tp}`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sfxmix",
3
3
  "description": "🎧 SfxMix - Powerful and easy-to-use module for processing audio.",
4
- "version": "1.4.2",
4
+ "version": "1.4.6",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "fluent-ffmpeg": "^2.1.3"