@ttt-productions/media-processing-core 0.8.16 → 0.8.18
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/dist/audio/audio-processor.d.ts +2 -2
- package/dist/audio/audio-processor.d.ts.map +1 -1
- package/dist/audio/audio-processor.js +63 -50
- package/dist/audio/audio-processor.js.map +1 -1
- package/dist/audio/probe.d.ts +1 -1
- package/dist/audio/probe.d.ts.map +1 -1
- package/dist/audio/probe.js +6 -5
- package/dist/audio/probe.js.map +1 -1
- package/dist/duration/media-duration.d.ts +2 -13
- package/dist/duration/media-duration.d.ts.map +1 -1
- package/dist/duration/media-duration.js +24 -49
- package/dist/duration/media-duration.js.map +1 -1
- package/dist/image/formats.d.ts +2 -13
- package/dist/image/formats.d.ts.map +1 -1
- package/dist/image/formats.js +25 -35
- package/dist/image/formats.js.map +1 -1
- package/dist/image/image-processor.d.ts +2 -2
- package/dist/image/image-processor.d.ts.map +1 -1
- package/dist/image/image-processor.js +48 -57
- package/dist/image/image-processor.js.map +1 -1
- package/dist/image/resize.d.ts +2 -16
- package/dist/image/resize.d.ts.map +1 -1
- package/dist/image/resize.js +13 -28
- package/dist/image/resize.js.map +1 -1
- package/dist/index.d.ts +19 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -16
- package/dist/index.js.map +1 -1
- package/dist/inspection/inspect-media.d.ts +13 -20
- package/dist/inspection/inspect-media.d.ts.map +1 -1
- package/dist/inspection/inspect-media.js +197 -201
- package/dist/inspection/inspect-media.js.map +1 -1
- package/dist/io/fs.d.ts +1 -1
- package/dist/io/fs.d.ts.map +1 -1
- package/dist/io/fs.js +4 -5
- package/dist/io/fs.js.map +1 -1
- package/dist/io/local-input.d.ts +1 -21
- package/dist/io/local-input.d.ts.map +1 -1
- package/dist/io/local-input.js +15 -35
- package/dist/io/local-input.js.map +1 -1
- package/dist/io/types.d.ts +0 -3
- package/dist/io/types.d.ts.map +1 -1
- package/dist/io/types.js.map +1 -1
- package/dist/moderation/merge.d.ts +1 -1
- package/dist/moderation/merge.d.ts.map +1 -1
- package/dist/moderation/merge.js +1 -2
- package/dist/moderation/merge.js.map +1 -1
- package/dist/moderation/types.d.ts +3 -5
- package/dist/moderation/types.d.ts.map +1 -1
- package/dist/moderation/types.js.map +1 -1
- package/dist/process-media.d.ts +2 -3
- package/dist/process-media.d.ts.map +1 -1
- package/dist/process-media.js +9 -9
- package/dist/process-media.js.map +1 -1
- package/dist/processing/errors.d.ts.map +1 -1
- package/dist/processing/errors.js +1 -1
- package/dist/processing/errors.js.map +1 -1
- package/dist/processing/result.d.ts +2 -2
- package/dist/processing/result.js +1 -1
- package/dist/processing/result.js.map +1 -1
- package/dist/run-pipeline.d.ts +5 -22
- package/dist/run-pipeline.d.ts.map +1 -1
- package/dist/run-pipeline.js +72 -81
- package/dist/run-pipeline.js.map +1 -1
- package/dist/server/download-token.d.ts +0 -6
- package/dist/server/download-token.d.ts.map +1 -1
- package/dist/server/download-token.js +3 -13
- package/dist/server/download-token.js.map +1 -1
- package/dist/server/firebase-media-io.d.ts +3 -13
- package/dist/server/firebase-media-io.d.ts.map +1 -1
- package/dist/server/firebase-media-io.js +1 -6
- package/dist/server/firebase-media-io.js.map +1 -1
- package/dist/server/gcs-errors.d.ts +0 -8
- package/dist/server/gcs-errors.d.ts.map +1 -1
- package/dist/server/gcs-errors.js +2 -10
- package/dist/server/gcs-errors.js.map +1 -1
- package/dist/server/index.d.ts +7 -6
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +7 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/pending-media.d.ts +3 -34
- package/dist/server/pending-media.d.ts.map +1 -1
- package/dist/server/pending-media.js +13 -30
- package/dist/server/pending-media.js.map +1 -1
- package/dist/server/r2-retry.d.ts +4 -52
- package/dist/server/r2-retry.d.ts.map +1 -1
- package/dist/server/r2-retry.js +49 -134
- package/dist/server/r2-retry.js.map +1 -1
- package/dist/server/staged-object.d.ts +2 -46
- package/dist/server/staged-object.d.ts.map +1 -1
- package/dist/server/staged-object.js +4 -48
- package/dist/server/staged-object.js.map +1 -1
- package/dist/server/storage-ops.d.ts +5 -67
- package/dist/server/storage-ops.d.ts.map +1 -1
- package/dist/server/storage-ops.js +110 -185
- package/dist/server/storage-ops.js.map +1 -1
- package/dist/types.d.ts +2 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/log.d.ts.map +1 -1
- package/dist/utils/log.js +3 -3
- package/dist/utils/log.js.map +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +1 -4
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/safe-path.d.ts.map +1 -1
- package/dist/utils/safe-path.js +8 -9
- package/dist/utils/safe-path.js.map +1 -1
- package/dist/validation/validate-duration.d.ts.map +1 -1
- package/dist/validation/validate-duration.js.map +1 -1
- package/dist/validation/validate-mime.d.ts.map +1 -1
- package/dist/validation/validate-mime.js.map +1 -1
- package/dist/validation/validate-size.d.ts.map +1 -1
- package/dist/validation/validate-size.js.map +1 -1
- package/dist/video/ffmpeg.d.ts +1 -23
- package/dist/video/ffmpeg.d.ts.map +1 -1
- package/dist/video/ffmpeg.js +57 -64
- package/dist/video/ffmpeg.js.map +1 -1
- package/dist/video/probe.d.ts +1 -1
- package/dist/video/probe.d.ts.map +1 -1
- package/dist/video/probe.js +10 -8
- package/dist/video/probe.js.map +1 -1
- package/dist/video/video-processor.d.ts +2 -2
- package/dist/video/video-processor.d.ts.map +1 -1
- package/dist/video/video-processor.js +125 -96
- package/dist/video/video-processor.js.map +1 -1
- package/dist/workspace/temp.js +4 -4
- package/dist/workspace/temp.js.map +1 -1
- package/package.json +2 -2
- package/dist/audio/index.d.ts +0 -3
- package/dist/audio/index.d.ts.map +0 -1
- package/dist/audio/index.js +0 -3
- package/dist/audio/index.js.map +0 -1
- package/dist/image/index.d.ts +0 -4
- package/dist/image/index.d.ts.map +0 -1
- package/dist/image/index.js +0 -4
- package/dist/image/index.js.map +0 -1
- package/dist/video/index.d.ts +0 -4
- package/dist/video/index.d.ts.map +0 -1
- package/dist/video/index.js +0 -4
- package/dist/video/index.js.map +0 -1
package/dist/video/ffmpeg.js
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
const MAX_CAPTURE_BYTES = 1024 * 1024;
|
|
3
|
-
/** Wall-clock ceiling for an ffprobe call that opens a file and reads its
|
|
4
|
-
* stream table (metadata only, never every packet). */
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
const MAX_CAPTURE_BYTES = 1024 * 1024;
|
|
5
3
|
export const DEFAULT_PROBE_TIMEOUT_MS = 20_000;
|
|
6
4
|
let ffmpegChecked = null;
|
|
7
5
|
let ffmpegVersion = null;
|
|
8
6
|
export function runCmd(cmd, args, opts) {
|
|
9
7
|
return new Promise((resolve, reject) => {
|
|
10
|
-
const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: [
|
|
11
|
-
let stdout =
|
|
12
|
-
let stderr =
|
|
8
|
+
const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
9
|
+
let stdout = '';
|
|
10
|
+
let stderr = '';
|
|
13
11
|
let timedOut = false;
|
|
14
12
|
let truncated = false;
|
|
15
13
|
const kill = () => {
|
|
16
14
|
try {
|
|
17
|
-
p.kill(
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
/* already dead */
|
|
15
|
+
p.kill('SIGKILL');
|
|
21
16
|
}
|
|
17
|
+
catch { }
|
|
22
18
|
};
|
|
23
19
|
const timer = opts?.timeoutMs && opts.timeoutMs > 0
|
|
24
20
|
? setTimeout(() => {
|
|
@@ -27,31 +23,30 @@ export function runCmd(cmd, args, opts) {
|
|
|
27
23
|
}, opts.timeoutMs)
|
|
28
24
|
: null;
|
|
29
25
|
const onAbort = () => {
|
|
30
|
-
timedOut = true;
|
|
26
|
+
timedOut = true;
|
|
31
27
|
kill();
|
|
32
28
|
};
|
|
33
29
|
if (opts?.signal) {
|
|
34
30
|
if (opts.signal.aborted)
|
|
35
31
|
onAbort();
|
|
36
32
|
else
|
|
37
|
-
opts.signal.addEventListener(
|
|
33
|
+
opts.signal.addEventListener('abort', onAbort, { once: true });
|
|
38
34
|
}
|
|
39
35
|
const onLine = opts?.onStdoutLine;
|
|
40
|
-
let lineBuf =
|
|
41
|
-
// Decodes across chunk boundaries, so a multi-byte character split
|
|
42
|
-
// between two chunks reaches the line intact.
|
|
36
|
+
let lineBuf = '';
|
|
37
|
+
// Decodes across chunk boundaries, so a multi-byte character split between chunks stays intact.
|
|
43
38
|
if (onLine)
|
|
44
|
-
p.stdout.setEncoding(
|
|
45
|
-
p.stdout.on(
|
|
39
|
+
p.stdout.setEncoding('utf8');
|
|
40
|
+
p.stdout.on('data', (d) => {
|
|
46
41
|
if (onLine) {
|
|
47
42
|
if (truncated)
|
|
48
43
|
return;
|
|
49
44
|
const lines = (lineBuf + String(d)).split(/\r?\n/);
|
|
50
|
-
lineBuf = lines.pop() ??
|
|
45
|
+
lineBuf = lines.pop() ?? '';
|
|
51
46
|
for (const line of [...lines, lineBuf]) {
|
|
52
47
|
if (line.length > MAX_CAPTURE_BYTES) {
|
|
53
48
|
truncated = true;
|
|
54
|
-
lineBuf =
|
|
49
|
+
lineBuf = '';
|
|
55
50
|
return;
|
|
56
51
|
}
|
|
57
52
|
}
|
|
@@ -64,48 +59,53 @@ export function runCmd(cmd, args, opts) {
|
|
|
64
59
|
else
|
|
65
60
|
truncated = true;
|
|
66
61
|
});
|
|
67
|
-
p.stderr.on(
|
|
62
|
+
p.stderr.on('data', (d) => {
|
|
68
63
|
if (stderr.length < MAX_CAPTURE_BYTES)
|
|
69
64
|
stderr += String(d);
|
|
70
65
|
else
|
|
71
66
|
truncated = true;
|
|
72
67
|
});
|
|
73
|
-
p.on(
|
|
68
|
+
p.on('error', (e) => {
|
|
74
69
|
if (timer)
|
|
75
70
|
clearTimeout(timer);
|
|
76
71
|
if (opts?.signal)
|
|
77
|
-
opts.signal.removeEventListener(
|
|
72
|
+
opts.signal.removeEventListener('abort', onAbort);
|
|
78
73
|
reject(e);
|
|
79
74
|
});
|
|
80
|
-
p.on(
|
|
75
|
+
p.on('close', (code) => {
|
|
81
76
|
if (timer)
|
|
82
77
|
clearTimeout(timer);
|
|
83
78
|
if (opts?.signal)
|
|
84
|
-
opts.signal.removeEventListener(
|
|
79
|
+
opts.signal.removeEventListener('abort', onAbort);
|
|
85
80
|
if (onLine && !truncated && lineBuf)
|
|
86
81
|
onLine(lineBuf);
|
|
87
|
-
resolve({
|
|
82
|
+
resolve({
|
|
83
|
+
code: code ?? -1,
|
|
84
|
+
stdout,
|
|
85
|
+
stderr,
|
|
86
|
+
...(timedOut ? { timedOut } : {}),
|
|
87
|
+
...(truncated ? { truncated } : {}),
|
|
88
|
+
});
|
|
88
89
|
});
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
function abortError() {
|
|
92
|
-
return new DOMException(
|
|
93
|
+
return new DOMException('Aborted', 'AbortError');
|
|
93
94
|
}
|
|
94
|
-
//
|
|
95
|
-
// We harden parsing and sanity-check values.
|
|
95
|
+
// ffmpeg's `out_time_ms` is in microseconds.
|
|
96
96
|
function parseOutTimeMicroseconds(value) {
|
|
97
97
|
const n = Number(value);
|
|
98
98
|
if (!Number.isFinite(n) || n < 0)
|
|
99
99
|
return undefined;
|
|
100
|
-
// Discard absurd values (100 years in microseconds).
|
|
101
100
|
const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;
|
|
102
101
|
if (n > MAX_REASONABLE_US)
|
|
103
102
|
return undefined;
|
|
104
103
|
return n;
|
|
105
104
|
}
|
|
106
105
|
function parseOutTimeHmsToMs(value) {
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
const m = String(value)
|
|
107
|
+
.trim()
|
|
108
|
+
.match(/^(\d+):([0-5]\d):([0-5]\d)(?:\.(\d{1,6}))?$/);
|
|
109
109
|
if (!m)
|
|
110
110
|
return undefined;
|
|
111
111
|
const hh = Number(m[1]);
|
|
@@ -113,8 +113,8 @@ function parseOutTimeHmsToMs(value) {
|
|
|
113
113
|
const ss = Number(m[3]);
|
|
114
114
|
if (!Number.isFinite(hh) || !Number.isFinite(mm) || !Number.isFinite(ss))
|
|
115
115
|
return undefined;
|
|
116
|
-
const frac = m[4] ??
|
|
117
|
-
const us = frac ? Number((frac +
|
|
116
|
+
const frac = m[4] ?? '';
|
|
117
|
+
const us = frac ? Number((frac + '000000').slice(0, 6)) : 0;
|
|
118
118
|
if (!Number.isFinite(us) || us < 0)
|
|
119
119
|
return undefined;
|
|
120
120
|
const totalMs = (hh * 3600 + mm * 60 + ss) * 1000 + Math.floor(us / 1000);
|
|
@@ -129,31 +129,26 @@ function clamp01(n) {
|
|
|
129
129
|
return 1;
|
|
130
130
|
return n;
|
|
131
131
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Runs ffmpeg with optional AbortSignal + progress reporting.
|
|
134
|
-
* If onProgress is provided, ffmpeg is invoked with "-progress pipe:1".
|
|
135
|
-
*/
|
|
136
132
|
export function runFfmpeg(args, opts) {
|
|
137
|
-
const phase = opts?.phase ??
|
|
133
|
+
const phase = opts?.phase ?? 'transcode';
|
|
138
134
|
const withProgress = !!opts?.onProgress;
|
|
139
|
-
const ffArgs = withProgress ? [
|
|
135
|
+
const ffArgs = withProgress ? ['-progress', 'pipe:1', '-nostats', ...args] : args;
|
|
140
136
|
return new Promise((resolve, reject) => {
|
|
141
137
|
if (opts?.signal?.aborted)
|
|
142
138
|
return reject(abortError());
|
|
143
|
-
const p = spawn(
|
|
144
|
-
let stdout =
|
|
145
|
-
let stderr =
|
|
146
|
-
let progressBuf =
|
|
147
|
-
// keep progress monotonic
|
|
139
|
+
const p = spawn('ffmpeg', ffArgs, { cwd: opts?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
140
|
+
let stdout = '';
|
|
141
|
+
let stderr = '';
|
|
142
|
+
let progressBuf = '';
|
|
148
143
|
let lastOutTimeMs;
|
|
149
144
|
const onAbort = () => {
|
|
150
145
|
try {
|
|
151
|
-
p.kill(
|
|
146
|
+
p.kill('SIGKILL');
|
|
152
147
|
}
|
|
153
148
|
catch { }
|
|
154
149
|
};
|
|
155
|
-
opts?.signal?.addEventListener(
|
|
156
|
-
p.stdout.on(
|
|
150
|
+
opts?.signal?.addEventListener('abort', onAbort, { once: true });
|
|
151
|
+
p.stdout.on('data', (d) => {
|
|
157
152
|
const s = String(d);
|
|
158
153
|
if (stdout.length < MAX_CAPTURE_BYTES)
|
|
159
154
|
stdout += s;
|
|
@@ -161,52 +156,50 @@ export function runFfmpeg(args, opts) {
|
|
|
161
156
|
return;
|
|
162
157
|
progressBuf += s;
|
|
163
158
|
const lines = progressBuf.split(/\r?\n/);
|
|
164
|
-
progressBuf = lines.pop() ??
|
|
159
|
+
progressBuf = lines.pop() ?? '';
|
|
165
160
|
let outTimeMs;
|
|
166
161
|
for (const line of lines) {
|
|
167
|
-
const idx = line.indexOf(
|
|
162
|
+
const idx = line.indexOf('=');
|
|
168
163
|
if (idx <= 0)
|
|
169
164
|
continue;
|
|
170
165
|
const k = line.slice(0, idx).trim();
|
|
171
166
|
const v = line.slice(idx + 1).trim();
|
|
172
|
-
if (k ===
|
|
167
|
+
if (k === 'out_time_ms') {
|
|
173
168
|
const us = parseOutTimeMicroseconds(v);
|
|
174
169
|
if (us !== undefined)
|
|
175
170
|
outTimeMs = Math.floor(us / 1000);
|
|
176
171
|
}
|
|
177
|
-
else if (k ===
|
|
172
|
+
else if (k === 'out_time' && outTimeMs == null) {
|
|
178
173
|
const ms = parseOutTimeHmsToMs(v);
|
|
179
174
|
if (ms !== undefined)
|
|
180
175
|
outTimeMs = ms;
|
|
181
176
|
}
|
|
182
|
-
else if (k ===
|
|
177
|
+
else if (k === 'progress' && v === 'end') {
|
|
183
178
|
opts?.onProgress?.({ phase, percent: 1, outTimeMs: lastOutTimeMs });
|
|
184
179
|
}
|
|
185
180
|
}
|
|
186
181
|
if (outTimeMs != null) {
|
|
187
|
-
// monotonic guard
|
|
188
182
|
if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {
|
|
189
183
|
lastOutTimeMs = outTimeMs;
|
|
190
184
|
}
|
|
191
185
|
const dur = opts?.durationMs;
|
|
192
186
|
const percent = dur && dur > 0 && lastOutTimeMs != null
|
|
193
|
-
?
|
|
194
|
-
clamp01(Math.min(0.999, lastOutTimeMs / dur))
|
|
187
|
+
? clamp01(Math.min(0.999, lastOutTimeMs / dur))
|
|
195
188
|
: undefined;
|
|
196
189
|
opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });
|
|
197
190
|
}
|
|
198
191
|
});
|
|
199
|
-
p.stderr.on(
|
|
192
|
+
p.stderr.on('data', (d) => {
|
|
200
193
|
const s = String(d);
|
|
201
194
|
if (stderr.length < MAX_CAPTURE_BYTES)
|
|
202
195
|
stderr += s;
|
|
203
196
|
});
|
|
204
|
-
p.on(
|
|
205
|
-
opts?.signal?.removeEventListener(
|
|
197
|
+
p.on('error', (e) => {
|
|
198
|
+
opts?.signal?.removeEventListener('abort', onAbort);
|
|
206
199
|
reject(e);
|
|
207
200
|
});
|
|
208
|
-
p.on(
|
|
209
|
-
opts?.signal?.removeEventListener(
|
|
201
|
+
p.on('close', (code) => {
|
|
202
|
+
opts?.signal?.removeEventListener('abort', onAbort);
|
|
210
203
|
if (opts?.signal?.aborted)
|
|
211
204
|
return reject(abortError());
|
|
212
205
|
resolve({ code: code ?? 0, stdout, stderr });
|
|
@@ -217,13 +210,13 @@ export async function ensureFfmpegAvailable() {
|
|
|
217
210
|
if (ffmpegChecked)
|
|
218
211
|
return { ok: true, version: ffmpegVersion ?? undefined };
|
|
219
212
|
try {
|
|
220
|
-
const r = await runCmd(
|
|
213
|
+
const r = await runCmd('ffmpeg', ['-version']);
|
|
221
214
|
if (r.code === 0) {
|
|
222
215
|
ffmpegChecked = true;
|
|
223
|
-
ffmpegVersion = (r.stdout || r.stderr ||
|
|
216
|
+
ffmpegVersion = (r.stdout || r.stderr || '').split(/\r?\n/)[0] || null;
|
|
224
217
|
return { ok: true, version: ffmpegVersion ?? undefined };
|
|
225
218
|
}
|
|
226
|
-
return { ok: false, error: new Error(
|
|
219
|
+
return { ok: false, error: new Error('ffmpeg not available') };
|
|
227
220
|
}
|
|
228
221
|
catch (e) {
|
|
229
222
|
return { ok: false, error: e };
|
package/dist/video/ffmpeg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\n\nexport interface RunCmdResult {\n /** The exit code; `-1` when the process was ended by a signal, so a child\n * killed partway (a crash, an outside kill) never reads as a clean exit. */\n code: number;\n stdout: string;\n stderr: string;\n /** True when the process was killed by the wall-clock timeout (see runCmd\n * opts.timeoutMs). Callers must treat a timed-out result as INVALID —\n * never parse its partial output as an answer. */\n timedOut?: boolean;\n /** True when a captured stream hit the 1 MiB cap — the output is truncated\n * and must not be parsed as complete (e.g. incomplete probe JSON). */\n truncated?: boolean;\n}\n\nexport type FfmpegProgressPhase = \"transcode\" | \"poster\";\n\nexport interface RunFfmpegOptions {\n cwd?: string;\n signal?: AbortSignal;\n /** Optional total duration in milliseconds (used to compute percent). */\n durationMs?: number;\n onProgress?: (p: { phase: FfmpegProgressPhase; percent?: number; outTimeMs?: number }) => void;\n /** Which phase to report in callbacks. */\n phase?: FfmpegProgressPhase;\n}\n\nconst MAX_CAPTURE_BYTES = 1024 * 1024; // 1MB per stream\n\n/** Wall-clock ceiling for an ffprobe call that opens a file and reads its\n * stream table (metadata only, never every packet). */\nexport const DEFAULT_PROBE_TIMEOUT_MS = 20_000;\n\nlet ffmpegChecked: boolean | null = null;\nlet ffmpegVersion: string | null = null;\n\nexport function runCmd(\n cmd: string,\n args: string[],\n opts?: {\n cwd?: string;\n /** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the\n * result resolves with `timedOut: true`. A malformed/adversarial input\n * must never be able to wedge a warm instance. */\n timeoutMs?: number;\n /** Abort kills the child immediately (same SIGKILL path as the timeout). */\n signal?: AbortSignal;\n /** Receives stdout one line at a time instead of capturing it, so a\n * listing of any length is read in bounded memory; `stdout` is then\n * empty. A single line over the capture cap sets `truncated`. */\n onStdoutLine?: (line: string) => void;\n }\n): Promise<RunCmdResult> {\n return new Promise((resolve, reject) => {\n const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let truncated = false;\n\n const kill = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {\n /* already dead */\n }\n };\n const timer =\n opts?.timeoutMs && opts.timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n kill();\n }, opts.timeoutMs)\n : null;\n const onAbort = () => {\n timedOut = true; // treated identically: the result is invalid\n kill();\n };\n if (opts?.signal) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n const onLine = opts?.onStdoutLine;\n let lineBuf = \"\";\n // Decodes across chunk boundaries, so a multi-byte character split\n // between two chunks reaches the line intact.\n if (onLine) p.stdout.setEncoding(\"utf8\");\n p.stdout.on(\"data\", (d) => {\n if (onLine) {\n if (truncated) return;\n const lines = (lineBuf + String(d)).split(/\\r?\\n/);\n lineBuf = lines.pop() ?? \"\";\n for (const line of [...lines, lineBuf]) {\n if (line.length > MAX_CAPTURE_BYTES) {\n truncated = true;\n lineBuf = \"\";\n return;\n }\n }\n for (const line of lines) onLine(line);\n return;\n }\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += String(d);\n else truncated = true;\n });\n p.stderr.on(\"data\", (d) => {\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += String(d);\n else truncated = true;\n });\n\n p.on(\"error\", (e) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n p.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n if (onLine && !truncated && lineBuf) onLine(lineBuf);\n resolve({ code: code ?? -1, stdout, stderr, ...(timedOut ? { timedOut } : {}), ...(truncated ? { truncated } : {}) });\n });\n });\n}\n\nfunction abortError(): DOMException {\n return new DOMException(\"Aborted\", \"AbortError\");\n}\n\n// NOTE: ffmpeg prints \"out_time_ms\" but it is microseconds in known builds.\n// We harden parsing and sanity-check values.\nfunction parseOutTimeMicroseconds(value: string): number | undefined {\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) return undefined;\n\n // Discard absurd values (100 years in microseconds).\n const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;\n if (n > MAX_REASONABLE_US) return undefined;\n\n return n;\n}\n\nfunction parseOutTimeHmsToMs(value: string): number | undefined {\n // \"HH:MM:SS.micro\" (micro may be omitted)\n const m = String(value).trim().match(/^(\\d+):([0-5]\\d):([0-5]\\d)(?:\\.(\\d{1,6}))?$/);\n if (!m) return undefined;\n\n const hh = Number(m[1]);\n const mm = Number(m[2]);\n const ss = Number(m[3]);\n if (!Number.isFinite(hh) || !Number.isFinite(mm) || !Number.isFinite(ss)) return undefined;\n\n const frac = m[4] ?? \"\";\n const us = frac ? Number((frac + \"000000\").slice(0, 6)) : 0;\n if (!Number.isFinite(us) || us < 0) return undefined;\n\n const totalMs = (hh * 3600 + mm * 60 + ss) * 1000 + Math.floor(us / 1000);\n return totalMs >= 0 ? totalMs : undefined;\n}\n\nfunction clamp01(n: number) {\n if (!Number.isFinite(n)) return undefined;\n if (n < 0) return 0;\n if (n > 1) return 1;\n return n;\n}\n\n/**\n * Runs ffmpeg with optional AbortSignal + progress reporting.\n * If onProgress is provided, ffmpeg is invoked with \"-progress pipe:1\".\n */\nexport function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunCmdResult> {\n const phase: FfmpegProgressPhase = opts?.phase ?? \"transcode\";\n const withProgress = !!opts?.onProgress;\n const ffArgs = withProgress ? [\"-progress\", \"pipe:1\", \"-nostats\", ...args] : args;\n\n return new Promise((resolve, reject) => {\n if (opts?.signal?.aborted) return reject(abortError());\n\n const p = spawn(\"ffmpeg\", ffArgs, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let progressBuf = \"\";\n\n // keep progress monotonic\n let lastOutTimeMs: number | undefined;\n\n const onAbort = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {}\n };\n opts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n p.stdout.on(\"data\", (d) => {\n const s = String(d);\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += s;\n\n if (!withProgress) return;\n\n progressBuf += s;\n const lines = progressBuf.split(/\\r?\\n/);\n progressBuf = lines.pop() ?? \"\";\n\n let outTimeMs: number | undefined;\n\n for (const line of lines) {\n const idx = line.indexOf(\"=\");\n if (idx <= 0) continue;\n\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n\n if (k === \"out_time_ms\") {\n const us = parseOutTimeMicroseconds(v);\n if (us !== undefined) outTimeMs = Math.floor(us / 1000);\n } else if (k === \"out_time\" && outTimeMs == null) {\n const ms = parseOutTimeHmsToMs(v);\n if (ms !== undefined) outTimeMs = ms;\n } else if (k === \"progress\" && v === \"end\") {\n opts?.onProgress?.({ phase, percent: 1, outTimeMs: lastOutTimeMs });\n }\n }\n\n if (outTimeMs != null) {\n // monotonic guard\n if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {\n lastOutTimeMs = outTimeMs;\n }\n\n const dur = opts?.durationMs;\n const percent =\n dur && dur > 0 && lastOutTimeMs != null\n ? // clamp to <1 during processing (end event sends 1)\n clamp01(Math.min(0.999, lastOutTimeMs / dur))\n : undefined;\n\n opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });\n }\n });\n\n p.stderr.on(\"data\", (d) => {\n const s = String(d);\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += s;\n });\n\n p.on(\"error\", (e) => {\n opts?.signal?.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n\n p.on(\"close\", (code) => {\n opts?.signal?.removeEventListener(\"abort\", onAbort);\n if (opts?.signal?.aborted) return reject(abortError());\n resolve({ code: code ?? 0, stdout, stderr });\n });\n });\n}\n\nexport async function ensureFfmpegAvailable(): Promise<\n { ok: true; version?: string } | { ok: false; error: unknown }\n> {\n if (ffmpegChecked) return { ok: true, version: ffmpegVersion ?? undefined };\n\n try {\n const r = await runCmd(\"ffmpeg\", [\"-version\"]);\n if (r.code === 0) {\n ffmpegChecked = true;\n ffmpegVersion = (r.stdout || r.stderr || \"\").split(/\\r?\\n/)[0] || null;\n return { ok: true, version: ffmpegVersion ?? undefined };\n }\n return { ok: false, error: new Error(\"ffmpeg not available\") };\n } catch (e) {\n return { ok: false, error: e };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\n\nexport interface RunCmdResult {\n code: number;\n stdout: string;\n stderr: string;\n timedOut?: boolean;\n truncated?: boolean;\n}\n\nexport type FfmpegProgressPhase = 'transcode' | 'poster';\n\nexport interface RunFfmpegOptions {\n cwd?: string;\n signal?: AbortSignal;\n durationMs?: number;\n onProgress?: (p: { phase: FfmpegProgressPhase; percent?: number; outTimeMs?: number }) => void;\n phase?: FfmpegProgressPhase;\n}\n\nconst MAX_CAPTURE_BYTES = 1024 * 1024;\n\nexport const DEFAULT_PROBE_TIMEOUT_MS = 20_000;\n\nlet ffmpegChecked: boolean | null = null;\nlet ffmpegVersion: string | null = null;\n\nexport function runCmd(\n cmd: string,\n args: string[],\n opts?: {\n cwd?: string;\n timeoutMs?: number;\n signal?: AbortSignal;\n onStdoutLine?: (line: string) => void;\n },\n): Promise<RunCmdResult> {\n return new Promise((resolve, reject) => {\n const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n\n let stdout = '';\n let stderr = '';\n let timedOut = false;\n let truncated = false;\n\n const kill = () => {\n try {\n p.kill('SIGKILL');\n } catch {}\n };\n const timer =\n opts?.timeoutMs && opts.timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n kill();\n }, opts.timeoutMs)\n : null;\n const onAbort = () => {\n timedOut = true;\n kill();\n };\n if (opts?.signal) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n const onLine = opts?.onStdoutLine;\n let lineBuf = '';\n // Decodes across chunk boundaries, so a multi-byte character split between chunks stays intact.\n if (onLine) p.stdout.setEncoding('utf8');\n p.stdout.on('data', (d) => {\n if (onLine) {\n if (truncated) return;\n const lines = (lineBuf + String(d)).split(/\\r?\\n/);\n lineBuf = lines.pop() ?? '';\n for (const line of [...lines, lineBuf]) {\n if (line.length > MAX_CAPTURE_BYTES) {\n truncated = true;\n lineBuf = '';\n return;\n }\n }\n for (const line of lines) onLine(line);\n return;\n }\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += String(d);\n else truncated = true;\n });\n p.stderr.on('data', (d) => {\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += String(d);\n else truncated = true;\n });\n\n p.on('error', (e) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener('abort', onAbort);\n reject(e);\n });\n p.on('close', (code) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener('abort', onAbort);\n if (onLine && !truncated && lineBuf) onLine(lineBuf);\n resolve({\n code: code ?? -1,\n stdout,\n stderr,\n ...(timedOut ? { timedOut } : {}),\n ...(truncated ? { truncated } : {}),\n });\n });\n });\n}\n\nfunction abortError(): DOMException {\n return new DOMException('Aborted', 'AbortError');\n}\n\n// ffmpeg's `out_time_ms` is in microseconds.\nfunction parseOutTimeMicroseconds(value: string): number | undefined {\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) return undefined;\n\n const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;\n if (n > MAX_REASONABLE_US) return undefined;\n\n return n;\n}\n\nfunction parseOutTimeHmsToMs(value: string): number | undefined {\n const m = String(value)\n .trim()\n .match(/^(\\d+):([0-5]\\d):([0-5]\\d)(?:\\.(\\d{1,6}))?$/);\n if (!m) return undefined;\n\n const hh = Number(m[1]);\n const mm = Number(m[2]);\n const ss = Number(m[3]);\n if (!Number.isFinite(hh) || !Number.isFinite(mm) || !Number.isFinite(ss)) return undefined;\n\n const frac = m[4] ?? '';\n const us = frac ? Number((frac + '000000').slice(0, 6)) : 0;\n if (!Number.isFinite(us) || us < 0) return undefined;\n\n const totalMs = (hh * 3600 + mm * 60 + ss) * 1000 + Math.floor(us / 1000);\n return totalMs >= 0 ? totalMs : undefined;\n}\n\nfunction clamp01(n: number) {\n if (!Number.isFinite(n)) return undefined;\n if (n < 0) return 0;\n if (n > 1) return 1;\n return n;\n}\n\nexport function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunCmdResult> {\n const phase: FfmpegProgressPhase = opts?.phase ?? 'transcode';\n const withProgress = !!opts?.onProgress;\n const ffArgs = withProgress ? ['-progress', 'pipe:1', '-nostats', ...args] : args;\n\n return new Promise((resolve, reject) => {\n if (opts?.signal?.aborted) return reject(abortError());\n\n const p = spawn('ffmpeg', ffArgs, { cwd: opts?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n\n let stdout = '';\n let stderr = '';\n let progressBuf = '';\n\n let lastOutTimeMs: number | undefined;\n\n const onAbort = () => {\n try {\n p.kill('SIGKILL');\n } catch {}\n };\n opts?.signal?.addEventListener('abort', onAbort, { once: true });\n\n p.stdout.on('data', (d) => {\n const s = String(d);\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += s;\n\n if (!withProgress) return;\n\n progressBuf += s;\n const lines = progressBuf.split(/\\r?\\n/);\n progressBuf = lines.pop() ?? '';\n\n let outTimeMs: number | undefined;\n\n for (const line of lines) {\n const idx = line.indexOf('=');\n if (idx <= 0) continue;\n\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n\n if (k === 'out_time_ms') {\n const us = parseOutTimeMicroseconds(v);\n if (us !== undefined) outTimeMs = Math.floor(us / 1000);\n } else if (k === 'out_time' && outTimeMs == null) {\n const ms = parseOutTimeHmsToMs(v);\n if (ms !== undefined) outTimeMs = ms;\n } else if (k === 'progress' && v === 'end') {\n opts?.onProgress?.({ phase, percent: 1, outTimeMs: lastOutTimeMs });\n }\n }\n\n if (outTimeMs != null) {\n if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {\n lastOutTimeMs = outTimeMs;\n }\n\n const dur = opts?.durationMs;\n const percent =\n dur && dur > 0 && lastOutTimeMs != null\n ? clamp01(Math.min(0.999, lastOutTimeMs / dur))\n : undefined;\n\n opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });\n }\n });\n\n p.stderr.on('data', (d) => {\n const s = String(d);\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += s;\n });\n\n p.on('error', (e) => {\n opts?.signal?.removeEventListener('abort', onAbort);\n reject(e);\n });\n\n p.on('close', (code) => {\n opts?.signal?.removeEventListener('abort', onAbort);\n if (opts?.signal?.aborted) return reject(abortError());\n resolve({ code: code ?? 0, stdout, stderr });\n });\n });\n}\n\nexport async function ensureFfmpegAvailable(): Promise<\n { ok: true; version?: string } | { ok: false; error: unknown }\n> {\n if (ffmpegChecked) return { ok: true, version: ffmpegVersion ?? undefined };\n\n try {\n const r = await runCmd('ffmpeg', ['-version']);\n if (r.code === 0) {\n ffmpegChecked = true;\n ffmpegVersion = (r.stdout || r.stderr || '').split(/\\r?\\n/)[0] || null;\n return { ok: true, version: ffmpegVersion ?? undefined };\n }\n return { ok: false, error: new Error('ffmpeg not available') };\n } catch (e) {\n return { ok: false, error: e };\n }\n}\n"]}
|
package/dist/video/probe.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,UAAU,
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,UAAU,CAAC,CA8BrB"}
|
package/dist/video/probe.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from
|
|
2
|
-
import { measureDurationSec } from
|
|
1
|
+
import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from './ffmpeg.js';
|
|
2
|
+
import { measureDurationSec } from '../duration/media-duration.js';
|
|
3
3
|
export async function probeVideo(inputPath, opts) {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
4
|
+
const args = ['-v', 'error', '-print_format', 'json', '-show_streams', inputPath];
|
|
5
|
+
const r = await runCmd('ffprobe', args, {
|
|
6
|
+
timeoutMs: DEFAULT_PROBE_TIMEOUT_MS,
|
|
7
|
+
signal: opts?.signal,
|
|
8
|
+
});
|
|
7
9
|
if (r.timedOut || r.code !== 0) {
|
|
8
10
|
throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);
|
|
9
11
|
}
|
|
10
|
-
const json = JSON.parse(r.stdout ||
|
|
12
|
+
const json = JSON.parse(r.stdout || '{}');
|
|
11
13
|
const streams = Array.isArray(json.streams) ? json.streams : [];
|
|
12
|
-
const v = streams.find((s) => s.codec_type ===
|
|
13
|
-
const a = streams.find((s) => s.codec_type ===
|
|
14
|
+
const v = streams.find((s) => s.codec_type === 'video');
|
|
15
|
+
const a = streams.find((s) => s.codec_type === 'audio');
|
|
14
16
|
const durationSec = await measureDurationSec(inputPath, opts);
|
|
15
17
|
const width = v?.width ? Number(v.width) : undefined;
|
|
16
18
|
const height = v?.height ? Number(v.height) : undefined;
|
package/dist/video/probe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAA+B,MAAM,+BAA+B,CAAC;AAShG,MAAM,CAAC,KAAK,UAAU,UAAU,
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAA+B,MAAM,+BAA+B,CAAC;AAShG,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,SAAiB,EACjB,IAA6B;IAE7B,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAElF,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE;QACtC,SAAS,EAAE,wBAAwB;QACnC,MAAM,EAAE,IAAI,EAAE,MAAM;KACrB,CAAC,CAAC;IAEH,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAU,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IAExD,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAE9D,MAAM,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAExD,OAAO;QACL,WAAW;QACX,KAAK;QACL,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,CAAC;KACd,CAAC;AACJ,CAAC","sourcesContent":["import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from './ffmpeg.js';\nimport { measureDurationSec, type MeasureDurationOptions } from '../duration/media-duration.js';\n\nexport interface VideoProbe {\n durationSec?: number;\n width?: number;\n height?: number;\n hasAudio?: boolean;\n}\n\nexport async function probeVideo(\n inputPath: string,\n opts?: MeasureDurationOptions,\n): Promise<VideoProbe> {\n const args = ['-v', 'error', '-print_format', 'json', '-show_streams', inputPath];\n\n const r = await runCmd('ffprobe', args, {\n timeoutMs: DEFAULT_PROBE_TIMEOUT_MS,\n signal: opts?.signal,\n });\n\n if (r.timedOut || r.code !== 0) {\n throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);\n }\n\n const json = JSON.parse(r.stdout || '{}');\n\n const streams: any[] = Array.isArray(json.streams) ? json.streams : [];\n\n const v = streams.find((s) => s.codec_type === 'video');\n const a = streams.find((s) => s.codec_type === 'audio');\n\n const durationSec = await measureDurationSec(inputPath, opts);\n\n const width = v?.width ? Number(v.width) : undefined;\n const height = v?.height ? Number(v.height) : undefined;\n\n return {\n durationSec,\n width,\n height,\n hasAudio: !!a,\n };\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MediaProcessingResult, MediaProcessingSpec } from
|
|
2
|
-
import type { ProcessMediaOptions } from
|
|
1
|
+
import type { MediaProcessingResult, MediaProcessingSpec } from '@ttt-productions/media-schemas';
|
|
2
|
+
import type { ProcessMediaOptions } from '../types.js';
|
|
3
3
|
export declare function processVideo(spec: MediaProcessingSpec, ctx: {
|
|
4
4
|
inputPath: string;
|
|
5
5
|
outputBasePath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-processor.d.ts","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"video-processor.d.ts","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EAEpB,MAAM,gCAAgC,CAAC;AAOxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AA6DvD,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,EACD,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,qBAAqB,CAAC,CAyShC"}
|