ffmpeg-progress 1.6.0 → 1.8.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/bin.js +2 -0
- package/cli.js +130 -0
- package/core.d.ts +8 -2
- package/core.js +54 -35
- package/package.json +8 -2
package/bin.js
ADDED
package/cli.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const child_process_1 = require("child_process");
|
|
4
|
+
const core_1 = require("./core");
|
|
5
|
+
let args = process.argv.slice(2);
|
|
6
|
+
let custom_args = [];
|
|
7
|
+
let verbose = false;
|
|
8
|
+
for (let arg of args) {
|
|
9
|
+
if (arg == '-h' || arg == '--help') {
|
|
10
|
+
console.log(`
|
|
11
|
+
ffmpeg-progress - A progress monitor for FFmpeg operations
|
|
12
|
+
|
|
13
|
+
USAGE:
|
|
14
|
+
ffmpeg-progress <option>
|
|
15
|
+
ffmpeg-progress <ffmpeg-args...>
|
|
16
|
+
ffmpeg <ffmpeg-args...> 2>&1 | ffmpeg-progress
|
|
17
|
+
|
|
18
|
+
EXAMPLES:
|
|
19
|
+
# as drop-in replacement for ffmpeg:
|
|
20
|
+
ffmpeg-progress -i input.mp4 -c:v libx264 output.mp4
|
|
21
|
+
|
|
22
|
+
# pipe from ffmpeg output (need to redirect stderr):
|
|
23
|
+
ffmpeg -i input.mp4 -c:v libx264 output.mp4 2>&1 | ffmpeg-progress
|
|
24
|
+
|
|
25
|
+
# using calling other script that use ffmpeg indirectly
|
|
26
|
+
to-mp4 --auto-name input.mov | ffmpeg-progress
|
|
27
|
+
|
|
28
|
+
OPTIONS:
|
|
29
|
+
-h, --help show this help message and exit
|
|
30
|
+
--version show version and exit
|
|
31
|
+
--verbose verbose mode
|
|
32
|
+
|
|
33
|
+
NOTES:
|
|
34
|
+
- Pipe mode requires redirecting ffmpeg stderr to stdout (2>&1)
|
|
35
|
+
- Wrapped mode automatically captures ffmpeg progress from stderr
|
|
36
|
+
- ffmpeg outputs progress information to stderr, not stdout
|
|
37
|
+
`.trim());
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
if (arg == '-version' || arg == '--version') {
|
|
41
|
+
let pkg = require('./package.json');
|
|
42
|
+
console.log(`ffmpeg-progress ${pkg.version}`);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
if (arg == '--verbose') {
|
|
46
|
+
verbose = true;
|
|
47
|
+
custom_args.push(arg);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
args = args.filter(arg => !custom_args.includes(arg));
|
|
52
|
+
let errorLines = [];
|
|
53
|
+
function checkOverwrite(chunk) {
|
|
54
|
+
let str = chunk.toString();
|
|
55
|
+
if (str.includes('Overwrite?')) {
|
|
56
|
+
console.error(str);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
errorLines.push(str);
|
|
60
|
+
}
|
|
61
|
+
function f(time) {
|
|
62
|
+
return (0, core_1.secondsToString)(Math.round(time));
|
|
63
|
+
}
|
|
64
|
+
let lastMessageLength = 0;
|
|
65
|
+
function writeProgress(message) {
|
|
66
|
+
let output = '\r' + message.padEnd(lastMessageLength, ' ');
|
|
67
|
+
lastMessageLength = message.length;
|
|
68
|
+
process.stdout.write(output);
|
|
69
|
+
}
|
|
70
|
+
let startTime = 0;
|
|
71
|
+
function onProgress(args) {
|
|
72
|
+
startTime ||= Date.now();
|
|
73
|
+
let passedTime = Date.now() - startTime;
|
|
74
|
+
let progress = `${f(args.currentSeconds)}/${f(args.totalSeconds)}`;
|
|
75
|
+
let speed = (args.currentSeconds / (passedTime / 1000)).toFixed(1);
|
|
76
|
+
let elapsed = f(passedTime / 1000);
|
|
77
|
+
let eta = f((args.totalSeconds - args.currentSeconds) /
|
|
78
|
+
(args.currentSeconds / (passedTime / 1000)));
|
|
79
|
+
writeProgress(`progress=${progress} speed=${speed}x elapsed=${elapsed} eta=${eta}`);
|
|
80
|
+
}
|
|
81
|
+
if (args.length == 0) {
|
|
82
|
+
writeProgress('reading ffmpeg output from pipe...');
|
|
83
|
+
(0, core_1.attachStream)({
|
|
84
|
+
stream: process.stdin,
|
|
85
|
+
onData: checkOverwrite,
|
|
86
|
+
onProgress,
|
|
87
|
+
}).on('end', () => {
|
|
88
|
+
process.stdout.write('\n');
|
|
89
|
+
if (verbose) {
|
|
90
|
+
console.log('end of ffmpeg output.');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
if (verbose) {
|
|
96
|
+
let cmd = 'ffmpeg';
|
|
97
|
+
for (let arg of args) {
|
|
98
|
+
let str = JSON.stringify(arg);
|
|
99
|
+
if (str == `"${arg}"`) {
|
|
100
|
+
cmd += ' ' + arg;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
cmd += ' ' + str;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
console.log('> ' + cmd);
|
|
107
|
+
}
|
|
108
|
+
let childProcess = (0, child_process_1.spawn)('ffmpeg', args, {
|
|
109
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
110
|
+
});
|
|
111
|
+
(0, core_1.attachChildProcess)({
|
|
112
|
+
childProcess,
|
|
113
|
+
onStderr: checkOverwrite,
|
|
114
|
+
onProgress,
|
|
115
|
+
})
|
|
116
|
+
.then(() => {
|
|
117
|
+
process.stdout.write('\n');
|
|
118
|
+
if (verbose) {
|
|
119
|
+
console.log('ffmpeg process finished.');
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
.catch(error => {
|
|
123
|
+
process.stderr.write('\n');
|
|
124
|
+
for (let line of errorLines) {
|
|
125
|
+
console.error(line);
|
|
126
|
+
}
|
|
127
|
+
console.error('ffmpeg process error:', error);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
});
|
|
130
|
+
}
|
package/core.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChildProcessWithoutNullStreams } from 'child_process';
|
|
1
|
+
import { ChildProcessByStdio, ChildProcessWithoutNullStreams } from 'child_process';
|
|
2
|
+
import Stream from 'stream';
|
|
2
3
|
/** @description
|
|
3
4
|
* from "00:01:00.03" to 60.03;
|
|
4
5
|
* from "N/A" to NaN;
|
|
@@ -27,6 +28,7 @@ export type ScanVideoResult = {
|
|
|
27
28
|
export declare function parseVideoMetadata(stdout: string): ScanVideoResult;
|
|
28
29
|
export declare function scanVideo(file: string): Promise<ScanVideoResult>;
|
|
29
30
|
export type ProgressArgs = {
|
|
31
|
+
onStderr?: (chunk: Buffer) => void;
|
|
30
32
|
onData?: (chunk: Buffer) => void;
|
|
31
33
|
onDuration?: (duration: string) => void;
|
|
32
34
|
onTime?: (time: string) => void;
|
|
@@ -53,8 +55,12 @@ export declare function rotateVideo(args: {
|
|
|
53
55
|
angle: 90 | 180 | 270;
|
|
54
56
|
} & ProgressArgs): Promise<void>;
|
|
55
57
|
export declare function attachChildProcess(args: {
|
|
56
|
-
childProcess: ChildProcessWithoutNullStreams
|
|
58
|
+
childProcess: ChildProcessWithoutNullStreams | ChildProcessByStdio<null, Stream.Readable, Stream.Readable>;
|
|
57
59
|
} & ProgressArgs): Promise<void>;
|
|
60
|
+
export declare function attachStream(args: {
|
|
61
|
+
stream: Stream.Readable;
|
|
62
|
+
abort?: () => void;
|
|
63
|
+
} & ProgressArgs): Stream.Readable;
|
|
58
64
|
export declare function estimateOutSize(args: {
|
|
59
65
|
inSize: number;
|
|
60
66
|
currentOutSize: number;
|
package/core.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.scanVideo = scanVideo;
|
|
|
7
7
|
exports.convertFile = convertFile;
|
|
8
8
|
exports.rotateVideo = rotateVideo;
|
|
9
9
|
exports.attachChildProcess = attachChildProcess;
|
|
10
|
+
exports.attachStream = attachStream;
|
|
10
11
|
exports.estimateOutSize = estimateOutSize;
|
|
11
12
|
exports.getVideoResolution = getVideoResolution;
|
|
12
13
|
exports.getVideoDuration = getVideoDuration;
|
|
@@ -137,6 +138,9 @@ function scanVideo(file) {
|
|
|
137
138
|
});
|
|
138
139
|
}
|
|
139
140
|
async function convertFile(args) {
|
|
141
|
+
if (args.inFile === args.outFile) {
|
|
142
|
+
throw new Error('ffmpeg cannot edit files in-place, input and output file cannot be the same.');
|
|
143
|
+
}
|
|
140
144
|
let ffmpegCommand = ['-y', '-i', args.inFile];
|
|
141
145
|
if (args.ffmpegArgs) {
|
|
142
146
|
ffmpegCommand.push(...args.ffmpegArgs);
|
|
@@ -171,47 +175,17 @@ async function rotateVideo(args) {
|
|
|
171
175
|
async function attachChildProcess(args) {
|
|
172
176
|
let { code, signal } = await new Promise((resolve, reject) => {
|
|
173
177
|
let { childProcess } = args;
|
|
174
|
-
let duration = '';
|
|
175
|
-
let time = '';
|
|
176
|
-
let totalSeconds = 0;
|
|
177
|
-
let lastSeconds = 0;
|
|
178
178
|
function abort() {
|
|
179
179
|
childProcess.kill('SIGKILL');
|
|
180
180
|
}
|
|
181
181
|
if (args.onData) {
|
|
182
182
|
childProcess.stdout.on('data', args.onData);
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
totalSeconds = parseToSeconds(duration);
|
|
190
|
-
if (args.onDuration) {
|
|
191
|
-
args.onDuration(duration);
|
|
192
|
-
}
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
match = str.match(/frame=\s*\d+\s+fps=.*time=([0-9:.]+)\s/);
|
|
196
|
-
if (match) {
|
|
197
|
-
time = match[1];
|
|
198
|
-
if (args.onTime) {
|
|
199
|
-
args.onTime(time);
|
|
200
|
-
}
|
|
201
|
-
if (args.onProgress) {
|
|
202
|
-
let currentSeconds = parseToSeconds(time);
|
|
203
|
-
let deltaSeconds = currentSeconds - lastSeconds;
|
|
204
|
-
lastSeconds = currentSeconds;
|
|
205
|
-
args.onProgress({
|
|
206
|
-
deltaSeconds,
|
|
207
|
-
currentSeconds,
|
|
208
|
-
totalSeconds,
|
|
209
|
-
time,
|
|
210
|
-
duration,
|
|
211
|
-
abort,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
184
|
+
attachStream({
|
|
185
|
+
stream: childProcess.stderr,
|
|
186
|
+
onData: undefined,
|
|
187
|
+
abort,
|
|
188
|
+
...args,
|
|
215
189
|
});
|
|
216
190
|
childProcess.on('exit', (code, signal) => {
|
|
217
191
|
resolve({ code, signal });
|
|
@@ -221,6 +195,51 @@ async function attachChildProcess(args) {
|
|
|
221
195
|
return;
|
|
222
196
|
throw new Error(`ffmpeg exit abnormally, exit code: ${code}, signal: ${signal}`);
|
|
223
197
|
}
|
|
198
|
+
function attachStream(args) {
|
|
199
|
+
let abort = args.abort || (() => { });
|
|
200
|
+
let duration = '';
|
|
201
|
+
let time = '';
|
|
202
|
+
let totalSeconds = 0;
|
|
203
|
+
let lastSeconds = 0;
|
|
204
|
+
return args.stream.on('data', (data) => {
|
|
205
|
+
if (args.onData) {
|
|
206
|
+
args.onData(data);
|
|
207
|
+
}
|
|
208
|
+
if (args.onStderr) {
|
|
209
|
+
args.onStderr(data);
|
|
210
|
+
}
|
|
211
|
+
let str = data.toString();
|
|
212
|
+
let match = str.match(/Duration: ([0-9:.]+),/);
|
|
213
|
+
if (match) {
|
|
214
|
+
duration = match[1];
|
|
215
|
+
totalSeconds = parseToSeconds(duration);
|
|
216
|
+
if (args.onDuration) {
|
|
217
|
+
args.onDuration(duration);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
match = str.match(/frame=\s*\d+\s+fps=.*time=([0-9:.]+)\s/);
|
|
222
|
+
if (match) {
|
|
223
|
+
time = match[1];
|
|
224
|
+
if (args.onTime) {
|
|
225
|
+
args.onTime(time);
|
|
226
|
+
}
|
|
227
|
+
if (args.onProgress) {
|
|
228
|
+
let currentSeconds = parseToSeconds(time);
|
|
229
|
+
let deltaSeconds = currentSeconds - lastSeconds;
|
|
230
|
+
lastSeconds = currentSeconds;
|
|
231
|
+
args.onProgress({
|
|
232
|
+
deltaSeconds,
|
|
233
|
+
currentSeconds,
|
|
234
|
+
totalSeconds,
|
|
235
|
+
time,
|
|
236
|
+
duration,
|
|
237
|
+
abort,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
224
243
|
function estimateOutSize(args) {
|
|
225
244
|
let estimatedRate = args.currentOutSize / args.currentSeconds;
|
|
226
245
|
let remindSeconds = args.totalSeconds - args.currentSeconds;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ffmpeg-progress",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Extract progress from ffmpeg child_process",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ffmpeg",
|
|
@@ -29,15 +29,21 @@
|
|
|
29
29
|
},
|
|
30
30
|
"main": "core.js",
|
|
31
31
|
"types": "./core.d.ts",
|
|
32
|
+
"bin": {
|
|
33
|
+
"ffmpeg-progress": "./bin.js"
|
|
34
|
+
},
|
|
32
35
|
"directories": {
|
|
33
36
|
"test": "test"
|
|
34
37
|
},
|
|
35
38
|
"files": [
|
|
39
|
+
"bin.js",
|
|
40
|
+
"cli.js",
|
|
36
41
|
"core.d.ts",
|
|
37
42
|
"core.js"
|
|
38
43
|
],
|
|
39
44
|
"scripts": {
|
|
40
|
-
"
|
|
45
|
+
"clean": "rm -f *.js && git checkout bin.js",
|
|
46
|
+
"mocha": "npm run clean && ts-mocha \"*.{test,spec}.ts\"",
|
|
41
47
|
"test": "npm run mocha && tsc --noEmit",
|
|
42
48
|
"build": "tsc -p ."
|
|
43
49
|
},
|