@ttt-productions/media-processing-core 0.8.13 → 0.8.15
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 +15 -4
- package/dist/audio/audio-processor.js.map +1 -1
- package/dist/audio/probe.d.ts +2 -1
- package/dist/audio/probe.d.ts.map +1 -1
- package/dist/audio/probe.js +8 -10
- package/dist/audio/probe.js.map +1 -1
- package/dist/duration/media-duration.d.ts +26 -0
- package/dist/duration/media-duration.d.ts.map +1 -0
- package/dist/duration/media-duration.js +141 -0
- package/dist/duration/media-duration.js.map +1 -0
- package/dist/image/image-processor.d.ts.map +1 -1
- package/dist/image/image-processor.js +2 -1
- package/dist/image/image-processor.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/inspection/inspect-media.d.ts +5 -1
- package/dist/inspection/inspect-media.d.ts.map +1 -1
- package/dist/inspection/inspect-media.js +17 -12
- package/dist/inspection/inspect-media.js.map +1 -1
- package/dist/video/ffmpeg.d.ts +10 -2
- package/dist/video/ffmpeg.d.ts.map +1 -1
- package/dist/video/ffmpeg.js +28 -1
- package/dist/video/ffmpeg.js.map +1 -1
- package/dist/video/probe.d.ts +2 -1
- package/dist/video/probe.d.ts.map +1 -1
- package/dist/video/probe.js +8 -17
- package/dist/video/probe.js.map +1 -1
- package/dist/video/video-processor.d.ts.map +1 -1
- package/dist/video/video-processor.js +14 -6
- package/dist/video/video-processor.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-processor.d.ts","sourceRoot":"","sources":["../../src/audio/audio-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"audio-processor.d.ts","sourceRoot":"","sources":["../../src/audio/audio-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAO9G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AA4BvD,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,CA4IhC"}
|
|
@@ -3,6 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { safeOutputPathFor } from "../utils/safe-path.js";
|
|
4
4
|
import { ensureFfmpegAvailable, runFfmpeg } from "../video/ffmpeg.js";
|
|
5
5
|
import { probeAudio } from "./probe.js";
|
|
6
|
+
import { unreadableLengthError } from "../duration/media-duration.js";
|
|
7
|
+
import { TIMED_MEDIA_MAIN_OUTPUT_KEY } from "@ttt-productions/media-schemas";
|
|
6
8
|
function matchMime(accepted, actual) {
|
|
7
9
|
const a = accepted.trim().toLowerCase();
|
|
8
10
|
const m = actual.trim().toLowerCase();
|
|
@@ -63,9 +65,15 @@ export async function processAudio(spec, ctx, opts) {
|
|
|
63
65
|
}
|
|
64
66
|
const outDir = path.dirname(ctx.outputBasePath);
|
|
65
67
|
await mkdir(outDir, { recursive: true });
|
|
66
|
-
const probe = await probeAudio(ctx.inputPath);
|
|
68
|
+
const probe = await probeAudio(ctx.inputPath, { signal: opts?.signal });
|
|
69
|
+
if (opts?.signal?.aborted) {
|
|
70
|
+
return { ok: false, mediaType: "audio", error: { code: "processing_canceled", message: "Processing canceled." } };
|
|
71
|
+
}
|
|
67
72
|
const maxDur = spec.maxDurationSec ?? spec.audio?.maxDurationSec;
|
|
68
|
-
if (maxDur && probe.durationSec
|
|
73
|
+
if (maxDur && probe.durationSec === undefined) {
|
|
74
|
+
return { ok: false, mediaType: "audio", error: unreadableLengthError("audio", maxDur) };
|
|
75
|
+
}
|
|
76
|
+
if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {
|
|
69
77
|
return {
|
|
70
78
|
ok: false,
|
|
71
79
|
mediaType: "audio",
|
|
@@ -77,7 +85,7 @@ export async function processAudio(spec, ctx, opts) {
|
|
|
77
85
|
};
|
|
78
86
|
}
|
|
79
87
|
// Normalize to AAC in m4a (small + widely supported)
|
|
80
|
-
const outPath = outputPathFor(ctx.outputBasePath,
|
|
88
|
+
const outPath = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, "m4a");
|
|
81
89
|
tempFiles.push(outPath);
|
|
82
90
|
const args = [
|
|
83
91
|
"-y",
|
|
@@ -112,7 +120,7 @@ export async function processAudio(spec, ctx, opts) {
|
|
|
112
120
|
const s = await stat(outPath);
|
|
113
121
|
const outputs = [
|
|
114
122
|
{
|
|
115
|
-
key:
|
|
123
|
+
key: TIMED_MEDIA_MAIN_OUTPUT_KEY,
|
|
116
124
|
url: `file://${outPath}`,
|
|
117
125
|
path: outPath,
|
|
118
126
|
mime: "audio/mp4",
|
|
@@ -132,6 +140,9 @@ export async function processAudio(spec, ctx, opts) {
|
|
|
132
140
|
};
|
|
133
141
|
}
|
|
134
142
|
catch (e) {
|
|
143
|
+
if (opts?.signal?.aborted) {
|
|
144
|
+
return { ok: false, mediaType: "audio", error: { code: "processing_canceled", message: "Processing canceled." } };
|
|
145
|
+
}
|
|
135
146
|
return {
|
|
136
147
|
ok: false,
|
|
137
148
|
mediaType: "audio",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio-processor.js","sourceRoot":"","sources":["../../src/audio/audio-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC,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,CAAC,2BAA2B;IAChE,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,aAAa,CAAC,IAAY,EAAE,GAAW,EAAE,GAAW;IAC3D,OAAO,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC3C,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,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC;QACpH,CAAC;QACD,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;QACD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,OAAO;gBAClB,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oCAAoC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;aACjH,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,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,iCAAiC,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;aACxI,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,CAAC,CAAC;QAE9C,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC;QACjE,IAAI,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG,MAAM,EAAE,CAAC;YAC9D,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,qDAAqD;QACrD,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QAEjE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAExB,MAAM,IAAI,GAAG;YACX,IAAI;YACJ,IAAI;YACJ,GAAG,CAAC,SAAS;YACb,KAAK;YACL,MAAM;YACN,KAAK;YACL,MAAM;YACN,MAAM;YACN,OAAO;SACR,CAAC;QAEF,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE;YAC9B,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,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YAC5H,CAAC;YACD,KAAK,EAAE,WAAW;SACnB,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACjB,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,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;iBAChD;aACF,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;QAE9B,MAAM,OAAO,GAAkB;YAC7B;gBACE,GAAG,EAAE,MAAM;gBACX,GAAG,EAAE,UAAU,OAAO,EAAE;gBACxB,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,WAAW;gBACjB,SAAS,EAAE,CAAC,CAAC,IAAI;gBACjB,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B;SACF,CAAC;QAEF,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,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,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 { MediaOutput, MediaProcessingResult, MediaProcessingSpec } from \"@ttt-productions/media-schemas\";\nimport { mkdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { safeOutputPathFor } from \"../utils/safe-path.js\";\nimport { ensureFfmpegAvailable, runFfmpeg } from \"../video/ffmpeg.js\";\nimport { probeAudio } from \"./probe.js\";\nimport type { ProcessMediaOptions } from \"../types.js\";\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 acceptAllowsAudio(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes(\"audio\");\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 outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\n}\n\nexport async function processAudio(\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 { ok: false, mediaType: \"audio\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n const ff = await ensureFfmpegAvailable();\n if (!ff.ok) {\n return {\n ok: false,\n mediaType: \"audio\",\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 if (!acceptAllowsAudio(spec)) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: { code: \"invalid_mime\", message: \"Spec does not allow audio uploads.\", details: { accept: spec.accept } },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: { code: \"invalid_mime\", message: \"Audio mime type is not allowed.\", details: { inputMime: ctx.inputMime, accept: spec.accept } },\n };\n }\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const probe = await probeAudio(ctx.inputPath);\n\n const maxDur = spec.maxDurationSec ?? spec.audio?.maxDurationSec;\n if (maxDur && probe.durationSec && probe.durationSec > maxDur) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: {\n code: \"too_long\",\n message: \"Audio is too long.\",\n details: { durationSec: probe.durationSec, maxDurationSec: maxDur },\n },\n };\n }\n\n // Normalize to AAC in m4a (small + widely supported)\n const outPath = outputPathFor(ctx.outputBasePath, \"main\", \"m4a\");\n\n tempFiles.push(outPath);\n\n const args = [\n \"-y\",\n \"-i\",\n ctx.inputPath,\n \"-vn\",\n \"-c:a\",\n \"aac\",\n \"-b:a\",\n \"128k\",\n outPath,\n ];\n\n const r = await runFfmpeg(args, {\n signal: opts?.signal,\n durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,\n onProgress: (p) => {\n opts?.onProgress?.({ phase: \"process\", percent: p.percent, detail: { step: \"audio_transcode\", outTimeMs: p.outTimeMs } });\n },\n phase: \"transcode\",\n });\n if (r.code !== 0) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: {\n code: \"processing_failed\",\n message: \"ffmpeg audio transcode failed.\",\n details: { stderr: r.stderr, stdout: r.stdout },\n },\n };\n }\n\n const s = await stat(outPath);\n\n const outputs: MediaOutput[] = [\n {\n key: \"main\",\n url: `file://${outPath}`,\n path: outPath,\n mime: \"audio/mp4\",\n sizeBytes: s.size,\n durationSec: probe.durationSec,\n },\n ];\n\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: \"audio\",\n outputs,\n meta: {\n mime: ctx.inputMime,\n durationSec: probe.durationSec,\n },\n };\n } catch (e: any) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: {\n code: \"processing_failed\",\n message: e?.message ? String(e.message) : \"Audio 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":"audio-processor.js","sourceRoot":"","sources":["../../src/audio/audio-processor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MediaOutput, MediaProcessingResult, MediaProcessingSpec } from \"@ttt-productions/media-schemas\";\nimport { mkdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { safeOutputPathFor } from \"../utils/safe-path.js\";\nimport { ensureFfmpegAvailable, runFfmpeg } from \"../video/ffmpeg.js\";\nimport { probeAudio } from \"./probe.js\";\nimport { unreadableLengthError } from \"../duration/media-duration.js\";\nimport type { ProcessMediaOptions } from \"../types.js\";\nimport { TIMED_MEDIA_MAIN_OUTPUT_KEY } 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 acceptAllowsAudio(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes(\"audio\");\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 outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\n}\n\nexport async function processAudio(\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 { ok: false, mediaType: \"audio\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n const ff = await ensureFfmpegAvailable();\n if (!ff.ok) {\n return {\n ok: false,\n mediaType: \"audio\",\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 if (!acceptAllowsAudio(spec)) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: { code: \"invalid_mime\", message: \"Spec does not allow audio uploads.\", details: { accept: spec.accept } },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: { code: \"invalid_mime\", message: \"Audio mime type is not allowed.\", details: { inputMime: ctx.inputMime, accept: spec.accept } },\n };\n }\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const probe = await probeAudio(ctx.inputPath, { signal: opts?.signal });\n\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"audio\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n const maxDur = spec.maxDurationSec ?? spec.audio?.maxDurationSec;\n if (maxDur && probe.durationSec === undefined) {\n return { ok: false, mediaType: \"audio\", error: unreadableLengthError(\"audio\", maxDur) };\n }\n if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: {\n code: \"too_long\",\n message: \"Audio is too long.\",\n details: { durationSec: probe.durationSec, maxDurationSec: maxDur },\n },\n };\n }\n\n // Normalize to AAC in m4a (small + widely supported)\n const outPath = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, \"m4a\");\n\n tempFiles.push(outPath);\n\n const args = [\n \"-y\",\n \"-i\",\n ctx.inputPath,\n \"-vn\",\n \"-c:a\",\n \"aac\",\n \"-b:a\",\n \"128k\",\n outPath,\n ];\n\n const r = await runFfmpeg(args, {\n signal: opts?.signal,\n durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,\n onProgress: (p) => {\n opts?.onProgress?.({ phase: \"process\", percent: p.percent, detail: { step: \"audio_transcode\", outTimeMs: p.outTimeMs } });\n },\n phase: \"transcode\",\n });\n if (r.code !== 0) {\n return {\n ok: false,\n mediaType: \"audio\",\n error: {\n code: \"processing_failed\",\n message: \"ffmpeg audio transcode failed.\",\n details: { stderr: r.stderr, stdout: r.stdout },\n },\n };\n }\n\n const s = await stat(outPath);\n\n const outputs: MediaOutput[] = [\n {\n key: TIMED_MEDIA_MAIN_OUTPUT_KEY,\n url: `file://${outPath}`,\n path: outPath,\n mime: \"audio/mp4\",\n sizeBytes: s.size,\n durationSec: probe.durationSec,\n },\n ];\n\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: \"audio\",\n outputs,\n meta: {\n mime: ctx.inputMime,\n durationSec: probe.durationSec,\n },\n };\n } catch (e: any) {\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"audio\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n return {\n ok: false,\n mediaType: \"audio\",\n error: {\n code: \"processing_failed\",\n message: e?.message ? String(e.message) : \"Audio 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/dist/audio/probe.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type MeasureDurationOptions } from "../duration/media-duration.js";
|
|
1
2
|
export interface AudioProbe {
|
|
2
3
|
durationSec?: number;
|
|
3
4
|
}
|
|
4
|
-
export declare function probeAudio(inputPath: string): Promise<AudioProbe>;
|
|
5
|
+
export declare function probeAudio(inputPath: string, opts?: MeasureDurationOptions): Promise<AudioProbe>;
|
|
5
6
|
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/audio/probe.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/audio/probe.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtG"}
|
package/dist/audio/probe.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { runCmd } from "../video/ffmpeg.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from "../video/ffmpeg.js";
|
|
2
|
+
import { measureDurationSec } from "../duration/media-duration.js";
|
|
3
|
+
export async function probeAudio(inputPath, opts) {
|
|
4
|
+
// ffprobe opening the file is the readability check: a file it cannot open
|
|
5
|
+
// throws here rather than reaching the length rule as merely unmeasurable.
|
|
6
|
+
const r = await runCmd("ffprobe", ["-v", "error", inputPath], { timeoutMs: DEFAULT_PROBE_TIMEOUT_MS, signal: opts?.signal });
|
|
7
|
+
if (r.timedOut || r.code !== 0) {
|
|
6
8
|
throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);
|
|
7
9
|
}
|
|
8
|
-
|
|
9
|
-
const format = json.format ?? {};
|
|
10
|
-
const durationStr = format.duration;
|
|
11
|
-
const durationSec = durationStr ? Number(durationStr) : undefined;
|
|
12
|
-
return { durationSec: Number.isFinite(durationSec) ? durationSec : undefined };
|
|
10
|
+
return { durationSec: await measureDurationSec(inputPath, opts) };
|
|
13
11
|
}
|
|
14
12
|
//# sourceMappingURL=probe.js.map
|
package/dist/audio/probe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/audio/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/audio/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAA+B,MAAM,+BAA+B,CAAC;AAMhG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,SAAiB,EAAE,IAA6B;IAC/E,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAE7H,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,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC;AACpE,CAAC","sourcesContent":["import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from \"../video/ffmpeg.js\";\nimport { measureDurationSec, type MeasureDurationOptions } from \"../duration/media-duration.js\";\n\nexport interface AudioProbe {\n durationSec?: number;\n}\n\nexport async function probeAudio(inputPath: string, opts?: MeasureDurationOptions): Promise<AudioProbe> {\n // ffprobe opening the file is the readability check: a file it cannot open\n // throws here rather than reaching the length rule as merely unmeasurable.\n const r = await runCmd(\"ffprobe\", [\"-v\", \"error\", inputPath], { timeoutMs: DEFAULT_PROBE_TIMEOUT_MS, signal: opts?.signal });\n\n if (r.timedOut || r.code !== 0) {\n throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);\n }\n\n return { durationSec: await measureDurationSec(inputPath, opts) };\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MediaProcessingError } from "@ttt-productions/media-schemas";
|
|
2
|
+
/** Wall-clock ceiling for the measuring pass; it bounds an adversarial input,
|
|
3
|
+
* not a real upload (a 920 MB, 30-minute WebM measures in about a second). */
|
|
4
|
+
export declare const DEFAULT_DURATION_MEASURE_TIMEOUT_MS = 120000;
|
|
5
|
+
export interface PacketLengthFold {
|
|
6
|
+
/** One line of ffprobe's `-of compact` packet and stream listing. */
|
|
7
|
+
add(line: string): void;
|
|
8
|
+
/** Seconds from the earliest packet start (never before zero) to the latest
|
|
9
|
+
* packet end across the timed (audio, and non-cover-art video) streams, or
|
|
10
|
+
* `undefined`. */
|
|
11
|
+
lengthSec(): number | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function createPacketLengthFold(): PacketLengthFold;
|
|
14
|
+
export interface MeasureDurationOptions {
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
/** The file's length in seconds, or `undefined` when it cannot be measured. */
|
|
19
|
+
export declare function measureDurationSec(localPath: string, opts?: MeasureDurationOptions): Promise<number | undefined>;
|
|
20
|
+
export type MeasureDurationFn = (localPath: string, opts?: MeasureDurationOptions) => Promise<number | undefined>;
|
|
21
|
+
/** The refusal for a file with no length under a spec that caps length: a
|
|
22
|
+
* length that cannot be measured cannot be shown to be within the cap. The
|
|
23
|
+
* code is `unsupported_format`, never `too_long` — the file is not known to
|
|
24
|
+
* be too long, and its copy must not say so. */
|
|
25
|
+
export declare function unreadableLengthError(kind: "video" | "audio", maxDurationSec: number): MediaProcessingError;
|
|
26
|
+
//# sourceMappingURL=media-duration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-duration.d.ts","sourceRoot":"","sources":["../../src/duration/media-duration.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E;+EAC+E;AAC/E,eAAO,MAAM,mCAAmC,SAAU,CAAC;AAwB3D,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;uBAEmB;IACnB,SAAS,IAAI,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,CAgDzD;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,+EAA+E;AAC/E,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA0BtH;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAElH;;;iDAGiD;AACjD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,oBAAoB,CAM3G"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The ONE rule for a timed media file's length (ENG-002). The inspection's
|
|
2
|
+
// `durationSec` and both processors' too-long checks read it here, so they can
|
|
3
|
+
// never disagree about the same file.
|
|
4
|
+
//
|
|
5
|
+
// 1. The length is always MEASURED from the packets themselves: ffprobe
|
|
6
|
+
// lists every packet's timestamp and duration (demux only, no decode),
|
|
7
|
+
// and the length is the span from the earliest packet start (never before
|
|
8
|
+
// zero) to the latest packet end across the audio and video streams. A
|
|
9
|
+
// container header's declared length (`format.duration`) is never used:
|
|
10
|
+
// a header can claim a short length for a long file, and Chromium's
|
|
11
|
+
// MediaRecorder writes WebM with none. ffmpeg's `-progress` output time
|
|
12
|
+
// is not used either: it trails the true end by the B-frame reorder
|
|
13
|
+
// delay. The listing is read one line at a time and folded into a few
|
|
14
|
+
// numbers per stream, so a file of any packet count is measured in
|
|
15
|
+
// bounded memory.
|
|
16
|
+
// 2. A measure that fails, times out, is aborted, is cut short, or finds no
|
|
17
|
+
// timed packet is NO length. Every caller treats that as unknown and
|
|
18
|
+
// fails closed.
|
|
19
|
+
import { runCmd } from "../video/ffmpeg.js";
|
|
20
|
+
/** Wall-clock ceiling for the measuring pass; it bounds an adversarial input,
|
|
21
|
+
* not a real upload (a 920 MB, 30-minute WebM measures in about a second). */
|
|
22
|
+
export const DEFAULT_DURATION_MEASURE_TIMEOUT_MS = 120_000;
|
|
23
|
+
/** More streams than this is an adversarial file, and is no length. */
|
|
24
|
+
const MAX_TRACKED_STREAMS = 256;
|
|
25
|
+
const SECONDS = /^-?\d+(\.\d+)?$/;
|
|
26
|
+
const STREAM_INDEX = /^\d+$/;
|
|
27
|
+
function seconds(raw) {
|
|
28
|
+
if (raw === undefined || !SECONDS.test(raw))
|
|
29
|
+
return undefined;
|
|
30
|
+
const n = Number(raw);
|
|
31
|
+
return Number.isFinite(n) ? n : undefined;
|
|
32
|
+
}
|
|
33
|
+
function fields(line) {
|
|
34
|
+
const [section = "", ...pairs] = line.split("|");
|
|
35
|
+
const values = new Map();
|
|
36
|
+
for (const pair of pairs) {
|
|
37
|
+
const eq = pair.indexOf("=");
|
|
38
|
+
if (eq > 0)
|
|
39
|
+
values.set(pair.slice(0, eq), pair.slice(eq + 1).trim());
|
|
40
|
+
}
|
|
41
|
+
return { section, values };
|
|
42
|
+
}
|
|
43
|
+
export function createPacketLengthFold() {
|
|
44
|
+
const spans = new Map();
|
|
45
|
+
const timed = new Set();
|
|
46
|
+
let overflow = false;
|
|
47
|
+
return {
|
|
48
|
+
add(line) {
|
|
49
|
+
const { section, values } = fields(line);
|
|
50
|
+
if (section === "packet") {
|
|
51
|
+
const rawIndex = values.get("stream_index");
|
|
52
|
+
if (rawIndex === undefined || !STREAM_INDEX.test(rawIndex))
|
|
53
|
+
return;
|
|
54
|
+
const index = Number(rawIndex);
|
|
55
|
+
const start = seconds(values.get("pts_time")) ?? seconds(values.get("dts_time"));
|
|
56
|
+
if (start === undefined)
|
|
57
|
+
return;
|
|
58
|
+
const end = start + Math.max(0, seconds(values.get("duration_time")) ?? 0);
|
|
59
|
+
const span = spans.get(index);
|
|
60
|
+
if (span) {
|
|
61
|
+
if (start < span.start)
|
|
62
|
+
span.start = start;
|
|
63
|
+
if (end > span.end)
|
|
64
|
+
span.end = end;
|
|
65
|
+
}
|
|
66
|
+
else if (spans.size >= MAX_TRACKED_STREAMS) {
|
|
67
|
+
overflow = true;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
spans.set(index, { start, end });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (section === "stream") {
|
|
74
|
+
const rawIndex = values.get("index");
|
|
75
|
+
if (rawIndex === undefined || !STREAM_INDEX.test(rawIndex))
|
|
76
|
+
return;
|
|
77
|
+
const type = values.get("codec_type");
|
|
78
|
+
const coverArt = values.get("disposition:attached_pic") === "1";
|
|
79
|
+
if (type === "audio" || (type === "video" && !coverArt))
|
|
80
|
+
timed.add(Number(rawIndex));
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
lengthSec() {
|
|
84
|
+
if (overflow)
|
|
85
|
+
return undefined;
|
|
86
|
+
let start = Infinity;
|
|
87
|
+
let end = -Infinity;
|
|
88
|
+
for (const index of timed) {
|
|
89
|
+
const span = spans.get(index);
|
|
90
|
+
if (!span)
|
|
91
|
+
continue;
|
|
92
|
+
if (span.start < start)
|
|
93
|
+
start = span.start;
|
|
94
|
+
if (span.end > end)
|
|
95
|
+
end = span.end;
|
|
96
|
+
}
|
|
97
|
+
// Packets before zero are encoder pre-roll (AAC priming, Opus pre-skip)
|
|
98
|
+
// that a player discards, so they add no playable length.
|
|
99
|
+
const sec = Math.round((end - Math.max(0, start)) * 1_000_000) / 1_000_000;
|
|
100
|
+
return Number.isFinite(sec) && sec > 0 ? sec : undefined;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** The file's length in seconds, or `undefined` when it cannot be measured. */
|
|
105
|
+
export async function measureDurationSec(localPath, opts) {
|
|
106
|
+
const fold = createPacketLengthFold();
|
|
107
|
+
let r;
|
|
108
|
+
try {
|
|
109
|
+
r = await runCmd("ffprobe", [
|
|
110
|
+
"-v",
|
|
111
|
+
"error",
|
|
112
|
+
"-show_entries",
|
|
113
|
+
"stream=index,codec_type:stream_disposition=attached_pic:packet=stream_index,pts_time,dts_time,duration_time",
|
|
114
|
+
"-of",
|
|
115
|
+
"compact",
|
|
116
|
+
localPath,
|
|
117
|
+
], {
|
|
118
|
+
timeoutMs: opts?.timeoutMs ?? DEFAULT_DURATION_MEASURE_TIMEOUT_MS,
|
|
119
|
+
signal: opts?.signal,
|
|
120
|
+
onStdoutLine: (line) => fold.add(line),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (r.timedOut || r.truncated || r.code !== 0)
|
|
127
|
+
return undefined;
|
|
128
|
+
return fold.lengthSec();
|
|
129
|
+
}
|
|
130
|
+
/** The refusal for a file with no length under a spec that caps length: a
|
|
131
|
+
* length that cannot be measured cannot be shown to be within the cap. The
|
|
132
|
+
* code is `unsupported_format`, never `too_long` — the file is not known to
|
|
133
|
+
* be too long, and its copy must not say so. */
|
|
134
|
+
export function unreadableLengthError(kind, maxDurationSec) {
|
|
135
|
+
return {
|
|
136
|
+
code: "unsupported_format",
|
|
137
|
+
message: kind === "video" ? "The video's length could not be read." : "The audio's length could not be read.",
|
|
138
|
+
details: { maxDurationSec },
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=media-duration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-duration.js","sourceRoot":"","sources":["../../src/duration/media-duration.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,+EAA+E;AAC/E,sCAAsC;AACtC,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,wEAAwE;AACxE,uBAAuB;AACvB,8EAA8E;AAC9E,0EAA0E;AAC1E,qBAAqB;AAGrB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;+EAC+E;AAC/E,MAAM,CAAC,MAAM,mCAAmC,GAAG,OAAO,CAAC;AAE3D,uEAAuE;AACvE,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAClC,MAAM,YAAY,GAAG,OAAO,CAAC;AAE7B,SAAS,OAAO,CAAC,GAAuB;IACtC,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,MAAM,CAAC,OAAO,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAWD,MAAM,UAAU,sBAAsB;IACpC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0C,CAAC;IAChE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,OAAO;QACL,GAAG,CAAC,IAAI;YACN,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAC5C,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAAE,OAAO;gBACnE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;gBACjF,IAAI,KAAK,KAAK,SAAS;oBAAE,OAAO;gBAChC,MAAM,GAAG,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC3E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC9B,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK;wBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;oBAC3C,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG;wBAAE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;gBACrC,CAAC;qBAAM,IAAI,KAAK,CAAC,IAAI,IAAI,mBAAmB,EAAE,CAAC;oBAC7C,QAAQ,GAAG,IAAI,CAAC;gBAClB,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;iBAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACrC,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAAE,OAAO;gBACnE,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,KAAK,GAAG,CAAC;gBAChE,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;QACD,SAAS;YACP,IAAI,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAC/B,IAAI,KAAK,GAAG,QAAQ,CAAC;YACrB,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;YACpB,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC9B,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK;oBAAE,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBAC3C,IAAI,IAAI,CAAC,GAAG,GAAG,GAAG;oBAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;YACrC,CAAC;YACD,wEAAwE;YACxE,0DAA0D;YAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;YAC3E,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3D,CAAC;KACF,CAAC;AACJ,CAAC;AAOD,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAiB,EAAE,IAA6B;IACvF,MAAM,IAAI,GAAG,sBAAsB,EAAE,CAAC;IACtC,IAAI,CAAC,CAAC;IACN,IAAI,CAAC;QACH,CAAC,GAAG,MAAM,MAAM,CACd,SAAS,EACT;YACE,IAAI;YACJ,OAAO;YACP,eAAe;YACf,6GAA6G;YAC7G,KAAK;YACL,SAAS;YACT,SAAS;SACV,EACD;YACE,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,mCAAmC;YACjE,MAAM,EAAE,IAAI,EAAE,MAAM;YACpB,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;SACvC,CACF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;AAC1B,CAAC;AAID;;;iDAGiD;AACjD,MAAM,UAAU,qBAAqB,CAAC,IAAuB,EAAE,cAAsB;IACnF,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC,CAAC,uCAAuC;QAC7G,OAAO,EAAE,EAAE,cAAc,EAAE;KAC5B,CAAC;AACJ,CAAC","sourcesContent":["// The ONE rule for a timed media file's length (ENG-002). The inspection's\n// `durationSec` and both processors' too-long checks read it here, so they can\n// never disagree about the same file.\n//\n// 1. The length is always MEASURED from the packets themselves: ffprobe\n// lists every packet's timestamp and duration (demux only, no decode),\n// and the length is the span from the earliest packet start (never before\n// zero) to the latest packet end across the audio and video streams. A\n// container header's declared length (`format.duration`) is never used:\n// a header can claim a short length for a long file, and Chromium's\n// MediaRecorder writes WebM with none. ffmpeg's `-progress` output time\n// is not used either: it trails the true end by the B-frame reorder\n// delay. The listing is read one line at a time and folded into a few\n// numbers per stream, so a file of any packet count is measured in\n// bounded memory.\n// 2. A measure that fails, times out, is aborted, is cut short, or finds no\n// timed packet is NO length. Every caller treats that as unknown and\n// fails closed.\n\nimport type { MediaProcessingError } from \"@ttt-productions/media-schemas\";\nimport { runCmd } from \"../video/ffmpeg.js\";\n\n/** Wall-clock ceiling for the measuring pass; it bounds an adversarial input,\n * not a real upload (a 920 MB, 30-minute WebM measures in about a second). */\nexport const DEFAULT_DURATION_MEASURE_TIMEOUT_MS = 120_000;\n\n/** More streams than this is an adversarial file, and is no length. */\nconst MAX_TRACKED_STREAMS = 256;\n\nconst SECONDS = /^-?\\d+(\\.\\d+)?$/;\nconst STREAM_INDEX = /^\\d+$/;\n\nfunction seconds(raw: string | undefined): number | undefined {\n if (raw === undefined || !SECONDS.test(raw)) return undefined;\n const n = Number(raw);\n return Number.isFinite(n) ? n : undefined;\n}\n\nfunction fields(line: string): { section: string; values: Map<string, string> } {\n const [section = \"\", ...pairs] = line.split(\"|\");\n const values = new Map<string, string>();\n for (const pair of pairs) {\n const eq = pair.indexOf(\"=\");\n if (eq > 0) values.set(pair.slice(0, eq), pair.slice(eq + 1).trim());\n }\n return { section, values };\n}\n\nexport interface PacketLengthFold {\n /** One line of ffprobe's `-of compact` packet and stream listing. */\n add(line: string): void;\n /** Seconds from the earliest packet start (never before zero) to the latest\n * packet end across the timed (audio, and non-cover-art video) streams, or\n * `undefined`. */\n lengthSec(): number | undefined;\n}\n\nexport function createPacketLengthFold(): PacketLengthFold {\n const spans = new Map<number, { start: number; end: number }>();\n const timed = new Set<number>();\n let overflow = false;\n\n return {\n add(line) {\n const { section, values } = fields(line);\n if (section === \"packet\") {\n const rawIndex = values.get(\"stream_index\");\n if (rawIndex === undefined || !STREAM_INDEX.test(rawIndex)) return;\n const index = Number(rawIndex);\n const start = seconds(values.get(\"pts_time\")) ?? seconds(values.get(\"dts_time\"));\n if (start === undefined) return;\n const end = start + Math.max(0, seconds(values.get(\"duration_time\")) ?? 0);\n const span = spans.get(index);\n if (span) {\n if (start < span.start) span.start = start;\n if (end > span.end) span.end = end;\n } else if (spans.size >= MAX_TRACKED_STREAMS) {\n overflow = true;\n } else {\n spans.set(index, { start, end });\n }\n } else if (section === \"stream\") {\n const rawIndex = values.get(\"index\");\n if (rawIndex === undefined || !STREAM_INDEX.test(rawIndex)) return;\n const type = values.get(\"codec_type\");\n const coverArt = values.get(\"disposition:attached_pic\") === \"1\";\n if (type === \"audio\" || (type === \"video\" && !coverArt)) timed.add(Number(rawIndex));\n }\n },\n lengthSec() {\n if (overflow) return undefined;\n let start = Infinity;\n let end = -Infinity;\n for (const index of timed) {\n const span = spans.get(index);\n if (!span) continue;\n if (span.start < start) start = span.start;\n if (span.end > end) end = span.end;\n }\n // Packets before zero are encoder pre-roll (AAC priming, Opus pre-skip)\n // that a player discards, so they add no playable length.\n const sec = Math.round((end - Math.max(0, start)) * 1_000_000) / 1_000_000;\n return Number.isFinite(sec) && sec > 0 ? sec : undefined;\n },\n };\n}\n\nexport interface MeasureDurationOptions {\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\n/** The file's length in seconds, or `undefined` when it cannot be measured. */\nexport async function measureDurationSec(localPath: string, opts?: MeasureDurationOptions): Promise<number | undefined> {\n const fold = createPacketLengthFold();\n let r;\n try {\n r = await runCmd(\n \"ffprobe\",\n [\n \"-v\",\n \"error\",\n \"-show_entries\",\n \"stream=index,codec_type:stream_disposition=attached_pic:packet=stream_index,pts_time,dts_time,duration_time\",\n \"-of\",\n \"compact\",\n localPath,\n ],\n {\n timeoutMs: opts?.timeoutMs ?? DEFAULT_DURATION_MEASURE_TIMEOUT_MS,\n signal: opts?.signal,\n onStdoutLine: (line) => fold.add(line),\n },\n );\n } catch {\n return undefined;\n }\n if (r.timedOut || r.truncated || r.code !== 0) return undefined;\n return fold.lengthSec();\n}\n\nexport type MeasureDurationFn = (localPath: string, opts?: MeasureDurationOptions) => Promise<number | undefined>;\n\n/** The refusal for a file with no length under a spec that caps length: a\n * length that cannot be measured cannot be shown to be within the cap. The\n * code is `unsupported_format`, never `too_long` — the file is not known to\n * be too long, and its copy must not say so. */\nexport function unreadableLengthError(kind: \"video\" | \"audio\", maxDurationSec: number): MediaProcessingError {\n return {\n code: \"unsupported_format\",\n message: kind === \"video\" ? \"The video's length could not be read.\" : \"The audio's length could not be read.\",\n details: { maxDurationSec },\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../src/image/image-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAMpI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../src/image/image-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqC,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAMpI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AA+LvD,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB,EACD,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,qBAAqB,CAAC,CA6FhC"}
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { safeOutputPathFor } from "../utils/safe-path.js";
|
|
5
5
|
import { applyImageFormat, mimeForImageFormat, pickFormat } from "./formats.js";
|
|
6
6
|
import { computeAspectCropBox, resizeCover, resizeInside, toSharpPosition, } from "./resize.js";
|
|
7
|
+
import { IMAGE_DEFAULT_OUTPUT_KEY } from "@ttt-productions/media-schemas";
|
|
7
8
|
function matchMime(accepted, actual) {
|
|
8
9
|
const a = accepted.trim().toLowerCase();
|
|
9
10
|
const m = actual.trim().toLowerCase();
|
|
@@ -168,7 +169,7 @@ export async function processImage(spec, ctx, opts) {
|
|
|
168
169
|
if (opts?.signal?.aborted) {
|
|
169
170
|
return { ok: false, mediaType: "image", error: { code: "processing_canceled", message: "Processing canceled." } };
|
|
170
171
|
}
|
|
171
|
-
const variants = spec.image?.variants?.length ? spec.image.variants : [{ key:
|
|
172
|
+
const variants = spec.image?.variants?.length ? spec.image.variants : [{ key: IMAGE_DEFAULT_OUTPUT_KEY }];
|
|
172
173
|
const outDir = path.dirname(ctx.outputBasePath);
|
|
173
174
|
await mkdir(outDir, { recursive: true });
|
|
174
175
|
const baseRes = await buildBase(spec, ctx.inputPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../src/image/image-processor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MediaOutput, MediaProcessingError, MediaProcessingResult, MediaProcessingSpec } from \"@ttt-productions/media-schemas\";\nimport sharp from \"sharp\";\nimport type { Sharp, Metadata } from \"sharp\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { safeOutputPathFor } from \"../utils/safe-path.js\";\nimport type { ProcessMediaOptions } from \"../types.js\";\nimport { applyImageFormat, mimeForImageFormat, pickFormat } from \"./formats.js\";\nimport {\n computeAspectCropBox,\n resizeCover,\n resizeInside,\n toSharpPosition,\n type Gravity,\n} from \"./resize.js\";\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 acceptAllowsImage(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes(\"image\");\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 outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\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\n/**\n * Build a \"normalized base\" pipeline that enforces spec-level uniformity:\n * - if imageCrop exists -> always crop to outputWidth/outputHeight\n * - else if requiredWidth/requiredHeight exist -> crop/resize to those\n * - else if requiredAspectRatio exists -> center-crop to that aspect (no scaling unless needed later)\n *\n * If the source violates constraints and we cannot/should not auto-fix, returns a rejection error.\n */\nasync function buildBase(\n spec: MediaProcessingSpec,\n inputPath: string\n): Promise<\n | { ok: true; base: Sharp; inputMeta: Metadata }\n | { ok: false; error: MediaProcessingError }\n> {\n const input = sharp(inputPath, { failOn: \"none\" });\n const meta = await input.metadata();\n\n if (!meta.width || !meta.height || meta.width <= 0 || meta.height <= 0) {\n return {\n ok: false,\n error: {\n code: \"processing_failed\",\n message: \"Unable to read valid image dimensions. File may be corrupt.\",\n details: { width: meta.width, height: meta.height, format: meta.format, path: inputPath },\n },\n };\n }\n \n if (!meta.format) {\n return {\n ok: false,\n error: {\n code: \"unsupported_format\",\n message: \"Unable to determine image format. File may be corrupt.\",\n details: { path: inputPath },\n },\n };\n }\n\n if (!acceptAllowsImage(spec)) {\n return {\n ok: false,\n error: {\n code: \"invalid_mime\",\n message: \"Spec does not allow image uploads.\",\n details: { accept: spec.accept },\n },\n };\n }\n \n const detectedMime = mimeForImageFormat(meta.format);\n if (!acceptAllowsMime(spec, detectedMime)) {\n return {\n ok: false,\n error: {\n code: \"invalid_mime\",\n message: \"Image mime type is not allowed.\",\n details: { detectedMime, accept: spec.accept },\n },\n };\n }\n\n const w = meta.width ?? undefined;\n const h = meta.height ?? undefined;\n\n if (!w || !h) {\n // Can't validate/crop reliably without dimensions.\n // We'll still proceed unless strict dims/aspect are required without crop/auto.\n if (spec.requiredWidth || spec.requiredHeight || spec.requiredAspectRatio) {\n if (!spec.allowAutoFormat && !spec.imageCrop) {\n return {\n ok: false,\n error: {\n code: \"dimensions_mismatch\",\n message: \"Unable to read image dimensions for validation.\",\n details: { width: w, height: h },\n },\n };\n }\n }\n }\n\n // Start from raw input each time; callers can clone as needed.\n let base = sharp(inputPath);\n\n // Keep metadata only if explicitly requested.\n if (spec.image?.stripMetadata === false) {\n base = base.withMetadata({ orientation: meta.orientation });\n }\n\n // 1) Explicit crop spec (most strict)\n const crop = spec.imageCrop;\n if (crop) {\n base = resizeCover(base, crop.outputWidth, crop.outputHeight);\n return { ok: true, base, inputMeta: meta };\n }\n\n // 2) Exact required size -> auto crop/resize only if allowAutoFormat OR the request is inherently \"uniform\"\n const reqW = spec.requiredWidth;\n const reqH = spec.requiredHeight;\n if (reqW && reqH) {\n const canFix = spec.allowAutoFormat === true;\n // If not allowed to auto-fix, require exact match\n if (!canFix && w && h && (w !== reqW || h !== reqH)) {\n return {\n ok: false,\n error: {\n code: \"dimensions_mismatch\",\n message: \"Image dimensions do not match the required format.\",\n details: { requiredWidth: reqW, requiredHeight: reqH, width: w, height: h },\n },\n };\n }\n\n // Auto-fix path (or exact already): enforce normalized base\n base = resizeCover(base, reqW, reqH);\n\n return { ok: true, base, inputMeta: meta };\n }\n\n // 3) Aspect ratio requirement (no exact size)\n const reqAspect = spec.requiredAspectRatio;\n if (reqAspect && w && h) {\n const a = aspect(w, h);\n const tol = spec.aspectRatioTolerance ?? 0.02;\n if (a && !aspectClose(a, reqAspect, tol)) {\n if (!spec.allowAutoFormat) {\n return {\n ok: false,\n error: {\n code: \"aspect_ratio_mismatch\",\n message: \"Image aspect ratio does not match the required format.\",\n details: { requiredAspectRatio: reqAspect, aspectRatio: a, width: w, height: h },\n },\n };\n }\n\n // Auto center-crop to required aspect ratio (no forced scale yet).\n base = base.extract(computeAspectCropBox(w, h, reqAspect));\n }\n }\n\n return { ok: true, base, inputMeta: meta };\n}\n\nexport async function processImage(\n spec: MediaProcessingSpec,\n ctx: {\n inputPath: string;\n outputBasePath: string;\n },\n opts?: ProcessMediaOptions\n): Promise<MediaProcessingResult> {\n const tempFiles: string[] = [];\n\n try {\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"image\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n const variants = spec.image?.variants?.length ? spec.image.variants : [{ key: \"original\" as const }];\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const baseRes = await buildBase(spec, ctx.inputPath);\n if (!baseRes.ok) {\n return { ok: false, mediaType: \"image\", error: baseRes.error };\n }\n\n const { base, inputMeta } = baseRes;\n\n const outputs: MediaOutput[] = [];\n const totalVariants = variants.length || 1;\n let idx = 0;\n\n for (const v of variants) {\n idx += 1;\n\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"image\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n opts?.onProgress?.({\n phase: \"process\",\n percent: (idx - 1) / totalVariants,\n detail: { step: \"image_variant\", key: v.key },\n });\n\n const { ext, format } = pickFormat(v.format);\n const outPath = outputPathFor(ctx.outputBasePath, v.key, ext);\n tempFiles.push(outPath);\n\n let p = base.clone();\n\n if (v.crop) {\n p = resizeCover(p, v.crop.width, v.crop.height, toSharpPosition((v.crop.gravity as Gravity) ?? \"center\"));\n } else if (v.maxWidth || v.maxHeight) {\n p = resizeInside(p, v.maxWidth, v.maxHeight);\n }\n\n p = applyImageFormat(p, format, v.quality);\n\n const info = await p.toFile(outPath);\n\n outputs.push({\n key: v.key,\n url: `file://${outPath}`,\n path: outPath,\n mime: mimeForImageFormat(info.format),\n sizeBytes: info.size,\n width: info.width,\n height: info.height,\n });\n }\n\n // success: keep outputs\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: \"image\",\n outputs,\n meta: {\n mime: mimeForImageFormat(inputMeta.format),\n width: inputMeta.width,\n height: inputMeta.height,\n },\n };\n } catch (e: any) {\n return {\n ok: false,\n mediaType: \"image\",\n error: {\n code: \"processing_failed\",\n message: e?.message ? String(e.message) : \"Image 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":"image-processor.js","sourceRoot":"","sources":["../../src/image/image-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,YAAY,EACZ,eAAe,GAEhB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE1E,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,CAAC,2BAA2B;IAChE,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,aAAa,CAAC,IAAY,EAAE,GAAW,EAAE,GAAW;IAC3D,OAAO,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC3C,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;;;;;;;GAOG;AACH,KAAK,UAAU,SAAS,CACtB,IAAyB,EACzB,SAAiB;IAKjB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;IAEpC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACvE,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE,6DAA6D;gBACtE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;aAC1F;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,wDAAwD;gBACjE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;aAC7B;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,oCAAoC;gBAC7C,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;aACjC;SACF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;QAC1C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,iCAAiC;gBAC1C,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;aAC/C;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,CAAC;IAClC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC;IAEnC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACb,mDAAmD;QACnD,gFAAgF;QAChF,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC1E,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC7C,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE;wBACL,IAAI,EAAE,qBAAqB;wBAC3B,OAAO,EAAE,iDAAiD;wBAC1D,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;qBACjC;iBACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,IAAI,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAE5B,8CAA8C;IAC9C,IAAI,IAAI,CAAC,KAAK,EAAE,aAAa,KAAK,KAAK,EAAE,CAAC;QACxC,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,sCAAsC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;IAC5B,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC9D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,4GAA4G;IAC5G,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC;IACjC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,KAAK,IAAI,CAAC;QAC7C,kDAAkD;QAClD,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACpD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE;oBACL,IAAI,EAAE,qBAAqB;oBAC3B,OAAO,EAAE,oDAAoD;oBAC7D,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;iBAC5E;aACF,CAAC;QACJ,CAAC;QAED,4DAA4D;QAC5D,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAErC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,8CAA8C;IAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC;IAC3C,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,oBAAoB,IAAI,IAAI,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC1B,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE;wBACL,IAAI,EAAE,uBAAuB;wBAC7B,OAAO,EAAE,wDAAwD;wBACjE,OAAO,EAAE,EAAE,mBAAmB,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;qBACjF;iBACF,CAAC;YACJ,CAAC;YAED,mEAAmE;YACnE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAyB,EACzB,GAGC,EACD,IAA0B;IAE1B,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,IAAI,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC1B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC;QACpH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAE1G,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,OAAO,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QACjE,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAEpC,MAAM,OAAO,GAAkB,EAAE,CAAC;QAClC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QAC3C,IAAI,GAAG,GAAG,CAAC,CAAC;QAEZ,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,GAAG,IAAI,CAAC,CAAC;YAET,IAAI,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;gBAC1B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,CAAC;YACpH,CAAC;YAED,IAAI,EAAE,UAAU,EAAE,CAAC;gBACjB,KAAK,EAAE,SAAS;gBAChB,OAAO,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,aAAa;gBAClC,MAAM,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;aAC9C,CAAC,CAAC;YAEH,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9D,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAExB,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAErB,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBACX,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAE,CAAC,CAAC,IAAI,CAAC,OAAmB,IAAI,QAAQ,CAAC,CAAC,CAAC;YAC5G,CAAC;iBAAM,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;gBACrC,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;YAC/C,CAAC;YAED,CAAC,GAAG,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;YAE3C,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAErC,OAAO,CAAC,IAAI,CAAC;gBACX,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,GAAG,EAAE,UAAU,OAAO,EAAE;gBACxB,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC;gBACrC,SAAS,EAAE,IAAI,CAAC,IAAI;gBACpB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAAC;QACL,CAAC;QAED,wBAAwB;QACxB,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QAErB,OAAO;YACL,EAAE,EAAE,IAAI;YACR,SAAS,EAAE,OAAO;YAClB,OAAO;YACP,IAAI,EAAE;gBACJ,IAAI,EAAE,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC;gBAC1C,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,MAAM,EAAE,SAAS,CAAC,MAAM;aACzB;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,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 { MediaOutput, MediaProcessingError, MediaProcessingResult, MediaProcessingSpec } from \"@ttt-productions/media-schemas\";\nimport sharp from \"sharp\";\nimport type { Sharp, Metadata } from \"sharp\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { safeOutputPathFor } from \"../utils/safe-path.js\";\nimport type { ProcessMediaOptions } from \"../types.js\";\nimport { applyImageFormat, mimeForImageFormat, pickFormat } from \"./formats.js\";\nimport {\n computeAspectCropBox,\n resizeCover,\n resizeInside,\n toSharpPosition,\n type Gravity,\n} from \"./resize.js\";\nimport { IMAGE_DEFAULT_OUTPUT_KEY } 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 acceptAllowsImage(spec: MediaProcessingSpec): boolean {\n const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];\n return kinds.length === 0 ? true : kinds.includes(\"image\");\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 outputPathFor(base: string, key: string, ext: string): string {\n return safeOutputPathFor(base, key, ext);\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\n/**\n * Build a \"normalized base\" pipeline that enforces spec-level uniformity:\n * - if imageCrop exists -> always crop to outputWidth/outputHeight\n * - else if requiredWidth/requiredHeight exist -> crop/resize to those\n * - else if requiredAspectRatio exists -> center-crop to that aspect (no scaling unless needed later)\n *\n * If the source violates constraints and we cannot/should not auto-fix, returns a rejection error.\n */\nasync function buildBase(\n spec: MediaProcessingSpec,\n inputPath: string\n): Promise<\n | { ok: true; base: Sharp; inputMeta: Metadata }\n | { ok: false; error: MediaProcessingError }\n> {\n const input = sharp(inputPath, { failOn: \"none\" });\n const meta = await input.metadata();\n\n if (!meta.width || !meta.height || meta.width <= 0 || meta.height <= 0) {\n return {\n ok: false,\n error: {\n code: \"processing_failed\",\n message: \"Unable to read valid image dimensions. File may be corrupt.\",\n details: { width: meta.width, height: meta.height, format: meta.format, path: inputPath },\n },\n };\n }\n \n if (!meta.format) {\n return {\n ok: false,\n error: {\n code: \"unsupported_format\",\n message: \"Unable to determine image format. File may be corrupt.\",\n details: { path: inputPath },\n },\n };\n }\n\n if (!acceptAllowsImage(spec)) {\n return {\n ok: false,\n error: {\n code: \"invalid_mime\",\n message: \"Spec does not allow image uploads.\",\n details: { accept: spec.accept },\n },\n };\n }\n \n const detectedMime = mimeForImageFormat(meta.format);\n if (!acceptAllowsMime(spec, detectedMime)) {\n return {\n ok: false,\n error: {\n code: \"invalid_mime\",\n message: \"Image mime type is not allowed.\",\n details: { detectedMime, accept: spec.accept },\n },\n };\n }\n\n const w = meta.width ?? undefined;\n const h = meta.height ?? undefined;\n\n if (!w || !h) {\n // Can't validate/crop reliably without dimensions.\n // We'll still proceed unless strict dims/aspect are required without crop/auto.\n if (spec.requiredWidth || spec.requiredHeight || spec.requiredAspectRatio) {\n if (!spec.allowAutoFormat && !spec.imageCrop) {\n return {\n ok: false,\n error: {\n code: \"dimensions_mismatch\",\n message: \"Unable to read image dimensions for validation.\",\n details: { width: w, height: h },\n },\n };\n }\n }\n }\n\n // Start from raw input each time; callers can clone as needed.\n let base = sharp(inputPath);\n\n // Keep metadata only if explicitly requested.\n if (spec.image?.stripMetadata === false) {\n base = base.withMetadata({ orientation: meta.orientation });\n }\n\n // 1) Explicit crop spec (most strict)\n const crop = spec.imageCrop;\n if (crop) {\n base = resizeCover(base, crop.outputWidth, crop.outputHeight);\n return { ok: true, base, inputMeta: meta };\n }\n\n // 2) Exact required size -> auto crop/resize only if allowAutoFormat OR the request is inherently \"uniform\"\n const reqW = spec.requiredWidth;\n const reqH = spec.requiredHeight;\n if (reqW && reqH) {\n const canFix = spec.allowAutoFormat === true;\n // If not allowed to auto-fix, require exact match\n if (!canFix && w && h && (w !== reqW || h !== reqH)) {\n return {\n ok: false,\n error: {\n code: \"dimensions_mismatch\",\n message: \"Image dimensions do not match the required format.\",\n details: { requiredWidth: reqW, requiredHeight: reqH, width: w, height: h },\n },\n };\n }\n\n // Auto-fix path (or exact already): enforce normalized base\n base = resizeCover(base, reqW, reqH);\n\n return { ok: true, base, inputMeta: meta };\n }\n\n // 3) Aspect ratio requirement (no exact size)\n const reqAspect = spec.requiredAspectRatio;\n if (reqAspect && w && h) {\n const a = aspect(w, h);\n const tol = spec.aspectRatioTolerance ?? 0.02;\n if (a && !aspectClose(a, reqAspect, tol)) {\n if (!spec.allowAutoFormat) {\n return {\n ok: false,\n error: {\n code: \"aspect_ratio_mismatch\",\n message: \"Image aspect ratio does not match the required format.\",\n details: { requiredAspectRatio: reqAspect, aspectRatio: a, width: w, height: h },\n },\n };\n }\n\n // Auto center-crop to required aspect ratio (no forced scale yet).\n base = base.extract(computeAspectCropBox(w, h, reqAspect));\n }\n }\n\n return { ok: true, base, inputMeta: meta };\n}\n\nexport async function processImage(\n spec: MediaProcessingSpec,\n ctx: {\n inputPath: string;\n outputBasePath: string;\n },\n opts?: ProcessMediaOptions\n): Promise<MediaProcessingResult> {\n const tempFiles: string[] = [];\n\n try {\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"image\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n const variants = spec.image?.variants?.length ? spec.image.variants : [{ key: IMAGE_DEFAULT_OUTPUT_KEY }];\n\n const outDir = path.dirname(ctx.outputBasePath);\n await mkdir(outDir, { recursive: true });\n\n const baseRes = await buildBase(spec, ctx.inputPath);\n if (!baseRes.ok) {\n return { ok: false, mediaType: \"image\", error: baseRes.error };\n }\n\n const { base, inputMeta } = baseRes;\n\n const outputs: MediaOutput[] = [];\n const totalVariants = variants.length || 1;\n let idx = 0;\n\n for (const v of variants) {\n idx += 1;\n\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"image\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n opts?.onProgress?.({\n phase: \"process\",\n percent: (idx - 1) / totalVariants,\n detail: { step: \"image_variant\", key: v.key },\n });\n\n const { ext, format } = pickFormat(v.format);\n const outPath = outputPathFor(ctx.outputBasePath, v.key, ext);\n tempFiles.push(outPath);\n\n let p = base.clone();\n\n if (v.crop) {\n p = resizeCover(p, v.crop.width, v.crop.height, toSharpPosition((v.crop.gravity as Gravity) ?? \"center\"));\n } else if (v.maxWidth || v.maxHeight) {\n p = resizeInside(p, v.maxWidth, v.maxHeight);\n }\n\n p = applyImageFormat(p, format, v.quality);\n\n const info = await p.toFile(outPath);\n\n outputs.push({\n key: v.key,\n url: `file://${outPath}`,\n path: outPath,\n mime: mimeForImageFormat(info.format),\n sizeBytes: info.size,\n width: info.width,\n height: info.height,\n });\n }\n\n // success: keep outputs\n tempFiles.length = 0;\n\n return {\n ok: true,\n mediaType: \"image\",\n outputs,\n meta: {\n mime: mimeForImageFormat(inputMeta.format),\n width: inputMeta.width,\n height: inputMeta.height,\n },\n };\n } catch (e: any) {\n return {\n ok: false,\n mediaType: \"image\",\n error: {\n code: \"processing_failed\",\n message: e?.message ? String(e.message) : \"Image 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/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./audio/audio-processor.js";
|
|
|
9
9
|
export * from "./io/types.js";
|
|
10
10
|
export * from "./run-pipeline.js";
|
|
11
11
|
export * from "./inspection/inspect-media.js";
|
|
12
|
+
export * from "./duration/media-duration.js";
|
|
12
13
|
export * from "./io/fs.js";
|
|
13
14
|
export * from "./io/local-input.js";
|
|
14
15
|
export * from "./moderation/types.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAE3C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAE3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAE3C,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,cAAc,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAE3C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAE3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAE3C,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AAEpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./audio/audio-processor.js";
|
|
|
9
9
|
export * from "./io/types.js";
|
|
10
10
|
export * from "./run-pipeline.js";
|
|
11
11
|
export * from "./inspection/inspect-media.js";
|
|
12
|
+
export * from "./duration/media-duration.js";
|
|
12
13
|
export * from "./io/fs.js";
|
|
13
14
|
export * from "./io/local-input.js";
|
|
14
15
|
export * from "./moderation/types.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC,CAAW,iBAAiB;AAC7D,cAAc,4BAA4B,CAAC;AAE3C,cAAc,kBAAkB,CAAC,CAAW,iBAAiB;AAC7D,cAAc,4BAA4B,CAAC;AAE3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC,CAAW,iBAAiB;AAC7D,cAAc,4BAA4B,CAAC;AAE3C,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,cAAc,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC,CAAW,iBAAiB;AAC7D,cAAc,4BAA4B,CAAC;AAE3C,cAAc,kBAAkB,CAAC,CAAW,iBAAiB;AAC7D,cAAc,4BAA4B,CAAC;AAE3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC,CAAW,iBAAiB;AAC7D,cAAc,4BAA4B,CAAC;AAE3C,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AAEpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC","sourcesContent":["export * from \"./image/index.js\"; // Not ./image.js\nexport * from \"./image/image-processor.js\";\n\nexport * from \"./video/index.js\"; // Not ./video.js\nexport * from \"./video/video-processor.js\";\n\nexport * from \"./process-media.js\";\nexport * from \"./workspace/temp.js\";\n\nexport * from \"./audio/index.js\"; // Not ./audio.js\nexport * from \"./audio/audio-processor.js\";\n\nexport * from \"./io/types.js\";\nexport * from \"./run-pipeline.js\";\n\nexport * from \"./inspection/inspect-media.js\";\nexport * from \"./duration/media-duration.js\";\n\nexport * from \"./io/fs.js\";\nexport * from \"./io/local-input.js\";\n\nexport * from \"./moderation/types.js\";\nexport * from \"./moderation/merge.js\";"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MediaInspectionResult, type MediaFormatId } from "@ttt-productions/media-schemas";
|
|
2
|
+
import { type MeasureDurationFn } from "../duration/media-duration.js";
|
|
2
3
|
/** Bump on any classification-rule change (rides every decision event). */
|
|
3
|
-
export declare const INSPECTOR_VERSION = "mpc-inspect-
|
|
4
|
+
export declare const INSPECTOR_VERSION = "mpc-inspect-2";
|
|
4
5
|
type SignatureFamily = {
|
|
5
6
|
family: "image";
|
|
6
7
|
formatId: MediaFormatId;
|
|
@@ -43,6 +44,9 @@ export interface InspectMediaDeps {
|
|
|
43
44
|
decodeImage?: (localPath: string) => Promise<{
|
|
44
45
|
frames: number;
|
|
45
46
|
} | null>;
|
|
47
|
+
/** Measure the file's length; default reads its packet timestamps
|
|
48
|
+
* (`measureDurationSec`). */
|
|
49
|
+
measureDuration?: MeasureDurationFn;
|
|
46
50
|
/** Injected ffprobe version (default: cached `ffprobe -version` first line). */
|
|
47
51
|
ffprobeVersion?: string;
|
|
48
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-media.d.ts","sourceRoot":"","sources":["../../src/inspection/inspect-media.ts"],"names":[],"mappings":"AA8BA,OAAO,EAEL,KAAK,qBAAqB,EAG1B,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"inspect-media.d.ts","sourceRoot":"","sources":["../../src/inspection/inspect-media.ts"],"names":[],"mappings":"AA8BA,OAAO,EAEL,KAAK,qBAAqB,EAG1B,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAE3F,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAwDjD,KAAK,eAAe,GAChB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/D;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAY1B,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,UAAU,GAAG,eAAe,CAiCpE;AAID,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CACrF;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,GAC3B;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAA;KAAE,CACzE,CAAC;IACF;sDACkD;IAClD,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxE;kCAC8B;IAC9B,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA0GD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA0HzF"}
|