@ttt-productions/media-processing-core 0.8.15 → 0.8.17

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 (132) hide show
  1. package/dist/audio/audio-processor.d.ts +2 -2
  2. package/dist/audio/audio-processor.d.ts.map +1 -1
  3. package/dist/audio/audio-processor.js +62 -48
  4. package/dist/audio/audio-processor.js.map +1 -1
  5. package/dist/audio/probe.d.ts +1 -1
  6. package/dist/audio/probe.d.ts.map +1 -1
  7. package/dist/audio/probe.js +6 -3
  8. package/dist/audio/probe.js.map +1 -1
  9. package/dist/duration/media-duration.d.ts +2 -2
  10. package/dist/duration/media-duration.d.ts.map +1 -1
  11. package/dist/duration/media-duration.js +23 -21
  12. package/dist/duration/media-duration.js.map +1 -1
  13. package/dist/image/formats.d.ts +2 -2
  14. package/dist/image/formats.js +25 -25
  15. package/dist/image/formats.js.map +1 -1
  16. package/dist/image/image-processor.d.ts +2 -2
  17. package/dist/image/image-processor.d.ts.map +1 -1
  18. package/dist/image/image-processor.js +47 -37
  19. package/dist/image/image-processor.js.map +1 -1
  20. package/dist/image/resize.d.ts +2 -2
  21. package/dist/image/resize.d.ts.map +1 -1
  22. package/dist/image/resize.js +13 -13
  23. package/dist/image/resize.js.map +1 -1
  24. package/dist/index.d.ts +19 -16
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +18 -16
  27. package/dist/index.js.map +1 -1
  28. package/dist/inspection/inspect-media.d.ts +13 -13
  29. package/dist/inspection/inspect-media.d.ts.map +1 -1
  30. package/dist/inspection/inspect-media.js +196 -140
  31. package/dist/inspection/inspect-media.js.map +1 -1
  32. package/dist/io/fs.d.ts +1 -1
  33. package/dist/io/fs.js +4 -4
  34. package/dist/io/fs.js.map +1 -1
  35. package/dist/io/local-input.d.ts +1 -1
  36. package/dist/io/local-input.d.ts.map +1 -1
  37. package/dist/io/local-input.js +15 -15
  38. package/dist/io/local-input.js.map +1 -1
  39. package/dist/io/types.d.ts.map +1 -1
  40. package/dist/io/types.js.map +1 -1
  41. package/dist/moderation/merge.d.ts +1 -1
  42. package/dist/moderation/merge.d.ts.map +1 -1
  43. package/dist/moderation/merge.js +1 -1
  44. package/dist/moderation/merge.js.map +1 -1
  45. package/dist/moderation/types.d.ts +3 -3
  46. package/dist/moderation/types.js.map +1 -1
  47. package/dist/process-media.d.ts +2 -3
  48. package/dist/process-media.d.ts.map +1 -1
  49. package/dist/process-media.js +9 -9
  50. package/dist/process-media.js.map +1 -1
  51. package/dist/processing/errors.d.ts.map +1 -1
  52. package/dist/processing/errors.js +1 -1
  53. package/dist/processing/errors.js.map +1 -1
  54. package/dist/processing/result.d.ts +2 -2
  55. package/dist/processing/result.js +1 -1
  56. package/dist/processing/result.js.map +1 -1
  57. package/dist/run-pipeline.d.ts +5 -5
  58. package/dist/run-pipeline.d.ts.map +1 -1
  59. package/dist/run-pipeline.js +70 -54
  60. package/dist/run-pipeline.js.map +1 -1
  61. package/dist/server/download-token.d.ts +14 -0
  62. package/dist/server/download-token.d.ts.map +1 -0
  63. package/dist/server/download-token.js +19 -0
  64. package/dist/server/download-token.js.map +1 -0
  65. package/dist/server/firebase-media-io.d.ts +2 -2
  66. package/dist/server/firebase-media-io.js +1 -1
  67. package/dist/server/firebase-media-io.js.map +1 -1
  68. package/dist/server/gcs-errors.js +2 -2
  69. package/dist/server/gcs-errors.js.map +1 -1
  70. package/dist/server/index.d.ts +7 -5
  71. package/dist/server/index.d.ts.map +1 -1
  72. package/dist/server/index.js +7 -5
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/server/pending-media.d.ts +2 -2
  75. package/dist/server/pending-media.d.ts.map +1 -1
  76. package/dist/server/pending-media.js +12 -6
  77. package/dist/server/pending-media.js.map +1 -1
  78. package/dist/server/r2-retry.d.ts +4 -4
  79. package/dist/server/r2-retry.d.ts.map +1 -1
  80. package/dist/server/r2-retry.js +48 -47
  81. package/dist/server/r2-retry.js.map +1 -1
  82. package/dist/server/staged-object.d.ts +2 -2
  83. package/dist/server/staged-object.d.ts.map +1 -1
  84. package/dist/server/staged-object.js +4 -4
  85. package/dist/server/staged-object.js.map +1 -1
  86. package/dist/server/storage-ops.d.ts +3 -5
  87. package/dist/server/storage-ops.d.ts.map +1 -1
  88. package/dist/server/storage-ops.js +101 -97
  89. package/dist/server/storage-ops.js.map +1 -1
  90. package/dist/types.d.ts +2 -2
  91. package/dist/types.js.map +1 -1
  92. package/dist/utils/log.d.ts.map +1 -1
  93. package/dist/utils/log.js +3 -3
  94. package/dist/utils/log.js.map +1 -1
  95. package/dist/utils/paths.d.ts.map +1 -1
  96. package/dist/utils/paths.js +1 -4
  97. package/dist/utils/paths.js.map +1 -1
  98. package/dist/utils/safe-path.js +8 -8
  99. package/dist/utils/safe-path.js.map +1 -1
  100. package/dist/validation/validate-duration.d.ts.map +1 -1
  101. package/dist/validation/validate-duration.js.map +1 -1
  102. package/dist/validation/validate-mime.d.ts.map +1 -1
  103. package/dist/validation/validate-mime.js.map +1 -1
  104. package/dist/validation/validate-size.d.ts.map +1 -1
  105. package/dist/validation/validate-size.js.map +1 -1
  106. package/dist/video/ffmpeg.d.ts +1 -1
  107. package/dist/video/ffmpeg.d.ts.map +1 -1
  108. package/dist/video/ffmpeg.js +51 -43
  109. package/dist/video/ffmpeg.js.map +1 -1
  110. package/dist/video/probe.d.ts +1 -1
  111. package/dist/video/probe.d.ts.map +1 -1
  112. package/dist/video/probe.js +10 -7
  113. package/dist/video/probe.js.map +1 -1
  114. package/dist/video/video-processor.d.ts +2 -2
  115. package/dist/video/video-processor.d.ts.map +1 -1
  116. package/dist/video/video-processor.js +123 -81
  117. package/dist/video/video-processor.js.map +1 -1
  118. package/dist/workspace/temp.js +4 -4
  119. package/dist/workspace/temp.js.map +1 -1
  120. package/package.json +2 -2
  121. package/dist/audio/index.d.ts +0 -3
  122. package/dist/audio/index.d.ts.map +0 -1
  123. package/dist/audio/index.js +0 -3
  124. package/dist/audio/index.js.map +0 -1
  125. package/dist/image/index.d.ts +0 -4
  126. package/dist/image/index.d.ts.map +0 -1
  127. package/dist/image/index.js +0 -4
  128. package/dist/image/index.js.map +0 -1
  129. package/dist/video/index.d.ts +0 -4
  130. package/dist/video/index.d.ts.map +0 -1
  131. package/dist/video/index.js +0 -4
  132. package/dist/video/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\n\nexport interface RunCmdResult {\n /** The exit code; `-1` when the process was ended by a signal, so a child\n * killed partway (a crash, an outside kill) never reads as a clean exit. */\n code: number;\n stdout: string;\n stderr: string;\n /** True when the process was killed by the wall-clock timeout (see runCmd\n * opts.timeoutMs). Callers must treat a timed-out result as INVALID —\n * never parse its partial output as an answer. */\n timedOut?: boolean;\n /** True when a captured stream hit the 1 MiB cap — the output is truncated\n * and must not be parsed as complete (e.g. incomplete probe JSON). */\n truncated?: boolean;\n}\n\nexport type FfmpegProgressPhase = \"transcode\" | \"poster\";\n\nexport interface RunFfmpegOptions {\n cwd?: string;\n signal?: AbortSignal;\n /** Optional total duration in milliseconds (used to compute percent). */\n durationMs?: number;\n onProgress?: (p: { phase: FfmpegProgressPhase; percent?: number; outTimeMs?: number }) => void;\n /** Which phase to report in callbacks. */\n phase?: FfmpegProgressPhase;\n}\n\nconst MAX_CAPTURE_BYTES = 1024 * 1024; // 1MB per stream\n\n/** Wall-clock ceiling for an ffprobe call that opens a file and reads its\n * stream table (metadata only, never every packet). */\nexport const DEFAULT_PROBE_TIMEOUT_MS = 20_000;\n\nlet ffmpegChecked: boolean | null = null;\nlet ffmpegVersion: string | null = null;\n\nexport function runCmd(\n cmd: string,\n args: string[],\n opts?: {\n cwd?: string;\n /** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the\n * result resolves with `timedOut: true`. A malformed/adversarial input\n * must never be able to wedge a warm instance. */\n timeoutMs?: number;\n /** Abort kills the child immediately (same SIGKILL path as the timeout). */\n signal?: AbortSignal;\n /** Receives stdout one line at a time instead of capturing it, so a\n * listing of any length is read in bounded memory; `stdout` is then\n * empty. A single line over the capture cap sets `truncated`. */\n onStdoutLine?: (line: string) => void;\n }\n): Promise<RunCmdResult> {\n return new Promise((resolve, reject) => {\n const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let truncated = false;\n\n const kill = () => {\n try {\n p.kill(\"SIGKILL\");\n } catch {\n /* already dead */\n }\n };\n const timer =\n opts?.timeoutMs && opts.timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n kill();\n }, opts.timeoutMs)\n : null;\n const onAbort = () => {\n timedOut = true; // treated identically: the result is invalid\n kill();\n };\n if (opts?.signal) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n const onLine = opts?.onStdoutLine;\n let lineBuf = \"\";\n // Decodes across chunk boundaries, so a multi-byte character split\n // between two chunks reaches the line intact.\n if (onLine) p.stdout.setEncoding(\"utf8\");\n p.stdout.on(\"data\", (d) => {\n if (onLine) {\n if (truncated) return;\n const lines = (lineBuf + String(d)).split(/\\r?\\n/);\n lineBuf = lines.pop() ?? \"\";\n for (const line of [...lines, lineBuf]) {\n if (line.length > MAX_CAPTURE_BYTES) {\n truncated = true;\n lineBuf = \"\";\n return;\n }\n }\n for (const line of lines) onLine(line);\n return;\n }\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += String(d);\n else truncated = true;\n });\n p.stderr.on(\"data\", (d) => {\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += String(d);\n else truncated = true;\n });\n\n p.on(\"error\", (e) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n reject(e);\n });\n p.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n if (onLine && !truncated && lineBuf) onLine(lineBuf);\n resolve({ 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
+ {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/video/ffmpeg.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from 'node:child_process';\n\nexport interface RunCmdResult {\n /** The exit code; `-1` when the process was ended by a signal, so a child\n * killed partway (a crash, an outside kill) never reads as a clean exit. */\n code: number;\n stdout: string;\n stderr: string;\n /** True when the process was killed by the wall-clock timeout (see runCmd\n * opts.timeoutMs). Callers must treat a timed-out result as INVALID —\n * never parse its partial output as an answer. */\n timedOut?: boolean;\n /** True when a captured stream hit the 1 MiB cap — the output is truncated\n * and must not be parsed as complete (e.g. incomplete probe JSON). */\n truncated?: boolean;\n}\n\nexport type FfmpegProgressPhase = 'transcode' | 'poster';\n\nexport interface RunFfmpegOptions {\n cwd?: string;\n signal?: AbortSignal;\n /** Optional total duration in milliseconds (used to compute percent). */\n durationMs?: number;\n onProgress?: (p: { phase: FfmpegProgressPhase; percent?: number; outTimeMs?: number }) => void;\n /** Which phase to report in callbacks. */\n phase?: FfmpegProgressPhase;\n}\n\nconst MAX_CAPTURE_BYTES = 1024 * 1024; // 1MB per stream\n\n/** Wall-clock ceiling for an ffprobe call that opens a file and reads its\n * stream table (metadata only, never every packet). */\nexport const DEFAULT_PROBE_TIMEOUT_MS = 20_000;\n\nlet ffmpegChecked: boolean | null = null;\nlet ffmpegVersion: string | null = null;\n\nexport function runCmd(\n cmd: string,\n args: string[],\n opts?: {\n cwd?: string;\n /** Hard wall-clock ceiling; on expiry the child is SIGKILLed and the\n * result resolves with `timedOut: true`. A malformed/adversarial input\n * must never be able to wedge a warm instance. */\n timeoutMs?: number;\n /** Abort kills the child immediately (same SIGKILL path as the timeout). */\n signal?: AbortSignal;\n /** Receives stdout one line at a time instead of capturing it, so a\n * listing of any length is read in bounded memory; `stdout` is then\n * empty. A single line over the capture cap sets `truncated`. */\n onStdoutLine?: (line: string) => void;\n },\n): Promise<RunCmdResult> {\n return new Promise((resolve, reject) => {\n const p = spawn(cmd, args, { cwd: opts?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n\n let stdout = '';\n let stderr = '';\n let timedOut = false;\n let truncated = false;\n\n const kill = () => {\n try {\n p.kill('SIGKILL');\n } catch {\n /* already dead */\n }\n };\n const timer =\n opts?.timeoutMs && opts.timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n kill();\n }, opts.timeoutMs)\n : null;\n const onAbort = () => {\n timedOut = true; // treated identically: the result is invalid\n kill();\n };\n if (opts?.signal) {\n if (opts.signal.aborted) onAbort();\n else opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n const onLine = opts?.onStdoutLine;\n let lineBuf = '';\n // Decodes across chunk boundaries, so a multi-byte character split\n // between two chunks reaches the line intact.\n if (onLine) p.stdout.setEncoding('utf8');\n p.stdout.on('data', (d) => {\n if (onLine) {\n if (truncated) return;\n const lines = (lineBuf + String(d)).split(/\\r?\\n/);\n lineBuf = lines.pop() ?? '';\n for (const line of [...lines, lineBuf]) {\n if (line.length > MAX_CAPTURE_BYTES) {\n truncated = true;\n lineBuf = '';\n return;\n }\n }\n for (const line of lines) onLine(line);\n return;\n }\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += String(d);\n else truncated = true;\n });\n p.stderr.on('data', (d) => {\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += String(d);\n else truncated = true;\n });\n\n p.on('error', (e) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener('abort', onAbort);\n reject(e);\n });\n p.on('close', (code) => {\n if (timer) clearTimeout(timer);\n if (opts?.signal) opts.signal.removeEventListener('abort', onAbort);\n if (onLine && !truncated && lineBuf) onLine(lineBuf);\n resolve({\n code: code ?? -1,\n stdout,\n stderr,\n ...(timedOut ? { timedOut } : {}),\n ...(truncated ? { truncated } : {}),\n });\n });\n });\n}\n\nfunction abortError(): DOMException {\n return new DOMException('Aborted', 'AbortError');\n}\n\n// NOTE: ffmpeg prints \"out_time_ms\" but it is microseconds in known builds.\n// We harden parsing and sanity-check values.\nfunction parseOutTimeMicroseconds(value: string): number | undefined {\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) return undefined;\n\n // Discard absurd values (100 years in microseconds).\n const MAX_REASONABLE_US = 100 * 365 * 24 * 60 * 60 * 1_000_000;\n if (n > MAX_REASONABLE_US) return undefined;\n\n return n;\n}\n\nfunction parseOutTimeHmsToMs(value: string): number | undefined {\n // \"HH:MM:SS.micro\" (micro may be omitted)\n const m = String(value)\n .trim()\n .match(/^(\\d+):([0-5]\\d):([0-5]\\d)(?:\\.(\\d{1,6}))?$/);\n if (!m) return undefined;\n\n const hh = Number(m[1]);\n const mm = Number(m[2]);\n const ss = Number(m[3]);\n if (!Number.isFinite(hh) || !Number.isFinite(mm) || !Number.isFinite(ss)) return undefined;\n\n const frac = m[4] ?? '';\n const us = frac ? Number((frac + '000000').slice(0, 6)) : 0;\n if (!Number.isFinite(us) || us < 0) return undefined;\n\n const totalMs = (hh * 3600 + mm * 60 + ss) * 1000 + Math.floor(us / 1000);\n return totalMs >= 0 ? totalMs : undefined;\n}\n\nfunction clamp01(n: number) {\n if (!Number.isFinite(n)) return undefined;\n if (n < 0) return 0;\n if (n > 1) return 1;\n return n;\n}\n\n/**\n * Runs ffmpeg with optional AbortSignal + progress reporting.\n * If onProgress is provided, ffmpeg is invoked with \"-progress pipe:1\".\n */\nexport function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunCmdResult> {\n const phase: FfmpegProgressPhase = opts?.phase ?? 'transcode';\n const withProgress = !!opts?.onProgress;\n const ffArgs = withProgress ? ['-progress', 'pipe:1', '-nostats', ...args] : args;\n\n return new Promise((resolve, reject) => {\n if (opts?.signal?.aborted) return reject(abortError());\n\n const p = spawn('ffmpeg', ffArgs, { cwd: opts?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n\n let stdout = '';\n let stderr = '';\n let progressBuf = '';\n\n // keep progress monotonic\n let lastOutTimeMs: number | undefined;\n\n const onAbort = () => {\n try {\n p.kill('SIGKILL');\n } catch {}\n };\n opts?.signal?.addEventListener('abort', onAbort, { once: true });\n\n p.stdout.on('data', (d) => {\n const s = String(d);\n if (stdout.length < MAX_CAPTURE_BYTES) stdout += s;\n\n if (!withProgress) return;\n\n progressBuf += s;\n const lines = progressBuf.split(/\\r?\\n/);\n progressBuf = lines.pop() ?? '';\n\n let outTimeMs: number | undefined;\n\n for (const line of lines) {\n const idx = line.indexOf('=');\n if (idx <= 0) continue;\n\n const k = line.slice(0, idx).trim();\n const v = line.slice(idx + 1).trim();\n\n if (k === 'out_time_ms') {\n const us = parseOutTimeMicroseconds(v);\n if (us !== undefined) outTimeMs = Math.floor(us / 1000);\n } else if (k === 'out_time' && outTimeMs == null) {\n const ms = parseOutTimeHmsToMs(v);\n if (ms !== undefined) outTimeMs = ms;\n } else if (k === 'progress' && v === 'end') {\n opts?.onProgress?.({ phase, percent: 1, outTimeMs: lastOutTimeMs });\n }\n }\n\n if (outTimeMs != null) {\n // monotonic guard\n if (lastOutTimeMs == null || outTimeMs >= lastOutTimeMs) {\n lastOutTimeMs = outTimeMs;\n }\n\n const dur = opts?.durationMs;\n const percent =\n dur && dur > 0 && lastOutTimeMs != null\n ? // clamp to <1 during processing (end event sends 1)\n clamp01(Math.min(0.999, lastOutTimeMs / dur))\n : undefined;\n\n opts?.onProgress?.({ phase, percent, outTimeMs: lastOutTimeMs });\n }\n });\n\n p.stderr.on('data', (d) => {\n const s = String(d);\n if (stderr.length < MAX_CAPTURE_BYTES) stderr += s;\n });\n\n p.on('error', (e) => {\n opts?.signal?.removeEventListener('abort', onAbort);\n reject(e);\n });\n\n p.on('close', (code) => {\n opts?.signal?.removeEventListener('abort', onAbort);\n if (opts?.signal?.aborted) return reject(abortError());\n resolve({ code: code ?? 0, stdout, stderr });\n });\n });\n}\n\nexport async function ensureFfmpegAvailable(): Promise<\n { ok: true; version?: string } | { ok: false; error: unknown }\n> {\n if (ffmpegChecked) return { ok: true, version: ffmpegVersion ?? undefined };\n\n try {\n const r = await runCmd('ffmpeg', ['-version']);\n if (r.code === 0) {\n ffmpegChecked = true;\n ffmpegVersion = (r.stdout || r.stderr || '').split(/\\r?\\n/)[0] || null;\n return { ok: true, version: ffmpegVersion ?? undefined };\n }\n return { ok: false, error: new Error('ffmpeg not available') };\n } catch (e) {\n return { ok: false, error: e };\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type MeasureDurationOptions } from "../duration/media-duration.js";
1
+ import { type MeasureDurationOptions } from '../duration/media-duration.js';
2
2
  export interface VideoProbe {
3
3
  durationSec?: number;
4
4
  width?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4BtG"}
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEhG,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,UAAU,CAAC,CA+BrB"}
@@ -1,16 +1,19 @@
1
- import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from "./ffmpeg.js";
2
- import { measureDurationSec } from "../duration/media-duration.js";
1
+ import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from './ffmpeg.js';
2
+ import { measureDurationSec } from '../duration/media-duration.js';
3
3
  export async function probeVideo(inputPath, opts) {
4
4
  // ffprobe must exist in runtime environment
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 });
5
+ const args = ['-v', 'error', '-print_format', 'json', '-show_streams', inputPath];
6
+ const r = await runCmd('ffprobe', args, {
7
+ timeoutMs: DEFAULT_PROBE_TIMEOUT_MS,
8
+ signal: opts?.signal,
9
+ });
7
10
  if (r.timedOut || r.code !== 0) {
8
11
  throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);
9
12
  }
10
- const json = JSON.parse(r.stdout || "{}");
13
+ const json = JSON.parse(r.stdout || '{}');
11
14
  const streams = Array.isArray(json.streams) ? json.streams : [];
12
- const v = streams.find((s) => s.codec_type === "video");
13
- const a = streams.find((s) => s.codec_type === "audio");
15
+ const v = streams.find((s) => s.codec_type === 'video');
16
+ const a = streams.find((s) => s.codec_type === 'audio');
14
17
  const durationSec = await measureDurationSec(inputPath, opts);
15
18
  const width = v?.width ? Number(v.width) : undefined;
16
19
  const height = v?.height ? Number(v.height) : undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAA+B,MAAM,+BAA+B,CAAC;AAShG,MAAM,CAAC,KAAK,UAAU,UAAU,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
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/video/probe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAA+B,MAAM,+BAA+B,CAAC;AAShG,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,SAAiB,EACjB,IAA6B;IAE7B,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;QACtC,SAAS,EAAE,wBAAwB;QACnC,MAAM,EAAE,IAAI,EAAE,MAAM;KACrB,CAAC,CAAC;IAEH,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAU,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;IAExD,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAE9D,MAAM,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAExD,OAAO;QACL,WAAW;QACX,KAAK;QACL,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,CAAC;KACd,CAAC;AACJ,CAAC","sourcesContent":["import { DEFAULT_PROBE_TIMEOUT_MS, runCmd } from './ffmpeg.js';\nimport { measureDurationSec, type MeasureDurationOptions } from '../duration/media-duration.js';\n\nexport interface VideoProbe {\n durationSec?: number;\n width?: number;\n height?: number;\n hasAudio?: boolean;\n}\n\nexport async function probeVideo(\n inputPath: string,\n opts?: MeasureDurationOptions,\n): Promise<VideoProbe> {\n // 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, {\n timeoutMs: DEFAULT_PROBE_TIMEOUT_MS,\n signal: opts?.signal,\n });\n\n if (r.timedOut || r.code !== 0) {\n throw new Error(`ffprobe failed (${r.code}): ${r.stderr || r.stdout}`);\n }\n\n const json = JSON.parse(r.stdout || '{}');\n\n const streams: any[] = Array.isArray(json.streams) ? json.streams : [];\n\n const v = streams.find((s) => s.codec_type === 'video');\n const a = streams.find((s) => s.codec_type === 'audio');\n\n const durationSec = await measureDurationSec(inputPath, opts);\n\n const width = v?.width ? Number(v.width) : undefined;\n const height = v?.height ? Number(v.height) : undefined;\n\n return {\n durationSec,\n width,\n height,\n hasAudio: !!a,\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
- import type { MediaProcessingResult, MediaProcessingSpec } from "@ttt-productions/media-schemas";
2
- import type { ProcessMediaOptions } from "../types.js";
1
+ import type { MediaProcessingResult, MediaProcessingSpec } from '@ttt-productions/media-schemas';
2
+ import type { ProcessMediaOptions } from '../types.js';
3
3
  export declare function processVideo(spec: MediaProcessingSpec, ctx: {
4
4
  inputPath: string;
5
5
  outputBasePath: string;
@@ -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;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"}
1
+ {"version":3,"file":"video-processor.d.ts","sourceRoot":"","sources":["../../src/video/video-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EAEpB,MAAM,gCAAgC,CAAC;AAOxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AA6DvD,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,EACD,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,qBAAqB,CAAC,CAuThC"}
@@ -1,24 +1,24 @@
1
- import { mkdir, stat } from "node:fs/promises";
2
- import path from "node:path";
3
- import { ensureFfmpegAvailable, runFfmpeg } from "./ffmpeg.js";
4
- import { probeVideo } from "./probe.js";
5
- import { unreadableLengthError } from "../duration/media-duration.js";
6
- import { safeOutputPathFor } from "../utils/safe-path.js";
7
- import { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from "@ttt-productions/media-schemas";
1
+ import { mkdir, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureFfmpegAvailable, runFfmpeg } from './ffmpeg.js';
4
+ import { probeVideo } from './probe.js';
5
+ import { unreadableLengthError } from '../duration/media-duration.js';
6
+ import { safeOutputPathFor } from '../utils/safe-path.js';
7
+ import { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY, } from '@ttt-productions/media-schemas';
8
8
  function matchMime(accepted, actual) {
9
9
  const a = accepted.trim().toLowerCase();
10
10
  const m = actual.trim().toLowerCase();
11
11
  if (!a)
12
12
  return true;
13
- if (a === "*/*")
13
+ if (a === '*/*')
14
14
  return true;
15
- if (a.endsWith("/*"))
15
+ if (a.endsWith('/*'))
16
16
  return m.startsWith(a.slice(0, -1));
17
17
  return a === m;
18
18
  }
19
19
  function acceptAllowsVideo(spec) {
20
20
  const kinds = spec.accept?.kinds?.filter(Boolean) ?? [];
21
- return kinds.length === 0 ? true : kinds.includes("video");
21
+ return kinds.length === 0 ? true : kinds.includes('video');
22
22
  }
23
23
  function acceptAllowsMime(spec, actualMime) {
24
24
  const mimes = spec.accept?.mimes?.filter(Boolean) ?? [];
@@ -40,13 +40,13 @@ function orientationOf(width, height) {
40
40
  if (!width || !height)
41
41
  return undefined;
42
42
  if (width === height)
43
- return "any";
44
- return height > width ? "vertical" : "horizontal";
43
+ return 'any';
44
+ return height > width ? 'vertical' : 'horizontal';
45
45
  }
46
46
  function orientationOk(required, actual) {
47
- if (!required || required === "any")
47
+ if (!required || required === 'any')
48
48
  return true;
49
- if (!actual || actual === "any")
49
+ if (!actual || actual === 'any')
50
50
  return true; // treat square/unknown as ok
51
51
  return required === actual;
52
52
  }
@@ -54,41 +54,53 @@ function outputPathFor(base, key, ext) {
54
54
  return safeOutputPathFor(base, key, ext);
55
55
  }
56
56
  function mimeFromExt(ext) {
57
- if (ext === "mp4")
58
- return "video/mp4";
59
- if (ext === "webm")
60
- return "video/webm";
57
+ if (ext === 'mp4')
58
+ return 'video/mp4';
59
+ if (ext === 'webm')
60
+ return 'video/webm';
61
61
  return undefined;
62
62
  }
63
63
  export async function processVideo(spec, ctx, opts) {
64
64
  const tempFiles = [];
65
65
  try {
66
66
  if (opts?.signal?.aborted) {
67
- return { ok: false, mediaType: "video", error: { code: "processing_canceled", message: "Processing canceled." } };
67
+ return {
68
+ ok: false,
69
+ mediaType: 'video',
70
+ error: { code: 'processing_canceled', message: 'Processing canceled.' },
71
+ };
68
72
  }
69
73
  if (!acceptAllowsVideo(spec)) {
70
74
  return {
71
75
  ok: false,
72
- mediaType: "video",
73
- error: { code: "invalid_mime", message: "Spec does not allow video uploads.", details: { accept: spec.accept } },
76
+ mediaType: 'video',
77
+ error: {
78
+ code: 'invalid_mime',
79
+ message: 'Spec does not allow video uploads.',
80
+ details: { accept: spec.accept },
81
+ },
74
82
  };
75
83
  }
76
84
  if (!acceptAllowsMime(spec, ctx.inputMime)) {
77
85
  return {
78
86
  ok: false,
79
- mediaType: "video",
80
- error: { code: "invalid_mime", message: "Video mime type is not allowed.", details: { inputMime: ctx.inputMime, accept: spec.accept } },
87
+ mediaType: 'video',
88
+ error: {
89
+ code: 'invalid_mime',
90
+ message: 'Video mime type is not allowed.',
91
+ details: { inputMime: ctx.inputMime, accept: spec.accept },
92
+ },
81
93
  };
82
94
  }
83
95
  const ff = await ensureFfmpegAvailable();
84
96
  if (!ff.ok) {
85
97
  return {
86
98
  ok: false,
87
- mediaType: "video",
99
+ mediaType: 'video',
88
100
  error: {
89
- code: "processing_failed",
90
- message: "ffmpeg is not available.",
91
- details: { error: String(ff.error ?? "unknown") },
101
+ code: 'processing_failed',
102
+ message: 'ffmpeg is not available.',
103
+ details: { error: String(ff.error ?? 'unknown') },
92
104
  },
93
105
  };
94
106
  }
@@ -96,20 +108,24 @@ export async function processVideo(spec, ctx, opts) {
96
108
  await mkdir(outDir, { recursive: true });
97
109
  const probe = await probeVideo(ctx.inputPath, { signal: opts?.signal });
98
110
  if (opts?.signal?.aborted) {
99
- return { ok: false, mediaType: "video", error: { code: "processing_canceled", message: "Processing canceled." } };
111
+ return {
112
+ ok: false,
113
+ mediaType: 'video',
114
+ error: { code: 'processing_canceled', message: 'Processing canceled.' },
115
+ };
100
116
  }
101
117
  // duration enforcement
102
118
  const maxDur = spec.maxDurationSec ?? spec.video?.maxDurationSec;
103
119
  if (maxDur && probe.durationSec === undefined) {
104
- return { ok: false, mediaType: "video", error: unreadableLengthError("video", maxDur) };
120
+ return { ok: false, mediaType: 'video', error: unreadableLengthError('video', maxDur) };
105
121
  }
106
122
  if (maxDur && probe.durationSec !== undefined && probe.durationSec > maxDur) {
107
123
  return {
108
124
  ok: false,
109
- mediaType: "video",
125
+ mediaType: 'video',
110
126
  error: {
111
- code: "too_long",
112
- message: "Video is too long.",
127
+ code: 'too_long',
128
+ message: 'Video is too long.',
113
129
  details: { durationSec: probe.durationSec, maxDurationSec: maxDur },
114
130
  },
115
131
  };
@@ -123,17 +139,21 @@ export async function processVideo(spec, ctx, opts) {
123
139
  const actualAspect = aspect(probe.width, probe.height);
124
140
  const okOri = orientationOk(requiredOri, actualOri);
125
141
  const tol = spec.aspectRatioTolerance ?? 0.02;
126
- const okAspect = requiredAspect ? (actualAspect ? aspectClose(actualAspect, requiredAspect, tol) : false) : true;
142
+ const okAspect = requiredAspect
143
+ ? actualAspect
144
+ ? aspectClose(actualAspect, requiredAspect, tol)
145
+ : false
146
+ : true;
127
147
  const okDims = !requiredW || !requiredH ? true : probe.width === requiredW && probe.height === requiredH;
128
148
  const needsAuto = !okOri || !okAspect || !okDims;
129
149
  if (needsAuto && !spec.allowAutoFormat) {
130
150
  if (!okOri) {
131
151
  return {
132
152
  ok: false,
133
- mediaType: "video",
153
+ mediaType: 'video',
134
154
  error: {
135
- code: "orientation_mismatch",
136
- message: "Video orientation does not match the required format.",
155
+ code: 'orientation_mismatch',
156
+ message: 'Video orientation does not match the required format.',
137
157
  details: { requiredOri, actualOri, width: probe.width, height: probe.height },
138
158
  },
139
159
  };
@@ -141,38 +161,48 @@ export async function processVideo(spec, ctx, opts) {
141
161
  if (!okAspect) {
142
162
  return {
143
163
  ok: false,
144
- mediaType: "video",
164
+ mediaType: 'video',
145
165
  error: {
146
- code: "aspect_ratio_mismatch",
147
- message: "Video aspect ratio does not match the required format.",
148
- details: { requiredAspectRatio: requiredAspect, aspectRatio: actualAspect, width: probe.width, height: probe.height },
166
+ code: 'aspect_ratio_mismatch',
167
+ message: 'Video aspect ratio does not match the required format.',
168
+ details: {
169
+ requiredAspectRatio: requiredAspect,
170
+ aspectRatio: actualAspect,
171
+ width: probe.width,
172
+ height: probe.height,
173
+ },
149
174
  },
150
175
  };
151
176
  }
152
177
  if (!okDims) {
153
178
  return {
154
179
  ok: false,
155
- mediaType: "video",
180
+ mediaType: 'video',
156
181
  error: {
157
- code: "dimensions_mismatch",
158
- message: "Video dimensions do not match the required format.",
159
- details: { requiredWidth: requiredW, requiredHeight: requiredH, width: probe.width, height: probe.height },
182
+ code: 'dimensions_mismatch',
183
+ message: 'Video dimensions do not match the required format.',
184
+ details: {
185
+ requiredWidth: requiredW,
186
+ requiredHeight: requiredH,
187
+ width: probe.width,
188
+ height: probe.height,
189
+ },
160
190
  },
161
191
  };
162
192
  }
163
193
  }
164
- const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, "mp4");
165
- const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, "jpg");
194
+ const videoOut = outputPathFor(ctx.outputBasePath, TIMED_MEDIA_MAIN_OUTPUT_KEY, 'mp4');
195
+ const posterOut = outputPathFor(ctx.outputBasePath, VIDEO_POSTER_OUTPUT_KEY, 'jpg');
166
196
  tempFiles.push(videoOut, posterOut);
167
197
  // Build a basic filter graph to enforce size/aspect when auto-format is enabled.
168
198
  // - If exact WxH required: scale+crop to WxH
169
199
  // - Else if aspect required: crop to aspect then scale to original (or keep)
170
200
  // - Else: keep original
171
- const scaleMode = spec.video?.scaleMode ?? "fit";
172
- let vf = "";
201
+ const scaleMode = spec.video?.scaleMode ?? 'fit';
202
+ let vf = '';
173
203
  if (spec.allowAutoFormat) {
174
204
  if (requiredW && requiredH) {
175
- if (scaleMode === "fit") {
205
+ if (scaleMode === 'fit') {
176
206
  // letterbox: scale to fit inside, pad with black bars
177
207
  vf = `scale=${requiredW}:${requiredH}:force_original_aspect_ratio=decrease,pad=${requiredW}:${requiredH}:(ow-iw)/2:(oh-ih)/2`;
178
208
  }
@@ -182,7 +212,7 @@ export async function processVideo(spec, ctx, opts) {
182
212
  }
183
213
  }
184
214
  else if (requiredAspect) {
185
- if (scaleMode === "fit") {
215
+ if (scaleMode === 'fit') {
186
216
  // fit to aspect ratio without forced dimensions — no padding possible without target size, just crop
187
217
  vf = `crop='if(gt(iw/ih,${requiredAspect}),ih*${requiredAspect},iw)':'if(gt(iw/ih,${requiredAspect}),ih,iw/${requiredAspect})'`;
188
218
  }
@@ -191,53 +221,61 @@ export async function processVideo(spec, ctx, opts) {
191
221
  }
192
222
  }
193
223
  }
194
- const videoPreset = spec.video?.preset ?? "veryfast";
224
+ const videoPreset = spec.video?.preset ?? 'veryfast';
195
225
  const videoCrf = spec.video?.crf ?? 23;
196
226
  const ffmpegArgs = [
197
- "-y",
198
- "-i",
227
+ '-y',
228
+ '-i',
199
229
  ctx.inputPath,
200
- ...(vf ? ["-vf", vf] : []),
201
- "-c:v",
202
- "libx264",
203
- "-preset",
230
+ ...(vf ? ['-vf', vf] : []),
231
+ '-c:v',
232
+ 'libx264',
233
+ '-preset',
204
234
  videoPreset,
205
- "-crf",
235
+ '-crf',
206
236
  String(videoCrf),
207
- "-pix_fmt",
208
- "yuv420p",
209
- "-movflags",
210
- "+faststart",
211
- ...(probe.hasAudio ? ["-c:a", "aac", "-b:a", "128k"] : ["-an"]),
237
+ '-pix_fmt',
238
+ 'yuv420p',
239
+ '-movflags',
240
+ '+faststart',
241
+ ...(probe.hasAudio ? ['-c:a', 'aac', '-b:a', '128k'] : ['-an']),
212
242
  videoOut,
213
243
  ];
214
244
  const r1 = await runFfmpeg(ffmpegArgs, {
215
245
  signal: opts?.signal,
216
246
  durationMs: probe.durationSec ? probe.durationSec * 1000 : undefined,
217
247
  onProgress: (p) => {
218
- opts?.onProgress?.({ phase: "process", percent: p.percent, detail: { step: "transcode", outTimeMs: p.outTimeMs } });
248
+ opts?.onProgress?.({
249
+ phase: 'process',
250
+ percent: p.percent,
251
+ detail: { step: 'transcode', outTimeMs: p.outTimeMs },
252
+ });
219
253
  },
220
- phase: "transcode",
254
+ phase: 'transcode',
221
255
  });
222
256
  if (r1.code !== 0) {
223
257
  return {
224
258
  ok: false,
225
- mediaType: "video",
259
+ mediaType: 'video',
226
260
  error: {
227
- code: "processing_failed",
228
- message: "ffmpeg video transcode failed.",
261
+ code: 'processing_failed',
262
+ message: 'ffmpeg video transcode failed.',
229
263
  details: { stderr: r1.stderr, stdout: r1.stdout },
230
264
  },
231
265
  };
232
266
  }
233
267
  // Poster (frame at 1s; fallback to 0 if needed)
234
- const posterArgs = ["-y", "-ss", "1", "-i", videoOut, "-frames:v", "1", "-q:v", "2", posterOut];
268
+ const posterArgs = ['-y', '-ss', '1', '-i', videoOut, '-frames:v', '1', '-q:v', '2', posterOut];
235
269
  const r2 = await runFfmpeg(posterArgs, {
236
270
  signal: opts?.signal,
237
271
  onProgress: (p) => {
238
- opts?.onProgress?.({ phase: "process", percent: p.percent, detail: { step: "poster", outTimeMs: p.outTimeMs } });
272
+ opts?.onProgress?.({
273
+ phase: 'process',
274
+ percent: p.percent,
275
+ detail: { step: 'poster', outTimeMs: p.outTimeMs },
276
+ });
239
277
  },
240
- phase: "poster",
278
+ phase: 'poster',
241
279
  });
242
280
  if (r2.code !== 0) {
243
281
  // non-fatal; still succeed with video
@@ -254,7 +292,7 @@ export async function processVideo(spec, ctx, opts) {
254
292
  key: TIMED_MEDIA_MAIN_OUTPUT_KEY,
255
293
  url: `file://${videoOut}`,
256
294
  path: videoOut,
257
- mime: mimeFromExt("mp4"),
295
+ mime: mimeFromExt('mp4'),
258
296
  sizeBytes: vs.size,
259
297
  width: requiredW ?? probe.width,
260
298
  height: requiredH ?? probe.height,
@@ -263,22 +301,22 @@ export async function processVideo(spec, ctx, opts) {
263
301
  ];
264
302
  const warnings = [];
265
303
  if (needsAuto && spec.allowAutoFormat)
266
- warnings.push("auto_formatted");
304
+ warnings.push('auto_formatted');
267
305
  if (r2.code !== 0)
268
- warnings.push("poster_failed");
306
+ warnings.push('poster_failed');
269
307
  if (psSize > 0) {
270
308
  outputs.push({
271
309
  key: VIDEO_POSTER_OUTPUT_KEY,
272
310
  url: `file://${posterOut}`,
273
311
  path: posterOut,
274
- mime: "image/jpeg",
312
+ mime: 'image/jpeg',
275
313
  sizeBytes: psSize,
276
314
  });
277
315
  }
278
316
  tempFiles.length = 0;
279
317
  return {
280
318
  ok: true,
281
- mediaType: "video",
319
+ mediaType: 'video',
282
320
  outputs,
283
321
  meta: {
284
322
  mime: ctx.inputMime,
@@ -292,21 +330,25 @@ export async function processVideo(spec, ctx, opts) {
292
330
  }
293
331
  catch (e) {
294
332
  if (opts?.signal?.aborted) {
295
- return { ok: false, mediaType: "video", error: { code: "processing_canceled", message: "Processing canceled." } };
333
+ return {
334
+ ok: false,
335
+ mediaType: 'video',
336
+ error: { code: 'processing_canceled', message: 'Processing canceled.' },
337
+ };
296
338
  }
297
339
  return {
298
340
  ok: false,
299
- mediaType: "video",
341
+ mediaType: 'video',
300
342
  error: {
301
- code: "processing_failed",
302
- message: e?.message ? String(e.message) : "Video processing failed",
343
+ code: 'processing_failed',
344
+ message: e?.message ? String(e.message) : 'Video processing failed',
303
345
  details: { name: e?.name },
304
346
  },
305
347
  };
306
348
  }
307
349
  finally {
308
350
  if (tempFiles.length) {
309
- const { rm } = await import("node:fs/promises");
351
+ const { rm } = await import('node:fs/promises');
310
352
  await Promise.all(tempFiles.map((f) => rm(f, { force: true }).catch(() => { })));
311
353
  }
312
354
  }