@ttt-productions/media-processing-core 0.8.14 → 0.8.16

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.
Files changed (39) hide show
  1. package/dist/audio/audio-processor.d.ts.map +1 -1
  2. package/dist/audio/audio-processor.js +12 -2
  3. package/dist/audio/audio-processor.js.map +1 -1
  4. package/dist/audio/probe.d.ts +2 -1
  5. package/dist/audio/probe.d.ts.map +1 -1
  6. package/dist/audio/probe.js +8 -10
  7. package/dist/audio/probe.js.map +1 -1
  8. package/dist/duration/media-duration.d.ts +26 -0
  9. package/dist/duration/media-duration.d.ts.map +1 -0
  10. package/dist/duration/media-duration.js +141 -0
  11. package/dist/duration/media-duration.js.map +1 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/inspection/inspect-media.d.ts +5 -1
  17. package/dist/inspection/inspect-media.d.ts.map +1 -1
  18. package/dist/inspection/inspect-media.js +17 -12
  19. package/dist/inspection/inspect-media.js.map +1 -1
  20. package/dist/server/download-token.d.ts +14 -0
  21. package/dist/server/download-token.d.ts.map +1 -0
  22. package/dist/server/download-token.js +19 -0
  23. package/dist/server/download-token.js.map +1 -0
  24. package/dist/server/index.d.ts +1 -0
  25. package/dist/server/index.d.ts.map +1 -1
  26. package/dist/server/index.js +1 -0
  27. package/dist/server/index.js.map +1 -1
  28. package/dist/video/ffmpeg.d.ts +10 -2
  29. package/dist/video/ffmpeg.d.ts.map +1 -1
  30. package/dist/video/ffmpeg.js +28 -1
  31. package/dist/video/ffmpeg.js.map +1 -1
  32. package/dist/video/probe.d.ts +2 -1
  33. package/dist/video/probe.d.ts.map +1 -1
  34. package/dist/video/probe.js +8 -17
  35. package/dist/video/probe.js.map +1 -1
  36. package/dist/video/video-processor.d.ts.map +1 -1
  37. package/dist/video/video-processor.js +9 -2
  38. package/dist/video/video-processor.js.map +1 -1
  39. package/package.json +2 -2
@@ -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 (canonical-upload-content-classification)\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 (reviewed 2026-07-31; see the consuming app's\n// CODE_CHANGE_canonical_upload_content_classification.md):\n// - family selection by a bounded 64 KiB SIGNATURE read (header only —\n// semantic elements are NEVER searched for in arbitrary payload; that\n// flat-scan heuristic caused the WebM audio misclassification twice);\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 { runCmd } from \"../video/ffmpeg.js\";\n\n/** Bump on any classification-rule change (rides every decision event). */\nexport const INSPECTOR_VERSION = \"mpc-inspect-1\";\n\nconst SIGNATURE_READ_BYTES = 64 * 1024;\nconst DEFAULT_PROBE_TIMEOUT_MS = 20_000;\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 nb_frames: z.string().max(20).optional(),\n duration: z.string().max(32).optional(),\n })\n .loose();\n\nconst ProbeJsonSchema = z\n .object({\n format: z.object({ format_name: z.string().max(200).optional() }).loose().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([\"avif\", \"avis\", \"heic\", \"heix\", \"heim\", \"heis\", \"hevc\", \"hevx\", \"mif1\", \"msf1\"]);\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) return { family: \"image\", formatId: \"jpeg\", container: \"jpeg\" };\n if (b[0] === 0x89 && ascii(b, 1, \"PNG\")) 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\")) return { family: \"image\", formatId: \"webp\", container: \"webp\" };\n if (b[0] === 0x42 && b[1] === 0x4d) return { family: \"image\", formatId: \"bmp\", container: \"bmp\" };\n if ((b[0] === 0x49 && b[1] === 0x49 && b[2] === 0x2a) || (b[0] === 0x4d && b[1] === 0x4d && b[3] === 0x2a)) {\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 (prefix.includes(\"<svg\") && (prefix.trimStart().startsWith(\"<?xml\") || prefix.trimStart().startsWith(\"<svg\") || prefix.includes(\"<!doctype svg\"))) {\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?: (localPath: string, opts: { timeoutMs: number; signal?: AbortSignal }) => 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 /** 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<{ ok: true; json: unknown } | { ok: false; reason: \"timeout\" | \"truncated\" | \"failed\" | \"bad_json\" }> {\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, { limitInputPixels: 268402689 /* sharp default ~16k x 16k */ }).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 ?? { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: 0, auxiliary: 0 },\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 };\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 until a sanitize/rasterize design exists (launch\n // policy 2026-07-31). 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) return baseResult(\n { status: \"malformed\", container: sig.container, formatId: sig.formatId, safetyPlan: \"strict-video-fallback\", reasonCode: \"image_decode_failed\" },\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: { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: frames, auxiliary: 0 },\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 { status: \"unsupported\", container: \"heif\", formatId, safetyPlan: \"strict-video-fallback\", reasonCode: \"unsupported_format:heic\" },\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(\"heif\", decoded ? \"image_brand_with_audio\" : \"image_decode_failed\", ffprobeVersion, formatId);\n }\n const frames = decoded.frames;\n return baseResult(\n {\n status: \"definitive\",\n canonicalKind: \"image\",\n container: \"heif\",\n formatId,\n streams: { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: frames, auxiliary: 0 },\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) return indeterminate(container, \"probe_schema_invalid\", ffprobeVersion, formatId);\n\n const rawStreams = parsed.data.streams ?? [];\n if (rawStreams.length === 0) return indeterminate(container, \"no_streams\", ffprobeVersion, formatId);\n\n const counts: MediaInspectionStreams = { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: 0, auxiliary: 0 };\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\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 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 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 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":["// =============================================================================\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.object({ format_name: z.string().max(200).optional() }).loose().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([\"avif\", \"avis\", \"heic\", \"heix\", \"heim\", \"heis\", \"hevc\", \"hevx\", \"mif1\", \"msf1\"]);\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) return { family: \"image\", formatId: \"jpeg\", container: \"jpeg\" };\n if (b[0] === 0x89 && ascii(b, 1, \"PNG\")) 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\")) return { family: \"image\", formatId: \"webp\", container: \"webp\" };\n if (b[0] === 0x42 && b[1] === 0x4d) return { family: \"image\", formatId: \"bmp\", container: \"bmp\" };\n if ((b[0] === 0x49 && b[1] === 0x49 && b[2] === 0x2a) || (b[0] === 0x4d && b[1] === 0x4d && b[3] === 0x2a)) {\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 (prefix.includes(\"<svg\") && (prefix.trimStart().startsWith(\"<?xml\") || prefix.trimStart().startsWith(\"<svg\") || prefix.includes(\"<!doctype svg\"))) {\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?: (localPath: string, opts: { timeoutMs: number; signal?: AbortSignal }) => 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<{ ok: true; json: unknown } | { ok: false; reason: \"timeout\" | \"truncated\" | \"failed\" | \"bad_json\" }> {\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, { limitInputPixels: 268402689 /* sharp default ~16k x 16k */ }).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 ?? { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: 0, auxiliary: 0 },\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) return baseResult(\n { status: \"malformed\", container: sig.container, formatId: sig.formatId, safetyPlan: \"strict-video-fallback\", reasonCode: \"image_decode_failed\" },\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: { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: frames, auxiliary: 0 },\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 { status: \"unsupported\", container: \"heif\", formatId, safetyPlan: \"strict-video-fallback\", reasonCode: \"unsupported_format:heic\" },\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(\"heif\", decoded ? \"image_brand_with_audio\" : \"image_decode_failed\", ffprobeVersion, formatId);\n }\n const frames = decoded.frames;\n return baseResult(\n {\n status: \"definitive\",\n canonicalKind: \"image\",\n container: \"heif\",\n formatId,\n streams: { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: frames, auxiliary: 0 },\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) return indeterminate(container, \"probe_schema_invalid\", ffprobeVersion, formatId);\n\n const rawStreams = parsed.data.streams ?? [];\n if (rawStreams.length === 0) return indeterminate(container, \"no_streams\", ffprobeVersion, formatId);\n\n const counts: MediaInspectionStreams = { audio: 0, timedVideo: 0, attachedPictures: 0, imageFrames: 0, auxiliary: 0 };\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"]}
@@ -0,0 +1,14 @@
1
+ /** The one method of a Storage object handle this module uses. */
2
+ export interface DownloadTokenWritable {
3
+ setMetadata(update: {
4
+ metadata: Record<string, string | null>;
5
+ }): Promise<unknown>;
6
+ }
7
+ /** True when the object's stored metadata still carries a download token. */
8
+ export declare function hasDownloadToken(objectMetadata: Readonly<Record<string, unknown>> | undefined): boolean;
9
+ /**
10
+ * Remove the object's download token and nothing else: a null custom-metadata value deletes that
11
+ * one key, leaving every other entry. A no-op write when the object has none.
12
+ */
13
+ export declare function removeDownloadToken(file: DownloadTokenWritable): Promise<void>;
14
+ //# sourceMappingURL=download-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-token.d.ts","sourceRoot":"","sources":["../../src/server/download-token.ts"],"names":[],"mappings":"AAKA,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAKD,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,GAAG,OAAO,CAGvG;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF"}
@@ -0,0 +1,19 @@
1
+ // The one remover of a Storage object's Firebase download token. The Firebase Storage client gives
2
+ // every object it uploads a `firebaseStorageDownloadTokens` metadata entry: a never-expiring link
3
+ // that reads the object signed out, around the Storage rules. An app that serves no object through
4
+ // one removes it as soon as the server takes the object over.
5
+ /** The custom-metadata key the Firebase Storage client writes on upload. */
6
+ const DOWNLOAD_TOKENS_KEY = "firebaseStorageDownloadTokens";
7
+ /** True when the object's stored metadata still carries a download token. */
8
+ export function hasDownloadToken(objectMetadata) {
9
+ const custom = objectMetadata?.["metadata"];
10
+ return typeof custom === "object" && custom !== null && DOWNLOAD_TOKENS_KEY in custom;
11
+ }
12
+ /**
13
+ * Remove the object's download token and nothing else: a null custom-metadata value deletes that
14
+ * one key, leaving every other entry. A no-op write when the object has none.
15
+ */
16
+ export async function removeDownloadToken(file) {
17
+ await file.setMetadata({ metadata: { [DOWNLOAD_TOKENS_KEY]: null } });
18
+ }
19
+ //# sourceMappingURL=download-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-token.js","sourceRoot":"","sources":["../../src/server/download-token.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,kGAAkG;AAClG,mGAAmG;AACnG,8DAA8D;AAO9D,4EAA4E;AAC5E,MAAM,mBAAmB,GAAG,+BAA+B,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,cAA6D;IAC5F,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC,UAAU,CAAC,CAAC;IAC5C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,mBAAmB,IAAI,MAAM,CAAC;AACxF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAA2B;IACnE,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC","sourcesContent":["// The one remover of a Storage object's Firebase download token. The Firebase Storage client gives\n// every object it uploads a `firebaseStorageDownloadTokens` metadata entry: a never-expiring link\n// that reads the object signed out, around the Storage rules. An app that serves no object through\n// one removes it as soon as the server takes the object over.\n\n/** The one method of a Storage object handle this module uses. */\nexport interface DownloadTokenWritable {\n setMetadata(update: { metadata: Record<string, string | null> }): Promise<unknown>;\n}\n\n/** The custom-metadata key the Firebase Storage client writes on upload. */\nconst DOWNLOAD_TOKENS_KEY = \"firebaseStorageDownloadTokens\";\n\n/** True when the object's stored metadata still carries a download token. */\nexport function hasDownloadToken(objectMetadata: Readonly<Record<string, unknown>> | undefined): boolean {\n const custom = objectMetadata?.[\"metadata\"];\n return typeof custom === \"object\" && custom !== null && DOWNLOAD_TOKENS_KEY in custom;\n}\n\n/**\n * Remove the object's download token and nothing else: a null custom-metadata value deletes that\n * one key, leaving every other entry. A no-op write when the object has none.\n */\nexport async function removeDownloadToken(file: DownloadTokenWritable): Promise<void> {\n await file.setMetadata({ metadata: { [DOWNLOAD_TOKENS_KEY]: null } });\n}\n"]}
@@ -3,4 +3,5 @@ export * from "./firebase-media-io.js";
3
3
  export * from "./pending-media.js";
4
4
  export * from "./staged-object.js";
5
5
  export * from "./gcs-errors.js";
6
+ export * from "./download-token.js";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
@@ -3,4 +3,5 @@ export * from "./firebase-media-io.js";
3
3
  export * from "./pending-media.js";
4
4
  export * from "./staged-object.js";
5
5
  export * from "./gcs-errors.js";
6
+ export * from "./download-token.js";
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC","sourcesContent":["export * from \"./storage-ops.js\";\nexport * from \"./firebase-media-io.js\";\nexport * from \"./pending-media.js\";\nexport * from \"./staged-object.js\";\nexport * from \"./gcs-errors.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC","sourcesContent":["export * from \"./storage-ops.js\";\nexport * from \"./firebase-media-io.js\";\nexport * from \"./pending-media.js\";\nexport * from \"./staged-object.js\";\nexport * from \"./gcs-errors.js\";\nexport * from \"./download-token.js\";\n"]}
@@ -1,4 +1,6 @@
1
1
  export interface RunCmdResult {
2
+ /** The exit code; `-1` when the process was ended by a signal, so a child
3
+ * killed partway (a crash, an outside kill) never reads as a clean exit. */
2
4
  code: number;
3
5
  stdout: string;
4
6
  stderr: string;
@@ -24,15 +26,21 @@ export interface RunFfmpegOptions {
24
26
  /** Which phase to report in callbacks. */
25
27
  phase?: FfmpegProgressPhase;
26
28
  }
29
+ /** Wall-clock ceiling for an ffprobe call that opens a file and reads its
30
+ * stream table (metadata only, never every packet). */
31
+ export declare const DEFAULT_PROBE_TIMEOUT_MS = 20000;
27
32
  export declare function runCmd(cmd: string, args: string[], opts?: {
28
33
  cwd?: string;
29
34
  /** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the
30
35
  * result resolves with `timedOut: true`. A malformed/adversarial input
31
- * must never be able to wedge a warm instance (canonical-upload-content-
32
- * classification hardening, 2026-07-31). */
36
+ * must never be able to wedge a warm instance. */
33
37
  timeoutMs?: number;
34
38
  /** Abort kills the child immediately (same SIGKILL path as the timeout). */
35
39
  signal?: AbortSignal;
40
+ /** Receives stdout one line at a time instead of capturing it, so a
41
+ * listing of any length is read in bounded memory; `stdout` is then
42
+ * empty. A single line over the capture cap sets `truncated`. */
43
+ onStdoutLine?: (line: string) => void;
36
44
  }): Promise<RunCmdResult>;
37
45
  /**
38
46
  * Runs ffmpeg with optional AbortSignal + progress reporting.
@@ -1 +1 @@
1
- {"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;uDAEmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;2EACuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,KAAK,EAAE,mBAAmB,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,0CAA0C;IAC1C,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC7B;AAOD,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;iDAG6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GACA,OAAO,CAAC,YAAY,CAAC,CAoDvB;AA4CD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuFxF;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CACpD;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAC/D,CAcA"}
1
+ {"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B;iFAC6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;uDAEmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;2EACuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,KAAK,EAAE,mBAAmB,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,0CAA0C;IAC1C,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC7B;AAID;wDACwD;AACxD,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAK/C,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;uDAEmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;sEAEkE;IAClE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,GACA,OAAO,CAAC,YAAY,CAAC,CAwEvB;AA4CD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuFxF;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CACpD;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAC/D,CAcA"}
@@ -1,5 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  const MAX_CAPTURE_BYTES = 1024 * 1024; // 1MB per stream
3
+ /** Wall-clock ceiling for an ffprobe call that opens a file and reads its
4
+ * stream table (metadata only, never every packet). */
5
+ export const DEFAULT_PROBE_TIMEOUT_MS = 20_000;
3
6
  let ffmpegChecked = null;
4
7
  let ffmpegVersion = null;
5
8
  export function runCmd(cmd, args, opts) {
@@ -33,7 +36,29 @@ export function runCmd(cmd, args, opts) {
33
36
  else
34
37
  opts.signal.addEventListener("abort", onAbort, { once: true });
35
38
  }
39
+ const onLine = opts?.onStdoutLine;
40
+ let lineBuf = "";
41
+ // Decodes across chunk boundaries, so a multi-byte character split
42
+ // between two chunks reaches the line intact.
43
+ if (onLine)
44
+ p.stdout.setEncoding("utf8");
36
45
  p.stdout.on("data", (d) => {
46
+ if (onLine) {
47
+ if (truncated)
48
+ return;
49
+ const lines = (lineBuf + String(d)).split(/\r?\n/);
50
+ lineBuf = lines.pop() ?? "";
51
+ for (const line of [...lines, lineBuf]) {
52
+ if (line.length > MAX_CAPTURE_BYTES) {
53
+ truncated = true;
54
+ lineBuf = "";
55
+ return;
56
+ }
57
+ }
58
+ for (const line of lines)
59
+ onLine(line);
60
+ return;
61
+ }
37
62
  if (stdout.length < MAX_CAPTURE_BYTES)
38
63
  stdout += String(d);
39
64
  else
@@ -57,7 +82,9 @@ export function runCmd(cmd, args, opts) {
57
82
  clearTimeout(timer);
58
83
  if (opts?.signal)
59
84
  opts.signal.removeEventListener("abort", onAbort);
60
- resolve({ code: code ?? 0, stdout, stderr, ...(timedOut ? { timedOut } : {}), ...(truncated ? { truncated } : {}) });
85
+ if (onLine && !truncated && lineBuf)
86
+ onLine(lineBuf);
87
+ resolve({ code: code ?? -1, stdout, stderr, ...(timedOut ? { timedOut } : {}), ...(truncated ? { truncated } : {}) });
61
88
  });
62
89
  });
63
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\n\nexport interface RunCmdResult {\n code: number;\n stdout: string;\n stderr: string;\n /** True when the process was killed by the wall-clock timeout (see runCmd\n * opts.timeoutMs). Callers must treat a timed-out result as INVALID —\n * never parse its partial output as an answer. */\n timedOut?: boolean;\n /** True when a captured stream hit the 1 MiB cap — the output is truncated\n * and must not be parsed as complete (e.g. incomplete probe JSON). */\n truncated?: boolean;\n}\n\nexport type FfmpegProgressPhase = \"transcode\" | \"poster\";\n\nexport interface RunFfmpegOptions {\n cwd?: string;\n signal?: AbortSignal;\n /** Optional total duration in milliseconds (used to compute percent). */\n durationMs?: number;\n onProgress?: (p: { phase: FfmpegProgressPhase; percent?: number; outTimeMs?: number }) => void;\n /** Which phase to report in callbacks. */\n phase?: FfmpegProgressPhase;\n}\n\nconst MAX_CAPTURE_BYTES = 1024 * 1024; // 1MB per stream\n\nlet ffmpegChecked: boolean | null = null;\nlet ffmpegVersion: string | null = null;\n\nexport function runCmd(\n cmd: string,\n args: string[],\n opts?: {\n cwd?: string;\n /** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the\n * result resolves with `timedOut: true`. A malformed/adversarial input\n * must never be able to wedge a warm instance (canonical-upload-content-\n * classification hardening, 2026-07-31). */\n timeoutMs?: number;\n /** Abort kills the child immediately (same SIGKILL path as the timeout). */\n signal?: AbortSignal;\n }\n): Promise<RunCmdResult> {\n return new Promise((resolve, reject) => {\n const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let truncated = false;\n\n const kill = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {\n /* already dead */\n }\n };\n const timer =\n opts?.timeoutMs && opts.timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n kill();\n }, opts.timeoutMs)\n : null;\n const onAbort = () => {\n timedOut = true; // treated identically: the result is invalid\n kill();\n };\n if (opts?.signal) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n p.stdout.on(\"data\", (d) => {\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += String(d);\n else truncated = true;\n });\n p.stderr.on(\"data\", (d) => {\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += String(d);\n else truncated = true;\n });\n\n p.on(\"error\", (e) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n p.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n resolve({ code: code ?? 0, stdout, stderr, ...(timedOut ? { timedOut } : {}), ...(truncated ? { truncated } : {}) });\n });\n });\n}\n\nfunction abortError(): DOMException {\n return new DOMException(\"Aborted\", \"AbortError\");\n}\n\n// NOTE: ffmpeg prints \"out_time_ms\" but it is microseconds in known builds.\n// We harden parsing and sanity-check values.\nfunction parseOutTimeMicroseconds(value: string): number | undefined {\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) return undefined;\n\n // Discard absurd values (100 years in microseconds).\n const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;\n if (n > MAX_REASONABLE_US) return undefined;\n\n return n;\n}\n\nfunction parseOutTimeHmsToMs(value: string): number | undefined {\n // \"HH:MM:SS.micro\" (micro may be omitted)\n const m = String(value).trim().match(/^(\\d+):([0-5]\\d):([0-5]\\d)(?:\\.(\\d{1,6}))?$/);\n if (!m) return undefined;\n\n const hh = Number(m[1]);\n const mm = Number(m[2]);\n const ss = Number(m[3]);\n if (!Number.isFinite(hh) || !Number.isFinite(mm) || !Number.isFinite(ss)) return undefined;\n\n const frac = m[4] ?? \"\";\n const us = frac ? Number((frac + \"000000\").slice(0, 6)) : 0;\n if (!Number.isFinite(us) || us < 0) return undefined;\n\n const totalMs = (hh * 3600 + mm * 60 + ss) * 1000 + Math.floor(us / 1000);\n return totalMs >= 0 ? totalMs : undefined;\n}\n\nfunction clamp01(n: number) {\n if (!Number.isFinite(n)) return undefined;\n if (n < 0) return 0;\n if (n > 1) return 1;\n return n;\n}\n\n/**\n * Runs ffmpeg with optional AbortSignal + progress reporting.\n * If onProgress is provided, ffmpeg is invoked with \"-progress pipe:1\".\n */\nexport function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunCmdResult> {\n const phase: FfmpegProgressPhase = opts?.phase ?? \"transcode\";\n const withProgress = !!opts?.onProgress;\n const ffArgs = withProgress ? [\"-progress\", \"pipe:1\", \"-nostats\", ...args] : args;\n\n return new Promise((resolve, reject) => {\n if (opts?.signal?.aborted) return reject(abortError());\n\n const p = spawn(\"ffmpeg\", ffArgs, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let progressBuf = \"\";\n\n // keep progress monotonic\n let lastOutTimeMs: number | undefined;\n\n const onAbort = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {}\n };\n opts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n p.stdout.on(\"data\", (d) => {\n const s = String(d);\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += s;\n\n if (!withProgress) return;\n\n progressBuf += s;\n const lines = progressBuf.split(/\\r?\\n/);\n progressBuf = lines.pop() ?? \"\";\n\n let outTimeMs: number | undefined;\n\n for (const line of lines) {\n const idx = line.indexOf(\"=\");\n if (idx <= 0) continue;\n\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n\n if (k === \"out_time_ms\") {\n const us = parseOutTimeMicroseconds(v);\n if (us !== undefined) outTimeMs = Math.floor(us / 1000);\n } else if (k === \"out_time\" && outTimeMs == null) {\n const ms = parseOutTimeHmsToMs(v);\n if (ms !== undefined) outTimeMs = ms;\n } else if (k === \"progress\" && v === \"end\") {\n opts?.onProgress?.({ phase, percent: 1, outTimeMs: lastOutTimeMs });\n }\n }\n\n if (outTimeMs != null) {\n // monotonic guard\n if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {\n lastOutTimeMs = outTimeMs;\n }\n\n const dur = opts?.durationMs;\n const percent =\n dur && dur > 0 && lastOutTimeMs != null\n ? // clamp to <1 during processing (end event sends 1)\n clamp01(Math.min(0.999, lastOutTimeMs / dur))\n : undefined;\n\n opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });\n }\n });\n\n p.stderr.on(\"data\", (d) => {\n const s = String(d);\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += s;\n });\n\n p.on(\"error\", (e) => {\n opts?.signal?.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n\n p.on(\"close\", (code) => {\n opts?.signal?.removeEventListener(\"abort\", onAbort);\n if (opts?.signal?.aborted) return reject(abortError());\n resolve({ code: code ?? 0, stdout, stderr });\n });\n });\n}\n\nexport async function ensureFfmpegAvailable(): Promise<\n { ok: true; version?: string } | { ok: false; error: unknown }\n> {\n if (ffmpegChecked) return { ok: true, version: ffmpegVersion ?? undefined };\n\n try {\n const r = await runCmd(\"ffmpeg\", [\"-version\"]);\n if (r.code === 0) {\n ffmpegChecked = true;\n ffmpegVersion = (r.stdout || r.stderr || \"\").split(/\\r?\\n/)[0] || null;\n return { ok: true, version: ffmpegVersion ?? undefined };\n }\n return { ok: false, error: new Error(\"ffmpeg not available\") };\n } catch (e) {\n return { ok: false, error: e };\n }\n}\n"]}
1
+ {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\n\nexport interface RunCmdResult {\n /** The exit code; `-1` when the process was ended by a signal, so a child\n * killed partway (a crash, an outside kill) never reads as a clean exit. */\n code: number;\n stdout: string;\n stderr: string;\n /** True when the process was killed by the wall-clock timeout (see runCmd\n * opts.timeoutMs). Callers must treat a timed-out result as INVALID —\n * never parse its partial output as an answer. */\n timedOut?: boolean;\n /** True when a captured stream hit the 1 MiB cap — the output is truncated\n * and must not be parsed as complete (e.g. incomplete probe JSON). */\n truncated?: boolean;\n}\n\nexport type FfmpegProgressPhase = \"transcode\" | \"poster\";\n\nexport interface RunFfmpegOptions {\n cwd?: string;\n signal?: AbortSignal;\n /** Optional total duration in milliseconds (used to compute percent). */\n durationMs?: number;\n onProgress?: (p: { phase: FfmpegProgressPhase; percent?: number; outTimeMs?: number }) => void;\n /** Which phase to report in callbacks. */\n phase?: FfmpegProgressPhase;\n}\n\nconst MAX_CAPTURE_BYTES = 1024 * 1024; // 1MB per stream\n\n/** Wall-clock ceiling for an ffprobe call that opens a file and reads its\n * stream table (metadata only, never every packet). */\nexport const DEFAULT_PROBE_TIMEOUT_MS = 20_000;\n\nlet ffmpegChecked: boolean | null = null;\nlet ffmpegVersion: string | null = null;\n\nexport function runCmd(\n cmd: string,\n args: string[],\n opts?: {\n cwd?: string;\n /** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the\n * result resolves with `timedOut: true`. A malformed/adversarial input\n * must never be able to wedge a warm instance. */\n timeoutMs?: number;\n /** Abort kills the child immediately (same SIGKILL path as the timeout). */\n signal?: AbortSignal;\n /** Receives stdout one line at a time instead of capturing it, so a\n * listing of any length is read in bounded memory; `stdout` is then\n * empty. A single line over the capture cap sets `truncated`. */\n onStdoutLine?: (line: string) => void;\n }\n): Promise<RunCmdResult> {\n return new Promise((resolve, reject) => {\n const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let truncated = false;\n\n const kill = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {\n /* already dead */\n }\n };\n const timer =\n opts?.timeoutMs && opts.timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n kill();\n }, opts.timeoutMs)\n : null;\n const onAbort = () => {\n timedOut = true; // treated identically: the result is invalid\n kill();\n };\n if (opts?.signal) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n const onLine = opts?.onStdoutLine;\n let lineBuf = \"\";\n // Decodes across chunk boundaries, so a multi-byte character split\n // between two chunks reaches the line intact.\n if (onLine) p.stdout.setEncoding(\"utf8\");\n p.stdout.on(\"data\", (d) => {\n if (onLine) {\n if (truncated) return;\n const lines = (lineBuf + String(d)).split(/\\r?\\n/);\n lineBuf = lines.pop() ?? \"\";\n for (const line of [...lines, lineBuf]) {\n if (line.length > MAX_CAPTURE_BYTES) {\n truncated = true;\n lineBuf = \"\";\n return;\n }\n }\n for (const line of lines) onLine(line);\n return;\n }\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += String(d);\n else truncated = true;\n });\n p.stderr.on(\"data\", (d) => {\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += String(d);\n else truncated = true;\n });\n\n p.on(\"error\", (e) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n p.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n if (onLine && !truncated && lineBuf) onLine(lineBuf);\n resolve({ code: code ?? -1, stdout, stderr, ...(timedOut ? { timedOut } : {}), ...(truncated ? { truncated } : {}) });\n });\n });\n}\n\nfunction abortError(): DOMException {\n return new DOMException(\"Aborted\", \"AbortError\");\n}\n\n// NOTE: ffmpeg prints \"out_time_ms\" but it is microseconds in known builds.\n// We harden parsing and sanity-check values.\nfunction parseOutTimeMicroseconds(value: string): number | undefined {\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) return undefined;\n\n // Discard absurd values (100 years in microseconds).\n const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;\n if (n > MAX_REASONABLE_US) return undefined;\n\n return n;\n}\n\nfunction parseOutTimeHmsToMs(value: string): number | undefined {\n // \"HH:MM:SS.micro\" (micro may be omitted)\n const m = String(value).trim().match(/^(\\d+):([0-5]\\d):([0-5]\\d)(?:\\.(\\d{1,6}))?$/);\n if (!m) return undefined;\n\n const hh = Number(m[1]);\n const mm = Number(m[2]);\n const ss = Number(m[3]);\n if (!Number.isFinite(hh) || !Number.isFinite(mm) || !Number.isFinite(ss)) return undefined;\n\n const frac = m[4] ?? \"\";\n const us = frac ? Number((frac + \"000000\").slice(0, 6)) : 0;\n if (!Number.isFinite(us) || us < 0) return undefined;\n\n const totalMs = (hh * 3600 + mm * 60 + ss) * 1000 + Math.floor(us / 1000);\n return totalMs >= 0 ? totalMs : undefined;\n}\n\nfunction clamp01(n: number) {\n if (!Number.isFinite(n)) return undefined;\n if (n < 0) return 0;\n if (n > 1) return 1;\n return n;\n}\n\n/**\n * Runs ffmpeg with optional AbortSignal + progress reporting.\n * If onProgress is provided, ffmpeg is invoked with \"-progress pipe:1\".\n */\nexport function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunCmdResult> {\n const phase: FfmpegProgressPhase = opts?.phase ?? \"transcode\";\n const withProgress = !!opts?.onProgress;\n const ffArgs = withProgress ? [\"-progress\", \"pipe:1\", \"-nostats\", ...args] : args;\n\n return new Promise((resolve, reject) => {\n if (opts?.signal?.aborted) return reject(abortError());\n\n const p = spawn(\"ffmpeg\", ffArgs, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let progressBuf = \"\";\n\n // keep progress monotonic\n let lastOutTimeMs: number | undefined;\n\n const onAbort = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {}\n };\n opts?.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n p.stdout.on(\"data\", (d) => {\n const s = String(d);\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += s;\n\n if (!withProgress) return;\n\n progressBuf += s;\n const lines = progressBuf.split(/\\r?\\n/);\n progressBuf = lines.pop() ?? \"\";\n\n let outTimeMs: number | undefined;\n\n for (const line of lines) {\n const idx = line.indexOf(\"=\");\n if (idx <= 0) continue;\n\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n\n if (k === \"out_time_ms\") {\n const us = parseOutTimeMicroseconds(v);\n if (us !== undefined) outTimeMs = Math.floor(us / 1000);\n } else if (k === \"out_time\" && outTimeMs == null) {\n const ms = parseOutTimeHmsToMs(v);\n if (ms !== undefined) outTimeMs = ms;\n } else if (k === \"progress\" && v === \"end\") {\n opts?.onProgress?.({ phase, percent: 1, outTimeMs: lastOutTimeMs });\n }\n }\n\n if (outTimeMs != null) {\n // monotonic guard\n if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {\n lastOutTimeMs = outTimeMs;\n }\n\n const dur = opts?.durationMs;\n const percent =\n dur && dur > 0 && lastOutTimeMs != null\n ? // clamp to <1 during processing (end event sends 1)\n clamp01(Math.min(0.999, lastOutTimeMs / dur))\n : undefined;\n\n opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });\n }\n });\n\n p.stderr.on(\"data\", (d) => {\n const s = String(d);\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += s;\n });\n\n p.on(\"error\", (e) => {\n opts?.signal?.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n\n p.on(\"close\", (code) => {\n opts?.signal?.removeEventListener(\"abort\", onAbort);\n if (opts?.signal?.aborted) return reject(abortError());\n resolve({ code: code ?? 0, stdout, stderr });\n });\n });\n}\n\nexport async function ensureFfmpegAvailable(): Promise<\n { ok: true; version?: string } | { ok: false; error: unknown }\n> {\n if (ffmpegChecked) return { ok: true, version: ffmpegVersion ?? undefined };\n\n try {\n const r = await runCmd(\"ffmpeg\", [\"-version\"]);\n if (r.code === 0) {\n ffmpegChecked = true;\n ffmpegVersion = (r.stdout || r.stderr || \"\").split(/\\r?\\n/)[0] || null;\n return { ok: true, version: ffmpegVersion ?? undefined };\n }\n return { ok: false, error: new Error(\"ffmpeg not available\") };\n } catch (e) {\n return { ok: false, error: e };\n }\n}\n"]}
@@ -1,8 +1,9 @@
1
+ import { type MeasureDurationOptions } from "../duration/media-duration.js";
1
2
  export interface VideoProbe {
2
3
  durationSec?: number;
3
4
  width?: number;
4
5
  height?: number;
5
6
  hasAudio?: boolean;
6
7
  }
7
- export declare function probeVideo(inputPath: string): Promise<VideoProbe>;
8
+ export declare function probeVideo(inputPath: string, opts?: MeasureDurationOptions): Promise<VideoProbe>;
8
9
  //# sourceMappingURL=probe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsCvE"}
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4BtG"}
@@ -1,30 +1,21 @@
1
- import { runCmd } from "./ffmpeg.js";
2
- export async function probeVideo(inputPath) {
1
+ import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from "./ffmpeg.js";
2
+ import { measureDurationSec } from "../duration/media-duration.js";
3
+ export async function probeVideo(inputPath, opts) {
3
4
  // ffprobe must exist in runtime environment
4
- const args = [
5
- "-v",
6
- "error",
7
- "-print_format",
8
- "json",
9
- "-show_streams",
10
- "-show_format",
11
- inputPath,
12
- ];
13
- const r = await runCmd("ffprobe", args);
14
- if (r.code !== 0) {
5
+ const args = ["-v", "error", "-print_format", "json", "-show_streams", inputPath];
6
+ const r = await runCmd("ffprobe", args, { timeoutMs: DEFAULT_PROBE_TIMEOUT_MS, signal: opts?.signal });
7
+ if (r.timedOut || r.code !== 0) {
15
8
  throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);
16
9
  }
17
10
  const json = JSON.parse(r.stdout || "{}");
18
11
  const streams = Array.isArray(json.streams) ? json.streams : [];
19
- const format = json.format ?? {};
20
12
  const v = streams.find((s) => s.codec_type === "video");
21
13
  const a = streams.find((s) => s.codec_type === "audio");
22
- const durationStr = format.duration ?? v?.duration ?? a?.duration;
23
- const durationSec = durationStr ? Number(durationStr) : undefined;
14
+ const durationSec = await measureDurationSec(inputPath, opts);
24
15
  const width = v?.width ? Number(v.width) : undefined;
25
16
  const height = v?.height ? Number(v.height) : undefined;
26
17
  return {
27
- durationSec: Number.isFinite(durationSec) ? durationSec : undefined,
18
+ durationSec,
28
19
  width,
29
20
  height,
30
21
  hasAudio: !!a,
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AASrC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,SAAiB;IAChD,4CAA4C;IAC5C,MAAM,IAAI,GAAG;QACX,IAAI;QACJ,OAAO;QACP,eAAe;QACf,MAAM;QACN,eAAe;QACf,cAAc;QACd,SAAS;KACV,CAAC;IAEF,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAExC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAU,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,MAAM,MAAM,GAAQ,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;IAEtC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IAExD,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,EAAE,QAAQ,IAAI,CAAC,EAAE,QAAQ,CAAC;IAClE,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAElE,MAAM,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAExD,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QACnE,KAAK;QACL,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,CAAC;KACd,CAAC;AACJ,CAAC","sourcesContent":["import { runCmd } from \"./ffmpeg.js\";\n\nexport interface VideoProbe {\n durationSec?: number;\n width?: number;\n height?: number;\n hasAudio?: boolean;\n}\n\nexport async function probeVideo(inputPath: string): Promise<VideoProbe> {\n // ffprobe must exist in runtime environment\n const args = [\n \"-v\",\n \"error\",\n \"-print_format\",\n \"json\",\n \"-show_streams\",\n \"-show_format\",\n inputPath,\n ];\n\n const r = await runCmd(\"ffprobe\", args);\n\n if (r.code !== 0) {\n throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);\n }\n\n const json = JSON.parse(r.stdout || \"{}\");\n\n const streams: any[] = Array.isArray(json.streams) ? json.streams : [];\n const format: any = json.format ?? {};\n\n const v = streams.find((s) => s.codec_type === \"video\");\n const a = streams.find((s) => s.codec_type === \"audio\");\n\n const durationStr = format.duration ?? v?.duration ?? a?.duration;\n const durationSec = durationStr ? Number(durationStr) : undefined;\n\n const width = v?.width ? Number(v.width) : undefined;\n const height = v?.height ? Number(v.height) : undefined;\n\n return {\n durationSec: Number.isFinite(durationSec) ? durationSec : undefined,\n width,\n height,\n hasAudio: !!a,\n };\n}\n"]}
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAA+B,MAAM,+BAA+B,CAAC;AAShG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,SAAiB,EAAE,IAA6B;IAC/E,4CAA4C;IAC5C,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAElF,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAEvG,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAU,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IAExD,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAE9D,MAAM,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAExD,OAAO;QACL,WAAW;QACX,KAAK;QACL,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,CAAC;KACd,CAAC;AACJ,CAAC","sourcesContent":["import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from \"./ffmpeg.js\";\nimport { measureDurationSec, type MeasureDurationOptions } from \"../duration/media-duration.js\";\n\nexport interface VideoProbe {\n durationSec?: number;\n width?: number;\n height?: number;\n hasAudio?: boolean;\n}\n\nexport async function probeVideo(inputPath: string, opts?: MeasureDurationOptions): Promise<VideoProbe> {\n // ffprobe must exist in runtime environment\n const args = [\"-v\", \"error\", \"-print_format\", \"json\", \"-show_streams\", inputPath];\n\n const r = await runCmd(\"ffprobe\", args, { timeoutMs: DEFAULT_PROBE_TIMEOUT_MS, signal: opts?.signal });\n\n if (r.timedOut || r.code !== 0) {\n throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);\n }\n\n const json = JSON.parse(r.stdout || \"{}\");\n\n const streams: any[] = Array.isArray(json.streams) ? json.streams : [];\n\n const v = streams.find((s) => s.codec_type === \"video\");\n const a = streams.find((s) => s.codec_type === \"audio\");\n\n const durationSec = await measureDurationSec(inputPath, opts);\n\n const width = v?.width ? Number(v.width) : undefined;\n const height = v?.height ? Number(v.height) : undefined;\n\n return {\n durationSec,\n width,\n height,\n hasAudio: !!a,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"video-processor.d.ts","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,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"}
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;AAOhI,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,CA4QhC"}
@@ -2,6 +2,7 @@ import { mkdir, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { ensureFfmpegAvailable, runFfmpeg } from "./ffmpeg.js";
4
4
  import { probeVideo } from "./probe.js";
5
+ import { unreadableLengthError } from "../duration/media-duration.js";
5
6
  import { safeOutputPathFor } from "../utils/safe-path.js";
6
7
  import { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from "@ttt-productions/media-schemas";
7
8
  function matchMime(accepted, actual) {
@@ -93,13 +94,16 @@ export async function processVideo(spec, ctx, opts) {
93
94
  }
94
95
  const outDir = path.dirname(ctx.outputBasePath);
95
96
  await mkdir(outDir, { recursive: true });
96
- const probe = await probeVideo(ctx.inputPath);
97
+ const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });
97
98
  if (opts?.signal?.aborted) {
98
99
  return { ok: false, mediaType: "video", error: { code: "processing_canceled", message: "Processing canceled." } };
99
100
  }
100
101
  // duration enforcement
101
102
  const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;
102
- if (maxDur && probe.durationSec && probe.durationSec > maxDur) {
103
+ if (maxDur && probe.durationSec === undefined) {
104
+ return { ok: false, mediaType: "video", error: unreadableLengthError("video", maxDur) };
105
+ }
106
+ if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {
103
107
  return {
104
108
  ok: false,
105
109
  mediaType: "video",
@@ -287,6 +291,9 @@ export async function processVideo(spec, ctx, opts) {
287
291
  };
288
292
  }
289
293
  catch (e) {
294
+ if (opts?.signal?.aborted) {
295
+ return { ok: false, mediaType: "video", error: { code: "processing_canceled", message: "Processing canceled." } };
296
+ }
290
297
  return {
291
298
  ok: false,
292
299
  mediaType: "video",