@ttt-productions/media-processing-core 0.8.17 → 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.map +1 -1
- package/dist/audio/audio-processor.js +1 -2
- package/dist/audio/audio-processor.js.map +1 -1
- package/dist/audio/probe.d.ts.map +1 -1
- package/dist/audio/probe.js +0 -2
- package/dist/audio/probe.js.map +1 -1
- package/dist/duration/media-duration.d.ts +0 -11
- package/dist/duration/media-duration.d.ts.map +1 -1
- package/dist/duration/media-duration.js +1 -28
- package/dist/duration/media-duration.js.map +1 -1
- package/dist/image/formats.d.ts +0 -11
- package/dist/image/formats.d.ts.map +1 -1
- package/dist/image/formats.js +0 -10
- package/dist/image/formats.js.map +1 -1
- package/dist/image/image-processor.d.ts.map +1 -1
- package/dist/image/image-processor.js +1 -20
- package/dist/image/image-processor.js.map +1 -1
- package/dist/image/resize.d.ts +0 -14
- package/dist/image/resize.d.ts.map +1 -1
- package/dist/image/resize.js +0 -15
- package/dist/image/resize.js.map +1 -1
- package/dist/inspection/inspect-media.d.ts +0 -7
- package/dist/inspection/inspect-media.d.ts.map +1 -1
- package/dist/inspection/inspect-media.js +4 -64
- package/dist/inspection/inspect-media.js.map +1 -1
- package/dist/io/fs.d.ts.map +1 -1
- package/dist/io/fs.js +0 -1
- package/dist/io/fs.js.map +1 -1
- package/dist/io/local-input.d.ts +0 -20
- package/dist/io/local-input.d.ts.map +1 -1
- package/dist/io/local-input.js +0 -20
- 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.map +1 -1
- package/dist/moderation/merge.js +0 -1
- package/dist/moderation/merge.js.map +1 -1
- package/dist/moderation/types.d.ts +0 -2
- package/dist/moderation/types.d.ts.map +1 -1
- package/dist/moderation/types.js.map +1 -1
- package/dist/run-pipeline.d.ts +0 -17
- package/dist/run-pipeline.d.ts.map +1 -1
- package/dist/run-pipeline.js +2 -27
- 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 +0 -10
- package/dist/server/download-token.js.map +1 -1
- package/dist/server/firebase-media-io.d.ts +1 -11
- package/dist/server/firebase-media-io.d.ts.map +1 -1
- package/dist/server/firebase-media-io.js +0 -5
- 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 +0 -8
- package/dist/server/gcs-errors.js.map +1 -1
- package/dist/server/pending-media.d.ts +1 -32
- package/dist/server/pending-media.d.ts.map +1 -1
- package/dist/server/pending-media.js +1 -24
- package/dist/server/pending-media.js.map +1 -1
- package/dist/server/r2-retry.d.ts +0 -48
- package/dist/server/r2-retry.d.ts.map +1 -1
- package/dist/server/r2-retry.js +4 -90
- package/dist/server/r2-retry.js.map +1 -1
- package/dist/server/staged-object.d.ts +0 -44
- package/dist/server/staged-object.d.ts.map +1 -1
- package/dist/server/staged-object.js +0 -44
- package/dist/server/staged-object.js.map +1 -1
- package/dist/server/storage-ops.d.ts +2 -62
- package/dist/server/storage-ops.d.ts.map +1 -1
- package/dist/server/storage-ops.js +9 -88
- package/dist/server/storage-ops.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/safe-path.d.ts.map +1 -1
- package/dist/utils/safe-path.js +1 -2
- package/dist/utils/safe-path.js.map +1 -1
- package/dist/video/ffmpeg.d.ts +0 -22
- package/dist/video/ffmpeg.d.ts.map +1 -1
- package/dist/video/ffmpeg.js +6 -21
- package/dist/video/ffmpeg.js.map +1 -1
- package/dist/video/probe.d.ts.map +1 -1
- package/dist/video/probe.js +0 -1
- package/dist/video/probe.js.map +1 -1
- package/dist/video/video-processor.d.ts.map +1 -1
- package/dist/video/video-processor.js +2 -15
- package/dist/video/video-processor.js.map +1 -1
- package/package.json +2 -2
package/dist/video/ffmpeg.d.ts
CHANGED
|
@@ -1,51 +1,29 @@
|
|
|
1
1
|
export interface RunCmdResult {
|
|
2
|
-
/** The exit code; `-1` when the process was ended by a signal, so a child
|
|
3
|
-
* killed partway (a crash, an outside kill) never reads as a clean exit. */
|
|
4
2
|
code: number;
|
|
5
3
|
stdout: string;
|
|
6
4
|
stderr: string;
|
|
7
|
-
/** True when the process was killed by the wall-clock timeout (see runCmd
|
|
8
|
-
* opts.timeoutMs). Callers must treat a timed-out result as INVALID —
|
|
9
|
-
* never parse its partial output as an answer. */
|
|
10
5
|
timedOut?: boolean;
|
|
11
|
-
/** True when a captured stream hit the 1 MiB cap — the output is truncated
|
|
12
|
-
* and must not be parsed as complete (e.g. incomplete probe JSON). */
|
|
13
6
|
truncated?: boolean;
|
|
14
7
|
}
|
|
15
8
|
export type FfmpegProgressPhase = 'transcode' | 'poster';
|
|
16
9
|
export interface RunFfmpegOptions {
|
|
17
10
|
cwd?: string;
|
|
18
11
|
signal?: AbortSignal;
|
|
19
|
-
/** Optional total duration in milliseconds (used to compute percent). */
|
|
20
12
|
durationMs?: number;
|
|
21
13
|
onProgress?: (p: {
|
|
22
14
|
phase: FfmpegProgressPhase;
|
|
23
15
|
percent?: number;
|
|
24
16
|
outTimeMs?: number;
|
|
25
17
|
}) => void;
|
|
26
|
-
/** Which phase to report in callbacks. */
|
|
27
18
|
phase?: FfmpegProgressPhase;
|
|
28
19
|
}
|
|
29
|
-
/** Wall-clock ceiling for an ffprobe call that opens a file and reads its
|
|
30
|
-
* stream table (metadata only, never every packet). */
|
|
31
20
|
export declare const DEFAULT_PROBE_TIMEOUT_MS = 20000;
|
|
32
21
|
export declare function runCmd(cmd: string, args: string[], opts?: {
|
|
33
22
|
cwd?: string;
|
|
34
|
-
/** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the
|
|
35
|
-
* result resolves with `timedOut: true`. A malformed/adversarial input
|
|
36
|
-
* must never be able to wedge a warm instance. */
|
|
37
23
|
timeoutMs?: number;
|
|
38
|
-
/** Abort kills the child immediately (same SIGKILL path as the timeout). */
|
|
39
24
|
signal?: AbortSignal;
|
|
40
|
-
/** Receives stdout one line at a time instead of capturing it, so a
|
|
41
|
-
* listing of any length is read in bounded memory; `stdout` is then
|
|
42
|
-
* empty. A single line over the capture cap sets `truncated`. */
|
|
43
25
|
onStdoutLine?: (line: string) => void;
|
|
44
26
|
}): Promise<RunCmdResult>;
|
|
45
|
-
/**
|
|
46
|
-
* Runs ffmpeg with optional AbortSignal + progress reporting.
|
|
47
|
-
* If onProgress is provided, ffmpeg is invoked with "-progress pipe:1".
|
|
48
|
-
*/
|
|
49
27
|
export declare function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunCmdResult>;
|
|
50
28
|
export declare function ensureFfmpegAvailable(): Promise<{
|
|
51
29
|
ok: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,KAAK,EAAE,mBAAmB,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC7B;AAID,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAK/C,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,GACA,OAAO,CAAC,YAAY,CAAC,CA2EvB;AA2CD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoFxF;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CACpD;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAC/D,CAcA"}
|
package/dist/video/ffmpeg.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
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). */
|
|
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;
|
|
@@ -16,9 +14,7 @@ export function runCmd(cmd, args, opts) {
|
|
|
16
14
|
try {
|
|
17
15
|
p.kill('SIGKILL');
|
|
18
16
|
}
|
|
19
|
-
catch {
|
|
20
|
-
/* already dead */
|
|
21
|
-
}
|
|
17
|
+
catch { }
|
|
22
18
|
};
|
|
23
19
|
const timer = opts?.timeoutMs && opts.timeoutMs > 0
|
|
24
20
|
? setTimeout(() => {
|
|
@@ -27,7 +23,7 @@ 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) {
|
|
@@ -38,8 +34,7 @@ export function runCmd(cmd, args, opts) {
|
|
|
38
34
|
}
|
|
39
35
|
const onLine = opts?.onStdoutLine;
|
|
40
36
|
let lineBuf = '';
|
|
41
|
-
// Decodes across chunk boundaries, so a multi-byte character split
|
|
42
|
-
// between two chunks reaches the line intact.
|
|
37
|
+
// Decodes across chunk boundaries, so a multi-byte character split between chunks stays intact.
|
|
43
38
|
if (onLine)
|
|
44
39
|
p.stdout.setEncoding('utf8');
|
|
45
40
|
p.stdout.on('data', (d) => {
|
|
@@ -97,20 +92,17 @@ export function runCmd(cmd, args, opts) {
|
|
|
97
92
|
function abortError() {
|
|
98
93
|
return new DOMException('Aborted', 'AbortError');
|
|
99
94
|
}
|
|
100
|
-
//
|
|
101
|
-
// We harden parsing and sanity-check values.
|
|
95
|
+
// ffmpeg's `out_time_ms` is in microseconds.
|
|
102
96
|
function parseOutTimeMicroseconds(value) {
|
|
103
97
|
const n = Number(value);
|
|
104
98
|
if (!Number.isFinite(n) || n < 0)
|
|
105
99
|
return undefined;
|
|
106
|
-
// Discard absurd values (100 years in microseconds).
|
|
107
100
|
const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;
|
|
108
101
|
if (n > MAX_REASONABLE_US)
|
|
109
102
|
return undefined;
|
|
110
103
|
return n;
|
|
111
104
|
}
|
|
112
105
|
function parseOutTimeHmsToMs(value) {
|
|
113
|
-
// "HH:MM:SS.micro" (micro may be omitted)
|
|
114
106
|
const m = String(value)
|
|
115
107
|
.trim()
|
|
116
108
|
.match(/^(\d+):([0-5]\d):([0-5]\d)(?:\.(\d{1,6}))?$/);
|
|
@@ -137,10 +129,6 @@ function clamp01(n) {
|
|
|
137
129
|
return 1;
|
|
138
130
|
return n;
|
|
139
131
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Runs ffmpeg with optional AbortSignal + progress reporting.
|
|
142
|
-
* If onProgress is provided, ffmpeg is invoked with "-progress pipe:1".
|
|
143
|
-
*/
|
|
144
132
|
export function runFfmpeg(args, opts) {
|
|
145
133
|
const phase = opts?.phase ?? 'transcode';
|
|
146
134
|
const withProgress = !!opts?.onProgress;
|
|
@@ -152,7 +140,6 @@ export function runFfmpeg(args, opts) {
|
|
|
152
140
|
let stdout = '';
|
|
153
141
|
let stderr = '';
|
|
154
142
|
let progressBuf = '';
|
|
155
|
-
// keep progress monotonic
|
|
156
143
|
let lastOutTimeMs;
|
|
157
144
|
const onAbort = () => {
|
|
158
145
|
try {
|
|
@@ -192,14 +179,12 @@ export function runFfmpeg(args, opts) {
|
|
|
192
179
|
}
|
|
193
180
|
}
|
|
194
181
|
if (outTimeMs != null) {
|
|
195
|
-
// monotonic guard
|
|
196
182
|
if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {
|
|
197
183
|
lastOutTimeMs = outTimeMs;
|
|
198
184
|
}
|
|
199
185
|
const dur = opts?.durationMs;
|
|
200
186
|
const percent = dur && dur > 0 && lastOutTimeMs != null
|
|
201
|
-
?
|
|
202
|
-
clamp01(Math.min(0.999, lastOutTimeMs / dur))
|
|
187
|
+
? clamp01(Math.min(0.999, lastOutTimeMs / dur))
|
|
203
188
|
: undefined;
|
|
204
189
|
opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });
|
|
205
190
|
}
|
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({\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// 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)\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\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"]}
|
|
@@ -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,CAC9B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,UAAU,CAAC,
|
|
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,7 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from './ffmpeg.js';
|
|
2
2
|
import { measureDurationSec } from '../duration/media-duration.js';
|
|
3
3
|
export async function probeVideo(inputPath, opts) {
|
|
4
|
-
// ffprobe must exist in runtime environment
|
|
5
4
|
const args = ['-v', 'error', '-print_format', 'json', '-show_streams', inputPath];
|
|
6
5
|
const r = await runCmd('ffprobe', args, {
|
|
7
6
|
timeoutMs: DEFAULT_PROBE_TIMEOUT_MS,
|
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,CAC9B,SAAiB,EACjB,IAA6B;IAE7B,
|
|
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 +1 @@
|
|
|
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,
|
|
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"}
|
|
@@ -23,7 +23,7 @@ function acceptAllowsVideo(spec) {
|
|
|
23
23
|
function acceptAllowsMime(spec, actualMime) {
|
|
24
24
|
const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];
|
|
25
25
|
if (mimes.length === 0)
|
|
26
|
-
return true;
|
|
26
|
+
return true;
|
|
27
27
|
if (!actualMime)
|
|
28
28
|
return false;
|
|
29
29
|
return mimes.some((a) => matchMime(a, actualMime));
|
|
@@ -47,7 +47,7 @@ function orientationOk(required, actual) {
|
|
|
47
47
|
if (!required || required === 'any')
|
|
48
48
|
return true;
|
|
49
49
|
if (!actual || actual === 'any')
|
|
50
|
-
return true;
|
|
50
|
+
return true;
|
|
51
51
|
return required === actual;
|
|
52
52
|
}
|
|
53
53
|
function outputPathFor(base, key, ext) {
|
|
@@ -114,7 +114,6 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
114
114
|
error: { code: 'processing_canceled', message: 'Processing canceled.' },
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
-
// duration enforcement
|
|
118
117
|
const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;
|
|
119
118
|
if (maxDur && probe.durationSec === undefined) {
|
|
120
119
|
return { ok: false, mediaType: 'video', error: unreadableLengthError('video', maxDur) };
|
|
@@ -130,7 +129,6 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
130
129
|
},
|
|
131
130
|
};
|
|
132
131
|
}
|
|
133
|
-
// uniform enforcement
|
|
134
132
|
const requiredOri = spec.videoOrientation;
|
|
135
133
|
const requiredAspect = spec.requiredAspectRatio;
|
|
136
134
|
const requiredW = spec.requiredWidth;
|
|
@@ -194,26 +192,19 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
194
192
|
const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, 'mp4');
|
|
195
193
|
const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, 'jpg');
|
|
196
194
|
tempFiles.push(videoOut, posterOut);
|
|
197
|
-
// Build a basic filter graph to enforce size/aspect when auto-format is enabled.
|
|
198
|
-
// - If exact WxH required: scale+crop to WxH
|
|
199
|
-
// - Else if aspect required: crop to aspect then scale to original (or keep)
|
|
200
|
-
// - Else: keep original
|
|
201
195
|
const scaleMode = spec.video?.scaleMode ?? 'fit';
|
|
202
196
|
let vf = '';
|
|
203
197
|
if (spec.allowAutoFormat) {
|
|
204
198
|
if (requiredW && requiredH) {
|
|
205
199
|
if (scaleMode === 'fit') {
|
|
206
|
-
// letterbox: scale to fit inside, pad with black bars
|
|
207
200
|
vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;
|
|
208
201
|
}
|
|
209
202
|
else {
|
|
210
|
-
// crop: scale to cover then crop exactly
|
|
211
203
|
vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=increase,crop=${requiredW}:${requiredH}`;
|
|
212
204
|
}
|
|
213
205
|
}
|
|
214
206
|
else if (requiredAspect) {
|
|
215
207
|
if (scaleMode === 'fit') {
|
|
216
|
-
// fit to aspect ratio without forced dimensions — no padding possible without target size, just crop
|
|
217
208
|
vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;
|
|
218
209
|
}
|
|
219
210
|
else {
|
|
@@ -264,7 +255,6 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
264
255
|
},
|
|
265
256
|
};
|
|
266
257
|
}
|
|
267
|
-
// Poster (frame at 1s; fallback to 0 if needed)
|
|
268
258
|
const posterArgs = ['-y', '-ss', '1', '-i', videoOut, '-frames:v', '1', '-q:v', '2', posterOut];
|
|
269
259
|
const r2 = await runFfmpeg(posterArgs, {
|
|
270
260
|
signal: opts?.signal,
|
|
@@ -277,9 +267,6 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
277
267
|
},
|
|
278
268
|
phase: 'poster',
|
|
279
269
|
});
|
|
280
|
-
if (r2.code !== 0) {
|
|
281
|
-
// non-fatal; still succeed with video
|
|
282
|
-
}
|
|
283
270
|
const vs = await stat(videoOut);
|
|
284
271
|
let psSize = 0;
|
|
285
272
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-processor.js","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n MediaOutput,\n MediaProcessingResult,\n MediaProcessingSpec,\n VideoOrientation,\n} from '@ttt-productions/media-schemas';\nimport { mkdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { ensureFfmpegAvailable, runFfmpeg } from './ffmpeg.js';\nimport { probeVideo } from './probe.js';\nimport { unreadableLengthError } from '../duration/media-duration.js';\nimport { safeOutputPathFor } from '../utils/safe-path.js';\nimport type { ProcessMediaOptions } from '../types.js';\nimport {\n TIMED_MEDIA_MAIN_OUTPUT_KEY,\n VIDEO_POSTER_OUTPUT_KEY,\n} from '@ttt-productions/media-schemas';\n\nfunction matchMime(accepted: string, actual: string): boolean {\n const a = accepted.trim().toLowerCase();\n const m = actual.trim().toLowerCase();\n if (!a) return true;\n if (a === '*/*') return true;\n if (a.endsWith('/*')) return m.startsWith(a.slice(0, -1));\n return a === m;\n}\n\nfunction acceptAllowsVideo(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes('video');\n}\n\nfunction acceptAllowsMime(spec: MediaProcessingSpec, actualMime?: string): boolean {\n const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];\n if (mimes.length === 0) return true; // empty => accept anything\n if (!actualMime) return false;\n return mimes.some((a) => matchMime(a, actualMime));\n}\n\nfunction aspect(width?: number, height?: number): number | undefined {\n if (!width || !height) return undefined;\n return width / height;\n}\n\nfunction aspectClose(a: number, b: number, tolerance: number): boolean {\n return Math.abs(a - b) <= tolerance;\n}\n\nfunction orientationOf(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return 'any';\n return height > width ? 'vertical' : 'horizontal';\n}\n\nfunction orientationOk(\n required: VideoOrientation | undefined,\n actual: VideoOrientation | undefined,\n): boolean {\n if (!required || required === 'any') return true;\n if (!actual || actual === 'any') return true; // treat square/unknown as ok\n return required === actual;\n}\n\nfunction outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\n}\n\nfunction mimeFromExt(ext: string): string | undefined {\n if (ext === 'mp4') return 'video/mp4';\n if (ext === 'webm') return 'video/webm';\n return undefined;\n}\n\nexport async function processVideo(\n spec: MediaProcessingSpec,\n ctx: {\n inputPath: string;\n outputBasePath: string;\n inputMime?: string;\n },\n opts?: ProcessMediaOptions,\n): Promise<MediaProcessingResult> {\n const tempFiles: string[] = [];\n\n try {\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n if (!acceptAllowsVideo(spec)) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'invalid_mime',\n message: 'Spec does not allow video uploads.',\n details: { accept: spec.accept },\n },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'invalid_mime',\n message: 'Video mime type is not allowed.',\n details: { inputMime: ctx.inputMime, accept: spec.accept },\n },\n };\n }\n\n const ff = await ensureFfmpegAvailable();\n if (!ff.ok) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: 'ffmpeg is not available.',\n details: { error: String((ff as any).error ?? 'unknown') },\n },\n };\n }\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });\n\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n\n // duration enforcement\n const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;\n if (maxDur && probe.durationSec === undefined) {\n return { ok: false, mediaType: 'video', error: unreadableLengthError('video', maxDur) };\n }\n if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'too_long',\n message: 'Video is too long.',\n details: { durationSec: probe.durationSec, maxDurationSec: maxDur },\n },\n };\n }\n\n // uniform enforcement\n const requiredOri = spec.videoOrientation;\n const requiredAspect = spec.requiredAspectRatio;\n const requiredW = spec.requiredWidth;\n const requiredH = spec.requiredHeight;\n\n const actualOri = orientationOf(probe.width, probe.height);\n const actualAspect = aspect(probe.width, probe.height);\n\n const okOri = orientationOk(requiredOri, actualOri);\n const tol = spec.aspectRatioTolerance ?? 0.02;\n const okAspect = requiredAspect\n ? actualAspect\n ? aspectClose(actualAspect, requiredAspect, tol)\n : false\n : true;\n const okDims =\n !requiredW || !requiredH ? true : probe.width === requiredW && probe.height === requiredH;\n\n const needsAuto = !okOri || !okAspect || !okDims;\n\n if (needsAuto && !spec.allowAutoFormat) {\n if (!okOri) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'orientation_mismatch',\n message: 'Video orientation does not match the required format.',\n details: { requiredOri, actualOri, width: probe.width, height: probe.height },\n },\n };\n }\n if (!okAspect) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'aspect_ratio_mismatch',\n message: 'Video aspect ratio does not match the required format.',\n details: {\n requiredAspectRatio: requiredAspect,\n aspectRatio: actualAspect,\n width: probe.width,\n height: probe.height,\n },\n },\n };\n }\n if (!okDims) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'dimensions_mismatch',\n message: 'Video dimensions do not match the required format.',\n details: {\n requiredWidth: requiredW,\n requiredHeight: requiredH,\n width: probe.width,\n height: probe.height,\n },\n },\n };\n }\n }\n\n const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, 'mp4');\n const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, 'jpg');\n\n tempFiles.push(videoOut, posterOut);\n\n // Build a basic filter graph to enforce size/aspect when auto-format is enabled.\n // - If exact WxH required: scale+crop to WxH\n // - Else if aspect required: crop to aspect then scale to original (or keep)\n // - Else: keep original\n const scaleMode = spec.video?.scaleMode ?? 'fit';\n let vf = '';\n\n if (spec.allowAutoFormat) {\n if (requiredW && requiredH) {\n if (scaleMode === 'fit') {\n // letterbox: scale to fit inside, pad with black bars\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;\n } else {\n // crop: scale to cover then crop exactly\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=increase,crop=${requiredW}:${requiredH}`;\n }\n } else if (requiredAspect) {\n if (scaleMode === 'fit') {\n // fit to aspect ratio without forced dimensions — no padding possible without target size, just crop\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n } else {\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n }\n }\n }\n\n const videoPreset = spec.video?.preset ?? 'veryfast';\n const videoCrf = spec.video?.crf ?? 23;\n\n const ffmpegArgs = [\n '-y',\n '-i',\n ctx.inputPath,\n ...(vf ? ['-vf', vf] : []),\n '-c:v',\n 'libx264',\n '-preset',\n videoPreset,\n '-crf',\n String(videoCrf),\n '-pix_fmt',\n 'yuv420p',\n '-movflags',\n '+faststart',\n ...(probe.hasAudio ? ['-c:a', 'aac', '-b:a', '128k'] : ['-an']),\n videoOut,\n ];\n\n const r1 = await runFfmpeg(ffmpegArgs, {\n signal: opts?.signal,\n durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,\n onProgress: (p) => {\n opts?.onProgress?.({\n phase: 'process',\n percent: p.percent,\n detail: { step: 'transcode', outTimeMs: p.outTimeMs },\n });\n },\n phase: 'transcode',\n });\n if (r1.code !== 0) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: 'ffmpeg video transcode failed.',\n details: { stderr: r1.stderr, stdout: r1.stdout },\n },\n };\n }\n\n // Poster (frame at 1s; fallback to 0 if needed)\n const posterArgs = ['-y', '-ss', '1', '-i', videoOut, '-frames:v', '1', '-q:v', '2', posterOut];\n const r2 = await runFfmpeg(posterArgs, {\n signal: opts?.signal,\n onProgress: (p) => {\n opts?.onProgress?.({\n phase: 'process',\n percent: p.percent,\n detail: { step: 'poster', outTimeMs: p.outTimeMs },\n });\n },\n phase: 'poster',\n });\n if (r2.code !== 0) {\n // non-fatal; still succeed with video\n }\n\n const vs = await stat(videoOut);\n let psSize = 0;\n try {\n const ps = await stat(posterOut);\n psSize = ps.size;\n } catch {}\n\n const outputs: MediaOutput[] = [\n {\n key: TIMED_MEDIA_MAIN_OUTPUT_KEY,\n url: `file://${videoOut}`,\n path: videoOut,\n mime: mimeFromExt('mp4'),\n sizeBytes: vs.size,\n width: requiredW ?? probe.width,\n height: requiredH ?? probe.height,\n durationSec: probe.durationSec,\n },\n ];\n\n const warnings: string[] = [];\n if (needsAuto && spec.allowAutoFormat) warnings.push('auto_formatted');\n if (r2.code !== 0) warnings.push('poster_failed');\n\n if (psSize > 0) {\n outputs.push({\n key: VIDEO_POSTER_OUTPUT_KEY,\n url: `file://${posterOut}`,\n path: posterOut,\n mime: 'image/jpeg',\n sizeBytes: psSize,\n });\n }\n\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: 'video',\n outputs,\n meta: {\n mime: ctx.inputMime,\n sizeBytes: undefined,\n width: probe.width,\n height: probe.height,\n durationSec: probe.durationSec,\n },\n warnings: warnings.length ? warnings : undefined,\n };\n } catch (e: any) {\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: e?.message ? String(e.message) : 'Video processing failed',\n details: { name: e?.name },\n },\n };\n } finally {\n if (tempFiles.length) {\n const { rm } = await import('node:fs/promises');\n await Promise.all(tempFiles.map((f) => rm(f, { force: true }).catch(() => {})));\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"video-processor.js","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,gCAAgC,CAAC;AAExC,SAAS,SAAS,CAAC,QAAgB,EAAE,MAAc;IACjD,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAyB;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACxD,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAyB,EAAE,UAAmB;IACtE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACxD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,MAAM,CAAC,KAAc,EAAE,MAAe;IAC7C,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACxC,OAAO,KAAK,GAAG,MAAM,CAAC;AACxB,CAAC;AAED,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS,EAAE,SAAiB;IAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC;AACtC,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,MAAe;IACpD,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;AACpD,CAAC;AAED,SAAS,aAAa,CACpB,QAAsC,EACtC,MAAoC;IAEpC,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,GAAW,EAAE,GAAW;IAC3D,OAAO,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,GAAG,KAAK,KAAK;QAAE,OAAO,WAAW,CAAC;IACtC,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,YAAY,CAAC;IACxC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAyB,EACzB,GAIC,EACD,IAA0B;IAE1B,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,IAAI,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC1B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,sBAAsB,EAAE;aACxE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE;oBACL,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,oCAAoC;oBAC7C,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;iBACjC;aACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE;oBACL,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,iCAAiC;oBAC1C,OAAO,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;iBAC3D;aACF,CAAC;QACJ,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,qBAAqB,EAAE,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACX,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE;oBACL,IAAI,EAAE,mBAAmB;oBACzB,OAAO,EAAE,0BAA0B;oBACnC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAE,EAAU,CAAC,KAAK,IAAI,SAAS,CAAC,EAAE;iBAC3D;aACF,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAChD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEzC,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAExE,IAAI,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC1B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,sBAAsB,EAAE;aACxE,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC;QACjE,IAAI,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;QAC1F,CAAC;QACD,IAAI,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,GAAG,MAAM,EAAE,CAAC;YAC5E,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE;oBACL,IAAI,EAAE,UAAU;oBAChB,OAAO,EAAE,oBAAoB;oBAC7B,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE;iBACpE;aACF,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAC1C,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC;QAEtC,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAEvD,MAAM,KAAK,GAAG,aAAa,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,oBAAoB,IAAI,IAAI,CAAC;QAC9C,MAAM,QAAQ,GAAG,cAAc;YAC7B,CAAC,CAAC,YAAY;gBACZ,CAAC,CAAC,WAAW,CAAC,YAAY,EAAE,cAAc,EAAE,GAAG,CAAC;gBAChD,CAAC,CAAC,KAAK;YACT,CAAC,CAAC,IAAI,CAAC;QACT,MAAM,MAAM,GACV,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC;QAE5F,MAAM,SAAS,GAAG,CAAC,KAAK,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC;QAEjD,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YACvC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,SAAS,EAAE,OAAO;oBAClB,KAAK,EAAE;wBACL,IAAI,EAAE,sBAAsB;wBAC5B,OAAO,EAAE,uDAAuD;wBAChE,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;qBAC9E;iBACF,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,SAAS,EAAE,OAAO;oBAClB,KAAK,EAAE;wBACL,IAAI,EAAE,uBAAuB;wBAC7B,OAAO,EAAE,wDAAwD;wBACjE,OAAO,EAAE;4BACP,mBAAmB,EAAE,cAAc;4BACnC,WAAW,EAAE,YAAY;4BACzB,KAAK,EAAE,KAAK,CAAC,KAAK;4BAClB,MAAM,EAAE,KAAK,CAAC,MAAM;yBACrB;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,SAAS,EAAE,OAAO;oBAClB,KAAK,EAAE;wBACL,IAAI,EAAE,qBAAqB;wBAC3B,OAAO,EAAE,oDAAoD;wBAC7D,OAAO,EAAE;4BACP,aAAa,EAAE,SAAS;4BACxB,cAAc,EAAE,SAAS;4BACzB,KAAK,EAAE,KAAK,CAAC,KAAK;4BAClB,MAAM,EAAE,KAAK,CAAC,MAAM;yBACrB;qBACF;iBACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,cAAc,EAAE,2BAA2B,EAAE,KAAK,CAAC,CAAC;QACvF,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,cAAc,EAAE,uBAAuB,EAAE,KAAK,CAAC,CAAC;QAEpF,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC;QACjD,IAAI,EAAE,GAAG,EAAE,CAAC;QAEZ,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;gBAC3B,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;oBACxB,EAAE,GAAG,SAAS,SAAS,IAAI,SAAS,6CAA6C,SAAS,IAAI,SAAS,sBAAsB,CAAC;gBAChI,CAAC;qBAAM,CAAC;oBACN,EAAE,GAAG,SAAS,SAAS,IAAI,SAAS,8CAA8C,SAAS,IAAI,SAAS,EAAE,CAAC;gBAC7G,CAAC;YACH,CAAC;iBAAM,IAAI,cAAc,EAAE,CAAC;gBAC1B,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;oBACxB,EAAE,GAAG,qBAAqB,cAAc,QAAQ,cAAc,sBAAsB,cAAc,WAAW,cAAc,IAAI,CAAC;gBAClI,CAAC;qBAAM,CAAC;oBACN,EAAE,GAAG,qBAAqB,cAAc,QAAQ,cAAc,sBAAsB,cAAc,WAAW,cAAc,IAAI,CAAC;gBAClI,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,UAAU,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,EAAE,CAAC;QAEvC,MAAM,UAAU,GAAG;YACjB,IAAI;YACJ,IAAI;YACJ,GAAG,CAAC,SAAS;YACb,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1B,MAAM;YACN,SAAS;YACT,SAAS;YACT,WAAW;YACX,MAAM;YACN,MAAM,CAAC,QAAQ,CAAC;YAChB,UAAU;YACV,SAAS;YACT,WAAW;YACX,YAAY;YACZ,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC/D,QAAQ;SACT,CAAC;QAEF,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE;YACrC,MAAM,EAAE,IAAI,EAAE,MAAM;YACpB,UAAU,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS;YACpE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;gBAChB,IAAI,EAAE,UAAU,EAAE,CAAC;oBACjB,KAAK,EAAE,SAAS;oBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE;iBACtD,CAAC,CAAC;YACL,CAAC;YACD,KAAK,EAAE,WAAW;SACnB,CAAC,CAAC;QACH,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAClB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE;oBACL,IAAI,EAAE,mBAAmB;oBACzB,OAAO,EAAE,gCAAgC;oBACzC,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE;iBAClD;aACF,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QAChG,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE;YACrC,MAAM,EAAE,IAAI,EAAE,MAAM;YACpB,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;gBAChB,IAAI,EAAE,UAAU,EAAE,CAAC;oBACjB,KAAK,EAAE,SAAS;oBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE;iBACnD,CAAC,CAAC;YACL,CAAC;YACD,KAAK,EAAE,QAAQ;SAChB,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;YACjC,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAEV,MAAM,OAAO,GAAkB;YAC7B;gBACE,GAAG,EAAE,2BAA2B;gBAChC,GAAG,EAAE,UAAU,QAAQ,EAAE;gBACzB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC;gBACxB,SAAS,EAAE,EAAE,CAAC,IAAI;gBAClB,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC,KAAK;gBAC/B,MAAM,EAAE,SAAS,IAAI,KAAK,CAAC,MAAM;gBACjC,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B;SACF,CAAC;QAEF,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,SAAS,IAAI,IAAI,CAAC,eAAe;YAAE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACvE,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAElD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC;gBACX,GAAG,EAAE,uBAAuB;gBAC5B,GAAG,EAAE,UAAU,SAAS,EAAE;gBAC1B,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,YAAY;gBAClB,SAAS,EAAE,MAAM;aAClB,CAAC,CAAC;QACL,CAAC;QAED,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QAErB,OAAO;YACL,EAAE,EAAE,IAAI;YACR,SAAS,EAAE,OAAO;YAClB,OAAO;YACP,IAAI,EAAE;gBACJ,IAAI,EAAE,GAAG,CAAC,SAAS;gBACnB,SAAS,EAAE,SAAS;gBACpB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B;YACD,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;SACjD,CAAC;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC1B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,sBAAsB,EAAE;aACxE,CAAC;QACJ,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,SAAS,EAAE,OAAO;YAClB,KAAK,EAAE;gBACL,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,yBAAyB;gBACnE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE;aAC3B;SACF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;YAChD,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import type {\n MediaOutput,\n MediaProcessingResult,\n MediaProcessingSpec,\n VideoOrientation,\n} from '@ttt-productions/media-schemas';\nimport { mkdir, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport { ensureFfmpegAvailable, runFfmpeg } from './ffmpeg.js';\nimport { probeVideo } from './probe.js';\nimport { unreadableLengthError } from '../duration/media-duration.js';\nimport { safeOutputPathFor } from '../utils/safe-path.js';\nimport type { ProcessMediaOptions } from '../types.js';\nimport {\n TIMED_MEDIA_MAIN_OUTPUT_KEY,\n VIDEO_POSTER_OUTPUT_KEY,\n} from '@ttt-productions/media-schemas';\n\nfunction matchMime(accepted: string, actual: string): boolean {\n const a = accepted.trim().toLowerCase();\n const m = actual.trim().toLowerCase();\n if (!a) return true;\n if (a === '*/*') return true;\n if (a.endsWith('/*')) return m.startsWith(a.slice(0, -1));\n return a === m;\n}\n\nfunction acceptAllowsVideo(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes('video');\n}\n\nfunction acceptAllowsMime(spec: MediaProcessingSpec, actualMime?: string): boolean {\n const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];\n if (mimes.length === 0) return true;\n if (!actualMime) return false;\n return mimes.some((a) => matchMime(a, actualMime));\n}\n\nfunction aspect(width?: number, height?: number): number | undefined {\n if (!width || !height) return undefined;\n return width / height;\n}\n\nfunction aspectClose(a: number, b: number, tolerance: number): boolean {\n return Math.abs(a - b) <= tolerance;\n}\n\nfunction orientationOf(width?: number, height?: number): VideoOrientation | undefined {\n if (!width || !height) return undefined;\n if (width === height) return 'any';\n return height > width ? 'vertical' : 'horizontal';\n}\n\nfunction orientationOk(\n required: VideoOrientation | undefined,\n actual: VideoOrientation | undefined,\n): boolean {\n if (!required || required === 'any') return true;\n if (!actual || actual === 'any') return true;\n return required === actual;\n}\n\nfunction outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\n}\n\nfunction mimeFromExt(ext: string): string | undefined {\n if (ext === 'mp4') return 'video/mp4';\n if (ext === 'webm') return 'video/webm';\n return undefined;\n}\n\nexport async function processVideo(\n spec: MediaProcessingSpec,\n ctx: {\n inputPath: string;\n outputBasePath: string;\n inputMime?: string;\n },\n opts?: ProcessMediaOptions,\n): Promise<MediaProcessingResult> {\n const tempFiles: string[] = [];\n\n try {\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n if (!acceptAllowsVideo(spec)) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'invalid_mime',\n message: 'Spec does not allow video uploads.',\n details: { accept: spec.accept },\n },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'invalid_mime',\n message: 'Video mime type is not allowed.',\n details: { inputMime: ctx.inputMime, accept: spec.accept },\n },\n };\n }\n\n const ff = await ensureFfmpegAvailable();\n if (!ff.ok) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: 'ffmpeg is not available.',\n details: { error: String((ff as any).error ?? 'unknown') },\n },\n };\n }\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });\n\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n\n const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;\n if (maxDur && probe.durationSec === undefined) {\n return { ok: false, mediaType: 'video', error: unreadableLengthError('video', maxDur) };\n }\n if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'too_long',\n message: 'Video is too long.',\n details: { durationSec: probe.durationSec, maxDurationSec: maxDur },\n },\n };\n }\n\n const requiredOri = spec.videoOrientation;\n const requiredAspect = spec.requiredAspectRatio;\n const requiredW = spec.requiredWidth;\n const requiredH = spec.requiredHeight;\n\n const actualOri = orientationOf(probe.width, probe.height);\n const actualAspect = aspect(probe.width, probe.height);\n\n const okOri = orientationOk(requiredOri, actualOri);\n const tol = spec.aspectRatioTolerance ?? 0.02;\n const okAspect = requiredAspect\n ? actualAspect\n ? aspectClose(actualAspect, requiredAspect, tol)\n : false\n : true;\n const okDims =\n !requiredW || !requiredH ? true : probe.width === requiredW && probe.height === requiredH;\n\n const needsAuto = !okOri || !okAspect || !okDims;\n\n if (needsAuto && !spec.allowAutoFormat) {\n if (!okOri) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'orientation_mismatch',\n message: 'Video orientation does not match the required format.',\n details: { requiredOri, actualOri, width: probe.width, height: probe.height },\n },\n };\n }\n if (!okAspect) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'aspect_ratio_mismatch',\n message: 'Video aspect ratio does not match the required format.',\n details: {\n requiredAspectRatio: requiredAspect,\n aspectRatio: actualAspect,\n width: probe.width,\n height: probe.height,\n },\n },\n };\n }\n if (!okDims) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'dimensions_mismatch',\n message: 'Video dimensions do not match the required format.',\n details: {\n requiredWidth: requiredW,\n requiredHeight: requiredH,\n width: probe.width,\n height: probe.height,\n },\n },\n };\n }\n }\n\n const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, 'mp4');\n const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, 'jpg');\n\n tempFiles.push(videoOut, posterOut);\n\n const scaleMode = spec.video?.scaleMode ?? 'fit';\n let vf = '';\n\n if (spec.allowAutoFormat) {\n if (requiredW && requiredH) {\n if (scaleMode === 'fit') {\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;\n } else {\n vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=increase,crop=${requiredW}:${requiredH}`;\n }\n } else if (requiredAspect) {\n if (scaleMode === 'fit') {\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n } else {\n vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;\n }\n }\n }\n\n const videoPreset = spec.video?.preset ?? 'veryfast';\n const videoCrf = spec.video?.crf ?? 23;\n\n const ffmpegArgs = [\n '-y',\n '-i',\n ctx.inputPath,\n ...(vf ? ['-vf', vf] : []),\n '-c:v',\n 'libx264',\n '-preset',\n videoPreset,\n '-crf',\n String(videoCrf),\n '-pix_fmt',\n 'yuv420p',\n '-movflags',\n '+faststart',\n ...(probe.hasAudio ? ['-c:a', 'aac', '-b:a', '128k'] : ['-an']),\n videoOut,\n ];\n\n const r1 = await runFfmpeg(ffmpegArgs, {\n signal: opts?.signal,\n durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,\n onProgress: (p) => {\n opts?.onProgress?.({\n phase: 'process',\n percent: p.percent,\n detail: { step: 'transcode', outTimeMs: p.outTimeMs },\n });\n },\n phase: 'transcode',\n });\n if (r1.code !== 0) {\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: 'ffmpeg video transcode failed.',\n details: { stderr: r1.stderr, stdout: r1.stdout },\n },\n };\n }\n\n const posterArgs = ['-y', '-ss', '1', '-i', videoOut, '-frames:v', '1', '-q:v', '2', posterOut];\n const r2 = await runFfmpeg(posterArgs, {\n signal: opts?.signal,\n onProgress: (p) => {\n opts?.onProgress?.({\n phase: 'process',\n percent: p.percent,\n detail: { step: 'poster', outTimeMs: p.outTimeMs },\n });\n },\n phase: 'poster',\n });\n const vs = await stat(videoOut);\n let psSize = 0;\n try {\n const ps = await stat(posterOut);\n psSize = ps.size;\n } catch {}\n\n const outputs: MediaOutput[] = [\n {\n key: TIMED_MEDIA_MAIN_OUTPUT_KEY,\n url: `file://${videoOut}`,\n path: videoOut,\n mime: mimeFromExt('mp4'),\n sizeBytes: vs.size,\n width: requiredW ?? probe.width,\n height: requiredH ?? probe.height,\n durationSec: probe.durationSec,\n },\n ];\n\n const warnings: string[] = [];\n if (needsAuto && spec.allowAutoFormat) warnings.push('auto_formatted');\n if (r2.code !== 0) warnings.push('poster_failed');\n\n if (psSize > 0) {\n outputs.push({\n key: VIDEO_POSTER_OUTPUT_KEY,\n url: `file://${posterOut}`,\n path: posterOut,\n mime: 'image/jpeg',\n sizeBytes: psSize,\n });\n }\n\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: 'video',\n outputs,\n meta: {\n mime: ctx.inputMime,\n sizeBytes: undefined,\n width: probe.width,\n height: probe.height,\n durationSec: probe.durationSec,\n },\n warnings: warnings.length ? warnings : undefined,\n };\n } catch (e: any) {\n if (opts?.signal?.aborted) {\n return {\n ok: false,\n mediaType: 'video',\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n return {\n ok: false,\n mediaType: 'video',\n error: {\n code: 'processing_failed',\n message: e?.message ? String(e.message) : 'Video processing failed',\n details: { name: e?.name },\n },\n };\n } finally {\n if (tempFiles.length) {\n const { rm } = await import('node:fs/promises');\n await Promise.all(tempFiles.map((f) => rm(f, { force: true }).catch(() => {})));\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/media-processing-core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.18",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ttt-productions/ttt-packages.git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"prepublishOnly": "npm run clean && npm run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ttt-productions/media-schemas": "^0.14.
|
|
34
|
+
"@ttt-productions/media-schemas": "^0.14.12",
|
|
35
35
|
"aws4fetch": "^1.0.20",
|
|
36
36
|
"sharp": "^0.35.3",
|
|
37
37
|
"zod": "^4.4.3"
|