ffmpeg-progress 1.6.0 → 1.7.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.
Files changed (5) hide show
  1. package/bin.js +2 -0
  2. package/cli.js +112 -0
  3. package/core.d.ts +8 -2
  4. package/core.js +54 -35
  5. package/package.json +8 -2
package/bin.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('./cli')
package/cli.js ADDED
@@ -0,0 +1,112 @@
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
+ for (let arg of args) {
7
+ if (arg == '-h' || arg == '--help') {
8
+ console.log(`
9
+ ffmpeg-progress - A progress monitor for FFmpeg operations
10
+
11
+ USAGE:
12
+ ffmpeg-progress <option>
13
+ ffmpeg-progress <ffmpeg-args...>
14
+ ffmpeg <ffmpeg-args...> 2>&1 | ffmpeg-progress
15
+
16
+ EXAMPLES:
17
+ # as drop-in replacement for ffmpeg:
18
+ ffmpeg-progress -i input.mp4 -c:v libx264 output.mp4
19
+
20
+ # pipe from ffmpeg output (need to redirect stderr):
21
+ ffmpeg -i input.mp4 -c:v libx264 output.mp4 2>&1 | ffmpeg-progress
22
+
23
+ OPTIONS:
24
+ -h, --help show this help message and exit
25
+ -v, --version show version and exit
26
+
27
+ NOTES:
28
+ - Pipe mode requires redirecting ffmpeg stderr to stdout (2>&1)
29
+ - Wrapped mode automatically captures ffmpeg progress from stderr
30
+ - ffmpeg outputs progress information to stderr, not stdout
31
+ `.trim());
32
+ process.exit(0);
33
+ }
34
+ if (arg == '-version' || arg == '--version') {
35
+ let pkg = require('./package.json');
36
+ console.log(`ffmpeg-progress ${pkg.version}`);
37
+ process.exit(0);
38
+ }
39
+ }
40
+ let errorLines = [];
41
+ function checkOverwrite(chunk) {
42
+ let str = chunk.toString();
43
+ if (str.includes('Overwrite?')) {
44
+ console.error(str);
45
+ return;
46
+ }
47
+ errorLines.push(str);
48
+ }
49
+ function f(time) {
50
+ return (0, core_1.secondsToString)(Math.round(time));
51
+ }
52
+ let lastMessageLength = 0;
53
+ function writeProgress(message) {
54
+ let output = '\r' + message.padEnd(lastMessageLength, ' ');
55
+ lastMessageLength = message.length;
56
+ process.stdout.write(output);
57
+ }
58
+ let startTime = 0;
59
+ function onProgress(args) {
60
+ startTime ||= Date.now();
61
+ let passedTime = Date.now() - startTime;
62
+ let progress = `${f(args.currentSeconds)}/${f(args.totalSeconds)}`;
63
+ let speed = (args.currentSeconds / (passedTime / 1000)).toFixed(1);
64
+ let elapsed = f(passedTime / 1000);
65
+ let eta = f((args.totalSeconds - args.currentSeconds) /
66
+ (args.currentSeconds / (passedTime / 1000)));
67
+ writeProgress(`progress=${progress} speed=${speed}x elapsed=${elapsed} eta=${eta}`);
68
+ }
69
+ if (args.length == 0) {
70
+ console.log('reading ffmpeg output from pipe...');
71
+ (0, core_1.attachStream)({
72
+ stream: process.stdin,
73
+ onData: checkOverwrite,
74
+ onProgress,
75
+ }).on('end', () => {
76
+ process.stdout.write('\n');
77
+ console.log('end of ffmpeg output.');
78
+ });
79
+ }
80
+ else {
81
+ let cmd = 'ffmpeg';
82
+ for (let arg of args) {
83
+ let str = JSON.stringify(arg);
84
+ if (str == `"${arg}"`) {
85
+ cmd += ' ' + arg;
86
+ }
87
+ else {
88
+ cmd += ' ' + str;
89
+ }
90
+ }
91
+ console.log('> ' + cmd);
92
+ let childProcess = (0, child_process_1.spawn)('ffmpeg', args, {
93
+ stdio: ['inherit', 'pipe', 'pipe'],
94
+ });
95
+ (0, core_1.attachChildProcess)({
96
+ childProcess,
97
+ onStderr: checkOverwrite,
98
+ onProgress,
99
+ })
100
+ .then(() => {
101
+ process.stdout.write('\n');
102
+ console.log('ffmpeg process finished.');
103
+ })
104
+ .catch(error => {
105
+ process.stderr.write('\n');
106
+ for (let line of errorLines) {
107
+ console.error(line);
108
+ }
109
+ console.error('ffmpeg process error:', error);
110
+ process.exit(1);
111
+ });
112
+ }
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
- childProcess.stderr.on('data', (data) => {
185
- let str = data.toString();
186
- let match = str.match(/Duration: ([0-9:.]+),/);
187
- if (match) {
188
- duration = match[1];
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.6.0",
3
+ "version": "1.7.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
- "mocha": "rm -f *.js && ts-mocha \"*.{test,spec}.ts\"",
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
  },