@mediaproc/video 1.3.0 → 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 (47) hide show
  1. package/dist/cli.js +25 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/compress.d.ts.map +1 -1
  4. package/dist/commands/compress.js +18 -14
  5. package/dist/commands/compress.js.map +1 -1
  6. package/dist/commands/convert.d.ts.map +1 -1
  7. package/dist/commands/convert.js +40 -29
  8. package/dist/commands/convert.js.map +1 -1
  9. package/dist/commands/extract.d.ts.map +1 -1
  10. package/dist/commands/extract.js +98 -31
  11. package/dist/commands/extract.js.map +1 -1
  12. package/dist/commands/merge.d.ts.map +1 -1
  13. package/dist/commands/merge.js +161 -47
  14. package/dist/commands/merge.js.map +1 -1
  15. package/dist/commands/metadata.d.ts +3 -0
  16. package/dist/commands/metadata.d.ts.map +1 -0
  17. package/dist/commands/metadata.js +310 -0
  18. package/dist/commands/metadata.js.map +1 -0
  19. package/dist/commands/resize.d.ts.map +1 -1
  20. package/dist/commands/resize.js +50 -16
  21. package/dist/commands/resize.js.map +1 -1
  22. package/dist/commands/transcode.d.ts.map +1 -1
  23. package/dist/commands/transcode.js +39 -23
  24. package/dist/commands/transcode.js.map +1 -1
  25. package/dist/commands/trim.d.ts.map +1 -1
  26. package/dist/commands/trim.js +55 -20
  27. package/dist/commands/trim.js.map +1 -1
  28. package/dist/register.d.ts +2 -1
  29. package/dist/register.d.ts.map +1 -1
  30. package/dist/register.js +28 -8
  31. package/dist/register.js.map +1 -1
  32. package/dist/types.d.ts +32 -35
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/utils/ffmpeg-output.d.ts +7 -3
  35. package/dist/utils/ffmpeg-output.d.ts.map +1 -1
  36. package/dist/utils/ffmpeg-output.js +56 -65
  37. package/dist/utils/ffmpeg-output.js.map +1 -1
  38. package/dist/utils/ffmpeg.d.ts +26 -22
  39. package/dist/utils/ffmpeg.d.ts.map +1 -1
  40. package/dist/utils/ffmpeg.js +211 -93
  41. package/dist/utils/ffmpeg.js.map +1 -1
  42. package/dist/utils/ffmpegLogger.d.ts +3 -10
  43. package/dist/utils/ffmpegLogger.d.ts.map +1 -1
  44. package/dist/utils/ffmpegLogger.js +15 -51
  45. package/dist/utils/ffmpegLogger.js.map +1 -1
  46. package/package.json +3 -7
  47. package/bin/cli.js +0 -5
@@ -1,94 +1,85 @@
1
1
  import chalk from 'chalk';
2
2
  /**
3
- * Parse and style FFmpeg output for better readability
3
+ * Style FFmpeg output line with appropriate colors and formatting (video only)
4
4
  */
5
5
  export function styleFFmpegOutput(line) {
6
- line = line.trim();
7
- // Skip empty lines
8
- if (!line)
6
+ const trimmed = line.trim();
7
+ if (!trimmed)
9
8
  return '';
10
- // Error messages (red)
11
- if (line.includes('Error') || line.includes('error') || line.includes('failed')) {
12
- return chalk.red(line);
13
- }
14
- // Warning messages (yellow)
15
- if (line.includes('Warning') || line.includes('warning')) {
16
- return chalk.yellow(line);
17
- }
18
- // Progress information (cyan)
19
- if (line.includes('frame=') || line.includes('fps=') || line.includes('time=') || line.includes('speed=')) {
20
- // Parse progress line
21
- const frameMatch = line.match(/frame=\s*(\d+)/);
22
- const fpsMatch = line.match(/fps=\s*([\d.]+)/);
23
- const timeMatch = line.match(/time=\s*([\d:\.]+)/);
24
- const speedMatch = line.match(/speed=\s*([\d.]+x)/);
25
- const sizeMatch = line.match(/size=\s*(\d+\w+)/);
26
- const bitrateMatch = line.match(/bitrate=\s*([\d.]+\w+)/);
27
- let output = chalk.cyan('⚡ Progress: ');
9
+ // Progress updates (frame=, fps=, time=, speed=)
10
+ if (trimmed.match(/frame=|fps=|time=|speed=/)) {
11
+ // Parse progress line for richer output
12
+ const frameMatch = trimmed.match(/frame=\s*(\d+)/);
13
+ const fpsMatch = trimmed.match(/fps=\s*([\d.]+)/);
14
+ const timeMatch = trimmed.match(/time=\s*([\d:\.]+)/);
15
+ const speedMatch = trimmed.match(/speed=\s*([\d.]+x)/);
16
+ const sizeMatch = trimmed.match(/size=\s*(\d+\w+)/);
17
+ const bitrateMatch = trimmed.match(/bitrate=\s*([\d.]+\w+)/);
18
+ let output = chalk.cyan.bold('\u23f3 Progress: ');
28
19
  if (frameMatch)
29
20
  output += chalk.white(`Frame ${frameMatch[1]} `);
30
21
  if (fpsMatch)
31
- output += chalk.gray(`• ${fpsMatch[1]} fps `);
22
+ output += chalk.gray(`\u2022 ${fpsMatch[1]} fps `);
32
23
  if (timeMatch)
33
- output += chalk.white(`• ${timeMatch[1]} `);
24
+ output += chalk.white(`\u2022 ${timeMatch[1]} `);
34
25
  if (sizeMatch)
35
- output += chalk.gray(`• ${sizeMatch[1]} `);
26
+ output += chalk.gray(`\u2022 ${sizeMatch[1]} `);
36
27
  if (bitrateMatch)
37
- output += chalk.gray(`• ${bitrateMatch[1]} `);
28
+ output += chalk.gray(`\u2022 ${bitrateMatch[1]} `);
38
29
  if (speedMatch)
39
- output += chalk.green(`• ${speedMatch[1]}`);
30
+ output += chalk.green(`\u2022 ${speedMatch[1]}`);
40
31
  return output;
41
32
  }
42
- // Input/Output file info (blue)
43
- if (line.includes('Input #') || line.includes('Output #')) {
44
- return chalk.blue.bold(line);
33
+ // Input file info
34
+ if (trimmed.startsWith('Input #') || trimmed.includes('Duration:') || trimmed.includes('Stream #')) {
35
+ return chalk.blue(trimmed);
45
36
  }
46
- // Stream info (magenta)
47
- if (line.includes('Stream #')) {
48
- return chalk.magenta(line);
37
+ // Output file info
38
+ if (trimmed.startsWith('Output #')) {
39
+ return chalk.green(trimmed);
49
40
  }
50
- // Duration and metadata (green)
51
- if (line.includes('Duration:') || line.includes('Metadata:')) {
52
- return chalk.green(line);
41
+ // Warnings
42
+ if (trimmed.toLowerCase().includes('warning')) {
43
+ return chalk.yellow(trimmed);
53
44
  }
54
- // Configuration info (gray)
55
- if (line.includes('configuration:') || line.includes('libav') || line.includes('built with')) {
56
- return chalk.gray(line);
45
+ // Errors
46
+ if (trimmed.toLowerCase().includes('error') || trimmed.toLowerCase().includes('failed')) {
47
+ return chalk.red(trimmed);
57
48
  }
58
- // Success messages (green)
59
- if (line.includes('successfully') || line.includes('completed')) {
60
- return chalk.green(line);
49
+ // Success messages
50
+ if (trimmed.includes('successfully') || trimmed.includes('complete')) {
51
+ return chalk.green(trimmed);
61
52
  }
62
- // Default: dim for general info
63
- return chalk.dim(line);
53
+ // Default gray for other lines
54
+ return chalk.gray(trimmed);
64
55
  }
65
56
  /**
66
- * Check if line should be displayed based on verbosity
57
+ * Check if a line should be displayed based on content (video only)
67
58
  */
68
- export function shouldDisplayLine(line, verbose) {
69
- line = line.trim();
70
- if (!line)
59
+ export function shouldDisplayLine(line) {
60
+ const trimmed = line.trim();
61
+ if (!trimmed)
71
62
  return false;
72
63
  // Always show errors and warnings
73
- if (line.includes('Error') || line.includes('error') || line.includes('Warning') || line.includes('warning')) {
64
+ if (trimmed.toLowerCase().includes('error') || trimmed.toLowerCase().includes('failed') || trimmed.toLowerCase().includes('warning'))
74
65
  return true;
75
- }
76
- // Always show progress
77
- if (line.includes('frame=') && line.includes('time=')) {
66
+ // Show progress and status lines always
67
+ if (trimmed.match(/frame=|fps=|time=|speed=/))
78
68
  return true;
69
+ // Show input/output info
70
+ if (trimmed.startsWith('Input #') || trimmed.startsWith('Output #'))
71
+ return true;
72
+ return false;
73
+ }
74
+ /**
75
+ * Log FFmpeg output (video only, plain, no styling)
76
+ */
77
+ export function logFFmpegOutput(output, verbose = false) {
78
+ const lines = output.split('\n');
79
+ for (const line of lines) {
80
+ if (shouldDisplayLine(line) || verbose) {
81
+ console.log(line);
82
+ }
79
83
  }
80
- // Show important info
81
- if (line.includes('Input #') || line.includes('Output #') || line.includes('Stream #')) {
82
- return verbose;
83
- }
84
- // Show duration and metadata if verbose
85
- if (line.includes('Duration:') || line.includes('Metadata:')) {
86
- return verbose;
87
- }
88
- // Filter out configuration and build info unless very verbose
89
- if (line.includes('configuration:') || line.includes('built with')) {
90
- return false;
91
- }
92
- return verbose;
93
84
  }
94
85
  //# sourceMappingURL=ffmpeg-output.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ffmpeg-output.js","sourceRoot":"","sources":["../../src/utils/ffmpeg-output.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ffmpeg-output.js","sourceRoot":"","sources":["../../src/utils/ffmpeg-output.ts"],"names":[],"mappings":"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"}
@@ -1,46 +1,50 @@
1
1
  export interface VideoMetadata {
2
2
  duration: number;
3
+ codec: string;
3
4
  width: number;
4
5
  height: number;
5
- codec: string;
6
- fps: number;
7
6
  bitrate: number;
8
7
  format: string;
9
8
  }
9
+ export interface StreamInfo {
10
+ hasVideo: boolean;
11
+ hasAudio: boolean;
12
+ videoStreamIndex: number;
13
+ audioStreamIndex: number;
14
+ audioStreams: number[];
15
+ videoStreams: number[];
16
+ }
10
17
  /**
11
- * Execute ffmpeg command
12
- */
13
- export declare function runFFmpeg(args: string[], verbose?: boolean, onOutput?: (line: string) => void): Promise<void>;
14
- /**
15
- * Get video metadata using ffprobe
18
+ * Check if ffmpeg and ffprobe are available, with styled output
19
+ * If strict=true, both must be present. If strict=false, only ffmpeg is checked.
16
20
  */
17
- export declare function getVideoMetadata(input: string): Promise<VideoMetadata>;
21
+ export declare function checkFFmpeg(strict?: boolean): Promise<boolean>;
18
22
  /**
19
- * Check if ffmpeg is available
23
+ * Format file size to human-readable string (styled)
20
24
  */
21
- export declare function checkFFmpeg(): Promise<boolean>;
25
+ export declare function formatFileSize(bytes: number): string;
22
26
  /**
23
- * Check if ffprobe is available
27
+ * Format duration to HH:MM:SS (styled)
24
28
  */
25
- export declare function checkFFprobe(): Promise<boolean>;
29
+ export declare function formatDuration(seconds: number): string;
26
30
  /**
27
- * Validate input file exists
31
+ * Parse time string (HH:MM:SS or seconds) to seconds
28
32
  */
29
- export declare function validateInputFile(input: string): string;
33
+ export declare function parseTime(time: string): number;
30
34
  /**
31
- * Generate output filename if not provided
35
+ * Format bitrate to human-readable string (styled)
32
36
  */
33
- export declare function generateOutputPath(input: string, suffix: string, extension?: string): string;
37
+ export declare function formatBitrate(bitrate: number): string;
34
38
  /**
35
- * Format time for display (seconds to HH:MM:SS)
39
+ * Execute ffmpeg command with detailed output and robust error handling
36
40
  */
37
- export declare function formatDuration(seconds: number): string;
41
+ export declare function runFFmpeg(args: string[], verbose?: boolean, onOutput?: (line: string, styledLine?: string) => void): Promise<void>;
38
42
  /**
39
- * Parse time string (HH:MM:SS) to seconds
43
+ * Get video metadata using ffprobe with detailed output and error reporting
40
44
  */
41
- export declare function parseTimeToSeconds(time: string): number;
45
+ export declare function getVideoMetadata(input: string): Promise<VideoMetadata>;
42
46
  /**
43
- * Format file size
47
+ * Get stream information (audio/video streams) from a media file
44
48
  */
45
- export declare function formatFileSize(bytes: number): string;
49
+ export declare function getStreamInfo(input: string): Promise<StreamInfo>;
46
50
  //# sourceMappingURL=ffmpeg.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/utils/ffmpeg.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,UAAQ,EACf,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0D5E;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAMpD;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAMrD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQvD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWpD"}
1
+ {"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/utils/ffmpeg.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAuBlE;AAgBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMpD;AAGD;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKtD;AAGD;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW9C;AAGD;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,UAAQ,EACf,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,GACrD,OAAO,CAAC,IAAI,CAAC,CA0Cf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAkE5E;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAmDtE"}
@@ -1,51 +1,169 @@
1
1
  import { spawn } from 'child_process';
2
- import { resolve } from 'path';
3
- import { fileExists } from '@mediaproc/core';
2
+ import { shouldDisplayLine, logFFmpegOutput } from './ffmpegLogger.js';
3
+ import { styleFFmpegOutput } from './ffmpeg-output.js';
4
+ // FFmpeg install guidance links (all platforms)
5
+ const FFMPEG_GUIDES = [
6
+ { label: 'Official guide', url: 'https://ffmpeg.org/download.html' },
7
+ { label: 'Community guide', url: 'https://github.com/adaptlearning/adapt_authoring/wiki/Installing-FFmpeg' },
8
+ { label: 'Multi-platform guide', url: 'https://github.com/ffmpegwasm/ffmpeg.wasm/blob/main/docs/install.md' },
9
+ ];
10
+ function printFFmpegInstallGuidance() {
11
+ const lines = [
12
+ '✗ FFmpeg or ffprobe not found on your system.',
13
+ 'Please install FFmpeg and ffprobe to use video features.',
14
+ ...FFMPEG_GUIDES.map(g => `${g.label}: ${g.url}`),
15
+ 'After installation, ensure ffmpeg and ffprobe are available in your PATH.'
16
+ ];
17
+ lines.forEach(line => console.error(styleFFmpegOutput(line)));
18
+ }
19
+ /**
20
+ * Check if ffmpeg and ffprobe are available, with styled output
21
+ * If strict=true, both must be present. If strict=false, only ffmpeg is checked.
22
+ */
23
+ export async function checkFFmpeg(strict = false) {
24
+ // Check ffmpeg
25
+ const ffmpegAvailable = await new Promise((resolve) => {
26
+ try {
27
+ const ffmpeg = spawn('ffmpeg', ['-version']);
28
+ ffmpeg.on('close', (code) => resolve(code === 0));
29
+ ffmpeg.on('error', () => resolve(false));
30
+ }
31
+ catch {
32
+ resolve(false);
33
+ }
34
+ });
35
+ if (!strict)
36
+ return ffmpegAvailable;
37
+ // Check ffprobe
38
+ const ffprobeAvailable = await new Promise((resolve) => {
39
+ try {
40
+ const ffprobe = spawn('ffprobe', ['-version']);
41
+ ffprobe.on('close', (code) => resolve(code === 0));
42
+ ffprobe.on('error', () => resolve(false));
43
+ }
44
+ catch {
45
+ resolve(false);
46
+ }
47
+ });
48
+ return ffmpegAvailable && ffprobeAvailable;
49
+ }
50
+ /**
51
+ * Centralized check and guidance for ffmpeg/ffprobe availability
52
+ */
53
+ async function ensureFFmpegAvailable() {
54
+ const available = await checkFFmpeg(true);
55
+ if (!available) {
56
+ printFFmpegInstallGuidance();
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ /**
62
+ * Format file size to human-readable string (styled)
63
+ */
64
+ export function formatFileSize(bytes) {
65
+ if (bytes === 0)
66
+ return '0 B';
67
+ const k = 1024;
68
+ const sizes = ['B', 'KB', 'MB', 'GB'];
69
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
70
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
71
+ }
4
72
  /**
5
- * Execute ffmpeg command
73
+ * Format duration to HH:MM:SS (styled)
74
+ */
75
+ export function formatDuration(seconds) {
76
+ const h = Math.floor(seconds / 3600);
77
+ const m = Math.floor((seconds % 3600) / 60);
78
+ const s = Math.floor(seconds % 60);
79
+ return [h, m, s].map(v => v.toString().padStart(2, '0')).join(':');
80
+ }
81
+ /**
82
+ * Parse time string (HH:MM:SS or seconds) to seconds
83
+ */
84
+ export function parseTime(time) {
85
+ if (/^\d+(\.\d+)?$/.test(time)) {
86
+ return parseFloat(time);
87
+ }
88
+ const parts = time.split(':').map(p => parseInt(p));
89
+ if (parts.length === 3) {
90
+ return parts[0] * 3600 + parts[1] * 60 + parts[2];
91
+ }
92
+ else if (parts.length === 2) {
93
+ return parts[0] * 60 + parts[1];
94
+ }
95
+ return parseFloat(time);
96
+ }
97
+ /**
98
+ * Format bitrate to human-readable string (styled)
99
+ */
100
+ export function formatBitrate(bitrate) {
101
+ if (bitrate === 0)
102
+ return 'unknown';
103
+ const kbps = bitrate / 1000;
104
+ return `${Math.round(kbps)} kbps`;
105
+ }
106
+ /**
107
+ * Execute ffmpeg command with detailed output and robust error handling
6
108
  */
7
109
  export async function runFFmpeg(args, verbose = false, onOutput) {
110
+ if (!(await ensureFFmpegAvailable()))
111
+ return;
8
112
  return new Promise((resolve, reject) => {
9
113
  const ffmpeg = spawn('ffmpeg', args);
10
114
  let stderr = '';
115
+ let allOutput = '';
11
116
  ffmpeg.stderr.on('data', (data) => {
12
117
  const output = data.toString();
13
118
  stderr += output;
14
- if (onOutput) {
15
- // Split by lines and call callback for each
16
- output.split('\n').forEach((line) => {
17
- if (line.trim()) {
18
- onOutput(line);
119
+ allOutput += output;
120
+ output.split('\n').forEach((line) => {
121
+ if (shouldDisplayLine(line) || verbose) {
122
+ const styled = styleFFmpegOutput(line);
123
+ logFFmpegOutput(line);
124
+ if (onOutput) {
125
+ onOutput(line, styled);
19
126
  }
20
- });
21
- }
22
- else if (verbose) {
23
- process.stderr.write(data);
24
- }
127
+ else {
128
+ if (styled)
129
+ console.log(styled);
130
+ }
131
+ }
132
+ });
25
133
  });
26
134
  ffmpeg.on('close', (code) => {
27
135
  if (code === 0) {
136
+ console.log(styleFFmpegOutput('FFmpeg finished successfully.'));
28
137
  resolve();
29
138
  }
30
139
  else {
140
+ stderr.split('\n').forEach((line) => {
141
+ if (shouldDisplayLine(line)) {
142
+ logFFmpegOutput(line);
143
+ const styled = styleFFmpegOutput(line);
144
+ if (styled)
145
+ console.error(styled);
146
+ }
147
+ });
31
148
  reject(new Error(`FFmpeg failed with code ${code}\n${stderr}`));
32
149
  }
33
150
  });
34
151
  ffmpeg.on('error', (error) => {
152
+ console.error(styleFFmpegOutput(`Failed to start ffmpeg: ${error.message}`));
35
153
  reject(new Error(`Failed to start ffmpeg: ${error.message}`));
36
154
  });
37
155
  });
38
156
  }
39
157
  /**
40
- * Get video metadata using ffprobe
158
+ * Get video metadata using ffprobe with detailed output and error reporting
41
159
  */
42
160
  export async function getVideoMetadata(input) {
161
+ if (!(await ensureFFmpegAvailable()))
162
+ return Promise.reject(new Error('FFmpeg/ffprobe not installed'));
43
163
  return new Promise((resolve, reject) => {
44
164
  const ffprobe = spawn('ffprobe', [
45
- '-v',
46
- 'quiet',
47
- '-print_format',
48
- 'json',
165
+ '-v', 'quiet',
166
+ '-print_format', 'json',
49
167
  '-show_format',
50
168
  '-show_streams',
51
169
  input,
@@ -56,10 +174,20 @@ export async function getVideoMetadata(input) {
56
174
  stdout += data.toString();
57
175
  });
58
176
  ffprobe.stderr.on('data', (data) => {
59
- stderr += data.toString();
177
+ const output = data.toString();
178
+ stderr += output;
179
+ output.split('\n').forEach((line) => {
180
+ if (shouldDisplayLine(line)) {
181
+ logFFmpegOutput(line);
182
+ const styled = styleFFmpegOutput(line);
183
+ if (styled)
184
+ console.error(styled);
185
+ }
186
+ });
60
187
  });
61
188
  ffprobe.on('close', (code) => {
62
189
  if (code !== 0) {
190
+ console.error(styleFFmpegOutput(`ffprobe failed with code ${code}`));
63
191
  reject(new Error(`ffprobe failed: ${stderr}`));
64
192
  return;
65
193
  }
@@ -67,101 +195,91 @@ export async function getVideoMetadata(input) {
67
195
  const data = JSON.parse(stdout);
68
196
  const videoStream = data.streams.find((s) => s.codec_type === 'video');
69
197
  if (!videoStream) {
198
+ console.error(styleFFmpegOutput('No video stream found in file.'));
70
199
  reject(new Error('No video stream found'));
71
200
  return;
72
201
  }
73
202
  const metadata = {
74
203
  duration: parseFloat(data.format.duration) || 0,
204
+ codec: videoStream.codec_name || 'unknown',
75
205
  width: videoStream.width || 0,
76
206
  height: videoStream.height || 0,
77
- codec: videoStream.codec_name || 'unknown',
78
- fps: eval(videoStream.r_frame_rate) || 0,
79
- bitrate: parseInt(data.format.bit_rate) || 0,
207
+ bitrate: parseInt(data.format.bit_rate) || parseInt(videoStream.bit_rate) || 0,
80
208
  format: data.format.format_name || 'unknown',
81
209
  };
210
+ // Detailed output of metadata
211
+ console.log(styleFFmpegOutput('Video Metadata:'));
212
+ Object.entries(metadata).forEach(([key, value]) => {
213
+ console.log(styleFFmpegOutput(` ${key}: ${value}`));
214
+ });
82
215
  resolve(metadata);
83
216
  }
84
217
  catch (error) {
85
- reject(new Error(`Failed to parse ffprobe output: ${error}`));
218
+ console.error(styleFFmpegOutput(`Failed to parse ffprobe output: ${error.message}`));
219
+ reject(new Error(`Failed to parse ffprobe output: ${error.message}`));
86
220
  }
87
221
  });
222
+ // Handle spawn error to prevent memory leaks
88
223
  ffprobe.on('error', (error) => {
224
+ console.error(styleFFmpegOutput(`Failed to start ffprobe: ${error.message}`));
89
225
  reject(new Error(`Failed to start ffprobe: ${error.message}`));
90
226
  });
91
227
  });
92
228
  }
93
229
  /**
94
- * Check if ffmpeg is available
230
+ * Get stream information (audio/video streams) from a media file
95
231
  */
96
- export async function checkFFmpeg() {
97
- return new Promise((resolve) => {
98
- const ffmpeg = spawn('ffmpeg', ['-version']);
99
- ffmpeg.on('close', (code) => resolve(code === 0));
100
- ffmpeg.on('error', () => resolve(false));
101
- });
102
- }
103
- /**
104
- * Check if ffprobe is available
105
- */
106
- export async function checkFFprobe() {
107
- return new Promise((resolve) => {
108
- const ffprobe = spawn('ffprobe', ['-version']);
109
- ffprobe.on('close', (code) => resolve(code === 0));
110
- ffprobe.on('error', () => resolve(false));
232
+ export async function getStreamInfo(input) {
233
+ if (!(await ensureFFmpegAvailable()))
234
+ return Promise.reject(new Error('FFmpeg/ffprobe not installed'));
235
+ return new Promise((resolve, reject) => {
236
+ const ffprobe = spawn('ffprobe', [
237
+ '-v', 'quiet',
238
+ '-print_format', 'json',
239
+ '-show_streams',
240
+ input,
241
+ ]);
242
+ let stdout = '';
243
+ let stderr = '';
244
+ ffprobe.stdout.on('data', (data) => {
245
+ stdout += data.toString();
246
+ });
247
+ ffprobe.stderr.on('data', (data) => {
248
+ stderr += data.toString();
249
+ });
250
+ ffprobe.on('close', (code) => {
251
+ if (code !== 0) {
252
+ reject(new Error(`ffprobe failed: ${stderr}`));
253
+ return;
254
+ }
255
+ try {
256
+ const data = JSON.parse(stdout);
257
+ const videoStreams = data.streams
258
+ .map((s, idx) => ({ ...s, index: idx }))
259
+ .filter((s) => s.codec_type === 'video')
260
+ .map((s) => s.index);
261
+ const audioStreams = data.streams
262
+ .map((s, idx) => ({ ...s, index: idx }))
263
+ .filter((s) => s.codec_type === 'audio')
264
+ .map((s) => s.index);
265
+ const streamInfo = {
266
+ hasVideo: videoStreams.length > 0,
267
+ hasAudio: audioStreams.length > 0,
268
+ videoStreamIndex: videoStreams[0] ?? -1,
269
+ audioStreamIndex: audioStreams[0] ?? -1,
270
+ videoStreams,
271
+ audioStreams,
272
+ };
273
+ resolve(streamInfo);
274
+ }
275
+ catch (error) {
276
+ reject(new Error(`Failed to parse ffprobe output: ${error.message}`));
277
+ }
278
+ });
279
+ // Handle spawn error to prevent memory leaks
280
+ ffprobe.on('error', (error) => {
281
+ reject(new Error(`Failed to start ffprobe: ${error.message}`));
282
+ });
111
283
  });
112
284
  }
113
- /**
114
- * Validate input file exists
115
- */
116
- export function validateInputFile(input) {
117
- const inputPath = resolve(input);
118
- if (!fileExists(inputPath)) {
119
- throw new Error(`Input file not found: ${input}`);
120
- }
121
- return inputPath;
122
- }
123
- /**
124
- * Generate output filename if not provided
125
- */
126
- export function generateOutputPath(input, suffix, extension) {
127
- const inputPath = resolve(input);
128
- const ext = extension || inputPath.split('.').pop();
129
- const base = inputPath.substring(0, inputPath.lastIndexOf('.'));
130
- return `${base}_${suffix}.${ext}`;
131
- }
132
- /**
133
- * Format time for display (seconds to HH:MM:SS)
134
- */
135
- export function formatDuration(seconds) {
136
- const hrs = Math.floor(seconds / 3600);
137
- const mins = Math.floor((seconds % 3600) / 60);
138
- const secs = Math.floor(seconds % 60);
139
- return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
140
- }
141
- /**
142
- * Parse time string (HH:MM:SS) to seconds
143
- */
144
- export function parseTimeToSeconds(time) {
145
- const parts = time.split(':').map(Number);
146
- if (parts.length === 3) {
147
- return parts[0] * 3600 + parts[1] * 60 + parts[2];
148
- }
149
- else if (parts.length === 2) {
150
- return parts[0] * 60 + parts[1];
151
- }
152
- return parts[0];
153
- }
154
- /**
155
- * Format file size
156
- */
157
- export function formatFileSize(bytes) {
158
- const units = ['B', 'KB', 'MB', 'GB'];
159
- let size = bytes;
160
- let unitIndex = 0;
161
- while (size >= 1024 && unitIndex < units.length - 1) {
162
- size /= 1024;
163
- unitIndex++;
164
- }
165
- return `${size.toFixed(2)} ${units[unitIndex]}`;
166
- }
167
285
  //# sourceMappingURL=ffmpeg.js.map