@ttt-productions/media-processing-core 0.8.13 → 0.8.14
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 +3 -2
- package/dist/audio/audio-processor.js.map +1 -1
- 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/video/video-processor.d.ts.map +1 -1
- package/dist/video/video-processor.js +5 -4
- 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;AAM9G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,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;AAM9G,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,CAkIhC"}
|
|
@@ -3,6 +3,7 @@ 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 { TIMED_MEDIA_MAIN_OUTPUT_KEY } from "@ttt-productions/media-schemas";
|
|
6
7
|
function matchMime(accepted, actual) {
|
|
7
8
|
const a = accepted.trim().toLowerCase();
|
|
8
9
|
const m = actual.trim().toLowerCase();
|
|
@@ -77,7 +78,7 @@ export async function processAudio(spec, ctx, opts) {
|
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
// Normalize to AAC in m4a (small + widely supported)
|
|
80
|
-
const outPath = outputPathFor(ctx.outputBasePath,
|
|
81
|
+
const outPath = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, "m4a");
|
|
81
82
|
tempFiles.push(outPath);
|
|
82
83
|
const args = [
|
|
83
84
|
"-y",
|
|
@@ -112,7 +113,7 @@ export async function processAudio(spec, ctx, opts) {
|
|
|
112
113
|
const s = await stat(outPath);
|
|
113
114
|
const outputs = [
|
|
114
115
|
{
|
|
115
|
-
key:
|
|
116
|
+
key: TIMED_MEDIA_MAIN_OUTPUT_KEY,
|
|
116
117
|
url: `file://${outPath}`,
|
|
117
118
|
path: outPath,
|
|
118
119
|
mime: "audio/mp4",
|
|
@@ -1 +1 @@
|
|
|
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 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 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);\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, 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 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 +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":"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;AAErB,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,UAAmB,EAAE,CAAC,CAAC;QAErG,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\";\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":"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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\";\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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-processor.d.ts","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,mBAAmB,EAAoB,MAAM,gCAAgC,CAAC;AAMhI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"video-processor.d.ts","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,qBAAqB,EAAE,mBAAmB,EAAoB,MAAM,gCAAgC,CAAC;AAMhI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAuDvD,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,CAsQhC"}
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { ensureFfmpegAvailable, runFfmpeg } from "./ffmpeg.js";
|
|
4
4
|
import { probeVideo } from "./probe.js";
|
|
5
5
|
import { safeOutputPathFor } from "../utils/safe-path.js";
|
|
6
|
+
import { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from "@ttt-productions/media-schemas";
|
|
6
7
|
function matchMime(accepted, actual) {
|
|
7
8
|
const a = accepted.trim().toLowerCase();
|
|
8
9
|
const m = actual.trim().toLowerCase();
|
|
@@ -156,8 +157,8 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
156
157
|
};
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
|
-
const videoOut = outputPathFor(ctx.outputBasePath,
|
|
160
|
-
const posterOut = outputPathFor(ctx.outputBasePath,
|
|
160
|
+
const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, "mp4");
|
|
161
|
+
const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, "jpg");
|
|
161
162
|
tempFiles.push(videoOut, posterOut);
|
|
162
163
|
// Build a basic filter graph to enforce size/aspect when auto-format is enabled.
|
|
163
164
|
// - If exact WxH required: scale+crop to WxH
|
|
@@ -246,7 +247,7 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
246
247
|
catch { }
|
|
247
248
|
const outputs = [
|
|
248
249
|
{
|
|
249
|
-
key:
|
|
250
|
+
key: TIMED_MEDIA_MAIN_OUTPUT_KEY,
|
|
250
251
|
url: `file://${videoOut}`,
|
|
251
252
|
path: videoOut,
|
|
252
253
|
mime: mimeFromExt("mp4"),
|
|
@@ -263,7 +264,7 @@ export async function processVideo(spec, ctx, opts) {
|
|
|
263
264
|
warnings.push("poster_failed");
|
|
264
265
|
if (psSize > 0) {
|
|
265
266
|
outputs.push({
|
|
266
|
-
key:
|
|
267
|
+
key: VIDEO_POSTER_OUTPUT_KEY,
|
|
267
268
|
url: `file://${posterOut}`,
|
|
268
269
|
path: posterOut,
|
|
269
270
|
mime: "image/jpeg",
|
|
@@ -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 { MediaOutput, MediaProcessingResult, MediaProcessingSpec, VideoOrientation } 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 { safeOutputPathFor } from \"../utils/safe-path.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 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(required: VideoOrientation | undefined, actual: VideoOrientation | undefined): 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 { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n if (!acceptAllowsVideo(spec)) {\n return {\n ok: false,\n mediaType: \"video\",\n error: { code: \"invalid_mime\", message: \"Spec does not allow video uploads.\", details: { accept: spec.accept } },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: \"video\",\n error: { code: \"invalid_mime\", message: \"Video mime type is not allowed.\", details: { inputMime: ctx.inputMime, accept: spec.accept } },\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);\n\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n // duration enforcement\n const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;\n if (maxDur && probe.durationSec && 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 ? (actualAspect ? aspectClose(actualAspect, requiredAspect, tol) : false) : true;\n const okDims = !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: { requiredAspectRatio: requiredAspect, aspectRatio: actualAspect, width: probe.width, height: probe.height },\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: { requiredWidth: requiredW, requiredHeight: requiredH, width: probe.width, height: probe.height },\n },\n };\n }\n }\n\n const videoOut = outputPathFor(ctx.outputBasePath, \"main\", \"mp4\");\n const posterOut = outputPathFor(ctx.outputBasePath, \"poster\", \"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?.({ phase: \"process\", percent: p.percent, detail: { step: \"transcode\", outTimeMs: p.outTimeMs } });\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?.({ phase: \"process\", percent: p.percent, detail: { step: \"poster\", outTimeMs: p.outTimeMs } });\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: \"main\",\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: \"poster\",\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 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":"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","sourcesContent":["import type { MediaOutput, MediaProcessingResult, MediaProcessingSpec, VideoOrientation } 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 { safeOutputPathFor } from \"../utils/safe-path.js\";\nimport type { ProcessMediaOptions } from \"../types.js\";\nimport { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_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 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(required: VideoOrientation | undefined, actual: VideoOrientation | undefined): 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 { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n if (!acceptAllowsVideo(spec)) {\n return {\n ok: false,\n mediaType: \"video\",\n error: { code: \"invalid_mime\", message: \"Spec does not allow video uploads.\", details: { accept: spec.accept } },\n };\n }\n\n if (!acceptAllowsMime(spec, ctx.inputMime)) {\n return {\n ok: false,\n mediaType: \"video\",\n error: { code: \"invalid_mime\", message: \"Video mime type is not allowed.\", details: { inputMime: ctx.inputMime, accept: spec.accept } },\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);\n\n if (opts?.signal?.aborted) {\n return { ok: false, mediaType: \"video\", error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\n }\n\n // duration enforcement\n const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;\n if (maxDur && probe.durationSec && 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 ? (actualAspect ? aspectClose(actualAspect, requiredAspect, tol) : false) : true;\n const okDims = !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: { requiredAspectRatio: requiredAspect, aspectRatio: actualAspect, width: probe.width, height: probe.height },\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: { requiredWidth: requiredW, requiredHeight: requiredH, width: probe.width, height: probe.height },\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?.({ phase: \"process\", percent: p.percent, detail: { step: \"transcode\", outTimeMs: p.outTimeMs } });\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?.({ phase: \"process\", percent: p.percent, detail: { step: \"poster\", outTimeMs: p.outTimeMs } });\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 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.14",
|
|
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.8",
|
|
35
35
|
"aws4fetch": "^1.0.20",
|
|
36
36
|
"sharp": "^0.35.3",
|
|
37
37
|
"zod": "^4.4.3"
|