@ttt-productions/media-processing-core 0.8.17 → 0.8.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio/audio-processor.d.ts.map +1 -1
- package/dist/audio/audio-processor.js +1 -2
- package/dist/audio/audio-processor.js.map +1 -1
- package/dist/audio/probe.d.ts.map +1 -1
- package/dist/audio/probe.js +0 -2
- package/dist/audio/probe.js.map +1 -1
- package/dist/duration/media-duration.d.ts +0 -11
- package/dist/duration/media-duration.d.ts.map +1 -1
- package/dist/duration/media-duration.js +1 -28
- package/dist/duration/media-duration.js.map +1 -1
- package/dist/image/formats.d.ts +0 -11
- package/dist/image/formats.d.ts.map +1 -1
- package/dist/image/formats.js +0 -10
- package/dist/image/formats.js.map +1 -1
- package/dist/image/image-processor.d.ts.map +1 -1
- package/dist/image/image-processor.js +1 -20
- package/dist/image/image-processor.js.map +1 -1
- package/dist/image/resize.d.ts +0 -14
- package/dist/image/resize.d.ts.map +1 -1
- package/dist/image/resize.js +0 -15
- package/dist/image/resize.js.map +1 -1
- package/dist/inspection/inspect-media.d.ts +0 -7
- package/dist/inspection/inspect-media.d.ts.map +1 -1
- package/dist/inspection/inspect-media.js +4 -64
- package/dist/inspection/inspect-media.js.map +1 -1
- package/dist/io/fs.d.ts.map +1 -1
- package/dist/io/fs.js +0 -1
- package/dist/io/fs.js.map +1 -1
- package/dist/io/local-input.d.ts +0 -20
- package/dist/io/local-input.d.ts.map +1 -1
- package/dist/io/local-input.js +0 -20
- package/dist/io/local-input.js.map +1 -1
- package/dist/io/types.d.ts +0 -3
- package/dist/io/types.d.ts.map +1 -1
- package/dist/io/types.js.map +1 -1
- package/dist/moderation/merge.d.ts.map +1 -1
- package/dist/moderation/merge.js +0 -1
- package/dist/moderation/merge.js.map +1 -1
- package/dist/moderation/types.d.ts +0 -2
- package/dist/moderation/types.d.ts.map +1 -1
- package/dist/moderation/types.js.map +1 -1
- package/dist/run-pipeline.d.ts +0 -17
- package/dist/run-pipeline.d.ts.map +1 -1
- package/dist/run-pipeline.js +2 -27
- package/dist/run-pipeline.js.map +1 -1
- package/dist/server/download-token.d.ts +0 -6
- package/dist/server/download-token.d.ts.map +1 -1
- package/dist/server/download-token.js +0 -10
- package/dist/server/download-token.js.map +1 -1
- package/dist/server/firebase-media-io.d.ts +1 -11
- package/dist/server/firebase-media-io.d.ts.map +1 -1
- package/dist/server/firebase-media-io.js +0 -5
- package/dist/server/firebase-media-io.js.map +1 -1
- package/dist/server/gcs-errors.d.ts +0 -8
- package/dist/server/gcs-errors.d.ts.map +1 -1
- package/dist/server/gcs-errors.js +0 -8
- package/dist/server/gcs-errors.js.map +1 -1
- package/dist/server/pending-media.d.ts +1 -32
- package/dist/server/pending-media.d.ts.map +1 -1
- package/dist/server/pending-media.js +1 -24
- package/dist/server/pending-media.js.map +1 -1
- package/dist/server/r2-retry.d.ts +0 -48
- package/dist/server/r2-retry.d.ts.map +1 -1
- package/dist/server/r2-retry.js +4 -90
- package/dist/server/r2-retry.js.map +1 -1
- package/dist/server/staged-object.d.ts +0 -44
- package/dist/server/staged-object.d.ts.map +1 -1
- package/dist/server/staged-object.js +0 -44
- package/dist/server/staged-object.js.map +1 -1
- package/dist/server/storage-ops.d.ts +2 -62
- package/dist/server/storage-ops.d.ts.map +1 -1
- package/dist/server/storage-ops.js +9 -88
- package/dist/server/storage-ops.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/safe-path.d.ts.map +1 -1
- package/dist/utils/safe-path.js +1 -2
- package/dist/utils/safe-path.js.map +1 -1
- package/dist/video/ffmpeg.d.ts +0 -22
- package/dist/video/ffmpeg.d.ts.map +1 -1
- package/dist/video/ffmpeg.js +6 -21
- package/dist/video/ffmpeg.js.map +1 -1
- package/dist/video/probe.d.ts.map +1 -1
- package/dist/video/probe.js +0 -1
- package/dist/video/probe.js.map +1 -1
- package/dist/video/video-processor.d.ts.map +1 -1
- package/dist/video/video-processor.js +2 -15
- package/dist/video/video-processor.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type MediaInspectionResult, type MediaFormatId } from '@ttt-productions/media-schemas';
|
|
2
2
|
import { type MeasureDurationFn } from '../duration/media-duration.js';
|
|
3
|
-
/** Bump on any classification-rule change (rides every decision event). */
|
|
4
3
|
export declare const INSPECTOR_VERSION = "mpc-inspect-2";
|
|
5
4
|
type SignatureFamily = {
|
|
6
5
|
family: 'image';
|
|
@@ -28,7 +27,6 @@ type SignatureFamily = {
|
|
|
28
27
|
};
|
|
29
28
|
export declare function detectSignatureFamily(b: Uint8Array): SignatureFamily;
|
|
30
29
|
export interface InspectMediaDeps {
|
|
31
|
-
/** Probe the local file; default shells to the hardened `ffprobe`. */
|
|
32
30
|
probe?: (localPath: string, opts: {
|
|
33
31
|
timeoutMs: number;
|
|
34
32
|
signal?: AbortSignal;
|
|
@@ -39,15 +37,10 @@ export interface InspectMediaDeps {
|
|
|
39
37
|
ok: false;
|
|
40
38
|
reason: 'timeout' | 'truncated' | 'failed' | 'bad_json';
|
|
41
39
|
}>;
|
|
42
|
-
/** Decode-prove an image; default uses sharp metadata. Returns frame count
|
|
43
|
-
* (pages) on success, null on decode failure. */
|
|
44
40
|
decodeImage?: (localPath: string) => Promise<{
|
|
45
41
|
frames: number;
|
|
46
42
|
} | null>;
|
|
47
|
-
/** Measure the file's length; default reads its packet timestamps
|
|
48
|
-
* (`measureDurationSec`). */
|
|
49
43
|
measureDuration?: MeasureDurationFn;
|
|
50
|
-
/** Injected ffprobe version (default: cached `ffprobe -version` first line). */
|
|
51
44
|
ffprobeVersion?: string;
|
|
52
45
|
}
|
|
53
46
|
export interface InspectMediaArgs {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-media.d.ts","sourceRoot":"","sources":["../../src/inspection/inspect-media.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"inspect-media.d.ts","sourceRoot":"","sources":["../../src/inspection/inspect-media.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,qBAAqB,EAG1B,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAE3F,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAqDjD,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;AAqB1B,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,UAAU,GAAG,eAAe,CAyCpE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,CACN,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAC9C,OAAO,CACR;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,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxE,eAAe,CAAC,EAAE,iBAAiB,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAiHD,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,CAuIzF"}
|
|
@@ -1,41 +1,12 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// Canonical media content inspection
|
|
3
|
-
// =============================================================================
|
|
4
|
-
//
|
|
5
|
-
// The ONE server-side classification authority. Consumes a LOCAL file (the
|
|
6
|
-
// pipeline's generation-pinned temp download), establishes what the bytes
|
|
7
|
-
// actually contain, and returns the bounded `MediaInspectionResult` contract
|
|
8
|
-
// from @ttt-productions/media-schemas. Everything downstream — origin/kind
|
|
9
|
-
// validation, processing-spec selection, the persisted asset kind, and the
|
|
10
|
-
// safety (PhotoDNA) routing — derives from this one result. Client MIME,
|
|
11
|
-
// filename, extension, container brand, and payload-byte heuristics are never
|
|
12
|
-
// consulted.
|
|
13
|
-
//
|
|
14
|
-
// Design:
|
|
15
|
-
// - family selection by a bounded 64 KiB SIGNATURE read (header only —
|
|
16
|
-
// semantic elements are NEVER searched for in arbitrary payload: payload
|
|
17
|
-
// bytes can mimic container elements, so a flat scan misclassifies WebM
|
|
18
|
-
// audio as video);
|
|
19
|
-
// - shared A/V containers (EBML/ISO-BMFF/Ogg/AVI) classified by a hardened
|
|
20
|
-
// `ffprobe` stream table (libavformat's structural parsers), with timeout,
|
|
21
|
-
// kill-on-abort, output cap, and strict schema validation;
|
|
22
|
-
// - image-family proof by DECODER (sharp) — a signature or `ftyp` brand is a
|
|
23
|
-
// candidate, not proof, and image-family ISO-BMFF (AVIF/HEIC) is resolved
|
|
24
|
-
// BEFORE the ordinary "any timed video stream wins" rule (a valid AVIF
|
|
25
|
-
// probes as a one-frame AV1 `video` stream);
|
|
26
|
-
// - FAIL-CLOSED: anything unproven is `indeterminate` with the
|
|
27
|
-
// `strict-video-fallback` safety plan — never audio, never publishable.
|
|
28
1
|
import { open } from 'node:fs/promises';
|
|
29
2
|
import { z } from 'zod';
|
|
30
3
|
import { MediaInspectionResultSchema, } from '@ttt-productions/media-schemas';
|
|
31
4
|
import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from '../video/ffmpeg.js';
|
|
32
5
|
import { measureDurationSec } from '../duration/media-duration.js';
|
|
33
|
-
/** Bump on any classification-rule change (rides every decision event). */
|
|
34
6
|
export const INSPECTOR_VERSION = 'mpc-inspect-2';
|
|
35
7
|
const SIGNATURE_READ_BYTES = 64 * 1024;
|
|
36
|
-
const PROBE_SIZE_BYTES = 4 * 1024 * 1024;
|
|
8
|
+
const PROBE_SIZE_BYTES = 4 * 1024 * 1024;
|
|
37
9
|
const ANALYZE_DURATION_US = 10 * 1000 * 1000;
|
|
38
|
-
// ---- ffprobe JSON boundary (strict; anything else is malformed) ----
|
|
39
10
|
const ProbeStreamSchema = z
|
|
40
11
|
.object({
|
|
41
12
|
codec_type: z.string().max(32).optional(),
|
|
@@ -55,7 +26,6 @@ const ProbeJsonSchema = z
|
|
|
55
26
|
streams: z.array(ProbeStreamSchema).max(256).optional(),
|
|
56
27
|
})
|
|
57
28
|
.loose();
|
|
58
|
-
// ---- codec normalization (bounded — raw strings never escape) ----
|
|
59
29
|
const CODEC_MAP = {
|
|
60
30
|
h264: 'h264',
|
|
61
31
|
hevc: 'hevc',
|
|
@@ -84,8 +54,6 @@ function normalizeCodec(name) {
|
|
|
84
54
|
return 'amr';
|
|
85
55
|
return 'other';
|
|
86
56
|
}
|
|
87
|
-
/** ISO-BMFF brands that declare an IMAGE-item file (HEIF family). Resolved via
|
|
88
|
-
* decoder proof BEFORE the timed-video rule — see the AVIF note above. */
|
|
89
57
|
const ISOBMFF_IMAGE_BRANDS = new Set([
|
|
90
58
|
'avif',
|
|
91
59
|
'avis',
|
|
@@ -109,7 +77,6 @@ function ascii(b, off, s) {
|
|
|
109
77
|
export function detectSignatureFamily(b) {
|
|
110
78
|
if (b.length < 12)
|
|
111
79
|
return { family: 'unknown' };
|
|
112
|
-
// images (single-format signatures)
|
|
113
80
|
if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
|
|
114
81
|
return { family: 'image', formatId: 'jpeg', container: 'jpeg' };
|
|
115
82
|
if (b[0] === 0x89 && ascii(b, 1, 'PNG'))
|
|
@@ -124,7 +91,6 @@ export function detectSignatureFamily(b) {
|
|
|
124
91
|
(b[0] === 0x4d && b[1] === 0x4d && b[3] === 0x2a)) {
|
|
125
92
|
return { family: 'image', formatId: 'tiff', container: 'tiff' };
|
|
126
93
|
}
|
|
127
|
-
// SVG: XML text — scan a short prefix for '<svg' (bounded; case-insensitive).
|
|
128
94
|
{
|
|
129
95
|
const prefix = Buffer.from(b.slice(0, Math.min(b.length, 4096)))
|
|
130
96
|
.toString('latin1')
|
|
@@ -136,7 +102,6 @@ export function detectSignatureFamily(b) {
|
|
|
136
102
|
return { family: 'svg' };
|
|
137
103
|
}
|
|
138
104
|
}
|
|
139
|
-
// containers
|
|
140
105
|
if (b[0] === 0x1a && b[1] === 0x45 && b[2] === 0xdf && b[3] === 0xa3)
|
|
141
106
|
return { family: 'ebml' };
|
|
142
107
|
if (ascii(b, 4, 'ftyp')) {
|
|
@@ -189,7 +154,7 @@ async function defaultProbe(localPath, opts) {
|
|
|
189
154
|
], { timeoutMs: opts.timeoutMs, signal: opts.signal });
|
|
190
155
|
}
|
|
191
156
|
catch {
|
|
192
|
-
return { ok: false, reason: 'failed' };
|
|
157
|
+
return { ok: false, reason: 'failed' };
|
|
193
158
|
}
|
|
194
159
|
if (r.timedOut)
|
|
195
160
|
return { ok: false, reason: 'timeout' };
|
|
@@ -208,7 +173,7 @@ async function defaultDecodeImage(localPath) {
|
|
|
208
173
|
try {
|
|
209
174
|
const sharp = (await import('sharp')).default;
|
|
210
175
|
const meta = await sharp(localPath, {
|
|
211
|
-
limitInputPixels: 268402689
|
|
176
|
+
limitInputPixels: 268402689,
|
|
212
177
|
}).metadata();
|
|
213
178
|
if (!meta.width || !meta.height)
|
|
214
179
|
return null;
|
|
@@ -218,7 +183,6 @@ async function defaultDecodeImage(localPath) {
|
|
|
218
183
|
return null;
|
|
219
184
|
}
|
|
220
185
|
}
|
|
221
|
-
// ---- result builders ----
|
|
222
186
|
function baseResult(partial, ffprobeVersion) {
|
|
223
187
|
const result = {
|
|
224
188
|
inspectorVersion: INSPECTOR_VERSION,
|
|
@@ -255,7 +219,6 @@ export async function inspectMedia(args) {
|
|
|
255
219
|
const probe = args.deps?.probe ?? defaultProbe;
|
|
256
220
|
const decodeImage = args.deps?.decodeImage ?? defaultDecodeImage;
|
|
257
221
|
const ffprobeVersion = args.deps?.ffprobeVersion ?? (await getFfprobeVersion());
|
|
258
|
-
// 1) bounded signature read (header only)
|
|
259
222
|
let head;
|
|
260
223
|
try {
|
|
261
224
|
const fh = await open(args.localPath, 'r');
|
|
@@ -272,14 +235,10 @@ export async function inspectMedia(args) {
|
|
|
272
235
|
return indeterminate('unknown', 'read_failed', ffprobeVersion);
|
|
273
236
|
}
|
|
274
237
|
const sig = detectSignatureFamily(head);
|
|
275
|
-
// 2) family routing
|
|
276
238
|
switch (sig.family) {
|
|
277
239
|
case 'unknown':
|
|
278
240
|
return indeterminate('unknown', 'unrecognized_signature', ffprobeVersion);
|
|
279
241
|
case 'svg':
|
|
280
|
-
// Scriptable XML "image" — structurally recognized, deliberately NOT a
|
|
281
|
-
// processable format: it can carry script and no sanitize/rasterize step
|
|
282
|
-
// exists. Never classified by decoder capability alone.
|
|
283
242
|
return baseResult({
|
|
284
243
|
status: 'unsupported',
|
|
285
244
|
container: 'svg',
|
|
@@ -288,8 +247,6 @@ export async function inspectMedia(args) {
|
|
|
288
247
|
reasonCode: 'unsupported_format:svg',
|
|
289
248
|
}, ffprobeVersion);
|
|
290
249
|
case 'image': {
|
|
291
|
-
// Single-format image signature: decoder proof required — a signature is
|
|
292
|
-
// a candidate, not a classification.
|
|
293
250
|
const decoded = await decodeImage(args.localPath);
|
|
294
251
|
if (!decoded)
|
|
295
252
|
return baseResult({
|
|
@@ -318,19 +275,14 @@ export async function inspectMedia(args) {
|
|
|
318
275
|
}
|
|
319
276
|
case 'isobmff':
|
|
320
277
|
if (ISOBMFF_IMAGE_BRANDS.has(sig.brand)) {
|
|
321
|
-
// IMAGE-FAMILY ISO-BMFF resolves BEFORE the timed-video rule: a valid
|
|
322
|
-
// AVIF probes as a one-frame AV1 `video` stream. Proof = image brand +
|
|
323
|
-
// successful bounded decode + no ordinary timed A/V presentation.
|
|
324
278
|
const probed = await probe(args.localPath, { timeoutMs, signal: args.signal });
|
|
325
279
|
const parsed = probed.ok ? ProbeJsonSchema.safeParse(probed.json) : null;
|
|
326
280
|
const streams = parsed?.success ? (parsed.data.streams ?? []) : null;
|
|
327
|
-
const hasAudio = streams ? streams.some((s) => s.codec_type === 'audio') : true;
|
|
281
|
+
const hasAudio = streams ? streams.some((s) => s.codec_type === 'audio') : true;
|
|
328
282
|
const formatId = sig.brand.startsWith('avi') ? 'avif' : 'heic';
|
|
329
283
|
const decoded = await decodeImage(args.localPath);
|
|
330
284
|
if (!decoded || hasAudio) {
|
|
331
285
|
if (formatId === 'heic' && !decoded && !hasAudio) {
|
|
332
|
-
// Recognized HEIC-family that the runtime cannot decode: explicit
|
|
333
|
-
// unsupported-image (launch policy) — NEVER relabeled video.
|
|
334
286
|
return baseResult({
|
|
335
287
|
status: 'unsupported',
|
|
336
288
|
container: 'heif',
|
|
@@ -339,9 +291,6 @@ export async function inspectMedia(args) {
|
|
|
339
291
|
reasonCode: 'unsupported_format:heic',
|
|
340
292
|
}, ffprobeVersion);
|
|
341
293
|
}
|
|
342
|
-
// Undecodable/ambiguous image-brand file, or one carrying audio (a
|
|
343
|
-
// real timed presentation wearing an image brand): fail toward the
|
|
344
|
-
// stricter visual path.
|
|
345
294
|
return indeterminate('heif', decoded ? 'image_brand_with_audio' : 'image_decode_failed', ffprobeVersion, formatId);
|
|
346
295
|
}
|
|
347
296
|
const frames = decoded.frames;
|
|
@@ -367,8 +316,6 @@ export async function inspectMedia(args) {
|
|
|
367
316
|
case 'ogg':
|
|
368
317
|
return classifyAvContainer(args, 'ogg', 'ogg', probe, timeoutMs, ffprobeVersion);
|
|
369
318
|
case 'avi':
|
|
370
|
-
// Real container ffprobe parses; no registry entry (no origin enables it
|
|
371
|
-
// today) — the stream truth still classifies it, policy rejects by format.
|
|
372
319
|
return classifyAvContainer(args, 'avi', undefined, probe, timeoutMs, ffprobeVersion);
|
|
373
320
|
case 'wav':
|
|
374
321
|
return classifyAvContainer(args, 'wav', 'wav', probe, timeoutMs, ffprobeVersion);
|
|
@@ -406,10 +353,6 @@ async function classifyAvContainer(args, container, formatId, probe, timeoutMs,
|
|
|
406
353
|
audioCodecs.add(normalizeCodec(s.codec_name));
|
|
407
354
|
}
|
|
408
355
|
else if (type === 'video') {
|
|
409
|
-
// An attached picture (cover art) is NOT a timed presentation — but the
|
|
410
|
-
// disposition flag is untrusted container data, so it is COUNTED and the
|
|
411
|
-
// safety plan forces every such picture through the image hash path
|
|
412
|
-
// before the audio guard may return clean.
|
|
413
356
|
if (s.disposition?.attached_pic === 1) {
|
|
414
357
|
counts.attachedPictures++;
|
|
415
358
|
}
|
|
@@ -430,7 +373,6 @@ async function classifyAvContainer(args, container, formatId, probe, timeoutMs,
|
|
|
430
373
|
const measure = args.deps?.measureDuration ?? measureDurationSec;
|
|
431
374
|
const durationSec = await measure(args.localPath, { signal: args.signal });
|
|
432
375
|
const duration = durationSec !== undefined ? { durationSec } : {};
|
|
433
|
-
// classification table (fail-closed)
|
|
434
376
|
if (counts.timedVideo > 0) {
|
|
435
377
|
return baseResult({
|
|
436
378
|
status: 'definitive',
|
|
@@ -457,8 +399,6 @@ async function classifyAvContainer(args, container, formatId, probe, timeoutMs,
|
|
|
457
399
|
reasonCode: 'ok',
|
|
458
400
|
}, ffprobeVersion);
|
|
459
401
|
}
|
|
460
|
-
// auxiliary-only, unknown stream types alongside audio, or nothing usable:
|
|
461
|
-
// never audio — the strict visual fallback owns it.
|
|
462
402
|
return baseResult({
|
|
463
403
|
status: 'indeterminate',
|
|
464
404
|
container,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect-media.js","sourceRoot":"","sources":["../../src/inspection/inspect-media.ts"],"names":[],"mappings":"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","sourcesContent":["// =============================================================================\n// Canonical media content inspection\n// =============================================================================\n//\n// The ONE server-side classification authority. Consumes a LOCAL file (the\n// pipeline's generation-pinned temp download), establishes what the bytes\n// actually contain, and returns the bounded `MediaInspectionResult` contract\n// from @ttt-productions/media-schemas. Everything downstream — origin/kind\n// validation, processing-spec selection, the persisted asset kind, and the\n// safety (PhotoDNA) routing — derives from this one result. Client MIME,\n// filename, extension, container brand, and payload-byte heuristics are never\n// consulted.\n//\n// Design:\n// - family selection by a bounded 64 KiB SIGNATURE read (header only —\n// semantic elements are NEVER searched for in arbitrary payload: payload\n// bytes can mimic container elements, so a flat scan misclassifies WebM\n// audio as video);\n// - shared A/V containers (EBML/ISO-BMFF/Ogg/AVI) classified by a hardened\n// `ffprobe` stream table (libavformat's structural parsers), with timeout,\n// kill-on-abort, output cap, and strict schema validation;\n// - image-family proof by DECODER (sharp) — a signature or `ftyp` brand is a\n// candidate, not proof, and image-family ISO-BMFF (AVIF/HEIC) is resolved\n// BEFORE the ordinary \"any timed video stream wins\" rule (a valid AVIF\n// probes as a one-frame AV1 `video` stream);\n// - FAIL-CLOSED: anything unproven is `indeterminate` with the\n// `strict-video-fallback` safety plan — never audio, never publishable.\n\nimport { open } from 'node:fs/promises';\nimport { z } from 'zod';\nimport {\n MediaInspectionResultSchema,\n type MediaInspectionResult,\n type MediaInspectionStreams,\n type NormalizedCodecId,\n type MediaFormatId,\n} from '@ttt-productions/media-schemas';\nimport { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from '../video/ffmpeg.js';\nimport { measureDurationSec, type MeasureDurationFn } from '../duration/media-duration.js';\n\n/** Bump on any classification-rule change (rides every decision event). */\nexport const INSPECTOR_VERSION = 'mpc-inspect-2';\n\nconst SIGNATURE_READ_BYTES = 64 * 1024;\nconst PROBE_SIZE_BYTES = 4 * 1024 * 1024; // -probesize: metadata, not playback\nconst ANALYZE_DURATION_US = 10 * 1000 * 1000;\n\n// ---- ffprobe JSON boundary (strict; anything else is malformed) ----\n\nconst ProbeStreamSchema = z\n .object({\n codec_type: z.string().max(32).optional(),\n codec_name: z.string().max(64).optional(),\n disposition: z\n .object({ attached_pic: z.number().int().min(0).max(1).optional() })\n .loose()\n .optional(),\n })\n .loose();\n\nconst ProbeJsonSchema = z\n .object({\n format: z\n .object({ format_name: z.string().max(200).optional() })\n .loose()\n .optional(),\n streams: z.array(ProbeStreamSchema).max(256).optional(),\n })\n .loose();\n\n// ---- codec normalization (bounded — raw strings never escape) ----\n\nconst CODEC_MAP: Record<string, NormalizedCodecId> = {\n h264: 'h264',\n hevc: 'hevc',\n vp8: 'vp8',\n vp9: 'vp9',\n av1: 'av1',\n theora: 'theora',\n mpeg4: 'mpeg4',\n mjpeg: 'mjpeg',\n aac: 'aac',\n alac: 'alac',\n opus: 'opus',\n vorbis: 'vorbis',\n mp3: 'mp3',\n flac: 'flac',\n};\n\nfunction normalizeCodec(name: string | undefined): NormalizedCodecId {\n if (!name) return 'other';\n const n = name.toLowerCase();\n if (CODEC_MAP[n]) return CODEC_MAP[n];\n if (n.startsWith('pcm_')) return 'pcm';\n if (n.startsWith('amr')) return 'amr';\n return 'other';\n}\n\n// ---- signature families ----\n\ntype SignatureFamily =\n | { family: 'image'; formatId: MediaFormatId; container: string }\n | { family: 'svg' }\n | { family: 'ebml' }\n | { family: 'isobmff'; brand: string }\n | { family: 'ogg' }\n | { family: 'avi' }\n | { family: 'wav' }\n | { family: 'mp3' }\n | { family: 'flac' }\n | { family: 'unknown' };\n\n/** ISO-BMFF brands that declare an IMAGE-item file (HEIF family). Resolved via\n * decoder proof BEFORE the timed-video rule — see the AVIF note above. */\nconst ISOBMFF_IMAGE_BRANDS = new Set([\n 'avif',\n 'avis',\n 'heic',\n 'heix',\n 'heim',\n 'heis',\n 'hevc',\n 'hevx',\n 'mif1',\n 'msf1',\n]);\n\nfunction ascii(b: Uint8Array, off: number, s: string): boolean {\n if (off + s.length > b.length) return false;\n for (let i = 0; i < s.length; i++) if (b[off + i] !== s.charCodeAt(i)) return false;\n return true;\n}\n\nexport function detectSignatureFamily(b: Uint8Array): SignatureFamily {\n if (b.length < 12) return { family: 'unknown' };\n // images (single-format signatures)\n if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)\n return { family: 'image', formatId: 'jpeg', container: 'jpeg' };\n if (b[0] === 0x89 && ascii(b, 1, 'PNG'))\n return { family: 'image', formatId: 'png', container: 'png' };\n if (ascii(b, 0, 'GIF8')) return { family: 'image', formatId: 'gif', container: 'gif' };\n if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'WEBP'))\n return { family: 'image', formatId: 'webp', container: 'webp' };\n if (b[0] === 0x42 && b[1] === 0x4d) return { family: 'image', formatId: 'bmp', container: 'bmp' };\n if (\n (b[0] === 0x49 && b[1] === 0x49 && b[2] === 0x2a) ||\n (b[0] === 0x4d && b[1] === 0x4d && b[3] === 0x2a)\n ) {\n return { family: 'image', formatId: 'tiff', container: 'tiff' };\n }\n // SVG: XML text — scan a short prefix for '<svg' (bounded; case-insensitive).\n {\n const prefix = Buffer.from(b.slice(0, Math.min(b.length, 4096)))\n .toString('latin1')\n .toLowerCase();\n if (\n prefix.includes('<svg') &&\n (prefix.trimStart().startsWith('<?xml') ||\n prefix.trimStart().startsWith('<svg') ||\n prefix.includes('<!doctype svg'))\n ) {\n return { family: 'svg' };\n }\n }\n // containers\n if (b[0] === 0x1a && b[1] === 0x45 && b[2] === 0xdf && b[3] === 0xa3) return { family: 'ebml' };\n if (ascii(b, 4, 'ftyp')) {\n const brand = String.fromCharCode(b[8], b[9], b[10], b[11]).trim().toLowerCase();\n return { family: 'isobmff', brand };\n }\n if (ascii(b, 0, 'OggS')) return { family: 'ogg' };\n if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'AVI ')) return { family: 'avi' };\n if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'WAVE')) return { family: 'wav' };\n if (ascii(b, 0, 'ID3')) return { family: 'mp3' };\n if (b[0] === 0xff && (b[1] & 0xe0) === 0xe0) return { family: 'mp3' };\n if (ascii(b, 0, 'fLaC')) return { family: 'flac' };\n return { family: 'unknown' };\n}\n\n// ---- dependency seams (tests inject; production uses the real tools) ----\n\nexport interface InspectMediaDeps {\n /** Probe the local file; default shells to the hardened `ffprobe`. */\n probe?: (\n localPath: string,\n opts: { timeoutMs: number; signal?: AbortSignal },\n ) => Promise<\n | { ok: true; json: unknown }\n | { ok: false; reason: 'timeout' | 'truncated' | 'failed' | 'bad_json' }\n >;\n /** Decode-prove an image; default uses sharp metadata. Returns frame count\n * (pages) on success, null on decode failure. */\n decodeImage?: (localPath: string) => Promise<{ frames: number } | null>;\n /** Measure the file's length; default reads its packet timestamps\n * (`measureDurationSec`). */\n measureDuration?: MeasureDurationFn;\n /** Injected ffprobe version (default: cached `ffprobe -version` first line). */\n ffprobeVersion?: string;\n}\n\nlet cachedFfprobeVersion: string | null | undefined;\n\nasync function getFfprobeVersion(): Promise<string | undefined> {\n if (cachedFfprobeVersion !== undefined) return cachedFfprobeVersion ?? undefined;\n try {\n const r = await runCmd('ffprobe', ['-version'], { timeoutMs: 5_000 });\n const first = (r.stdout || r.stderr).split(/\\r?\\n/)[0]?.trim() ?? '';\n cachedFfprobeVersion = first ? first.slice(0, 120) : null;\n } catch {\n cachedFfprobeVersion = null;\n }\n return cachedFfprobeVersion ?? undefined;\n}\n\nasync function defaultProbe(\n localPath: string,\n opts: { timeoutMs: number; signal?: AbortSignal },\n): Promise<\n | { ok: true; json: unknown }\n | { ok: false; reason: 'timeout' | 'truncated' | 'failed' | 'bad_json' }\n> {\n let r;\n try {\n r = await runCmd(\n 'ffprobe',\n [\n '-v',\n 'error',\n '-print_format',\n 'json',\n '-show_format',\n '-show_streams',\n '-probesize',\n String(PROBE_SIZE_BYTES),\n '-analyzeduration',\n String(ANALYZE_DURATION_US),\n localPath,\n ],\n { timeoutMs: opts.timeoutMs, signal: opts.signal },\n );\n } catch {\n return { ok: false, reason: 'failed' }; // spawn failure (ffprobe missing) — fail closed\n }\n if (r.timedOut) return { ok: false, reason: 'timeout' };\n if (r.truncated) return { ok: false, reason: 'truncated' };\n if (r.code !== 0) return { ok: false, reason: 'failed' };\n try {\n return { ok: true, json: JSON.parse(r.stdout || '{}') };\n } catch {\n return { ok: false, reason: 'bad_json' };\n }\n}\n\nasync function defaultDecodeImage(localPath: string): Promise<{ frames: number } | null> {\n try {\n const sharp = (await import('sharp')).default;\n const meta = await sharp(localPath, {\n limitInputPixels: 268402689 /* sharp default ~16k x 16k */,\n }).metadata();\n if (!meta.width || !meta.height) return null;\n return { frames: Math.max(1, meta.pages ?? 1) };\n } catch {\n return null;\n }\n}\n\n// ---- result builders ----\n\nfunction baseResult(\n partial: Omit<MediaInspectionResult, 'inspectorVersion' | 'streams' | 'codecs'> &\n Partial<Pick<MediaInspectionResult, 'streams' | 'codecs'>>,\n ffprobeVersion: string | undefined,\n): MediaInspectionResult {\n const result: MediaInspectionResult = {\n inspectorVersion: INSPECTOR_VERSION,\n ...(ffprobeVersion ? { ffprobeVersion } : {}),\n streams: partial.streams ?? {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: 0,\n auxiliary: 0,\n },\n codecs: partial.codecs ?? { audio: [], video: [] },\n status: partial.status,\n container: partial.container,\n safetyPlan: partial.safetyPlan,\n reasonCode: partial.reasonCode,\n ...(partial.canonicalKind ? { canonicalKind: partial.canonicalKind } : {}),\n ...(partial.formatId ? { formatId: partial.formatId } : {}),\n ...(partial.durationSec !== undefined ? { durationSec: partial.durationSec } : {}),\n };\n return MediaInspectionResultSchema.parse(result);\n}\n\nfunction indeterminate(\n container: string,\n reasonCode: string,\n ffprobeVersion: string | undefined,\n formatId?: MediaFormatId,\n): MediaInspectionResult {\n return baseResult(\n {\n status: 'indeterminate',\n container,\n safetyPlan: 'strict-video-fallback',\n reasonCode,\n ...(formatId ? { formatId } : {}),\n },\n ffprobeVersion,\n );\n}\n\n// ---- the inspector ----\n\nexport interface InspectMediaArgs {\n localPath: string;\n timeoutMs?: number;\n signal?: AbortSignal;\n deps?: InspectMediaDeps;\n}\n\nexport async function inspectMedia(args: InspectMediaArgs): Promise<MediaInspectionResult> {\n const timeoutMs = args.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;\n const probe = args.deps?.probe ?? defaultProbe;\n const decodeImage = args.deps?.decodeImage ?? defaultDecodeImage;\n const ffprobeVersion = args.deps?.ffprobeVersion ?? (await getFfprobeVersion());\n\n // 1) bounded signature read (header only)\n let head: Uint8Array;\n try {\n const fh = await open(args.localPath, 'r');\n try {\n const buf = Buffer.alloc(SIGNATURE_READ_BYTES);\n const { bytesRead } = await fh.read(buf, 0, SIGNATURE_READ_BYTES, 0);\n head = buf.subarray(0, bytesRead);\n } finally {\n await fh.close();\n }\n } catch {\n return indeterminate('unknown', 'read_failed', ffprobeVersion);\n }\n\n const sig = detectSignatureFamily(head);\n\n // 2) family routing\n switch (sig.family) {\n case 'unknown':\n return indeterminate('unknown', 'unrecognized_signature', ffprobeVersion);\n\n case 'svg':\n // Scriptable XML \"image\" — structurally recognized, deliberately NOT a\n // processable format: it can carry script and no sanitize/rasterize step\n // exists. Never classified by decoder capability alone.\n return baseResult(\n {\n status: 'unsupported',\n container: 'svg',\n formatId: 'svg',\n safetyPlan: 'strict-video-fallback',\n reasonCode: 'unsupported_format:svg',\n },\n ffprobeVersion,\n );\n\n case 'image': {\n // Single-format image signature: decoder proof required — a signature is\n // a candidate, not a classification.\n const decoded = await decodeImage(args.localPath);\n if (!decoded)\n return baseResult(\n {\n status: 'malformed',\n container: sig.container,\n formatId: sig.formatId,\n safetyPlan: 'strict-video-fallback',\n reasonCode: 'image_decode_failed',\n },\n ffprobeVersion,\n );\n const frames = decoded.frames;\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'image',\n container: sig.container,\n formatId: sig.formatId,\n streams: {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: frames,\n auxiliary: 0,\n },\n safetyPlan: frames > 1 ? 'animated-image-frames' : 'still-image',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n\n case 'isobmff':\n if (ISOBMFF_IMAGE_BRANDS.has(sig.brand)) {\n // IMAGE-FAMILY ISO-BMFF resolves BEFORE the timed-video rule: a valid\n // AVIF probes as a one-frame AV1 `video` stream. Proof = image brand +\n // successful bounded decode + no ordinary timed A/V presentation.\n const probed = await probe(args.localPath, { timeoutMs, signal: args.signal });\n const parsed = probed.ok ? ProbeJsonSchema.safeParse(probed.json) : null;\n const streams = parsed?.success ? (parsed.data.streams ?? []) : null;\n const hasAudio = streams ? streams.some((s) => s.codec_type === 'audio') : true; // unknown ⇒ assume worst\n const formatId: MediaFormatId | undefined = sig.brand.startsWith('avi') ? 'avif' : 'heic';\n const decoded = await decodeImage(args.localPath);\n if (!decoded || hasAudio) {\n if (formatId === 'heic' && !decoded && !hasAudio) {\n // Recognized HEIC-family that the runtime cannot decode: explicit\n // unsupported-image (launch policy) — NEVER relabeled video.\n return baseResult(\n {\n status: 'unsupported',\n container: 'heif',\n formatId,\n safetyPlan: 'strict-video-fallback',\n reasonCode: 'unsupported_format:heic',\n },\n ffprobeVersion,\n );\n }\n // Undecodable/ambiguous image-brand file, or one carrying audio (a\n // real timed presentation wearing an image brand): fail toward the\n // stricter visual path.\n return indeterminate(\n 'heif',\n decoded ? 'image_brand_with_audio' : 'image_decode_failed',\n ffprobeVersion,\n formatId,\n );\n }\n const frames = decoded.frames;\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'image',\n container: 'heif',\n formatId,\n streams: {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: frames,\n auxiliary: 0,\n },\n safetyPlan: frames > 1 ? 'animated-image-frames' : 'still-image',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n return classifyAvContainer(args, 'isobmff', 'isobmff', probe, timeoutMs, ffprobeVersion);\n\n case 'ebml':\n return classifyAvContainer(args, 'webm', 'webm', probe, timeoutMs, ffprobeVersion);\n case 'ogg':\n return classifyAvContainer(args, 'ogg', 'ogg', probe, timeoutMs, ffprobeVersion);\n case 'avi':\n // Real container ffprobe parses; no registry entry (no origin enables it\n // today) — the stream truth still classifies it, policy rejects by format.\n return classifyAvContainer(args, 'avi', undefined, probe, timeoutMs, ffprobeVersion);\n case 'wav':\n return classifyAvContainer(args, 'wav', 'wav', probe, timeoutMs, ffprobeVersion);\n case 'mp3':\n return classifyAvContainer(args, 'mp3', 'mp3', probe, timeoutMs, ffprobeVersion);\n case 'flac':\n return classifyAvContainer(args, 'flac', 'flac', probe, timeoutMs, ffprobeVersion);\n }\n}\n\nasync function classifyAvContainer(\n args: InspectMediaArgs,\n container: string,\n formatId: MediaFormatId | undefined,\n probe: NonNullable<InspectMediaDeps['probe']>,\n timeoutMs: number,\n ffprobeVersion: string | undefined,\n): Promise<MediaInspectionResult> {\n const probed = await probe(args.localPath, { timeoutMs, signal: args.signal });\n if (!probed.ok) {\n return indeterminate(container, `probe_${probed.reason}`, ffprobeVersion, formatId);\n }\n const parsed = ProbeJsonSchema.safeParse(probed.json);\n if (!parsed.success)\n return indeterminate(container, 'probe_schema_invalid', ffprobeVersion, formatId);\n\n const rawStreams = parsed.data.streams ?? [];\n if (rawStreams.length === 0)\n return indeterminate(container, 'no_streams', ffprobeVersion, formatId);\n\n const counts: MediaInspectionStreams = {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: 0,\n auxiliary: 0,\n };\n const audioCodecs = new Set<NormalizedCodecId>();\n const videoCodecs = new Set<NormalizedCodecId>();\n let sawUnknownType = false;\n\n for (const s of rawStreams) {\n const type = s.codec_type ?? '';\n if (type === 'audio') {\n counts.audio++;\n audioCodecs.add(normalizeCodec(s.codec_name));\n } else if (type === 'video') {\n // An attached picture (cover art) is NOT a timed presentation — but the\n // disposition flag is untrusted container data, so it is COUNTED and the\n // safety plan forces every such picture through the image hash path\n // before the audio guard may return clean.\n if (s.disposition?.attached_pic === 1) {\n counts.attachedPictures++;\n } else {\n counts.timedVideo++;\n videoCodecs.add(normalizeCodec(s.codec_name));\n }\n } else if (type === 'subtitle' || type === 'data' || type === 'attachment') {\n counts.auxiliary++;\n } else {\n counts.auxiliary++;\n sawUnknownType = true;\n }\n }\n\n const codecs = { audio: [...audioCodecs], video: [...videoCodecs] };\n const measure = args.deps?.measureDuration ?? measureDurationSec;\n const durationSec = await measure(args.localPath, { signal: args.signal });\n const duration = durationSec !== undefined ? { durationSec } : {};\n\n // classification table (fail-closed)\n if (counts.timedVideo > 0) {\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'video',\n container,\n ...(formatId ? { formatId } : {}),\n streams: counts,\n codecs,\n ...duration,\n safetyPlan: 'video-frames',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n if (counts.audio > 0 && !sawUnknownType) {\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'audio',\n container,\n ...(formatId ? { formatId } : {}),\n streams: counts,\n codecs,\n ...duration,\n safetyPlan: counts.attachedPictures > 0 ? 'audio-plus-artwork' : 'audio-only',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n // auxiliary-only, unknown stream types alongside audio, or nothing usable:\n // never audio — the strict visual fallback owns it.\n return baseResult(\n {\n status: 'indeterminate',\n container,\n ...(formatId ? { formatId } : {}),\n streams: counts,\n codecs,\n ...duration,\n safetyPlan: 'strict-video-fallback',\n reasonCode: sawUnknownType ? 'unknown_stream_type' : 'auxiliary_only',\n },\n ffprobeVersion,\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"inspect-media.js","sourceRoot":"","sources":["../../src/inspection/inspect-media.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from 'node:fs/promises';\nimport { z } from 'zod';\nimport {\n MediaInspectionResultSchema,\n type MediaInspectionResult,\n type MediaInspectionStreams,\n type NormalizedCodecId,\n type MediaFormatId,\n} from '@ttt-productions/media-schemas';\nimport { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from '../video/ffmpeg.js';\nimport { measureDurationSec, type MeasureDurationFn } from '../duration/media-duration.js';\n\nexport const INSPECTOR_VERSION = 'mpc-inspect-2';\n\nconst SIGNATURE_READ_BYTES = 64 * 1024;\nconst PROBE_SIZE_BYTES = 4 * 1024 * 1024;\nconst ANALYZE_DURATION_US = 10 * 1000 * 1000;\n\nconst ProbeStreamSchema = z\n .object({\n codec_type: z.string().max(32).optional(),\n codec_name: z.string().max(64).optional(),\n disposition: z\n .object({ attached_pic: z.number().int().min(0).max(1).optional() })\n .loose()\n .optional(),\n })\n .loose();\n\nconst ProbeJsonSchema = z\n .object({\n format: z\n .object({ format_name: z.string().max(200).optional() })\n .loose()\n .optional(),\n streams: z.array(ProbeStreamSchema).max(256).optional(),\n })\n .loose();\n\nconst CODEC_MAP: Record<string, NormalizedCodecId> = {\n h264: 'h264',\n hevc: 'hevc',\n vp8: 'vp8',\n vp9: 'vp9',\n av1: 'av1',\n theora: 'theora',\n mpeg4: 'mpeg4',\n mjpeg: 'mjpeg',\n aac: 'aac',\n alac: 'alac',\n opus: 'opus',\n vorbis: 'vorbis',\n mp3: 'mp3',\n flac: 'flac',\n};\n\nfunction normalizeCodec(name: string | undefined): NormalizedCodecId {\n if (!name) return 'other';\n const n = name.toLowerCase();\n if (CODEC_MAP[n]) return CODEC_MAP[n];\n if (n.startsWith('pcm_')) return 'pcm';\n if (n.startsWith('amr')) return 'amr';\n return 'other';\n}\n\ntype SignatureFamily =\n | { family: 'image'; formatId: MediaFormatId; container: string }\n | { family: 'svg' }\n | { family: 'ebml' }\n | { family: 'isobmff'; brand: string }\n | { family: 'ogg' }\n | { family: 'avi' }\n | { family: 'wav' }\n | { family: 'mp3' }\n | { family: 'flac' }\n | { family: 'unknown' };\n\nconst ISOBMFF_IMAGE_BRANDS = new Set([\n 'avif',\n 'avis',\n 'heic',\n 'heix',\n 'heim',\n 'heis',\n 'hevc',\n 'hevx',\n 'mif1',\n 'msf1',\n]);\n\nfunction ascii(b: Uint8Array, off: number, s: string): boolean {\n if (off + s.length > b.length) return false;\n for (let i = 0; i < s.length; i++) if (b[off + i] !== s.charCodeAt(i)) return false;\n return true;\n}\n\nexport function detectSignatureFamily(b: Uint8Array): SignatureFamily {\n if (b.length < 12) return { family: 'unknown' };\n if (b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)\n return { family: 'image', formatId: 'jpeg', container: 'jpeg' };\n if (b[0] === 0x89 && ascii(b, 1, 'PNG'))\n return { family: 'image', formatId: 'png', container: 'png' };\n if (ascii(b, 0, 'GIF8')) return { family: 'image', formatId: 'gif', container: 'gif' };\n if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'WEBP'))\n return { family: 'image', formatId: 'webp', container: 'webp' };\n if (b[0] === 0x42 && b[1] === 0x4d) return { family: 'image', formatId: 'bmp', container: 'bmp' };\n if (\n (b[0] === 0x49 && b[1] === 0x49 && b[2] === 0x2a) ||\n (b[0] === 0x4d && b[1] === 0x4d && b[3] === 0x2a)\n ) {\n return { family: 'image', formatId: 'tiff', container: 'tiff' };\n }\n {\n const prefix = Buffer.from(b.slice(0, Math.min(b.length, 4096)))\n .toString('latin1')\n .toLowerCase();\n if (\n prefix.includes('<svg') &&\n (prefix.trimStart().startsWith('<?xml') ||\n prefix.trimStart().startsWith('<svg') ||\n prefix.includes('<!doctype svg'))\n ) {\n return { family: 'svg' };\n }\n }\n if (b[0] === 0x1a && b[1] === 0x45 && b[2] === 0xdf && b[3] === 0xa3) return { family: 'ebml' };\n if (ascii(b, 4, 'ftyp')) {\n const brand = String.fromCharCode(b[8], b[9], b[10], b[11]).trim().toLowerCase();\n return { family: 'isobmff', brand };\n }\n if (ascii(b, 0, 'OggS')) return { family: 'ogg' };\n if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'AVI ')) return { family: 'avi' };\n if (ascii(b, 0, 'RIFF') && ascii(b, 8, 'WAVE')) return { family: 'wav' };\n if (ascii(b, 0, 'ID3')) return { family: 'mp3' };\n if (b[0] === 0xff && (b[1] & 0xe0) === 0xe0) return { family: 'mp3' };\n if (ascii(b, 0, 'fLaC')) return { family: 'flac' };\n return { family: 'unknown' };\n}\n\nexport interface InspectMediaDeps {\n probe?: (\n localPath: string,\n opts: { timeoutMs: number; signal?: AbortSignal },\n ) => Promise<\n | { ok: true; json: unknown }\n | { ok: false; reason: 'timeout' | 'truncated' | 'failed' | 'bad_json' }\n >;\n decodeImage?: (localPath: string) => Promise<{ frames: number } | null>;\n measureDuration?: MeasureDurationFn;\n ffprobeVersion?: string;\n}\n\nlet cachedFfprobeVersion: string | null | undefined;\n\nasync function getFfprobeVersion(): Promise<string | undefined> {\n if (cachedFfprobeVersion !== undefined) return cachedFfprobeVersion ?? undefined;\n try {\n const r = await runCmd('ffprobe', ['-version'], { timeoutMs: 5_000 });\n const first = (r.stdout || r.stderr).split(/\\r?\\n/)[0]?.trim() ?? '';\n cachedFfprobeVersion = first ? first.slice(0, 120) : null;\n } catch {\n cachedFfprobeVersion = null;\n }\n return cachedFfprobeVersion ?? undefined;\n}\n\nasync function defaultProbe(\n localPath: string,\n opts: { timeoutMs: number; signal?: AbortSignal },\n): Promise<\n | { ok: true; json: unknown }\n | { ok: false; reason: 'timeout' | 'truncated' | 'failed' | 'bad_json' }\n> {\n let r;\n try {\n r = await runCmd(\n 'ffprobe',\n [\n '-v',\n 'error',\n '-print_format',\n 'json',\n '-show_format',\n '-show_streams',\n '-probesize',\n String(PROBE_SIZE_BYTES),\n '-analyzeduration',\n String(ANALYZE_DURATION_US),\n localPath,\n ],\n { timeoutMs: opts.timeoutMs, signal: opts.signal },\n );\n } catch {\n return { ok: false, reason: 'failed' };\n }\n if (r.timedOut) return { ok: false, reason: 'timeout' };\n if (r.truncated) return { ok: false, reason: 'truncated' };\n if (r.code !== 0) return { ok: false, reason: 'failed' };\n try {\n return { ok: true, json: JSON.parse(r.stdout || '{}') };\n } catch {\n return { ok: false, reason: 'bad_json' };\n }\n}\n\nasync function defaultDecodeImage(localPath: string): Promise<{ frames: number } | null> {\n try {\n const sharp = (await import('sharp')).default;\n const meta = await sharp(localPath, {\n limitInputPixels: 268402689,\n }).metadata();\n if (!meta.width || !meta.height) return null;\n return { frames: Math.max(1, meta.pages ?? 1) };\n } catch {\n return null;\n }\n}\n\nfunction baseResult(\n partial: Omit<MediaInspectionResult, 'inspectorVersion' | 'streams' | 'codecs'> &\n Partial<Pick<MediaInspectionResult, 'streams' | 'codecs'>>,\n ffprobeVersion: string | undefined,\n): MediaInspectionResult {\n const result: MediaInspectionResult = {\n inspectorVersion: INSPECTOR_VERSION,\n ...(ffprobeVersion ? { ffprobeVersion } : {}),\n streams: partial.streams ?? {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: 0,\n auxiliary: 0,\n },\n codecs: partial.codecs ?? { audio: [], video: [] },\n status: partial.status,\n container: partial.container,\n safetyPlan: partial.safetyPlan,\n reasonCode: partial.reasonCode,\n ...(partial.canonicalKind ? { canonicalKind: partial.canonicalKind } : {}),\n ...(partial.formatId ? { formatId: partial.formatId } : {}),\n ...(partial.durationSec !== undefined ? { durationSec: partial.durationSec } : {}),\n };\n return MediaInspectionResultSchema.parse(result);\n}\n\nfunction indeterminate(\n container: string,\n reasonCode: string,\n ffprobeVersion: string | undefined,\n formatId?: MediaFormatId,\n): MediaInspectionResult {\n return baseResult(\n {\n status: 'indeterminate',\n container,\n safetyPlan: 'strict-video-fallback',\n reasonCode,\n ...(formatId ? { formatId } : {}),\n },\n ffprobeVersion,\n );\n}\n\nexport interface InspectMediaArgs {\n localPath: string;\n timeoutMs?: number;\n signal?: AbortSignal;\n deps?: InspectMediaDeps;\n}\n\nexport async function inspectMedia(args: InspectMediaArgs): Promise<MediaInspectionResult> {\n const timeoutMs = args.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;\n const probe = args.deps?.probe ?? defaultProbe;\n const decodeImage = args.deps?.decodeImage ?? defaultDecodeImage;\n const ffprobeVersion = args.deps?.ffprobeVersion ?? (await getFfprobeVersion());\n\n let head: Uint8Array;\n try {\n const fh = await open(args.localPath, 'r');\n try {\n const buf = Buffer.alloc(SIGNATURE_READ_BYTES);\n const { bytesRead } = await fh.read(buf, 0, SIGNATURE_READ_BYTES, 0);\n head = buf.subarray(0, bytesRead);\n } finally {\n await fh.close();\n }\n } catch {\n return indeterminate('unknown', 'read_failed', ffprobeVersion);\n }\n\n const sig = detectSignatureFamily(head);\n\n switch (sig.family) {\n case 'unknown':\n return indeterminate('unknown', 'unrecognized_signature', ffprobeVersion);\n\n case 'svg':\n return baseResult(\n {\n status: 'unsupported',\n container: 'svg',\n formatId: 'svg',\n safetyPlan: 'strict-video-fallback',\n reasonCode: 'unsupported_format:svg',\n },\n ffprobeVersion,\n );\n\n case 'image': {\n const decoded = await decodeImage(args.localPath);\n if (!decoded)\n return baseResult(\n {\n status: 'malformed',\n container: sig.container,\n formatId: sig.formatId,\n safetyPlan: 'strict-video-fallback',\n reasonCode: 'image_decode_failed',\n },\n ffprobeVersion,\n );\n const frames = decoded.frames;\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'image',\n container: sig.container,\n formatId: sig.formatId,\n streams: {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: frames,\n auxiliary: 0,\n },\n safetyPlan: frames > 1 ? 'animated-image-frames' : 'still-image',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n\n case 'isobmff':\n if (ISOBMFF_IMAGE_BRANDS.has(sig.brand)) {\n const probed = await probe(args.localPath, { timeoutMs, signal: args.signal });\n const parsed = probed.ok ? ProbeJsonSchema.safeParse(probed.json) : null;\n const streams = parsed?.success ? (parsed.data.streams ?? []) : null;\n const hasAudio = streams ? streams.some((s) => s.codec_type === 'audio') : true;\n const formatId: MediaFormatId | undefined = sig.brand.startsWith('avi') ? 'avif' : 'heic';\n const decoded = await decodeImage(args.localPath);\n if (!decoded || hasAudio) {\n if (formatId === 'heic' && !decoded && !hasAudio) {\n return baseResult(\n {\n status: 'unsupported',\n container: 'heif',\n formatId,\n safetyPlan: 'strict-video-fallback',\n reasonCode: 'unsupported_format:heic',\n },\n ffprobeVersion,\n );\n }\n return indeterminate(\n 'heif',\n decoded ? 'image_brand_with_audio' : 'image_decode_failed',\n ffprobeVersion,\n formatId,\n );\n }\n const frames = decoded.frames;\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'image',\n container: 'heif',\n formatId,\n streams: {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: frames,\n auxiliary: 0,\n },\n safetyPlan: frames > 1 ? 'animated-image-frames' : 'still-image',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n return classifyAvContainer(args, 'isobmff', 'isobmff', probe, timeoutMs, ffprobeVersion);\n\n case 'ebml':\n return classifyAvContainer(args, 'webm', 'webm', probe, timeoutMs, ffprobeVersion);\n case 'ogg':\n return classifyAvContainer(args, 'ogg', 'ogg', probe, timeoutMs, ffprobeVersion);\n case 'avi':\n return classifyAvContainer(args, 'avi', undefined, probe, timeoutMs, ffprobeVersion);\n case 'wav':\n return classifyAvContainer(args, 'wav', 'wav', probe, timeoutMs, ffprobeVersion);\n case 'mp3':\n return classifyAvContainer(args, 'mp3', 'mp3', probe, timeoutMs, ffprobeVersion);\n case 'flac':\n return classifyAvContainer(args, 'flac', 'flac', probe, timeoutMs, ffprobeVersion);\n }\n}\n\nasync function classifyAvContainer(\n args: InspectMediaArgs,\n container: string,\n formatId: MediaFormatId | undefined,\n probe: NonNullable<InspectMediaDeps['probe']>,\n timeoutMs: number,\n ffprobeVersion: string | undefined,\n): Promise<MediaInspectionResult> {\n const probed = await probe(args.localPath, { timeoutMs, signal: args.signal });\n if (!probed.ok) {\n return indeterminate(container, `probe_${probed.reason}`, ffprobeVersion, formatId);\n }\n const parsed = ProbeJsonSchema.safeParse(probed.json);\n if (!parsed.success)\n return indeterminate(container, 'probe_schema_invalid', ffprobeVersion, formatId);\n\n const rawStreams = parsed.data.streams ?? [];\n if (rawStreams.length === 0)\n return indeterminate(container, 'no_streams', ffprobeVersion, formatId);\n\n const counts: MediaInspectionStreams = {\n audio: 0,\n timedVideo: 0,\n attachedPictures: 0,\n imageFrames: 0,\n auxiliary: 0,\n };\n const audioCodecs = new Set<NormalizedCodecId>();\n const videoCodecs = new Set<NormalizedCodecId>();\n let sawUnknownType = false;\n\n for (const s of rawStreams) {\n const type = s.codec_type ?? '';\n if (type === 'audio') {\n counts.audio++;\n audioCodecs.add(normalizeCodec(s.codec_name));\n } else if (type === 'video') {\n if (s.disposition?.attached_pic === 1) {\n counts.attachedPictures++;\n } else {\n counts.timedVideo++;\n videoCodecs.add(normalizeCodec(s.codec_name));\n }\n } else if (type === 'subtitle' || type === 'data' || type === 'attachment') {\n counts.auxiliary++;\n } else {\n counts.auxiliary++;\n sawUnknownType = true;\n }\n }\n\n const codecs = { audio: [...audioCodecs], video: [...videoCodecs] };\n const measure = args.deps?.measureDuration ?? measureDurationSec;\n const durationSec = await measure(args.localPath, { signal: args.signal });\n const duration = durationSec !== undefined ? { durationSec } : {};\n\n if (counts.timedVideo > 0) {\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'video',\n container,\n ...(formatId ? { formatId } : {}),\n streams: counts,\n codecs,\n ...duration,\n safetyPlan: 'video-frames',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n if (counts.audio > 0 && !sawUnknownType) {\n return baseResult(\n {\n status: 'definitive',\n canonicalKind: 'audio',\n container,\n ...(formatId ? { formatId } : {}),\n streams: counts,\n codecs,\n ...duration,\n safetyPlan: counts.attachedPictures > 0 ? 'audio-plus-artwork' : 'audio-only',\n reasonCode: 'ok',\n },\n ffprobeVersion,\n );\n }\n return baseResult(\n {\n status: 'indeterminate',\n container,\n ...(formatId ? { formatId } : {}),\n streams: counts,\n codecs,\n ...duration,\n safetyPlan: 'strict-video-fallback',\n reasonCode: sawUnknownType ? 'unknown_stream_type' : 'auxiliary_only',\n },\n ffprobeVersion,\n );\n}\n"]}
|
package/dist/io/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/io/fs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/io/fs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAyBV"}
|
package/dist/io/fs.js
CHANGED
|
@@ -15,7 +15,6 @@ export function createFsIO(args) {
|
|
|
15
15
|
const ext = path.extname(localPath);
|
|
16
16
|
const safeKey = sanitizeKey(key);
|
|
17
17
|
const out = path.join(args.outputDir, `${safeKey}${ext}`);
|
|
18
|
-
// Ensure final path stays within outputDir.
|
|
19
18
|
const resolvedDir = path.resolve(args.outputDir);
|
|
20
19
|
const resolvedOut = path.resolve(out);
|
|
21
20
|
if (!resolvedOut.startsWith(resolvedDir + path.sep) && resolvedOut !== resolvedDir) {
|
package/dist/io/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/io/fs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,UAAU,UAAU,CAAC,IAI1B;IACC,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS;YACpB,KAAK,CAAC,UAAU,CAAC,SAAS;gBACxB,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YAC5C,CAAC;SACF;QACD,MAAM,EAAE;YACN,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,GAAG;gBAChC,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACpC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBACjC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC;gBAE1D,
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/io/fs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,UAAU,UAAU,CAAC,IAI1B;IACC,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS;YACpB,KAAK,CAAC,UAAU,CAAC,SAAS;gBACxB,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YAC5C,CAAC;SACF;QACD,MAAM,EAAE;YACN,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,GAAG;gBAChC,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACpC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBACjC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC;gBAE1D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACjD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACtC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,KAAK,WAAW,EAAE,CAAC;oBACnF,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;gBACxC,CAAC;gBACD,MAAM,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;gBAC/B,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,GAAG,EAAE,EAAE,CAAC;YAC7C,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { MediaIO } from './types.js';\nimport { copyFile, mkdir } from 'node:fs/promises';\nimport path from 'node:path';\nimport { sanitizeKey } from '../utils/safe-path.js';\n\nexport function createFsIO(args: {\n inputPath: string;\n inputMime?: string;\n outputDir: string;\n}): MediaIO {\n return {\n input: {\n mime: args.inputMime,\n async readToFile(localPath) {\n await copyFile(args.inputPath, localPath);\n },\n },\n output: {\n async writeFromFile(localPath, key) {\n await mkdir(args.outputDir, { recursive: true });\n const ext = path.extname(localPath);\n const safeKey = sanitizeKey(key);\n const out = path.join(args.outputDir, `${safeKey}${ext}`);\n\n const resolvedDir = path.resolve(args.outputDir);\n const resolvedOut = path.resolve(out);\n if (!resolvedOut.startsWith(resolvedDir + path.sep) && resolvedOut !== resolvedDir) {\n throw new Error('Unsafe output path');\n }\n await copyFile(localPath, out);\n return { path: out, url: `file://${out}` };\n },\n },\n };\n}\n"]}
|
package/dist/io/local-input.d.ts
CHANGED
|
@@ -1,39 +1,19 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
|
-
/** Thrown by {@link streamToTempFile} when the source exceeds `maxBytes`. */
|
|
3
2
|
export declare class ByteLimitExceededError extends Error {
|
|
4
3
|
readonly maxBytes: number;
|
|
5
4
|
constructor(maxBytes: number);
|
|
6
5
|
}
|
|
7
6
|
export interface TempFileInput {
|
|
8
|
-
/** Absolute path of the fully-written temp file. */
|
|
9
7
|
localPath: string;
|
|
10
|
-
/** Total bytes written (always <= maxBytes). */
|
|
11
8
|
bytesWritten: number;
|
|
12
|
-
/** Removes the temp file and its private directory. Idempotent; never throws. */
|
|
13
9
|
cleanup: () => Promise<void>;
|
|
14
10
|
}
|
|
15
11
|
export interface StreamToTempFileOptions {
|
|
16
|
-
/** HARD cap: the stream is destroyed and the temp file removed the moment the
|
|
17
|
-
* cap is crossed, so an oversized source never fully lands on disk. */
|
|
18
12
|
maxBytes: number;
|
|
19
|
-
/** Temp-dir prefix (default "media-input-"). */
|
|
20
13
|
prefix?: string;
|
|
21
14
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Streams a Readable into a private temp file with a hard byte cap and
|
|
24
|
-
* constant memory. On success the caller MUST invoke `cleanup()` in a
|
|
25
|
-
* `finally`. On any failure (cap exceeded, stream error) the temp file is
|
|
26
|
-
* removed before the error propagates.
|
|
27
|
-
*/
|
|
28
15
|
export declare function streamToTempFile(source: Readable, options: StreamToTempFileOptions): Promise<TempFileInput>;
|
|
29
|
-
/** Streams a file through SHA-256 (constant memory) and returns the lowercase hex digest. */
|
|
30
16
|
export declare function hashFileSha256(localPath: string): Promise<string>;
|
|
31
|
-
/** Default header window for signature-family checks (matches the inspector's sniff window). */
|
|
32
17
|
export declare const FILE_HEADER_BYTES: number;
|
|
33
|
-
/**
|
|
34
|
-
* Reads at most `maxBytes` (default 64 KiB) from the start of a file — the
|
|
35
|
-
* bounded input for {@link detectSignatureFamily}-style checks without loading
|
|
36
|
-
* the whole source.
|
|
37
|
-
*/
|
|
38
18
|
export declare function readFileHeader(localPath: string, maxBytes?: number): Promise<Uint8Array>;
|
|
39
19
|
//# sourceMappingURL=local-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-input.d.ts","sourceRoot":"","sources":["../../src/io/local-input.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local-input.d.ts","sourceRoot":"","sources":["../../src/io/local-input.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM;CAK7B;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,aAAa,CAAC,CA+BxB;AAED,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvE;AAED,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAE3C,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,MAA0B,GACnC,OAAO,CAAC,UAAU,CAAC,CASrB"}
|
package/dist/io/local-input.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
// Bounded-memory local-input primitives (canonical-upload-content-classification,
|
|
2
|
-
// bounded-memory unit). Safety scanning and hashing must never hold a full media
|
|
3
|
-
// source in JS memory: sources stream to a generation-pinned temp file with a
|
|
4
|
-
// hard byte cap, hashes stream from disk, and signature checks read only a
|
|
5
|
-
// bounded header. These primitives are generic (no storage/vendor deps) — the
|
|
6
|
-
// caller supplies the Readable and owns generation pinning.
|
|
7
1
|
import { createHash } from 'node:crypto';
|
|
8
2
|
import { createReadStream, createWriteStream } from 'node:fs';
|
|
9
3
|
import { mkdtemp, open, rm } from 'node:fs/promises';
|
|
10
4
|
import os from 'node:os';
|
|
11
5
|
import path from 'node:path';
|
|
12
6
|
import { pipeline } from 'node:stream/promises';
|
|
13
|
-
/** Thrown by {@link streamToTempFile} when the source exceeds `maxBytes`. */
|
|
14
7
|
export class ByteLimitExceededError extends Error {
|
|
15
8
|
maxBytes;
|
|
16
9
|
constructor(maxBytes) {
|
|
@@ -19,12 +12,6 @@ export class ByteLimitExceededError extends Error {
|
|
|
19
12
|
this.maxBytes = maxBytes;
|
|
20
13
|
}
|
|
21
14
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Streams a Readable into a private temp file with a hard byte cap and
|
|
24
|
-
* constant memory. On success the caller MUST invoke `cleanup()` in a
|
|
25
|
-
* `finally`. On any failure (cap exceeded, stream error) the temp file is
|
|
26
|
-
* removed before the error propagates.
|
|
27
|
-
*/
|
|
28
15
|
export async function streamToTempFile(source, options) {
|
|
29
16
|
const { maxBytes, prefix = 'media-input-' } = options;
|
|
30
17
|
if (!Number.isFinite(maxBytes) || maxBytes <= 0) {
|
|
@@ -54,7 +41,6 @@ export async function streamToTempFile(source, options) {
|
|
|
54
41
|
}
|
|
55
42
|
return { localPath, bytesWritten, cleanup };
|
|
56
43
|
}
|
|
57
|
-
/** Streams a file through SHA-256 (constant memory) and returns the lowercase hex digest. */
|
|
58
44
|
export async function hashFileSha256(localPath) {
|
|
59
45
|
const hash = createHash('sha256');
|
|
60
46
|
await pipeline(createReadStream(localPath), async (chunks) => {
|
|
@@ -63,13 +49,7 @@ export async function hashFileSha256(localPath) {
|
|
|
63
49
|
});
|
|
64
50
|
return hash.digest('hex');
|
|
65
51
|
}
|
|
66
|
-
/** Default header window for signature-family checks (matches the inspector's sniff window). */
|
|
67
52
|
export const FILE_HEADER_BYTES = 64 * 1024;
|
|
68
|
-
/**
|
|
69
|
-
* Reads at most `maxBytes` (default 64 KiB) from the start of a file — the
|
|
70
|
-
* bounded input for {@link detectSignatureFamily}-style checks without loading
|
|
71
|
-
* the whole source.
|
|
72
|
-
*/
|
|
73
53
|
export async function readFileHeader(localPath, maxBytes = FILE_HEADER_BYTES) {
|
|
74
54
|
const handle = await open(localPath, 'r');
|
|
75
55
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-input.js","sourceRoot":"","sources":["../../src/io/local-input.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"local-input.js","sourceRoot":"","sources":["../../src/io/local-input.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { createReadStream, createWriteStream } from 'node:fs';\nimport { mkdtemp, open, rm } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pipeline } from 'node:stream/promises';\nimport type { Readable } from 'node:stream';\n\nexport class ByteLimitExceededError extends Error {\n readonly maxBytes: number;\n constructor(maxBytes: number) {\n super(`source exceeded the ${maxBytes}-byte limit`);\n this.name = 'ByteLimitExceededError';\n this.maxBytes = maxBytes;\n }\n}\n\nexport interface TempFileInput {\n localPath: string;\n bytesWritten: number;\n cleanup: () => Promise<void>;\n}\n\nexport interface StreamToTempFileOptions {\n maxBytes: number;\n prefix?: string;\n}\n\nexport async function streamToTempFile(\n source: Readable,\n options: StreamToTempFileOptions,\n): Promise<TempFileInput> {\n const { maxBytes, prefix = 'media-input-' } = options;\n if (!Number.isFinite(maxBytes) || maxBytes <= 0) {\n throw new Error('streamToTempFile requires a positive finite maxBytes');\n }\n const dir = await mkdtemp(path.join(os.tmpdir(), prefix));\n const localPath = path.join(dir, 'input');\n const cleanup = async () => {\n await rm(dir, { recursive: true, force: true }).catch(() => {});\n };\n\n let bytesWritten = 0;\n try {\n const sink = createWriteStream(localPath, { flags: 'wx' });\n await pipeline(\n source,\n async function* (chunks: AsyncIterable<Buffer | string>) {\n for await (const chunk of chunks) {\n const buf = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;\n bytesWritten += buf.byteLength;\n if (bytesWritten > maxBytes) throw new ByteLimitExceededError(maxBytes);\n yield buf;\n }\n },\n sink,\n );\n } catch (err) {\n await cleanup();\n throw err;\n }\n return { localPath, bytesWritten, cleanup };\n}\n\nexport async function hashFileSha256(localPath: string): Promise<string> {\n const hash = createHash('sha256');\n await pipeline(createReadStream(localPath), async (chunks: AsyncIterable<Buffer>) => {\n for await (const chunk of chunks) hash.update(chunk);\n });\n return hash.digest('hex');\n}\n\nexport const FILE_HEADER_BYTES = 64 * 1024;\n\nexport async function readFileHeader(\n localPath: string,\n maxBytes: number = FILE_HEADER_BYTES,\n): Promise<Uint8Array> {\n const handle = await open(localPath, 'r');\n try {\n const buf = Buffer.alloc(maxBytes);\n const { bytesRead } = await handle.read(buf, 0, maxBytes, 0);\n return buf.subarray(0, bytesRead);\n } finally {\n await handle.close();\n }\n}\n"]}
|
package/dist/io/types.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
export interface MediaInputSource {
|
|
2
|
-
/** Copy source media into localPath */
|
|
3
2
|
readToFile(localPath: string): Promise<void>;
|
|
4
|
-
/** Optional mime hint */
|
|
5
3
|
mime?: string;
|
|
6
4
|
}
|
|
7
5
|
export interface MediaOutputTarget {
|
|
8
|
-
/** Persist a processed file from localPath */
|
|
9
6
|
writeFromFile(localPath: string, outputKey: string): Promise<{
|
|
10
7
|
url?: string;
|
|
11
8
|
path?: string;
|
package/dist/io/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/io/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/io/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QACT,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;CAC3B"}
|
package/dist/io/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/io/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface MediaInputSource {\n
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/io/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface MediaInputSource {\n readToFile(localPath: string): Promise<void>;\n\n mime?: string;\n}\n\nexport interface MediaOutputTarget {\n writeFromFile(\n localPath: string,\n outputKey: string,\n ): Promise<{\n url?: string;\n path?: string;\n }>;\n}\n\nexport interface MediaIO {\n input: MediaInputSource;\n output: MediaOutputTarget;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/moderation/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE5E,wBAAgB,eAAe,CAC7B,KAAK,CAAC,EAAE,qBAAqB,GAAG,IAAI,EACpC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GACpC,qBAAqB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/moderation/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE5E,wBAAgB,eAAe,CAC7B,KAAK,CAAC,EAAE,qBAAqB,GAAG,IAAI,EACpC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GACpC,qBAAqB,GAAG,SAAS,CAsBnC"}
|
package/dist/moderation/merge.js
CHANGED
|
@@ -7,7 +7,6 @@ export function mergeModeration(input, output) {
|
|
|
7
7
|
return b ?? undefined;
|
|
8
8
|
if (!b)
|
|
9
9
|
return a ?? undefined;
|
|
10
|
-
// worst status wins: rejected > flagged > error > passed
|
|
11
10
|
const rank = (s) => (s === 'rejected' ? 4 : s === 'flagged' ? 3 : s === 'error' ? 2 : 1);
|
|
12
11
|
const status = rank(a.status) >= rank(b.status) ? a.status : b.status;
|
|
13
12
|
const reasons = Array.from(new Set([...(a.reasons ?? []), ...(b.reasons ?? [])]));
|