@mediaproc/audio 1.1.1 ā 1.2.0
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/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/convert.d.ts.map +1 -1
- package/dist/commands/convert.js +31 -29
- package/dist/commands/convert.js.map +1 -1
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +57 -34
- package/dist/commands/extract.js.map +1 -1
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +100 -39
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/normalize.d.ts.map +1 -1
- package/dist/commands/normalize.js +92 -34
- package/dist/commands/normalize.js.map +1 -1
- package/dist/commands/trim.d.ts.map +1 -1
- package/dist/commands/trim.js +27 -22
- package/dist/commands/trim.js.map +1 -1
- package/dist/register.d.ts +2 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +16 -1
- package/dist/register.js.map +1 -1
- package/dist/types.d.ts +30 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/ffmpeg-output.d.ts +6 -2
- package/dist/utils/ffmpeg-output.d.ts.map +1 -1
- package/dist/utils/ffmpeg-output.js +51 -59
- package/dist/utils/ffmpeg-output.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +9 -8
- package/dist/utils/ffmpeg.d.ts.map +1 -1
- package/dist/utils/ffmpeg.js +107 -28
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/ffmpegLogger.d.ts +3 -7
- package/dist/utils/ffmpegLogger.d.ts.map +1 -1
- package/dist/utils/ffmpegLogger.js +16 -48
- package/dist/utils/ffmpegLogger.js.map +1 -1
- package/package.json +4 -8
- package/bin/cli.js +0 -5
package/dist/commands/merge.js
CHANGED
|
@@ -3,20 +3,25 @@ import { stat, writeFile, unlink } from 'fs/promises';
|
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { runFFmpeg, getAudioMetadata, checkFFmpeg, formatFileSize, formatDuration } from '../utils/ffmpeg.js';
|
|
5
5
|
import { styleFFmpegOutput, shouldDisplayLine } from '../utils/ffmpeg-output.js';
|
|
6
|
-
import { AUDIO_EXTENSIONS,
|
|
6
|
+
import { AUDIO_EXTENSIONS, validatePaths, createStandardHelp, resolveOutputPaths } from '@mediaproc/core';
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
export function mergeCommand(audioCmd) {
|
|
9
9
|
audioCmd
|
|
10
10
|
.command('merge [inputs...]')
|
|
11
11
|
.description('Merge multiple audio files into one')
|
|
12
|
-
.option('-o, --output <path>', 'Output file path
|
|
12
|
+
.option('-o, --output <path>', 'Output file path (must include extension)')
|
|
13
13
|
.option('--format <format>', 'Output format: mp3, aac, wav, flac, ogg', 'mp3')
|
|
14
|
+
.option('--codec <codec>', 'Specify output audio codec (overrides default for format)')
|
|
14
15
|
.option('--bitrate <bitrate>', 'Output bitrate (e.g., 192k, 320k)', '192k')
|
|
15
16
|
.option('--crossfade <seconds>', 'Crossfade duration between files (seconds)', parseFloat)
|
|
16
17
|
.option('--normalize', 'Normalize audio levels before merging')
|
|
18
|
+
.option('--fade-in <seconds>', 'Add fade-in effect (seconds)', parseFloat)
|
|
19
|
+
.option('--fade-out <seconds>', 'Add fade-out effect (seconds)', parseFloat)
|
|
20
|
+
.option('--remove-silence', 'Remove silence between tracks')
|
|
21
|
+
.option('--metadata <key=value>', 'Set custom metadata (repeatable)', (val, acc = []) => { acc.push(val); return acc; }, [])
|
|
17
22
|
.option('--dry-run', 'Preview command without executing')
|
|
18
23
|
.option('-v, --verbose', 'Show detailed FFmpeg output')
|
|
19
|
-
.option('--explain', '
|
|
24
|
+
.option('--explain [mode]', 'Show a detailed explanation of what this command will do, including technical and human-readable output. Modes: human, details, json. Adds context like timestamp, user, and platform.')
|
|
20
25
|
.option('-h, --help', 'Display help for merge command')
|
|
21
26
|
.action(async (inputs, options) => {
|
|
22
27
|
if (options.help || !inputs || inputs.length === 0) {
|
|
@@ -36,7 +41,7 @@ export function mergeCommand(audioCmd) {
|
|
|
36
41
|
{ flag: '--crossfade <seconds>', description: 'Crossfade duration between files in seconds (0-10)' },
|
|
37
42
|
{ flag: '--normalize', description: 'Normalize audio levels before merging for consistent volume' },
|
|
38
43
|
{ flag: '--dry-run', description: 'Preview FFmpeg command without executing' },
|
|
39
|
-
{ flag: '--explain', description: '
|
|
44
|
+
{ flag: '--explain [mode]', description: 'Show a detailed explanation of what this command will do, including technical and human-readable output. Modes: human, details, json. Adds context like timestamp, user, and platform.' },
|
|
40
45
|
{ flag: '-v, --verbose', description: 'Show detailed FFmpeg output and progress' }
|
|
41
46
|
],
|
|
42
47
|
examples: [
|
|
@@ -59,40 +64,61 @@ export function mergeCommand(audioCmd) {
|
|
|
59
64
|
console.error(chalk.red('\nā Error: At least 2 audio files required for merging'));
|
|
60
65
|
process.exit(1);
|
|
61
66
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const paths = parseInputPaths(input, AUDIO_EXTENSIONS);
|
|
68
|
-
validatedInputs.push(...paths);
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
console.warn(chalk.yellow(`ā Skipping invalid input: ${input}`));
|
|
72
|
-
}
|
|
67
|
+
// Provide default output file name if not provided
|
|
68
|
+
let outputPathArg = options.output;
|
|
69
|
+
if (!outputPathArg) {
|
|
70
|
+
outputPathArg = 'merged.mp3';
|
|
73
71
|
}
|
|
74
|
-
|
|
72
|
+
// Use pathValidator for all input/output validation
|
|
73
|
+
const { inputFiles, outputPath, errors } = validatePaths(inputs.join(','), outputPathArg, { allowedExtensions: AUDIO_EXTENSIONS });
|
|
74
|
+
if (errors.length > 0) {
|
|
75
|
+
errors.forEach(e => console.error(chalk.red(e)));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
if (inputFiles.length < 2) {
|
|
75
79
|
console.error(chalk.red('\nā Error: Not enough valid audio files found'));
|
|
76
80
|
process.exit(1);
|
|
77
81
|
}
|
|
78
|
-
//
|
|
82
|
+
// Always use resolveOutputPaths to generate output file(s)
|
|
83
|
+
const outputFormat = options.format || 'mp3';
|
|
84
|
+
const outputPathsMap = resolveOutputPaths(inputFiles, outputPath, {
|
|
85
|
+
suffix: '-merged',
|
|
86
|
+
newExtension: `.${outputFormat}`
|
|
87
|
+
});
|
|
88
|
+
const outputFiles = Array.from(outputPathsMap.values());
|
|
89
|
+
console.log(chalk.blue(`\nš Merging ${inputFiles.length} audio files...`));
|
|
90
|
+
// Show input files and collect extensions
|
|
79
91
|
let totalDuration = 0;
|
|
80
|
-
for (const inputFile of
|
|
92
|
+
for (const inputFile of inputFiles) {
|
|
81
93
|
const metadata = await getAudioMetadata(inputFile);
|
|
82
94
|
console.log(chalk.dim(` ${inputFile} (${formatDuration(metadata.duration)})`));
|
|
83
95
|
totalDuration += metadata.duration;
|
|
84
96
|
}
|
|
85
97
|
console.log(chalk.dim(`\nTotal duration: ${formatDuration(totalDuration)}`));
|
|
86
98
|
// Create concat file list
|
|
87
|
-
const concatFile = join(dirname(
|
|
88
|
-
const concatContent =
|
|
99
|
+
const concatFile = join(dirname(inputFiles[0]), '.concat-list.txt');
|
|
100
|
+
const concatContent = inputFiles.map(f => `file '${f}'`).join('\n');
|
|
89
101
|
await writeFile(concatFile, concatContent);
|
|
90
102
|
const args = ['-f', 'concat', '-safe', '0', '-i', concatFile, '-y'];
|
|
91
|
-
//
|
|
103
|
+
// Output codec selection
|
|
104
|
+
const codecMap = {
|
|
105
|
+
mp3: 'libmp3lame',
|
|
106
|
+
aac: 'aac',
|
|
107
|
+
flac: 'flac',
|
|
108
|
+
wav: 'pcm_s16le',
|
|
109
|
+
ogg: 'libvorbis',
|
|
110
|
+
};
|
|
111
|
+
let codec = options.codec || codecMap[outputFormat] || 'libmp3lame';
|
|
112
|
+
if (codec)
|
|
113
|
+
args.push('-c:a', codec);
|
|
114
|
+
if (options.bitrate)
|
|
115
|
+
args.push('-b:a', options.bitrate);
|
|
116
|
+
// Audio filter chain
|
|
117
|
+
let filterChain = '';
|
|
118
|
+
// Crossfade
|
|
92
119
|
if (options.crossfade) {
|
|
93
|
-
// Build complex filter for crossfade
|
|
94
120
|
const filterParts = [];
|
|
95
|
-
for (let i = 0; i <
|
|
121
|
+
for (let i = 0; i < inputFiles.length - 1; i++) {
|
|
96
122
|
if (i === 0) {
|
|
97
123
|
filterParts.push(`[0:a][1:a]acrossfade=d=${options.crossfade}[a01]`);
|
|
98
124
|
}
|
|
@@ -101,44 +127,79 @@ export function mergeCommand(audioCmd) {
|
|
|
101
127
|
}
|
|
102
128
|
}
|
|
103
129
|
args.push('-filter_complex', filterParts.join(';'));
|
|
104
|
-
args.push('-map', `[a0${
|
|
130
|
+
args.push('-map', `[a0${inputFiles.length - 1}]`);
|
|
105
131
|
}
|
|
106
|
-
// Normalization
|
|
132
|
+
// Normalization (if not crossfade)
|
|
107
133
|
if (options.normalize && !options.crossfade) {
|
|
108
|
-
|
|
134
|
+
filterChain += (filterChain ? ',' : '') + 'loudnorm=I=-16:TP=-1.5:LRA=11';
|
|
135
|
+
}
|
|
136
|
+
// Fade in/out
|
|
137
|
+
if (options.fadeIn) {
|
|
138
|
+
filterChain += (filterChain ? ',' : '') + `afade=t=in:st=0:d=${options.fadeIn}`;
|
|
139
|
+
}
|
|
140
|
+
if (options.fadeOut && totalDuration) {
|
|
141
|
+
const fadeStart = totalDuration - options.fadeOut;
|
|
142
|
+
filterChain += (filterChain ? ',' : '') + `afade=t=out:st=${fadeStart}:d=${options.fadeOut}`;
|
|
143
|
+
}
|
|
144
|
+
// Silence removal
|
|
145
|
+
if (options.removeSilence) {
|
|
146
|
+
filterChain += (filterChain ? ',' : '') + 'silenceremove=start_periods=1:start_silence=0.1:start_threshold=-50dB';
|
|
147
|
+
}
|
|
148
|
+
if (filterChain && !options.crossfade) {
|
|
149
|
+
args.push('-af', filterChain);
|
|
150
|
+
}
|
|
151
|
+
// Metadata
|
|
152
|
+
if (options.metadata && Array.isArray(options.metadata)) {
|
|
153
|
+
for (const entry of options.metadata) {
|
|
154
|
+
const [key, value] = entry.split('=');
|
|
155
|
+
if (key && value)
|
|
156
|
+
args.push('-metadata', `${key}=${value}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Output file (merge always produces a single output file)
|
|
160
|
+
if (outputFiles.length > 0) {
|
|
161
|
+
args.push(outputFiles[0]);
|
|
109
162
|
}
|
|
110
|
-
// Output bitrate
|
|
111
|
-
args.push('-b:a', options.bitrate);
|
|
112
|
-
args.push(options.output);
|
|
113
163
|
if (options.dryRun) {
|
|
114
164
|
console.log(chalk.yellow('\n[DRY RUN] Would execute:'));
|
|
115
165
|
console.log(chalk.dim(`ffmpeg ${args.join(' ')}`));
|
|
116
166
|
await unlink(concatFile);
|
|
117
|
-
showPluginBranding('Audio', '../../package.json');
|
|
118
167
|
return;
|
|
119
168
|
}
|
|
120
|
-
if (options.explain) {
|
|
121
|
-
console.log(chalk.gray('Explain mode is not yet available.'));
|
|
122
|
-
console.log(chalk.cyan('Planned for v0.8.x.'));
|
|
123
|
-
}
|
|
124
169
|
const spinner = ora('Merging audio files...').start();
|
|
125
170
|
try {
|
|
126
171
|
await runFFmpeg(args, options.verbose, (line) => {
|
|
127
|
-
if (shouldDisplayLine(line, options.verbose)) {
|
|
172
|
+
if (shouldDisplayLine(line, options.verbose ?? false)) {
|
|
128
173
|
console.log(styleFFmpegOutput(line));
|
|
129
174
|
}
|
|
130
175
|
});
|
|
131
|
-
|
|
176
|
+
let outputStat;
|
|
177
|
+
if (outputPath) {
|
|
178
|
+
outputStat = await stat(outputPath);
|
|
179
|
+
}
|
|
132
180
|
// Clean up concat file
|
|
133
181
|
await unlink(concatFile);
|
|
134
182
|
spinner.succeed(chalk.green('Merge complete'));
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
183
|
+
if (outputPath) {
|
|
184
|
+
console.log(chalk.green(`ā Output: ${outputPath}`));
|
|
185
|
+
if (outputStat) {
|
|
186
|
+
console.log(chalk.dim(`Duration: ${formatDuration(totalDuration)} ⢠Size: ${formatFileSize(outputStat.size)}`));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
138
189
|
}
|
|
139
190
|
catch (error) {
|
|
140
191
|
await unlink(concatFile).catch(() => { });
|
|
141
192
|
spinner.fail(chalk.red('Merge failed'));
|
|
193
|
+
// Enhanced error reporting for common ffmpeg concat/decoder issues
|
|
194
|
+
const errObj = error;
|
|
195
|
+
const errMsg = (errObj && errObj.message) ? errObj.message : String(error);
|
|
196
|
+
if (/Invalid data found when processing input|Error submitting packet to decoder/i.test(errMsg)) {
|
|
197
|
+
console.error(chalk.yellow('\nā ļø One or more input files may be corrupted, incompatible, or not suitable for merging.'));
|
|
198
|
+
console.error(chalk.yellow('Please ensure all input files are valid, have the same codec/sample rate/channels, and are not damaged.'));
|
|
199
|
+
}
|
|
200
|
+
else if (/Unable to choose an output format|Invalid argument|Error opening output file/i.test(errMsg)) {
|
|
201
|
+
console.error(chalk.yellow('\nā ļø Output path or format may be invalid. Make sure the output file has a valid extension and is not a directory.'));
|
|
202
|
+
}
|
|
142
203
|
throw error;
|
|
143
204
|
}
|
|
144
205
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/commands/merge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/commands/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/commands/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/commands/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CA4LxD"}
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { stat } from 'fs/promises';
|
|
3
3
|
import { runFFmpeg, getAudioMetadata, checkFFmpeg, formatFileSize, formatDuration } from '../utils/ffmpeg.js';
|
|
4
4
|
import { styleFFmpegOutput, shouldDisplayLine } from '../utils/ffmpeg-output.js';
|
|
5
|
-
import { AUDIO_EXTENSIONS,
|
|
5
|
+
import { AUDIO_EXTENSIONS, validatePaths, resolveOutputPaths, createStandardHelp } from '@mediaproc/core';
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
export function normalizeCommand(audioCmd) {
|
|
8
8
|
audioCmd
|
|
@@ -15,9 +15,9 @@ export function normalizeCommand(audioCmd) {
|
|
|
15
15
|
.option('--format <format>', 'Output format (default: same as input)')
|
|
16
16
|
.option('--dry-run', 'Preview command without executing')
|
|
17
17
|
.option('-v, --verbose', 'Show detailed FFmpeg output')
|
|
18
|
-
.option('--explain', '
|
|
18
|
+
.option('--explain [mode]', 'Show a detailed explanation of what this command will do, including technical and human-readable output. Modes: human, details, json. Adds context like timestamp, user, and platform.')
|
|
19
19
|
.option('-h, --help', 'Display help for normalize command')
|
|
20
|
-
.action(async (input, options)
|
|
20
|
+
.action(async function (input, options) {
|
|
21
21
|
if (options.help || !input) {
|
|
22
22
|
createStandardHelp({
|
|
23
23
|
commandName: 'normalize',
|
|
@@ -35,7 +35,7 @@ export function normalizeCommand(audioCmd) {
|
|
|
35
35
|
{ flag: '-m, --method <method>', description: 'Normalization method: loudnorm (EBU R128 standard), peak' },
|
|
36
36
|
{ flag: '--format <format>', description: 'Output format: mp3, aac, wav, flac (default: same as input)' },
|
|
37
37
|
{ flag: '--dry-run', description: 'Preview FFmpeg command without executing' },
|
|
38
|
-
{ flag: '--explain', description: '
|
|
38
|
+
{ flag: '--explain [mode]', description: 'Show a detailed explanation of what this command will do, including technical and human-readable output. Modes: human, details, json. Adds context like timestamp, user, and platform.' },
|
|
39
39
|
{ flag: '-v, --verbose', description: 'Show detailed FFmpeg output and progress' }
|
|
40
40
|
],
|
|
41
41
|
examples: [
|
|
@@ -54,14 +54,31 @@ export function normalizeCommand(audioCmd) {
|
|
|
54
54
|
console.error(chalk.red('\nā FFmpeg not found. Please install FFmpeg first.'));
|
|
55
55
|
process.exit(1);
|
|
56
56
|
}
|
|
57
|
-
const
|
|
57
|
+
const { inputFiles, outputPath, errors } = validatePaths(input, options.output, { allowedExtensions: AUDIO_EXTENSIONS });
|
|
58
|
+
if (errors.length > 0) {
|
|
59
|
+
errors.forEach(e => console.error(chalk.red(e)));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
58
62
|
const suffix = options.format ? `-normalized.${options.format}` : '-normalized';
|
|
59
|
-
const
|
|
60
|
-
const outputPathsMap = resolveOutputPaths(inputPaths, outputPath, { suffix });
|
|
63
|
+
const outputPathsMap = resolveOutputPaths(inputFiles, outputPath, { suffix });
|
|
61
64
|
const outputPaths = Array.from(outputPathsMap.values());
|
|
62
|
-
for (let i = 0; i <
|
|
63
|
-
const inputFile =
|
|
65
|
+
for (let i = 0; i < inputFiles.length; i++) {
|
|
66
|
+
const inputFile = inputFiles[i];
|
|
64
67
|
const outputFile = outputPaths[i];
|
|
68
|
+
// Warn if normalization is requested for a format known to be problematic
|
|
69
|
+
const unsupportedNormFormats = ['ogg'];
|
|
70
|
+
let outFormat = options.format;
|
|
71
|
+
if (!outFormat) {
|
|
72
|
+
const extMatch = outputFile.match(/\.([a-zA-Z0-9]+)$/);
|
|
73
|
+
outFormat = extMatch && [
|
|
74
|
+
'mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma', 'opus', 'ape', 'alac', 'mov', 'mkv'
|
|
75
|
+
].includes(extMatch[1].toLowerCase())
|
|
76
|
+
? extMatch[1].toLowerCase()
|
|
77
|
+
: undefined;
|
|
78
|
+
}
|
|
79
|
+
if (outFormat && unsupportedNormFormats.includes(outFormat) && (options.method === 'loudnorm' || options.method === 'peak')) {
|
|
80
|
+
console.warn(chalk.yellow(`ā ļø Warning: Normalization for format '${outFormat}' may not be supported or may fail. If you encounter errors, try using a different output format (e.g., wav, flac, mp3, aac).`));
|
|
81
|
+
}
|
|
65
82
|
console.log(chalk.blue(`\nš Normalizing: ${inputFile}`));
|
|
66
83
|
const metadata = await getAudioMetadata(inputFile);
|
|
67
84
|
const inputStat = await stat(inputFile);
|
|
@@ -69,16 +86,50 @@ export function normalizeCommand(audioCmd) {
|
|
|
69
86
|
`Sample Rate: ${metadata.sampleRate} Hz ⢠` +
|
|
70
87
|
`Channels: ${metadata.channels}`));
|
|
71
88
|
const args = ['-i', inputFile, '-y'];
|
|
89
|
+
let filterApplied = false;
|
|
72
90
|
if (options.method === 'loudnorm') {
|
|
73
|
-
// EBU R128 loudness normalization
|
|
74
91
|
args.push('-af', `loudnorm=I=${options.target}:TP=${options.maxLevel}:LRA=11:print_format=summary`);
|
|
92
|
+
filterApplied = true;
|
|
75
93
|
}
|
|
76
94
|
else if (options.method === 'peak') {
|
|
77
|
-
// Simple peak normalization
|
|
78
95
|
args.push('-af', 'volume=0dB');
|
|
96
|
+
filterApplied = true;
|
|
79
97
|
}
|
|
80
|
-
//
|
|
81
|
-
if (!
|
|
98
|
+
// Determine output format and codec
|
|
99
|
+
if (!outFormat) {
|
|
100
|
+
const extMatch = outputFile.match(/\.([a-zA-Z0-9]+)$/);
|
|
101
|
+
outFormat = extMatch && [
|
|
102
|
+
'mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma', 'opus', 'ape', 'alac', 'mov', 'mkv'
|
|
103
|
+
].includes(extMatch[1].toLowerCase())
|
|
104
|
+
? extMatch[1].toLowerCase()
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
// Prevent filter+streamcopy: show error if user tries to use filters with stream copy
|
|
108
|
+
if (filterApplied) {
|
|
109
|
+
if (outFormat === 'mp3') {
|
|
110
|
+
args.push('-c:a', 'libmp3lame');
|
|
111
|
+
}
|
|
112
|
+
else if (outFormat === 'aac' || outFormat === 'm4a') {
|
|
113
|
+
args.push('-c:a', 'aac');
|
|
114
|
+
}
|
|
115
|
+
else if (outFormat === 'flac') {
|
|
116
|
+
args.push('-c:a', 'flac');
|
|
117
|
+
}
|
|
118
|
+
else if (outFormat === 'wav') {
|
|
119
|
+
args.push('-c:a', 'pcm_s16le');
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
args.push('-c:a', 'libmp3lame'); // Default to mp3 codec for safety
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// No filter: safe to streamcopy
|
|
127
|
+
// But if user tries to add a filter (e.g. via method=peak) and also requests stream copy, show error
|
|
128
|
+
if (options.method === 'peak' || options.method === 'loudnorm') {
|
|
129
|
+
// Defensive: should not happen, but catch any logic error
|
|
130
|
+
console.error(chalk.red('\nā Error: Cannot use normalization filters with stream copy. Please remove normalization options or use a supported codec.'));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
82
133
|
args.push('-c:a', 'copy');
|
|
83
134
|
}
|
|
84
135
|
args.push(outputFile);
|
|
@@ -87,32 +138,39 @@ export function normalizeCommand(audioCmd) {
|
|
|
87
138
|
console.log(chalk.dim(`ffmpeg ${args.join(' ')}`));
|
|
88
139
|
continue;
|
|
89
140
|
}
|
|
90
|
-
if (options.explain) {
|
|
91
|
-
console.log(chalk.gray('Explain mode is not yet available.'));
|
|
92
|
-
console.log(chalk.cyan('Planned for v0.8.x.'));
|
|
93
|
-
}
|
|
94
141
|
const spinner = ora('Normalizing...').start();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
142
|
+
// Capture FFmpeg output to parse measured LUFS
|
|
143
|
+
let measuredLufs = undefined;
|
|
144
|
+
await runFFmpeg(args, options.verbose ?? false, (line) => {
|
|
145
|
+
if (shouldDisplayLine(line, options.verbose ?? false)) {
|
|
146
|
+
console.log(styleFFmpegOutput(line));
|
|
147
|
+
}
|
|
148
|
+
// Parse measured_I from loudnorm summary
|
|
149
|
+
if (options.method === 'loudnorm') {
|
|
150
|
+
const match = line.match(/Measured_I:\s*(-?\d+\.?\d*)/);
|
|
151
|
+
if (match)
|
|
152
|
+
measuredLufs = match[1];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const outputStat = await stat(outputFile);
|
|
156
|
+
spinner.succeed(chalk.green('Normalization complete'));
|
|
157
|
+
console.log(chalk.green(`ā Output: ${outputFile}`));
|
|
158
|
+
if (options.method === 'loudnorm') {
|
|
159
|
+
if (measuredLufs !== undefined) {
|
|
160
|
+
console.log(chalk.dim(`Measured: ${measuredLufs} LUFS ⢠Target: ${options.target} LUFS ⢠Peak limit: ${options.maxLevel} dB`));
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log(chalk.dim(`Target: ${options.target} LUFS ⢠Peak limit: ${options.maxLevel} dB (LUFS not available)`));
|
|
164
|
+
}
|
|
106
165
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
throw error;
|
|
166
|
+
else {
|
|
167
|
+
console.log(chalk.dim(`Peak limit: ${options.maxLevel} dB`));
|
|
110
168
|
}
|
|
169
|
+
console.log(chalk.dim(`Size: ${formatFileSize(inputStat.size)} ā ${formatFileSize(outputStat.size)}`));
|
|
111
170
|
}
|
|
112
|
-
if (
|
|
113
|
-
console.log(chalk.green(`\nā Normalized ${
|
|
171
|
+
if (inputFiles.length > 1) {
|
|
172
|
+
console.log(chalk.green(`\nā Normalized ${inputFiles.length} files successfully`));
|
|
114
173
|
}
|
|
115
|
-
showPluginBranding('Audio', '../../package.json');
|
|
116
174
|
}
|
|
117
175
|
catch (error) {
|
|
118
176
|
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/commands/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/commands/normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trim.d.ts","sourceRoot":"","sources":["../../src/commands/trim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"trim.d.ts","sourceRoot":"","sources":["../../src/commands/trim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAsLnD"}
|
package/dist/commands/trim.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { stat } from 'fs/promises';
|
|
3
3
|
import { runFFmpeg, getAudioMetadata, checkFFmpeg, formatFileSize, formatDuration, parseTime } from '../utils/ffmpeg.js';
|
|
4
4
|
import { styleFFmpegOutput, shouldDisplayLine } from '../utils/ffmpeg-output.js';
|
|
5
|
-
import { AUDIO_EXTENSIONS,
|
|
5
|
+
import { AUDIO_EXTENSIONS, validatePaths, resolveOutputPaths, createStandardHelp } from '@mediaproc/core';
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
export function trimCommand(audioCmd) {
|
|
8
8
|
audioCmd
|
|
@@ -20,9 +20,9 @@ export function trimCommand(audioCmd) {
|
|
|
20
20
|
.option('--metadata <key=value>', 'Set custom metadata (repeatable)', (val, acc = []) => { acc.push(val); return acc; }, [])
|
|
21
21
|
.option('--dry-run', 'Preview command without executing')
|
|
22
22
|
.option('-v, --verbose', 'Show detailed FFmpeg output')
|
|
23
|
-
.option('--explain', '
|
|
23
|
+
.option('--explain [mode]', 'Show a detailed explanation of what this command will do, including technical and human-readable output. Modes: human, details, json. Adds context like timestamp, user, and platform.')
|
|
24
24
|
.option('-h, --help', 'Display help for trim command')
|
|
25
|
-
.action(async (input, options)
|
|
25
|
+
.action(async function (input, options) {
|
|
26
26
|
if (options.help || !input) {
|
|
27
27
|
createStandardHelp({
|
|
28
28
|
commandName: 'trim',
|
|
@@ -45,7 +45,7 @@ export function trimCommand(audioCmd) {
|
|
|
45
45
|
{ flag: '--force', description: 'Overwrite output files without prompt' },
|
|
46
46
|
{ flag: '--metadata <key=value>', description: 'Set custom metadata (repeatable)' },
|
|
47
47
|
{ flag: '--dry-run', description: 'Preview FFmpeg command without executing' },
|
|
48
|
-
{ flag: '--explain', description: '
|
|
48
|
+
{ flag: '--explain [mode]', description: 'Show a detailed explanation of what this command will do, including technical and human-readable output. Modes: human, details, json. Adds context like timestamp, user, and platform.' },
|
|
49
49
|
{ flag: '-v, --verbose', description: 'Show detailed FFmpeg output and progress' }
|
|
50
50
|
],
|
|
51
51
|
examples: [
|
|
@@ -69,20 +69,33 @@ export function trimCommand(audioCmd) {
|
|
|
69
69
|
console.error(chalk.red('\nā Error: Either --end or --duration must be specified'));
|
|
70
70
|
process.exit(1);
|
|
71
71
|
}
|
|
72
|
-
const
|
|
72
|
+
const { inputFiles, outputPath, errors } = validatePaths(input, options.output, { allowedExtensions: AUDIO_EXTENSIONS });
|
|
73
|
+
if (errors.length > 0) {
|
|
74
|
+
errors.forEach(e => console.error(chalk.red(e)));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
73
77
|
const suffix = options.format ? `-trimmed.${options.format}` : '-trimmed';
|
|
74
|
-
const
|
|
75
|
-
const outputPathsMap = resolveOutputPaths(inputPaths, outputPath, { suffix });
|
|
78
|
+
const outputPathsMap = resolveOutputPaths(inputFiles, outputPath, { suffix });
|
|
76
79
|
const outputPaths = Array.from(outputPathsMap.values());
|
|
77
|
-
for (let i = 0; i <
|
|
78
|
-
const inputFile =
|
|
80
|
+
for (let i = 0; i < inputFiles.length; i++) {
|
|
81
|
+
const inputFile = inputFiles[i];
|
|
79
82
|
const outputFile = outputPaths[i];
|
|
80
83
|
console.log(chalk.blue(`\nāļø Trimming: ${inputFile}`));
|
|
81
84
|
const metadata = await getAudioMetadata(inputFile);
|
|
82
85
|
const inputStat = await stat(inputFile);
|
|
86
|
+
if (!options.start)
|
|
87
|
+
throw new Error('Start time is required for trim');
|
|
83
88
|
const startTime = parseTime(options.start);
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
let duration = 0;
|
|
90
|
+
let endTime = startTime;
|
|
91
|
+
if (options.end) {
|
|
92
|
+
endTime = parseTime(options.end);
|
|
93
|
+
duration = endTime - startTime;
|
|
94
|
+
}
|
|
95
|
+
else if (options.duration) {
|
|
96
|
+
duration = parseTime(options.duration);
|
|
97
|
+
endTime = startTime + duration;
|
|
98
|
+
}
|
|
86
99
|
console.log(chalk.dim(`Duration: ${formatDuration(metadata.duration)} ⢠` +
|
|
87
100
|
`Sample Rate: ${metadata.sampleRate} Hz`));
|
|
88
101
|
console.log(chalk.dim(`Trim: ${formatDuration(startTime)} ā ${formatDuration(endTime)} ` +
|
|
@@ -129,17 +142,12 @@ export function trimCommand(audioCmd) {
|
|
|
129
142
|
if (options.dryRun) {
|
|
130
143
|
console.log(chalk.yellow('\n[DRY RUN] Would execute:'));
|
|
131
144
|
console.log(chalk.dim(`ffmpeg ${args.join(' ')}`));
|
|
132
|
-
showPluginBranding('Audio', '../../package.json');
|
|
133
145
|
continue;
|
|
134
146
|
}
|
|
135
|
-
if (options.explain) {
|
|
136
|
-
console.log(chalk.gray('Explain mode is not yet available.'));
|
|
137
|
-
console.log(chalk.cyan('Planned for v0.8.x.'));
|
|
138
|
-
}
|
|
139
147
|
const spinner = ora('Trimming...').start();
|
|
140
148
|
try {
|
|
141
|
-
await runFFmpeg(args, options.verbose, (line) => {
|
|
142
|
-
if (shouldDisplayLine(line, options.verbose)) {
|
|
149
|
+
await runFFmpeg(args, options.verbose ?? false, (line) => {
|
|
150
|
+
if (shouldDisplayLine(line, options.verbose ?? false)) {
|
|
143
151
|
console.log(styleFFmpegOutput(line));
|
|
144
152
|
}
|
|
145
153
|
});
|
|
@@ -153,10 +161,7 @@ export function trimCommand(audioCmd) {
|
|
|
153
161
|
throw error;
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
|
-
|
|
157
|
-
console.log(chalk.green(`\nā Trimmed ${inputPaths.length} files successfully`));
|
|
158
|
-
}
|
|
159
|
-
showPluginBranding('Audio', '../../package.json');
|
|
164
|
+
console.log(chalk.green(`\nā Trimmed ${inputFiles.length} files successfully`));
|
|
160
165
|
}
|
|
161
166
|
catch (error) {
|
|
162
167
|
console.error(chalk.red(`\nā Error: ${error.message}`));
|