ffmpeg-progress 1.2.2 → 1.4.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 (4) hide show
  1. package/README.md +19 -0
  2. package/core.d.ts +17 -2
  3. package/core.js +95 -3
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,15 @@ await convertFile({
42
42
  timer.end()
43
43
  ```
44
44
 
45
+ **Get video resolution**:
46
+
47
+ ```typescript
48
+ import { getVideoResolution } from 'ffmpeg-progress'
49
+
50
+ console.log(await getVideoResolution('test/rotate.mp4'))
51
+ // { width: 3024, height: 4032 }
52
+ ```
53
+
45
54
  ## Typescript Types
46
55
 
47
56
  ```typescript
@@ -88,6 +97,16 @@ export function attachChildProcess(
88
97
  childProcess: ChildProcessWithoutNullStreams
89
98
  } & ProgressArgs,
90
99
  ): Promise<void>
100
+
101
+ /**
102
+ * take a image frame from the video,
103
+ * this is more accurate than parsing the resolution string in ffmpeg,
104
+ * because the video has rotation metadata.
105
+ */
106
+ export function getVideoResolution(video_file: string): Promise<{
107
+ width: number
108
+ height: number
109
+ }>
91
110
  ```
92
111
 
93
112
  ## License
package/core.d.ts CHANGED
@@ -15,8 +15,14 @@ export type ScanVideoResult = {
15
15
  duration: string;
16
16
  /** @description e.g. 180.03 or 0 */
17
17
  seconds: number;
18
- /** @description e.g. "4032x3024" */
19
- resolution: string;
18
+ /**
19
+ * @description e.g. "4032x3024"
20
+ * This is the stored resolution of the video, not the resolution of the video when it is played.
21
+ * The display image may be rotated if there are rotation metadata in the video.
22
+ */
23
+ resolution: string | null;
24
+ /** @description e.g. 44100 for 44.1kHz */
25
+ audioSampleRate: number | null;
20
26
  };
21
27
  export declare function parseVideoMetadata(stdout: string): ScanVideoResult;
22
28
  export declare function scanVideo(file: string): Promise<ScanVideoResult>;
@@ -47,3 +53,12 @@ export declare function estimateOutSize(args: {
47
53
  currentSeconds: number;
48
54
  totalSeconds: number;
49
55
  }): number;
56
+ /**
57
+ * take a image frame from the video,
58
+ * this is more accurate than parsing the resolution string in ffmpeg,
59
+ * because the video has rotation metadata.
60
+ */
61
+ export declare function getVideoResolution(video_file: string): Promise<{
62
+ width: number;
63
+ height: number;
64
+ }>;
package/core.js CHANGED
@@ -7,7 +7,10 @@ exports.scanVideo = scanVideo;
7
7
  exports.convertFile = convertFile;
8
8
  exports.attachChildProcess = attachChildProcess;
9
9
  exports.estimateOutSize = estimateOutSize;
10
+ exports.getVideoResolution = getVideoResolution;
10
11
  const child_process_1 = require("child_process");
12
+ const promises_1 = require("fs/promises");
13
+ const path_1 = require("path");
11
14
  /** @description
12
15
  * from "00:01:00.03" to 60.03;
13
16
  * from "N/A" to NaN;
@@ -68,9 +71,10 @@ function format_ms(ms) {
68
71
  let duration_regex = /Duration: ([0-9:.]+|N\/A),/;
69
72
  // e.g. " Stream #0:0[0x1](und): Video: h264 (Baseline) (avc1 / 0x31637661), yuvj420p(pc, progressive), 4032x3024, 2045 kb/s, 29.73 fps, 600 tbr, 600 tbn (default)"
70
73
  // e.g. " Stream #0:0[0x1](eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt470bg/unknown/unknown, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 3958 kb/s, 29.49 fps, 29.83 tbr, 11456 tbn (default)"
71
- // e.g. " Stream #0:0: Audio: mp3 (mp3float), 44100 Hz, stereo, fltp, 128 kb/s"
72
74
  // e.g. " Stream #0:1: Video: flv1 (flv), yuv420p, 1080x1920, 200 kb/s, 60 fps, 60 tbr, 1k tbn"
73
75
  let resolution_regex = /Stream #0:\d[\w\[\]\(\)]*: Video: .+ (\d+x\d+)[\s|,]/;
76
+ // e.g. " Stream #0:0: Audio: mp3 (mp3float), 44100 Hz, stereo, fltp, 128 kb/s"
77
+ let audio_sample_rate_regex = /Stream #0:\d[\w\[\]\(\)]*: Audio: .+ (\d+) Hz/;
74
78
  function parseVideoMetadata(stdout) {
75
79
  let lines = stdout
76
80
  .split('\n')
@@ -82,13 +86,36 @@ function parseVideoMetadata(stdout) {
82
86
  }
83
87
  let duration = match[1];
84
88
  let seconds = parseToSeconds(duration);
89
+ let resolution = parseResolution(lines);
90
+ let audioSampleRate = parseAudioSampleRate(lines);
91
+ return { duration, seconds, resolution, audioSampleRate };
92
+ }
93
+ function parseResolution(lines) {
94
+ let has_video = lines.find(line => line.trim().startsWith('Stream #0:') && line.includes(' Video: '));
95
+ if (!has_video)
96
+ return null;
85
97
  let line = lines.find(line => resolution_regex.test(line));
86
- match = line?.match(resolution_regex);
98
+ let match = line?.match(resolution_regex);
87
99
  if (!match) {
88
100
  throw new Error('failed to find video resolution');
89
101
  }
90
102
  let resolution = match[1];
91
- return { duration, seconds, resolution };
103
+ return resolution;
104
+ }
105
+ function parseAudioSampleRate(lines) {
106
+ let has_audio = lines.find(line => line.trim().startsWith('Stream #0:') && line.includes(' Audio: '));
107
+ if (!has_audio)
108
+ return null;
109
+ let line = lines.find(line => audio_sample_rate_regex.test(line));
110
+ let match = line?.match(audio_sample_rate_regex);
111
+ if (!match) {
112
+ throw new Error('failed to find audio sample rate');
113
+ }
114
+ let audioSampleRate = +match[1];
115
+ if (!audioSampleRate) {
116
+ throw new Error(`failed to parse audio sample rate: ${JSON.stringify(match[1])}`);
117
+ }
118
+ return audioSampleRate;
92
119
  }
93
120
  function scanVideo(file) {
94
121
  return new Promise((resolve, reject) => {
@@ -170,3 +197,68 @@ function estimateOutSize(args) {
170
197
  let estimatedOutSize = args.currentOutSize + estimatedRate * remindSeconds;
171
198
  return estimatedOutSize;
172
199
  }
200
+ /**
201
+ * take a image frame from the video,
202
+ * this is more accurate than parsing the resolution string in ffmpeg,
203
+ * because the video has rotation metadata.
204
+ */
205
+ async function getVideoResolution(video_file) {
206
+ let image_file = (0, path_1.join)('/tmp/', (0, path_1.basename)(video_file) + '.jpg');
207
+ // ffmpeg -ss 0 -i video.mp4 -frames:v 1 -f image2 -update 1 -y image.jpg
208
+ let childProcess = (0, child_process_1.spawn)('ffmpeg', [
209
+ '-ss',
210
+ '0',
211
+ '-i',
212
+ video_file,
213
+ '-frames:v',
214
+ '1',
215
+ '-f',
216
+ 'image2',
217
+ '-update',
218
+ '1',
219
+ '-y',
220
+ image_file,
221
+ ]);
222
+ var { stdout, stderr, code, signal } = await waitChildProcess(childProcess);
223
+ if (code != 0) {
224
+ throw new Error(`ffmpeg exit abnormally, exit code: ${code}, signal: ${signal}, stdout: ${stdout}, stderr: ${stderr}`);
225
+ }
226
+ childProcess = (0, child_process_1.spawn)('file', [image_file]);
227
+ var { stdout, stderr, code, signal } = await waitChildProcess(childProcess);
228
+ if (code != 0) {
229
+ throw new Error(`file exit abnormally, exit code: ${code}, signal: ${signal}, stdout: ${stdout}, stderr: ${stderr}`);
230
+ }
231
+ let resolution = parseImageResolution(stdout);
232
+ await (0, promises_1.unlink)(image_file);
233
+ return resolution;
234
+ }
235
+ function waitChildProcess(childProcess) {
236
+ return new Promise((resolve, reject) => {
237
+ let stdout = '';
238
+ let stderr = '';
239
+ childProcess.stdout.on('data', (data) => {
240
+ stdout += data.toString();
241
+ });
242
+ childProcess.stderr.on('data', (data) => {
243
+ stderr += data.toString();
244
+ });
245
+ childProcess.on('exit', (code, signal) => {
246
+ resolve({ stdout, stderr, code, signal });
247
+ });
248
+ });
249
+ }
250
+ // e.g. sample.jpg: JPEG image data, baseline, precision 8, 3024x4032, components 3
251
+ function parseImageResolution(text) {
252
+ let parts = text.split(', ').reverse();
253
+ for (let part of parts) {
254
+ let parts = part.split('x');
255
+ if (parts.length == 2) {
256
+ let width = +parts[0];
257
+ let height = +parts[1];
258
+ if (width && height) {
259
+ return { width, height };
260
+ }
261
+ }
262
+ }
263
+ throw new Error('failed to parse image resolution: ' + JSON.stringify(text));
264
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffmpeg-progress",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "Extract progress from ffmpeg child_process",
5
5
  "keywords": [
6
6
  "ffmpeg",