sfxmix 1.2.4 → 1.2.8
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/.claude/settings.local.json +9 -0
- package/demo/demo_7.mjs +23 -0
- package/index.js +83 -20
- package/package.json +3 -1
package/demo/demo_7.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import SfxMix from '../index.js';
|
|
2
|
+
const sfx = new SfxMix();
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// OGG conversion with custom options
|
|
6
|
+
await sfx
|
|
7
|
+
.add('part1.mp3')
|
|
8
|
+
.save('output.ogg', {
|
|
9
|
+
'c:a': 'libopus',
|
|
10
|
+
'b:a': '32k',
|
|
11
|
+
'ar': '48000',
|
|
12
|
+
'ac': '1',
|
|
13
|
+
'vbr': 'off',
|
|
14
|
+
'compression_level': '10',
|
|
15
|
+
'frame_duration': '20',
|
|
16
|
+
'application': 'voip'
|
|
17
|
+
})
|
|
18
|
+
.then(() => {
|
|
19
|
+
console.log('Successfully exported: output.ogg');
|
|
20
|
+
})
|
|
21
|
+
.catch((error) => {
|
|
22
|
+
console.error('Error during audio processing:', error);
|
|
23
|
+
});
|
package/index.js
CHANGED
|
@@ -48,14 +48,65 @@ class SfxMix {
|
|
|
48
48
|
return this;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
convertAudio(inputFile, outputFile, outputOptions = {}) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const command = ffmpeg().input(inputFile);
|
|
56
|
+
|
|
57
|
+
// Apply custom output options if provided
|
|
58
|
+
if (Object.keys(outputOptions).length > 0) {
|
|
59
|
+
// Convert object to array format for FFmpeg
|
|
60
|
+
const optionsArray = [];
|
|
61
|
+
for (const [key, value] of Object.entries(outputOptions)) {
|
|
62
|
+
optionsArray.push(`-${key}`, value);
|
|
63
|
+
}
|
|
64
|
+
command.outputOptions(optionsArray);
|
|
65
|
+
} else {
|
|
66
|
+
// Auto-detect format and apply defaults based on file extension
|
|
67
|
+
const ext = path.extname(outputFile).toLowerCase();
|
|
68
|
+
switch (ext) {
|
|
69
|
+
case '.ogg':
|
|
70
|
+
command.audioCodec('libopus').format('ogg');
|
|
71
|
+
break;
|
|
72
|
+
case '.wav':
|
|
73
|
+
command.audioCodec('pcm_s16le').format('wav');
|
|
74
|
+
break;
|
|
75
|
+
case '.flac':
|
|
76
|
+
command.audioCodec('flac').format('flac');
|
|
77
|
+
break;
|
|
78
|
+
case '.aac':
|
|
79
|
+
case '.m4a':
|
|
80
|
+
command.audioCodec('aac').format('mp4');
|
|
81
|
+
break;
|
|
82
|
+
case '.mp3':
|
|
83
|
+
default:
|
|
84
|
+
command.audioCodec('libmp3lame').format('mp3');
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
command
|
|
90
|
+
.output(outputFile)
|
|
91
|
+
.on('end', () => resolve())
|
|
92
|
+
.on('error', (err) => reject(err))
|
|
93
|
+
.run();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Keep convertToOgg for backward compatibility
|
|
98
|
+
convertToOgg(inputFile, outputFile, options = {}) {
|
|
99
|
+
return this.convertAudio(inputFile, outputFile, options);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
save(output, outputOptions = {}) {
|
|
52
103
|
return new Promise(async (resolve, reject) => {
|
|
53
104
|
try {
|
|
105
|
+
// Process all actions
|
|
54
106
|
for (let action of this.actions) {
|
|
55
107
|
if (action.type === 'add') {
|
|
56
|
-
// Existing add logic
|
|
57
108
|
if (this.currentFile == null) {
|
|
58
|
-
this.currentFile = action.input;
|
|
109
|
+
this.currentFile = path.isAbsolute(action.input) ? action.input : path.resolve(process.cwd(), action.input);
|
|
59
110
|
} else {
|
|
60
111
|
const tempFile = path.join(this.TMP_DIR, `temp_concat_${uuidv4()}.mp3`);
|
|
61
112
|
await this.concatenateAudioFiles([this.currentFile, action.input], tempFile);
|
|
@@ -65,7 +116,6 @@ class SfxMix {
|
|
|
65
116
|
this.currentFile = tempFile;
|
|
66
117
|
}
|
|
67
118
|
} else if (action.type === 'mix') {
|
|
68
|
-
// Existing mix logic
|
|
69
119
|
if (this.currentFile == null) {
|
|
70
120
|
throw new Error('No audio to mix with. Add or concatenate audio before mixing.');
|
|
71
121
|
}
|
|
@@ -76,7 +126,6 @@ class SfxMix {
|
|
|
76
126
|
}
|
|
77
127
|
this.currentFile = tempFile;
|
|
78
128
|
} else if (action.type === 'silence') {
|
|
79
|
-
// Existing silence logic
|
|
80
129
|
const tempSilenceFile = path.join(this.TMP_DIR, `temp_silence_${uuidv4()}.mp3`);
|
|
81
130
|
await this.generateSilence(action.duration, tempSilenceFile);
|
|
82
131
|
if (this.currentFile == null) {
|
|
@@ -87,11 +136,10 @@ class SfxMix {
|
|
|
87
136
|
if (this.isTempFile(this.currentFile)) {
|
|
88
137
|
fs.unlinkSync(this.currentFile);
|
|
89
138
|
}
|
|
90
|
-
fs.unlinkSync(tempSilenceFile);
|
|
139
|
+
fs.unlinkSync(tempSilenceFile);
|
|
91
140
|
this.currentFile = tempFile;
|
|
92
141
|
}
|
|
93
142
|
} else if (action.type === 'filter') {
|
|
94
|
-
// New filter logic
|
|
95
143
|
if (this.currentFile == null) {
|
|
96
144
|
throw new Error('No audio to apply filter to. Add audio before applying filters.');
|
|
97
145
|
}
|
|
@@ -103,32 +151,47 @@ class SfxMix {
|
|
|
103
151
|
this.currentFile = tempFile;
|
|
104
152
|
}
|
|
105
153
|
}
|
|
106
|
-
|
|
154
|
+
|
|
155
|
+
// Prepare output
|
|
107
156
|
const absoluteOutput = path.resolve(process.cwd(), output);
|
|
108
157
|
const outputDir = path.dirname(absoluteOutput);
|
|
109
158
|
|
|
110
|
-
// Ensure the output directory exists
|
|
111
159
|
if (!fs.existsSync(outputDir)) {
|
|
112
160
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
113
161
|
}
|
|
114
162
|
|
|
115
|
-
// Check if the source file exists
|
|
116
163
|
if (!fs.existsSync(this.currentFile)) {
|
|
117
164
|
throw new Error(`Source file does not exist: ${this.currentFile}`);
|
|
118
165
|
}
|
|
119
166
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
167
|
+
// Check if we need format conversion
|
|
168
|
+
const needsConversion = Object.keys(outputOptions).length > 0 ||
|
|
169
|
+
output.toLowerCase().endsWith('.ogg') ||
|
|
170
|
+
output.toLowerCase().endsWith('.wav') ||
|
|
171
|
+
output.toLowerCase().endsWith('.flac') ||
|
|
172
|
+
output.toLowerCase().endsWith('.aac') ||
|
|
173
|
+
output.toLowerCase().endsWith('.m4a');
|
|
174
|
+
|
|
175
|
+
if (needsConversion) {
|
|
176
|
+
// Use conversion with custom options
|
|
177
|
+
await this.convertAudio(this.currentFile, absoluteOutput, outputOptions);
|
|
178
|
+
} else {
|
|
179
|
+
// Save as MP3 or original format (no conversion needed)
|
|
180
|
+
try {
|
|
181
|
+
fs.renameSync(this.currentFile, absoluteOutput);
|
|
182
|
+
} catch (renameErr) {
|
|
183
|
+
// If rename fails, try to copy the file instead
|
|
184
|
+
fs.copyFileSync(this.currentFile, absoluteOutput);
|
|
185
|
+
fs.unlinkSync(this.currentFile);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Clean up temp file if it exists and wasn't renamed
|
|
190
|
+
if (this.isTempFile(this.currentFile) && fs.existsSync(this.currentFile)) {
|
|
126
191
|
fs.unlinkSync(this.currentFile);
|
|
127
192
|
}
|
|
128
193
|
|
|
129
|
-
// Reset internal state
|
|
130
194
|
this.reset();
|
|
131
|
-
|
|
132
195
|
resolve(absoluteOutput);
|
|
133
196
|
} catch (err) {
|
|
134
197
|
console.error('Error during audio processing:', err);
|
|
@@ -202,8 +265,8 @@ class SfxMix {
|
|
|
202
265
|
},
|
|
203
266
|
},
|
|
204
267
|
])
|
|
205
|
-
.audioCodec('libmp3lame')
|
|
206
|
-
.format('mp3')
|
|
268
|
+
.audioCodec('libmp3lame')
|
|
269
|
+
.format('mp3')
|
|
207
270
|
.output(outputFile)
|
|
208
271
|
.on('end', () => resolve())
|
|
209
272
|
.on('error', (err) => reject(err))
|
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.2.
|
|
4
|
+
"version": "1.2.8",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"fluent-ffmpeg": "^2.1.3",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"filters",
|
|
21
21
|
"mp3",
|
|
22
22
|
"wav",
|
|
23
|
+
"ogg",
|
|
24
|
+
"opus",
|
|
23
25
|
"echo",
|
|
24
26
|
"delay",
|
|
25
27
|
"tremolo",
|