@mediaproc/video 1.0.0 ā 1.1.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/README.md +487 -89
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compress.d.ts +3 -0
- package/dist/commands/compress.d.ts.map +1 -0
- package/dist/commands/compress.js +195 -0
- package/dist/commands/compress.js.map +1 -0
- package/dist/commands/extract.d.ts +3 -0
- package/dist/commands/extract.d.ts.map +1 -0
- package/dist/commands/extract.js +228 -0
- package/dist/commands/extract.js.map +1 -0
- package/dist/commands/merge.d.ts +3 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +214 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/resize.d.ts +3 -0
- package/dist/commands/resize.d.ts.map +1 -0
- package/dist/commands/resize.js +252 -0
- package/dist/commands/resize.js.map +1 -0
- package/dist/commands/transcode.d.ts +3 -0
- package/dist/commands/transcode.d.ts.map +1 -0
- package/dist/commands/transcode.js +108 -0
- package/dist/commands/transcode.js.map +1 -0
- package/dist/commands/trim.d.ts +3 -0
- package/dist/commands/trim.d.ts.map +1 -0
- package/dist/commands/trim.js +198 -0
- package/dist/commands/trim.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/register.d.ts +5 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +18 -0
- package/dist/register.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/ffmpeg-output.d.ts +9 -0
- package/dist/utils/ffmpeg-output.d.ts.map +1 -0
- package/dist/utils/ffmpeg-output.js +94 -0
- package/dist/utils/ffmpeg-output.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +46 -0
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +167 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/ffmpegLogger.d.ts +16 -0
- package/dist/utils/ffmpegLogger.d.ts.map +1 -0
- package/dist/utils/ffmpegLogger.js +66 -0
- package/dist/utils/ffmpegLogger.js.map +1 -0
- package/dist/utils/helpFormatter.d.ts +51 -0
- package/dist/utils/helpFormatter.d.ts.map +1 -0
- package/dist/utils/helpFormatter.js +134 -0
- package/dist/utils/helpFormatter.js.map +1 -0
- package/dist/utils/pathValidator.d.ts +60 -0
- package/dist/utils/pathValidator.d.ts.map +1 -0
- package/dist/utils/pathValidator.js +207 -0
- package/dist/utils/pathValidator.js.map +1 -0
- package/package.json +12 -3
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { writeFile, unlink, stat } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { runFFmpeg, getVideoMetadata, checkFFmpeg, formatFileSize, formatDuration, } from '../utils/ffmpeg.js';
|
|
7
|
+
import { fileExists, validatePaths, resolveOutputPaths } from '../utils/pathValidator.js';
|
|
8
|
+
import { logFFmpegOutput } from '../utils/ffmpegLogger.js';
|
|
9
|
+
import { createStandardHelp } from '../utils/helpFormatter.js';
|
|
10
|
+
export function mergeCommand(videoCmd) {
|
|
11
|
+
videoCmd
|
|
12
|
+
.command('merge <inputs...>')
|
|
13
|
+
.description('Merge multiple video files into a single video')
|
|
14
|
+
.option('-o, --output <path>', 'Output file path (default: merged.mp4)', 'merged.mp4')
|
|
15
|
+
.option('--re-encode', 'Re-encode videos (slower but handles different formats/codecs)')
|
|
16
|
+
.option('--transition <type>', 'Transition effect: fade, wipe, dissolve, none (default: none)', 'none')
|
|
17
|
+
.option('--transition-duration <seconds>', 'Transition duration in seconds (default: 1)', parseFloat, 1)
|
|
18
|
+
.option('-c, --codec <codec>', 'Video codec for re-encoding: h264, h265, vp9 (default: h264)', 'h264')
|
|
19
|
+
.option('--quality <crf>', 'CRF quality if re-encoding (default: 23)', parseInt, 23)
|
|
20
|
+
.option('--scale <resolution>', 'Scale all videos to same resolution (e.g., 1080p)')
|
|
21
|
+
.option('--audio-track <n>', 'Select audio track from videos (1-based, default: 1)', parseInt, 1)
|
|
22
|
+
.option('--audio-codec <codec>', 'Audio codec: aac, mp3, opus (default: aac)', 'aac')
|
|
23
|
+
.option('--normalize-audio', 'Normalize audio levels across videos')
|
|
24
|
+
.option('--format <format>', 'Output format: mp4, mkv, avi, webm (default: mp4)', 'mp4')
|
|
25
|
+
.option('--dry-run', 'Preview command without executing')
|
|
26
|
+
.option('-v, --verbose', 'Show detailed FFmpeg output')
|
|
27
|
+
.option('-h, --help', 'Display help for merge command')
|
|
28
|
+
.action(async (inputs, options) => {
|
|
29
|
+
if (options.help) {
|
|
30
|
+
createStandardHelp({
|
|
31
|
+
commandName: 'merge',
|
|
32
|
+
emoji: 'š',
|
|
33
|
+
description: 'Merge multiple video files into a single video. Supports fast concatenation (same format) or re-encoding (different formats). Can merge videos from different sources with automatic format conversion.',
|
|
34
|
+
usage: [
|
|
35
|
+
'merge <video1> <video2> [video3...] [options]',
|
|
36
|
+
'merge video1.mp4 video2.mp4 video3.mp4',
|
|
37
|
+
'merge part*.mp4 -o complete.mp4'
|
|
38
|
+
],
|
|
39
|
+
options: [
|
|
40
|
+
{ flag: '-o, --output <path>', description: 'Output file path (default: merged.mp4)' },
|
|
41
|
+
{ flag: '--re-encode', description: 'Re-encode videos (handles different formats/codecs)' },
|
|
42
|
+
{ flag: '--transition <type>', description: 'Transition effect: fade, wipe, dissolve, none (default: none)' },
|
|
43
|
+
{ flag: '--transition-duration <seconds>', description: 'Transition duration in seconds (default: 1)' },
|
|
44
|
+
{ flag: '-c, --codec <codec>', description: 'Video codec for re-encoding: h264, h265, vp9' },
|
|
45
|
+
{ flag: '--quality <crf>', description: 'CRF quality if re-encoding (default: 23)' },
|
|
46
|
+
{ flag: '--scale <resolution>', description: 'Scale all videos to same resolution (e.g., 1080p)' },
|
|
47
|
+
{ flag: '--audio-track <n>', description: 'Select audio track from videos (1-based, default: 1)' },
|
|
48
|
+
{ flag: '--audio-codec <codec>', description: 'Audio codec: aac, mp3, opus (default: aac)' },
|
|
49
|
+
{ flag: '--normalize-audio', description: 'Normalize audio levels across videos' },
|
|
50
|
+
{ flag: '--format <format>', description: 'Output format: mp4, mkv, avi, webm (default: mp4)' },
|
|
51
|
+
{ flag: '--dry-run', description: 'Preview FFmpeg command without executing' },
|
|
52
|
+
{ flag: '-v, --verbose', description: 'Show detailed FFmpeg output' }
|
|
53
|
+
],
|
|
54
|
+
examples: [
|
|
55
|
+
{ command: 'merge video1.mp4 video2.mp4 video3.mp4', description: 'Merge three videos quickly (same format)' },
|
|
56
|
+
{ command: 'merge part1.mp4 part2.mov part3.avi --re-encode', description: 'Merge different format videos with re-encoding' },
|
|
57
|
+
{ command: 'merge *.mp4 -o complete.mp4', description: 'Merge all MP4 files in current directory' },
|
|
58
|
+
{ command: 'merge video1.mp4 video2.mp4 -c h265 --re-encode', description: 'Merge and compress with HEVC codec' },
|
|
59
|
+
{ command: 'merge clip*.mp4 -o final.mkv --format mkv', description: 'Merge to MKV format' }
|
|
60
|
+
],
|
|
61
|
+
additionalSections: [
|
|
62
|
+
{
|
|
63
|
+
title: 'Merge Methods',
|
|
64
|
+
items: [
|
|
65
|
+
'Fast Concatenation - No re-encoding, very fast, requires same codec/format',
|
|
66
|
+
'Re-encoding - Slower but handles any format/codec combination',
|
|
67
|
+
'Auto Detection - Automatically chooses best method based on input files'
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: 'Requirements',
|
|
72
|
+
items: [
|
|
73
|
+
'All videos must have the same resolution for best results',
|
|
74
|
+
'Audio tracks will be merged in order',
|
|
75
|
+
'At least 2 videos required for merging',
|
|
76
|
+
'Videos are merged in the order specified'
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
tips: [
|
|
81
|
+
'Use fast concatenation (no --re-encode) when all videos have same format',
|
|
82
|
+
'Use --re-encode when merging different formats or codecs',
|
|
83
|
+
'Sort input files numerically if using wildcards: part1, part2, etc.',
|
|
84
|
+
'Check video compatibility with --dry-run before long merges'
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
const tempListFile = join(tmpdir(), `mediaproc-merge-${Date.now()}.txt`);
|
|
90
|
+
const spinner = ora('Processing video merge...').start();
|
|
91
|
+
try {
|
|
92
|
+
if (inputs.length < 2) {
|
|
93
|
+
spinner.fail(chalk.red('At least 2 videos are required for merging'));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
// Check ffmpeg
|
|
97
|
+
const ffmpegAvailable = await checkFFmpeg();
|
|
98
|
+
if (!ffmpegAvailable) {
|
|
99
|
+
spinner.fail(chalk.red('ffmpeg is not installed or not in PATH'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
spinner.text = 'Analyzing videos...';
|
|
103
|
+
const inputPaths = [];
|
|
104
|
+
const metadataList = [];
|
|
105
|
+
let totalDuration = 0;
|
|
106
|
+
let totalSize = 0;
|
|
107
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
108
|
+
const validation = validatePaths(inputs[i], undefined);
|
|
109
|
+
if (validation.errors.length > 0) {
|
|
110
|
+
throw new Error(`Input ${i + 1}: ${validation.errors.join(', ')}`);
|
|
111
|
+
}
|
|
112
|
+
const inputPath = validation.inputFiles[0];
|
|
113
|
+
// Check if input file exists
|
|
114
|
+
if (!(await fileExists(inputPath))) {
|
|
115
|
+
throw new Error(`Input ${i + 1} does not exist: ${inputPath}`);
|
|
116
|
+
}
|
|
117
|
+
inputPaths.push(inputPath);
|
|
118
|
+
const metadata = await getVideoMetadata(inputPath);
|
|
119
|
+
const fileStat = await stat(inputPath);
|
|
120
|
+
metadataList.push(metadata);
|
|
121
|
+
totalDuration += metadata.duration;
|
|
122
|
+
totalSize += fileStat.size;
|
|
123
|
+
console.log(chalk.gray(` ${i + 1}. ${inputs[i]}`));
|
|
124
|
+
console.log(chalk.dim(` ${metadata.width}x${metadata.height}, ${formatDuration(metadata.duration)}, ${metadata.codec}`));
|
|
125
|
+
}
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(chalk.gray(` Total duration: ${formatDuration(totalDuration)}`));
|
|
128
|
+
console.log(chalk.gray(` Total size: ${formatFileSize(totalSize)}`));
|
|
129
|
+
console.log();
|
|
130
|
+
// Check if all videos have same resolution/codec
|
|
131
|
+
const firstMeta = metadataList[0];
|
|
132
|
+
const needsReEncode = options.reEncode || metadataList.some((m) => m.width !== firstMeta.width || m.height !== firstMeta.height || m.codec !== firstMeta.codec);
|
|
133
|
+
if (needsReEncode && !options.reEncode) {
|
|
134
|
+
console.log(chalk.yellow('ā ļø Videos have different formats/resolutions'));
|
|
135
|
+
console.log(chalk.yellow(' Will re-encode for compatibility (slower)'));
|
|
136
|
+
console.log(chalk.dim(' Use --re-encode to skip this warning\n'));
|
|
137
|
+
}
|
|
138
|
+
// Validate and resolve output path
|
|
139
|
+
const outputValidation = validatePaths(inputPaths[0], options.output, {
|
|
140
|
+
newExtension: '.mp4'
|
|
141
|
+
});
|
|
142
|
+
if (outputValidation.errors.length > 0) {
|
|
143
|
+
throw new Error(`Output path invalid: ${outputValidation.errors.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
const outputMap = resolveOutputPaths([inputPaths[0]], options.output, {
|
|
146
|
+
newExtension: '.mp4'
|
|
147
|
+
});
|
|
148
|
+
const output = outputMap.get(inputPaths[0]);
|
|
149
|
+
// Check if output file already exists
|
|
150
|
+
if (await fileExists(output) && !options.dryRun) {
|
|
151
|
+
console.log(chalk.yellow(`ā ļø Output file exists and will be overwritten: ${output}\n`));
|
|
152
|
+
}
|
|
153
|
+
let args;
|
|
154
|
+
if (needsReEncode) {
|
|
155
|
+
// Re-encode mode: use filter_complex
|
|
156
|
+
const filterInputs = inputPaths.map((_, i) => `[${i}:v][${i}:a]`).join('');
|
|
157
|
+
const filterComplex = `${filterInputs}concat=n=${inputPaths.length}:v=1:a=1[outv][outa]`;
|
|
158
|
+
args = [];
|
|
159
|
+
inputPaths.forEach((path) => {
|
|
160
|
+
args.push('-i', path);
|
|
161
|
+
});
|
|
162
|
+
args.push('-filter_complex', filterComplex, '-map', '[outv]', '-map', '[outa]', '-c:v', 'libx264', '-crf', '23', '-c:a', 'aac', '-y', output);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Fast concat mode: use concat demuxer (no re-encode)
|
|
166
|
+
// Create concat list file
|
|
167
|
+
const listContent = inputPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join('\n');
|
|
168
|
+
await writeFile(tempListFile, listContent);
|
|
169
|
+
args = ['-f', 'concat', '-safe', '0', '-i', tempListFile, '-c', 'copy', '-y', output];
|
|
170
|
+
}
|
|
171
|
+
if (options.dryRun) {
|
|
172
|
+
console.log(chalk.yellow('š Dry run mode - no files will be created\n'));
|
|
173
|
+
console.log(chalk.dim('Method:'));
|
|
174
|
+
console.log(chalk.gray(` ${needsReEncode ? 'Re-encode (compatible)' : 'Fast concat (stream copy)'}`));
|
|
175
|
+
console.log(chalk.dim('\nCommand:'));
|
|
176
|
+
console.log(chalk.gray(` ffmpeg ${args.join(' ')}\n`));
|
|
177
|
+
console.log(chalk.green('ā Dry run complete'));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Run merge
|
|
181
|
+
console.log(chalk.dim(`š Merging videos (${needsReEncode ? 're-encoding' : 'fast mode'})...`));
|
|
182
|
+
if (options.verbose) {
|
|
183
|
+
console.log(chalk.dim(`ffmpeg ${args.join(' ')}\n`));
|
|
184
|
+
}
|
|
185
|
+
await runFFmpeg(args, options.verbose, (line) => {
|
|
186
|
+
if (options.verbose) {
|
|
187
|
+
logFFmpegOutput(line);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// Get output file size
|
|
191
|
+
const outputStat = await stat(output);
|
|
192
|
+
console.log();
|
|
193
|
+
console.log(chalk.green.bold('ā Merging Complete!\n'));
|
|
194
|
+
console.log(chalk.gray(` Videos merged: ${inputs.length}`));
|
|
195
|
+
console.log(chalk.gray(` Total duration: ${formatDuration(totalDuration)}`));
|
|
196
|
+
console.log(chalk.gray(` Output size: ${formatFileSize(outputStat.size)}`));
|
|
197
|
+
console.log(chalk.dim(`\n ${output}`));
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
// Clean up temp file
|
|
205
|
+
try {
|
|
206
|
+
await unlink(tempListFile);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Ignore cleanup errors
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/commands/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../../src/commands/resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CA+QrD"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { stat } from 'fs/promises';
|
|
3
|
+
import { runFFmpeg, getVideoMetadata, checkFFmpeg, formatFileSize, } from '../utils/ffmpeg.js';
|
|
4
|
+
import { parseInputPaths, resolveOutputPaths } from '../utils/pathValidator.js';
|
|
5
|
+
import { logFFmpegOutput } from '../utils/ffmpegLogger.js';
|
|
6
|
+
import { createStandardHelp } from '../utils/helpFormatter.js';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
export function resizeCommand(videoCmd) {
|
|
9
|
+
videoCmd
|
|
10
|
+
.command('resize [input]')
|
|
11
|
+
.description('Resize video to specified resolution with quality preservation')
|
|
12
|
+
.option('-s, --scale <scale>', 'Resolution: 360p, 480p, 720p, 1080p, 1440p, 2160p (4K), 4320p (8K), or WxH', '1080p')
|
|
13
|
+
.option('-o, --output <path>', 'Output file or directory path')
|
|
14
|
+
.option('-c, --codec <codec>', 'Video codec: h264, h265, vp9, av1 (default: h264)', 'h264')
|
|
15
|
+
.option('-q, --quality <quality>', 'CRF quality: 0-51, lower is better (default: 23)', parseInt, 23)
|
|
16
|
+
.option('--preset <preset>', 'Encoding preset: ultrafast, fast, medium, slow, veryslow (default: medium)', 'medium')
|
|
17
|
+
.option('-b, --bitrate <bitrate>', 'Target bitrate (e.g., 5M, 10M)')
|
|
18
|
+
.option('-a, --aspect <ratio>', 'Aspect ratio: 16:9, 4:3, 21:9, 1:1')
|
|
19
|
+
.option('--fps <fps>', 'Output frame rate (e.g., 24, 30, 60)', parseInt)
|
|
20
|
+
.option('--format <format>', 'Output format: mp4, mkv, webm, avi')
|
|
21
|
+
.option('--scale-algo <algorithm>', 'Scaling algorithm: bilinear, bicubic, lanczos, spline (default: lanczos)', 'lanczos')
|
|
22
|
+
.option('--deinterlace', 'Deinterlace video (for interlaced sources)')
|
|
23
|
+
.option('--rotate <degrees>', 'Rotate video: 90, 180, 270 degrees', parseInt)
|
|
24
|
+
.option('--flip <direction>', 'Flip video: horizontal, vertical, both')
|
|
25
|
+
.option('--crop <spec>', 'Crop video (width:height:x:y or preset: 16:9, 4:3, 1:1)')
|
|
26
|
+
.option('--threads <n>', 'Number of threads for encoding (default: auto)', parseInt)
|
|
27
|
+
.option('--hw-accel', 'Enable hardware acceleration (GPU)')
|
|
28
|
+
.option('--no-audio', 'Remove audio from output')
|
|
29
|
+
.option('--two-pass', 'Use two-pass encoding for better quality')
|
|
30
|
+
.option('--dry-run', 'Preview command without executing')
|
|
31
|
+
.option('-v, --verbose', 'Show detailed FFmpeg output')
|
|
32
|
+
.option('-h, --help', 'Display help for resize command')
|
|
33
|
+
.action(async (input, options) => {
|
|
34
|
+
// Show help if requested (before input validation)
|
|
35
|
+
if (options.help || !input) {
|
|
36
|
+
createStandardHelp({
|
|
37
|
+
commandName: 'resize',
|
|
38
|
+
emoji: 'š¬',
|
|
39
|
+
description: 'Resize videos to different resolutions while maintaining quality. Supports standard resolutions from 360p to 8K (4320p), custom dimensions, aspect ratios, and various codecs. Can process single files or entire directories.',
|
|
40
|
+
usage: [
|
|
41
|
+
'resize <input> [options]',
|
|
42
|
+
'resize video.mp4 -s 1080p',
|
|
43
|
+
'resize videos/ -s 4K -o output/'
|
|
44
|
+
],
|
|
45
|
+
options: [
|
|
46
|
+
{ flag: '-s, --scale <scale>', description: 'Resolution: 360p, 480p, 720p, 1080p, 1440p, 2160p (4K), 4320p (8K), or WxH' },
|
|
47
|
+
{ flag: '-o, --output <path>', description: 'Output file/directory (default: <input>-resized.<ext>)' },
|
|
48
|
+
{ flag: '-c, --codec <codec>', description: 'Video codec: h264, h265 (HEVC), vp9, av1 (default: h264)' },
|
|
49
|
+
{ flag: '-q, --quality <quality>', description: 'CRF quality 0-51, lower=better (default: 23)' },
|
|
50
|
+
{ flag: '--preset <preset>', description: 'Encoding preset: ultrafast, fast, medium, slow, veryslow' },
|
|
51
|
+
{ flag: '-b, --bitrate <bitrate>', description: 'Target bitrate (e.g., 5M for 5 Mbps, 10M for 10 Mbps)' },
|
|
52
|
+
{ flag: '-a, --aspect <ratio>', description: 'Aspect ratio: 16:9, 4:3, 21:9, 1:1' },
|
|
53
|
+
{ flag: '--fps <fps>', description: 'Output frame rate (e.g., 24, 30, 60)' },
|
|
54
|
+
{ flag: '--format <format>', description: 'Output format: mp4, mkv, webm, avi' },
|
|
55
|
+
{ flag: '--scale-algo <algorithm>', description: 'Scaling algorithm: bilinear, bicubic, lanczos, spline (default: lanczos)' },
|
|
56
|
+
{ flag: '--deinterlace', description: 'Deinterlace video (for interlaced sources)' },
|
|
57
|
+
{ flag: '--rotate <degrees>', description: 'Rotate video: 90, 180, 270 degrees' },
|
|
58
|
+
{ flag: '--flip <direction>', description: 'Flip video: horizontal, vertical, both' },
|
|
59
|
+
{ flag: '--crop <spec>', description: 'Crop video (width:height:x:y or preset: 16:9, 4:3, 1:1)' },
|
|
60
|
+
{ flag: '--threads <n>', description: 'Number of threads for encoding (default: auto)' },
|
|
61
|
+
{ flag: '--hw-accel', description: 'Enable hardware acceleration (GPU)' },
|
|
62
|
+
{ flag: '--no-audio', description: 'Remove audio track from output' },
|
|
63
|
+
{ flag: '--two-pass', description: 'Enable two-pass encoding for better quality' },
|
|
64
|
+
{ flag: '--dry-run', description: 'Preview FFmpeg command without executing' },
|
|
65
|
+
{ flag: '-v, --verbose', description: 'Show detailed FFmpeg output' }
|
|
66
|
+
],
|
|
67
|
+
examples: [
|
|
68
|
+
{ command: 'resize video.mp4 -s 720p', description: 'Resize to 720p HD resolution' },
|
|
69
|
+
{ command: 'resize video.mp4 -s 4K -c h265 --preset slow', description: 'Resize to 4K using HEVC with high quality' },
|
|
70
|
+
{ command: 'resize video.mp4 -s 1920x1080 -a 16:9', description: 'Resize to Full HD with 16:9 aspect ratio' },
|
|
71
|
+
{ command: 'resize videos/ -s 1080p -o output/', description: 'Resize all videos in folder to 1080p' },
|
|
72
|
+
{ command: 'resize video.mp4 -s 2160p --fps 60 -b 15M', description: 'Resize to 4K at 60fps with 15Mbps bitrate' },
|
|
73
|
+
{ command: 'resize video.mp4 -s 8K -c av1 --two-pass', description: 'Resize to 8K using AV1 codec with two-pass encoding' }
|
|
74
|
+
],
|
|
75
|
+
additionalSections: [
|
|
76
|
+
{
|
|
77
|
+
title: 'Supported Resolutions',
|
|
78
|
+
items: [
|
|
79
|
+
'360p - 640Ć360 (Low quality, mobile)',
|
|
80
|
+
'480p - 854Ć480 (SD quality)',
|
|
81
|
+
'720p - 1280Ć720 (HD ready)',
|
|
82
|
+
'1080p - 1920Ć1080 (Full HD)',
|
|
83
|
+
'1440p - 2560Ć1440 (2K/QHD)',
|
|
84
|
+
'2160p - 3840Ć2160 (4K/UHD)',
|
|
85
|
+
'4320p - 7680Ć4320 (8K/UHD)',
|
|
86
|
+
'Custom - WIDTHxHEIGHT (e.g., 1920x1080)'
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
title: 'Codec Recommendations',
|
|
91
|
+
items: [
|
|
92
|
+
'h264 - Best compatibility, fast encoding (recommended)',
|
|
93
|
+
'h265 - 50% better compression than h264, slower encoding',
|
|
94
|
+
'vp9 - WebM format, good for web streaming',
|
|
95
|
+
'av1 - Best compression, very slow encoding, future-proof'
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: 'Quality & Bitrate Guidelines',
|
|
100
|
+
items: [
|
|
101
|
+
'CRF 18-23 - High quality (default: 23)',
|
|
102
|
+
'CRF 24-28 - Medium quality',
|
|
103
|
+
'Bitrate: 720p=5M, 1080p=8M, 1440p=16M, 4K=35-45M, 8K=80-100M'
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
tips: [
|
|
108
|
+
'Use --dry-run to preview the FFmpeg command before executing',
|
|
109
|
+
'For folder input, all videos will be processed with same settings',
|
|
110
|
+
'Two-pass encoding produces better quality but takes twice as long',
|
|
111
|
+
'h265/HEVC provides better quality at lower file sizes than h264',
|
|
112
|
+
'Use slow/veryslow preset for archival, fast/ultrafast for quick previews'
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
const spinner = ora('Initializing...').start();
|
|
118
|
+
try {
|
|
119
|
+
// Check ffmpeg
|
|
120
|
+
spinner.text = 'Checking FFmpeg...';
|
|
121
|
+
const ffmpegAvailable = await checkFFmpeg();
|
|
122
|
+
if (!ffmpegAvailable) {
|
|
123
|
+
throw new Error('ffmpeg is not installed or not in PATH');
|
|
124
|
+
}
|
|
125
|
+
// Parse input files
|
|
126
|
+
spinner.text = 'Finding video files...';
|
|
127
|
+
const inputFiles = parseInputPaths(input);
|
|
128
|
+
if (inputFiles.length === 0) {
|
|
129
|
+
spinner.fail(chalk.red('No valid video files found'));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
spinner.succeed(chalk.green(`Found ${inputFiles.length} video file(s) to process`));
|
|
133
|
+
// Scale mapping with 8K support
|
|
134
|
+
const scaleMap = {
|
|
135
|
+
'360p': { width: 640, height: 360 },
|
|
136
|
+
'480p': { width: 854, height: 480 },
|
|
137
|
+
'720p': { width: 1280, height: 720 },
|
|
138
|
+
'1080p': { width: 1920, height: 1080 },
|
|
139
|
+
'1440p': { width: 2560, height: 1440 },
|
|
140
|
+
'2k': { width: 2560, height: 1440 },
|
|
141
|
+
'2K': { width: 2560, height: 1440 },
|
|
142
|
+
'2160p': { width: 3840, height: 2160 },
|
|
143
|
+
'4k': { width: 3840, height: 2160 },
|
|
144
|
+
'4K': { width: 3840, height: 2160 },
|
|
145
|
+
'uhd': { width: 3840, height: 2160 },
|
|
146
|
+
'UHD': { width: 3840, height: 2160 },
|
|
147
|
+
'4320p': { width: 7680, height: 4320 },
|
|
148
|
+
'8k': { width: 7680, height: 4320 },
|
|
149
|
+
'8K': { width: 7680, height: 4320 },
|
|
150
|
+
};
|
|
151
|
+
// Determine target resolution
|
|
152
|
+
let targetWidth;
|
|
153
|
+
let targetHeight;
|
|
154
|
+
if (options.scale.includes('x')) {
|
|
155
|
+
// Custom dimensions like 1920x1080
|
|
156
|
+
const [w, h] = options.scale.split('x').map(Number);
|
|
157
|
+
targetWidth = w;
|
|
158
|
+
targetHeight = h;
|
|
159
|
+
}
|
|
160
|
+
else if (scaleMap[options.scale]) {
|
|
161
|
+
const preset = scaleMap[options.scale];
|
|
162
|
+
targetWidth = preset.width;
|
|
163
|
+
targetHeight = preset.height;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
throw new Error(`Invalid scale: ${options.scale}. Use: 360p-8K or WIDTHxHEIGHT`);
|
|
167
|
+
}
|
|
168
|
+
// Ensure even dimensions
|
|
169
|
+
targetWidth = Math.round(targetWidth / 2) * 2;
|
|
170
|
+
targetHeight = Math.round(targetHeight / 2) * 2;
|
|
171
|
+
// Codec mapping
|
|
172
|
+
const codecMap = {
|
|
173
|
+
'h264': 'libx264',
|
|
174
|
+
'h265': 'libx265',
|
|
175
|
+
'hevc': 'libx265',
|
|
176
|
+
'vp9': 'libvpx-vp9',
|
|
177
|
+
'av1': 'libaom-av1',
|
|
178
|
+
};
|
|
179
|
+
const videoCodec = codecMap[options.codec] || 'libx264';
|
|
180
|
+
// Resolve output paths
|
|
181
|
+
const outputPaths = resolveOutputPaths(inputFiles, options.output, {
|
|
182
|
+
suffix: `-${targetWidth}x${targetHeight}`,
|
|
183
|
+
newExtension: options.format ? `.${options.format}` : undefined
|
|
184
|
+
});
|
|
185
|
+
console.log(chalk.cyan.bold(`\nš¬ Resizing ${inputFiles.length} video(s) to ${options.scale}\n`));
|
|
186
|
+
// Process each file
|
|
187
|
+
for (let i = 0; i < inputFiles.length; i++) {
|
|
188
|
+
const inputFile = inputFiles[i];
|
|
189
|
+
const outputFile = outputPaths.get(inputFile);
|
|
190
|
+
spinner.start(`[${i + 1}/${inputFiles.length}] Analyzing: ${inputFile}`);
|
|
191
|
+
// Get metadata
|
|
192
|
+
const metadata = await getVideoMetadata(inputFile);
|
|
193
|
+
const inputStat = await stat(inputFile);
|
|
194
|
+
// Build filter
|
|
195
|
+
let vf = `scale=${targetWidth}:${targetHeight}:force_original_aspect_ratio=decrease`;
|
|
196
|
+
if (options.aspect) {
|
|
197
|
+
const [w, h] = options.aspect.split(':');
|
|
198
|
+
vf += `,setdar=${w}/${h}`;
|
|
199
|
+
}
|
|
200
|
+
// Build FFmpeg args
|
|
201
|
+
const args = ['-i', inputFile, '-vf', vf, '-c:v', videoCodec];
|
|
202
|
+
// Quality/Bitrate
|
|
203
|
+
if (options.bitrate) {
|
|
204
|
+
args.push('-b:v', options.bitrate);
|
|
205
|
+
if (options.twoPass) {
|
|
206
|
+
args.push('-maxrate', options.bitrate, '-bufsize', `${parseInt(options.bitrate) * 2}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
args.push('-crf', options.quality.toString());
|
|
211
|
+
}
|
|
212
|
+
args.push('-preset', options.preset);
|
|
213
|
+
// FPS
|
|
214
|
+
if (options.fps) {
|
|
215
|
+
args.push('-r', options.fps.toString());
|
|
216
|
+
}
|
|
217
|
+
// Audio
|
|
218
|
+
if (options.audio === false) {
|
|
219
|
+
args.push('-an');
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
args.push('-c:a', 'aac', '-b:a', '192k');
|
|
223
|
+
}
|
|
224
|
+
args.push('-y', outputFile);
|
|
225
|
+
// Dry run
|
|
226
|
+
if (options.dryRun) {
|
|
227
|
+
spinner.info(chalk.yellow(`[${i + 1}/${inputFiles.length}] Dry run - would execute:`));
|
|
228
|
+
console.log(chalk.dim(' ffmpeg ' + args.join(' ') + '\n'));
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
// Execute
|
|
232
|
+
spinner.text = `[${i + 1}/${inputFiles.length}] Resizing: ${inputFile}`;
|
|
233
|
+
await runFFmpeg(args, options.verbose, (line) => {
|
|
234
|
+
if (options.verbose) {
|
|
235
|
+
logFFmpegOutput(line);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
const outputStat = await stat(outputFile);
|
|
239
|
+
spinner.succeed(chalk.green(`ā [${i + 1}/${inputFiles.length}] ${metadata.width}x${metadata.height} ā ${targetWidth}x${targetHeight} | ` +
|
|
240
|
+
`${formatFileSize(inputStat.size)} ā ${formatFileSize(outputStat.size)} | ${chalk.cyan(outputFile)}`));
|
|
241
|
+
}
|
|
242
|
+
if (!options.dryRun) {
|
|
243
|
+
console.log(chalk.green.bold(`\n⨠Successfully resized ${inputFiles.length} video(s)!`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=resize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.js","sourceRoot":"","sources":["../../src/commands/resize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcode.d.ts","sourceRoot":"","sources":["../../src/commands/transcode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAqHxD"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { stat } from 'fs/promises';
|
|
3
|
+
import { runFFmpeg, getVideoMetadata, checkFFmpeg, validateInputFile, generateOutputPath, formatFileSize, formatDuration, } from '../utils/ffmpeg.js';
|
|
4
|
+
import { styleFFmpegOutput, shouldDisplayLine } from '../utils/ffmpeg-output.js';
|
|
5
|
+
export function transcodeCommand(videoCmd) {
|
|
6
|
+
videoCmd
|
|
7
|
+
.command('transcode <input>')
|
|
8
|
+
.description('Transcode video to different format/codec')
|
|
9
|
+
.option('-o, --output <path>', 'Output file path')
|
|
10
|
+
.option('-f, --format <format>', 'Output format: mp4, webm, mkv, avi', 'mp4')
|
|
11
|
+
.option('--codec <codec>', 'Video codec: h264, h265, vp9, av1', 'h264')
|
|
12
|
+
.option('--bitrate <bitrate>', 'Target bitrate (e.g., 2M, 5000k)')
|
|
13
|
+
.option('--audio-codec <codec>', 'Audio codec: aac, opus, mp3', 'aac')
|
|
14
|
+
.option('--audio-bitrate <bitrate>', 'Audio bitrate (e.g., 128k)', '128k')
|
|
15
|
+
.option('--dry-run', 'Show what would be done')
|
|
16
|
+
.option('-v, --verbose', 'Verbose output')
|
|
17
|
+
.option('--help', 'Display help for command')
|
|
18
|
+
.action(async (input, options) => {
|
|
19
|
+
try {
|
|
20
|
+
console.log(chalk.blue.bold('š¬ Video Transcoding\n'));
|
|
21
|
+
// Check ffmpeg
|
|
22
|
+
const ffmpegAvailable = await checkFFmpeg();
|
|
23
|
+
if (!ffmpegAvailable) {
|
|
24
|
+
throw new Error('ffmpeg is not installed or not in PATH');
|
|
25
|
+
}
|
|
26
|
+
// Validate input
|
|
27
|
+
const inputPath = validateInputFile(input);
|
|
28
|
+
// Get input metadata
|
|
29
|
+
console.log(chalk.dim('š Analyzing video...'));
|
|
30
|
+
const metadata = await getVideoMetadata(inputPath);
|
|
31
|
+
const inputStat = await stat(inputPath);
|
|
32
|
+
console.log(chalk.gray(` Duration: ${formatDuration(metadata.duration)}`));
|
|
33
|
+
console.log(chalk.gray(` Resolution: ${metadata.width}x${metadata.height}`));
|
|
34
|
+
console.log(chalk.gray(` Current codec: ${metadata.codec}`));
|
|
35
|
+
console.log(chalk.gray(` Size: ${formatFileSize(inputStat.size)}`));
|
|
36
|
+
console.log();
|
|
37
|
+
// Generate output path
|
|
38
|
+
const format = options.format || 'mp4';
|
|
39
|
+
const output = options.output || generateOutputPath(inputPath, 'transcoded', format);
|
|
40
|
+
// Map codec to proper ffmpeg names
|
|
41
|
+
const codecMap = {
|
|
42
|
+
h264: 'libx264',
|
|
43
|
+
h265: 'libx265',
|
|
44
|
+
vp9: 'libvpx-vp9',
|
|
45
|
+
av1: 'libaom-av1',
|
|
46
|
+
};
|
|
47
|
+
const videoCodec = codecMap[options.codec || 'h264'] || 'libx264';
|
|
48
|
+
// Build ffmpeg arguments
|
|
49
|
+
const args = ['-i', inputPath, '-c:v', videoCodec];
|
|
50
|
+
// Add bitrate if specified
|
|
51
|
+
if (options.bitrate) {
|
|
52
|
+
args.push('-b:v', options.bitrate);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Use CRF for better quality
|
|
56
|
+
args.push('-crf', '23');
|
|
57
|
+
}
|
|
58
|
+
// Audio encoding
|
|
59
|
+
const audioCodec = options.audioCodec || 'aac';
|
|
60
|
+
args.push('-c:a', audioCodec);
|
|
61
|
+
args.push('-b:a', options.audioBitrate || '128k');
|
|
62
|
+
// Preset for encoding speed
|
|
63
|
+
args.push('-preset', 'medium');
|
|
64
|
+
// Output format specific options
|
|
65
|
+
if (format === 'webm') {
|
|
66
|
+
args.push('-f', 'webm');
|
|
67
|
+
}
|
|
68
|
+
else if (format === 'mkv') {
|
|
69
|
+
args.push('-f', 'matroska');
|
|
70
|
+
}
|
|
71
|
+
args.push('-y', output);
|
|
72
|
+
if (options.dryRun) {
|
|
73
|
+
console.log(chalk.yellow('š Dry run mode - no files will be created\n'));
|
|
74
|
+
console.log(chalk.dim('Command:'));
|
|
75
|
+
console.log(chalk.gray(` ffmpeg ${args.join(' ')}\n`));
|
|
76
|
+
console.log(chalk.dim('Output:'));
|
|
77
|
+
console.log(chalk.gray(` Format: ${format}`));
|
|
78
|
+
console.log(chalk.gray(` Video codec: ${options.codec || 'h264'}`));
|
|
79
|
+
console.log(chalk.gray(` Audio codec: ${audioCodec}`));
|
|
80
|
+
console.log(chalk.green('\nā Dry run complete'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Run transcode
|
|
84
|
+
console.log(chalk.dim('š Transcoding video...'));
|
|
85
|
+
if (options.verbose) {
|
|
86
|
+
console.log(chalk.dim(`ffmpeg ${args.join(' ')}\n`));
|
|
87
|
+
}
|
|
88
|
+
await runFFmpeg(args, options.verbose, (line) => {
|
|
89
|
+
if (shouldDisplayLine(line, options.verbose || false)) {
|
|
90
|
+
console.log(styleFFmpegOutput(line));
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// Get output file size
|
|
94
|
+
const outputStat = await stat(output);
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.green.bold('ā Transcoding Complete!\n'));
|
|
97
|
+
console.log(chalk.gray(` Format: ${metadata.format} ā ${format}`));
|
|
98
|
+
console.log(chalk.gray(` Codec: ${metadata.codec} ā ${options.codec || 'h264'}`));
|
|
99
|
+
console.log(chalk.gray(` Size: ${formatFileSize(inputStat.size)} ā ${formatFileSize(outputStat.size)}`));
|
|
100
|
+
console.log(chalk.dim(`\n ${output}`));
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=transcode.js.map
|