@hyperframes/studio-server 0.8.35 → 0.8.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5MUVDQOD.js → chunk-BVSPLTYT.js} +2 -2
- package/dist/{chunk-NJISQQTN.js → chunk-QJ73CZFQ.js} +52 -11
- package/dist/chunk-QJ73CZFQ.js.map +1 -0
- package/dist/{chunk-C6CSSUAY.js → chunk-QN7UPP3B.js} +8 -5
- package/dist/chunk-QN7UPP3B.js.map +1 -0
- package/dist/helpers/mediaCodecMap.d.ts +39 -2
- package/dist/helpers/mediaCodecMap.js +11 -3
- package/dist/helpers/mediaProxyPreview.d.ts +1 -1
- package/dist/helpers/mediaProxyPreview.js +3 -3
- package/dist/helpers/proxyTranscoder.js +2 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/{mediaProxyPreview-CGJ7f9JA.d.ts → mediaProxyPreview-BzPAj--m.d.ts} +9 -6
- package/package.json +3 -3
- package/dist/chunk-C6CSSUAY.js.map +0 -1
- package/dist/chunk-NJISQQTN.js.map +0 -1
- /package/dist/{chunk-5MUVDQOD.js.map → chunk-BVSPLTYT.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROXY_VARIANT_CONFIG,
|
|
3
3
|
probeMediaMetadata
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QJ73CZFQ.js";
|
|
5
5
|
|
|
6
6
|
// src/helpers/proxyTranscoder.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
@@ -463,4 +463,4 @@ export {
|
|
|
463
463
|
clearFailedTranscodesForTest,
|
|
464
464
|
resolveProxy
|
|
465
465
|
};
|
|
466
|
-
//# sourceMappingURL=chunk-
|
|
466
|
+
//# sourceMappingURL=chunk-BVSPLTYT.js.map
|
|
@@ -135,14 +135,51 @@ async function probeMediaMetadata(filePath, runner = execFileRunner) {
|
|
|
135
135
|
|
|
136
136
|
// src/helpers/mediaCodecMap.ts
|
|
137
137
|
var BROWSER_HOSTILE_CODECS = {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
138
|
+
// HEVC decode is platform-bound, not absent: macOS Chrome answers
|
|
139
|
+
// canPlayType with "probably" and keeps the source, while Chrome on
|
|
140
|
+
// Windows/Linux and Firefox everywhere need the substitute.
|
|
141
|
+
hevc: { representativeMime: 'video/mp4; codecs="hvc1.1.6.L120.B0"', prewarm: true },
|
|
142
|
+
prores: { representativeMime: null, prewarm: true },
|
|
143
|
+
// Every mainstream engine decodes AV1 and VP9, so canPlayType keeps the
|
|
144
|
+
// original and only the rare browser that cannot pays for a transcode.
|
|
145
|
+
av1: { representativeMime: 'video/mp4; codecs="av01.0.08M.08"', prewarm: false },
|
|
146
|
+
vp9: { representativeMime: 'video/webm; codecs="vp09.00.10.08"', prewarm: false }
|
|
145
147
|
};
|
|
148
|
+
function hostileCodecEntry(codecName) {
|
|
149
|
+
return Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName) ? BROWSER_HOSTILE_CODECS[codecName] : void 0;
|
|
150
|
+
}
|
|
151
|
+
function shouldPrewarmProxy(facts) {
|
|
152
|
+
return hostileCodecEntry(facts.codecName)?.prewarm === true;
|
|
153
|
+
}
|
|
154
|
+
var proxyDemand = { prewarmsRequested: 0, proxyRequests: 0 };
|
|
155
|
+
function mediaProxyDemand() {
|
|
156
|
+
return { ...proxyDemand };
|
|
157
|
+
}
|
|
158
|
+
function writeDemandLine(event) {
|
|
159
|
+
try {
|
|
160
|
+
process.stderr.write(
|
|
161
|
+
`[hyperframes:media-proxy] ${JSON.stringify({ event, ...proxyDemand })}
|
|
162
|
+
`
|
|
163
|
+
);
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function isMediaProxyDebugEnabled() {
|
|
168
|
+
const value = process.env.HYPERFRAMES_DEBUG_MEDIA_PROXY;
|
|
169
|
+
return value === "1" || value === "true";
|
|
170
|
+
}
|
|
171
|
+
var summaryHookInstalled = false;
|
|
172
|
+
function recordProxyPrewarm() {
|
|
173
|
+
proxyDemand.prewarmsRequested++;
|
|
174
|
+
if (!summaryHookInstalled) {
|
|
175
|
+
summaryHookInstalled = true;
|
|
176
|
+
process.once("exit", () => writeDemandLine("summary"));
|
|
177
|
+
}
|
|
178
|
+
if (isMediaProxyDebugEnabled()) writeDemandLine("prewarm_requested");
|
|
179
|
+
}
|
|
180
|
+
function recordProxyRequest() {
|
|
181
|
+
proxyDemand.proxyRequests++;
|
|
182
|
+
}
|
|
146
183
|
var PROXY_VARIANT_CONFIG = {
|
|
147
184
|
h264: { extension: ".mp4", contentType: "video/mp4" },
|
|
148
185
|
vp8: { extension: ".webm", contentType: "video/webm" }
|
|
@@ -166,11 +203,11 @@ function decideMediaProxyEligibility(facts) {
|
|
|
166
203
|
return { eligible: true };
|
|
167
204
|
}
|
|
168
205
|
function codecFactsFor(codecName, hasAlpha) {
|
|
169
|
-
const
|
|
206
|
+
const hostile = hostileCodecEntry(codecName);
|
|
170
207
|
return {
|
|
171
208
|
codecName,
|
|
172
|
-
browserHostile:
|
|
173
|
-
representativeMime:
|
|
209
|
+
browserHostile: hostile !== void 0,
|
|
210
|
+
representativeMime: hostile?.representativeMime ?? null,
|
|
174
211
|
hasAlpha
|
|
175
212
|
};
|
|
176
213
|
}
|
|
@@ -269,6 +306,10 @@ async function scanProjectMediaCodecMap(projectDir, htmlSources, options = {}) {
|
|
|
269
306
|
export {
|
|
270
307
|
probeMediaMetadata,
|
|
271
308
|
BROWSER_HOSTILE_CODECS,
|
|
309
|
+
shouldPrewarmProxy,
|
|
310
|
+
mediaProxyDemand,
|
|
311
|
+
recordProxyPrewarm,
|
|
312
|
+
recordProxyRequest,
|
|
272
313
|
PROXY_VARIANT_CONFIG,
|
|
273
314
|
isProxyVariant,
|
|
274
315
|
isProxyVariantRequest,
|
|
@@ -279,4 +320,4 @@ export {
|
|
|
279
320
|
createMediaCodecProbeCache,
|
|
280
321
|
scanProjectMediaCodecMap
|
|
281
322
|
};
|
|
282
|
-
//# sourceMappingURL=chunk-
|
|
323
|
+
//# sourceMappingURL=chunk-QJ73CZFQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/helpers/mediaCodecMap.ts","../src/helpers/mediaMetadata.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { relative, resolve, sep } from \"node:path\";\nimport { rewriteAssetPath } from \"@hyperframes/parsers/asset-paths\";\nimport {\n cleanAssetUrl,\n isRemoteOrInlineUrl,\n isUnresolvedAssetPlaceholder,\n maskNonScannableRanges,\n resolveLocalAssetCandidates,\n} from \"@hyperframes/parsers/asset-resolution\";\nimport { pixelFormatHasAlpha, probeMediaMetadata, type FfprobeRunner } from \"./mediaMetadata.js\";\n\n/**\n * One reusable answer to \"what codec is this asset, and is it browser-hostile?\",\n * built on top of `mediaMetadata.ts`'s ffprobe-backed prober so studio-server\n * probes each asset once instead of running a second prober.\n */\n\nexport interface AssetCodecFacts {\n codecName: string;\n browserHostile: boolean;\n /** Coarse `canPlayType()` input; `null` when not applicable (safe codec) or\n * when no representative mime exists (ProRes: browsers never decode it, so\n * the runtime always proxies rather than probing `canPlayType`). */\n representativeMime: string | null;\n /** Source carries an alpha channel (ffprobe pix_fmt). Alpha sources use a\n * VP8/WebM proxy so their transparency is preserved across Chromium builds. */\n hasAlpha: boolean;\n}\n\n/** Server-root-relative URL pathname -> that asset's codec facts. */\nexport type MediaCodecMap = Record<string, AssetCodecFacts>;\n\nexport interface BrowserHostileCodec {\n /** Coarse `canPlayType()` input; `null` when no representative mime exists\n * (ProRes: browsers never decode it, so the runtime always proxies rather\n * than probing `canPlayType`). */\n representativeMime: string | null;\n /**\n * Whether the server may transcode before any browser asks. True only where\n * no cross-platform decode exists, so some client is sure to need the\n * substitute; false where the first `?hf-proxy=` request can do it lazily.\n */\n prewarm: boolean;\n}\n\n/**\n * Browser-hostile codec table v1. One exported constant so extending it is a\n * one-line change. `ffprobe` cannot emit exact RFC 6381 codec strings, so\n * these `representativeMime` values are deliberately coarse (a false\n * positive costs one proxy transcode, never correctness; a false negative is\n * rescued by the runtime's reactive zero-videoWidth swap).\n */\nexport const BROWSER_HOSTILE_CODECS: Record<string, BrowserHostileCodec> = {\n // HEVC decode is platform-bound, not absent: macOS Chrome answers\n // canPlayType with \"probably\" and keeps the source, while Chrome on\n // Windows/Linux and Firefox everywhere need the substitute.\n hevc: { representativeMime: 'video/mp4; codecs=\"hvc1.1.6.L120.B0\"', prewarm: true },\n prores: { representativeMime: null, prewarm: true },\n // Every mainstream engine decodes AV1 and VP9, so canPlayType keeps the\n // original and only the rare browser that cannot pays for a transcode.\n av1: { representativeMime: 'video/mp4; codecs=\"av01.0.08M.08\"', prewarm: false },\n vp9: { representativeMime: 'video/webm; codecs=\"vp09.00.10.08\"', prewarm: false },\n};\n\n/** `Object.hasOwn` rather than a bare index: a codec named `constructor` or\n * `toString` would otherwise resolve against `Object.prototype`. */\nfunction hostileCodecEntry(codecName: string): BrowserHostileCodec | undefined {\n return Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName)\n ? BROWSER_HOSTILE_CODECS[codecName]\n : undefined;\n}\n\n/** The pre-warm gate: true only for codecs with no cross-platform browser\n * decode, so some client will ask. See `BrowserHostileCodec.prewarm`. */\nexport function shouldPrewarmProxy(facts: AssetCodecFacts): boolean {\n return hostileCodecEntry(facts.codecName)?.prewarm === true;\n}\n\n// Per process, and deliberately in one unit — a call to `resolveProxy` — so\n// the pair reads as a ratio. Summarised at exit on stderr, in the same\n// `[hyperframes:<area>] {json}` shape as `writeUrlDownloadTelemetry`.\nconst proxyDemand = { prewarmsRequested: 0, proxyRequests: 0 };\n\n/** Snapshot of this process's pre-warm demand counters. */\nexport function mediaProxyDemand(): { prewarmsRequested: number; proxyRequests: number } {\n return { ...proxyDemand };\n}\n\nfunction writeDemandLine(event: \"prewarm_requested\" | \"summary\"): void {\n try {\n process.stderr.write(\n `[hyperframes:media-proxy] ${JSON.stringify({ event, ...proxyDemand })}\\n`,\n );\n } catch {\n // Observability must never change proxy correctness.\n }\n}\n\n/** Mirrors `isGpuProbeDebugEnabled` in packages/engine/src/utils/gpuEncoder.ts. */\nfunction isMediaProxyDebugEnabled(): boolean {\n const value = process.env.HYPERFRAMES_DEBUG_MEDIA_PROXY;\n return value === \"1\" || value === \"true\";\n}\n\n// Registered on the first pre-warm rather than at import, so a process that\n// never pre-warms adds no handler and prints nothing. Sync-only, mirroring the\n// `process.on(\"exit\")` shutdown hooks in packages/cli/src/cli.ts:331 and\n// packages/cli/src/commands/preview.ts:1329.\nlet summaryHookInstalled = false;\n\n/**\n * One proxy asked for before any browser wanted it. \"Requested\", not\n * \"started\": a warm cache makes `resolveProxy` a no-op and this counter cannot\n * see that, so it is an upper bound on transcodes, not a measure of CPU. The\n * number it does answer exactly is the one that decides policy — a nonzero\n * count beside `proxyRequests: 0` means nothing ever redeemed the pre-warm.\n *\n * The per-asset line is debug-only: a composition with fifty hostile clips\n * would otherwise print fifty JSON lines into a clack-formatted terminal on\n * every re-render. The exit summary carries the same numbers unconditionally.\n */\nexport function recordProxyPrewarm(): void {\n proxyDemand.prewarmsRequested++;\n if (!summaryHookInstalled) {\n summaryHookInstalled = true;\n process.once(\"exit\", () => writeDemandLine(\"summary\"));\n }\n if (isMediaProxyDebugEnabled()) writeDemandLine(\"prewarm_requested\");\n}\n\n/** One proxy resolved for a browser that asked, counted on the path that calls\n * `resolveProxy` so it shares a unit with `prewarmsRequested`. A 304 does not\n * count; an unconditional Range refill still does, so read it as zero versus\n * nonzero. Never logged per event, only in the exit summary. */\nexport function recordProxyRequest(): void {\n proxyDemand.proxyRequests++;\n}\n\nexport type ProxyVariant = \"h264\" | \"vp8\";\nexport type ProxyVariantRequest = ProxyVariant | \"auto\";\n\nexport const PROXY_VARIANT_CONFIG: Record<\n ProxyVariant,\n { extension: \".mp4\" | \".webm\"; contentType: \"video/mp4\" | \"video/webm\" }\n> = {\n h264: { extension: \".mp4\", contentType: \"video/mp4\" },\n vp8: { extension: \".webm\", contentType: \"video/webm\" },\n};\n\nexport function isProxyVariant(value: string): value is ProxyVariant {\n return Object.hasOwn(PROXY_VARIANT_CONFIG, value);\n}\n\nexport function isProxyVariantRequest(value: string): value is ProxyVariantRequest {\n return value === \"auto\" || isProxyVariant(value);\n}\n\nexport function proxyVariantFor(facts: AssetCodecFacts): ProxyVariant {\n return facts.hasAlpha ? \"vp8\" : \"h264\";\n}\n\nexport function resolveProxyVariantRequest(\n request: ProxyVariantRequest,\n facts: AssetCodecFacts,\n): ProxyVariant | null {\n const expected = proxyVariantFor(facts);\n return request === \"auto\" || request === expected ? expected : null;\n}\n\nexport type MediaProxyIneligibilityReason = \"browser_safe_codec\" | \"unknown_codec\";\n\nexport type MediaProxyEligibility =\n | { eligible: true }\n | { eligible: false; reason: MediaProxyIneligibilityReason };\n\n/** Single policy gate shared by proactive scans and on-demand proxy routes. */\nexport function decideMediaProxyEligibility(facts: AssetCodecFacts | null): MediaProxyEligibility {\n if (!facts) return { eligible: false, reason: \"unknown_codec\" };\n if (!facts.browserHostile) return { eligible: false, reason: \"browser_safe_codec\" };\n return { eligible: true };\n}\n\nfunction codecFactsFor(codecName: string, hasAlpha: boolean): AssetCodecFacts {\n const hostile = hostileCodecEntry(codecName);\n return {\n codecName,\n browserHostile: hostile !== undefined,\n representativeMime: hostile?.representativeMime ?? null,\n hasAlpha,\n };\n}\n\n/**\n * Probe a single video asset. Best-effort: ffprobe missing, erroring, or\n * finding no video stream resolves to `null` (asset omitted by the caller),\n * never a throw. Async so a pool of probes runs concurrently (the default\n * runner is `execFile`-based).\n */\nexport async function probeAssetCodec(\n filePath: string,\n runner?: FfprobeRunner,\n): Promise<AssetCodecFacts | null> {\n const metadata = runner\n ? await probeMediaMetadata(filePath, runner)\n : await probeMediaMetadata(filePath);\n if (metadata.kind !== \"video\" || metadata.probeError) return null;\n const codecName = metadata.color.codecName;\n if (!codecName) return null;\n return codecFactsFor(codecName, pixelFormatHasAlpha(metadata.color.pixelFormat));\n}\n\ninterface CachedAssetProbe {\n mtimeMs: number;\n size: number;\n facts: AssetCodecFacts | null;\n}\n\n/** Per (path, mtime) probe cache. Construct one per project/server lifetime\n * and reuse it across scans; a fresh instance defeats the caching benefit. */\nexport type MediaCodecProbeCache = Map<string, CachedAssetProbe>;\n\nexport function createMediaCodecProbeCache(): MediaCodecProbeCache {\n return new Map();\n}\n\n// Used when a caller doesn't pass its own cache — still correct (probes every\n// time a fresh Map would), but callers that want the mtime-cache benefit\n// across repeated scans (the studio preview route, etc.) should construct\n// and hold their own cache via `createMediaCodecProbeCache`.\nconst defaultProbeCache: MediaCodecProbeCache = new Map();\nconst MAX_PROBE_CACHE_ENTRIES = 512;\n\nfunction rememberProbeResult(\n cache: MediaCodecProbeCache,\n filePath: string,\n result: CachedAssetProbe,\n): void {\n if (!cache.has(filePath) && cache.size >= MAX_PROBE_CACHE_ENTRIES) {\n const oldest = cache.keys().next().value;\n if (oldest) cache.delete(oldest);\n }\n // Refresh insertion order so frequently used assets remain resident.\n cache.delete(filePath);\n cache.set(filePath, result);\n}\n\nasync function probeAssetCodecCached(\n filePath: string,\n cache: MediaCodecProbeCache,\n runner?: FfprobeRunner,\n): Promise<AssetCodecFacts | null> {\n let stat: ReturnType<typeof statSync>;\n try {\n stat = statSync(filePath);\n } catch {\n return null;\n }\n const cached = cache.get(filePath);\n if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {\n rememberProbeResult(cache, filePath, cached);\n return cached.facts;\n }\n const facts = await probeAssetCodec(filePath, runner);\n rememberProbeResult(cache, filePath, { mtimeMs: stat.mtimeMs, size: stat.size, facts });\n return facts;\n}\n\n/** Structurally compatible with `packages/lint/src/hevcPreviewLint.ts`'s\n * (unexported) `HtmlSourceLike`. */\nexport interface HtmlSourceLike {\n html: string;\n compSrcPath?: string;\n}\n\n// --- <video src> collection: shared primitives live in\n// @hyperframes/parsers/asset-resolution; the <video>-specific regex and the\n// pinned key derivation stay here.\nconst VIDEO_SRC_RE = /<video\\b[^>]*\\bsrc\\s*=\\s*[\"']([^\"']+)[\"'][^>]*>/gi;\n\n/**\n * Resolve a `<video src>` reference to an existing local file.\n *\n * `rootRelativePathname` is the map key format PINNED by this plan's Key\n * Technical Decisions: project-root-relative URL pathname, percent-decoded,\n * query-string-stripped, forward-slash separated, leading-slash prefixed\n * (e.g. \"/assets/videos/clip.mp4\"). This must match what the runtime derives\n * via `new URL(el.currentSrc || el.src, document.baseURI).pathname`, because\n * server-side scanning resolves filesystem paths while the DOM sees served\n * URLs — a documented prior source of this exact class of bug.\n */\nfunction resolveExistingLocalAsset(\n projectDir: string,\n url: string,\n): { resolvedPath: string; rootRelativePathname: string } | null {\n const projectRoot = resolve(projectDir);\n const resolvedPath = resolveLocalAssetCandidates(projectRoot, url).find((candidate) =>\n existsSync(candidate),\n );\n if (!resolvedPath) return null;\n const rootRelative = relative(projectRoot, resolvedPath).split(sep).join(\"/\");\n return { resolvedPath, rootRelativePathname: `/${rootRelative}` };\n}\n\n/**\n * Collects local `<video src>` references, resolved to their absolute path\n * and deduped by that path, keyed by the pinned root-relative URL pathname.\n */\n// fallow-ignore-next-line complexity\nfunction collectLocalVideoAssets(\n projectDir: string,\n htmlSources: HtmlSourceLike[],\n): Map<string, string> {\n const candidates = new Map<string, string>();\n\n for (const { html, compSrcPath } of htmlSources) {\n const scannable = maskNonScannableRanges(html);\n const re = new RegExp(VIDEO_SRC_RE.source, VIDEO_SRC_RE.flags);\n let match: RegExpExecArray | null;\n while ((match = re.exec(scannable)) !== null) {\n const rawSrc = match[1] ?? \"\";\n // Placeholder check runs on the RAW value: cleanAssetUrl() splits on ?/# and would chop inside a ${...} token.\n if (isUnresolvedAssetPlaceholder(rawSrc)) continue;\n const src = cleanAssetUrl(rawSrc);\n if (!src || isRemoteOrInlineUrl(src)) continue;\n const rootRelativeSrc = compSrcPath ? rewriteAssetPath(compSrcPath, src) : src;\n const resolved = resolveExistingLocalAsset(projectDir, rootRelativeSrc);\n if (!resolved) continue;\n candidates.set(resolved.resolvedPath, resolved.rootRelativePathname);\n }\n }\n\n return candidates;\n}\n\n// Bounds concurrent ffprobe child processes for projects referencing many\n// videos, mirroring `PROBE_CONCURRENCY` in `hevcPreviewLint.ts`.\nconst PROBE_CONCURRENCY = 8;\n\nexport interface ScanProjectMediaCodecMapOptions {\n /** Persisted across calls by the caller for the mtime-cache benefit;\n * defaults to a shared module-level cache when omitted. */\n cache?: MediaCodecProbeCache;\n runner?: FfprobeRunner;\n}\n\n/**\n * Scans a project's composition HTML for local `<video src>` references and\n * returns the injection map: root-relative URL pathname -> codec facts.\n * Best-effort throughout — a video whose codec can't be determined (missing\n * ffprobe, probe error, no video stream) is simply omitted, never thrown.\n */\nexport async function scanProjectMediaCodecMap(\n projectDir: string,\n htmlSources: HtmlSourceLike[],\n options: ScanProjectMediaCodecMapOptions = {},\n): Promise<MediaCodecMap> {\n const candidates = collectLocalVideoAssets(projectDir, htmlSources);\n if (candidates.size === 0) return {};\n\n const cache = options.cache ?? defaultProbeCache;\n const entries = [...candidates.entries()]; // [resolvedPath, rootRelativePathname]\n const facts = new Array<AssetCodecFacts | null>(entries.length).fill(null);\n let nextIndex = 0;\n const workerCount = Math.min(PROBE_CONCURRENCY, entries.length);\n await Promise.all(\n Array.from({ length: workerCount }, async () => {\n while (nextIndex < entries.length) {\n const index = nextIndex++;\n const entry = entries[index];\n if (!entry) break;\n facts[index] = await probeAssetCodecCached(entry[0], cache, options.runner);\n }\n }),\n );\n\n const map: MediaCodecMap = {};\n entries.forEach(([, pathname], index) => {\n const entryFacts = facts[index];\n if (entryFacts?.browserHostile) map[pathname] = entryFacts;\n });\n return map;\n}\n","import { execFile } from \"node:child_process\";\nimport { extname } from \"node:path\";\nimport { findFfBinary } from \"@hyperframes/parsers/ff-binaries\";\n\nexport interface FfprobeRunResult {\n status: number | null;\n stdout: string | Buffer;\n stderr: string | Buffer;\n /** Spawn-level failure (covers both `NodeJS.ErrnoException` and\n * `ExecFileException`); only `code === \"ENOENT\"` is ever inspected. */\n error?: { code?: string | number | null | undefined };\n}\n\n/** Injectable ffprobe runner. May be synchronous (tests) or async (the\n * default `execFile`-based runner below), so cold scans can run many probes\n * concurrently off the event loop. */\nexport type FfprobeRunner = (\n command: string,\n args: string[],\n options?: { timeout?: number; maxBuffer?: number },\n) => FfprobeRunResult | Promise<FfprobeRunResult>;\n\n/** Default runner: genuinely async (`execFile`), unlike the previous\n * `spawnSync`-based one — a pool of concurrent probes actually parallelizes\n * (mirrors `execFileAsync` in packages/lint/src/hevcPreviewLint.ts). */\nconst execFileRunner: FfprobeRunner = (command, args, options) =>\n new Promise<FfprobeRunResult>((resolvePromise) => {\n execFile(\n command,\n args,\n { timeout: options?.timeout, maxBuffer: options?.maxBuffer, windowsHide: true },\n (error, stdout, stderr) => {\n if (error && error.code === \"ENOENT\") {\n resolvePromise({ status: null, stdout: \"\", stderr: \"\", error });\n return;\n }\n if (error) {\n // Nonzero exit / timeout / kill: report a nonzero status; callers\n // only distinguish \"ok\" (0) from \"failed\" from \"ENOENT\".\n const status = typeof error.code === \"number\" ? error.code : 1;\n resolvePromise({ status, stdout: stdout ?? \"\", stderr: stderr ?? \"\" });\n return;\n }\n resolvePromise({ status: 0, stdout: stdout ?? \"\", stderr: stderr ?? \"\" });\n },\n );\n });\n\nexport type MediaDynamicRange = \"hdr\" | \"sdr\" | \"unknown\";\nexport type MediaHdrTransfer = \"pq\" | \"hlg\" | \"unknown\";\n\nexport interface MediaColorMetadata {\n dynamicRange: MediaDynamicRange;\n hdrTransfer: MediaHdrTransfer | null;\n label: string;\n isHdr: boolean;\n codecName?: string;\n profile?: string;\n pixelFormat?: string;\n colorSpace?: string;\n colorTransfer?: string;\n colorPrimaries?: string;\n bitsPerRawSample?: string;\n}\n\nexport interface MediaMetadata {\n kind: \"video\" | \"image\" | \"audio\" | \"unknown\";\n color: MediaColorMetadata;\n probeError?: string;\n}\n\ninterface FfprobeStream {\n codec_type?: string;\n codec_name?: string;\n profile?: string;\n pix_fmt?: string;\n color_space?: string;\n color_transfer?: string;\n color_primaries?: string;\n bits_per_raw_sample?: string;\n disposition?: { attached_pic?: number };\n}\n\nconst VIDEO_EXT = new Set([\n \".mp4\",\n \".mov\",\n \".webm\",\n \".mkv\",\n \".avi\",\n \".m4v\",\n \".mxf\",\n \".mts\",\n \".m2ts\",\n \".ts\",\n]);\nconst IMAGE_EXT = new Set([\".jpg\", \".jpeg\", \".png\", \".webp\", \".avif\"]);\nconst AUDIO_EXT = new Set([\".mp3\", \".wav\", \".ogg\", \".m4a\", \".aac\"]);\n\nfunction lower(value: string | undefined): string {\n return value?.toLowerCase() ?? \"\";\n}\n\nfunction inferKindFromPath(path: string): MediaMetadata[\"kind\"] {\n const ext = extname(path).toLowerCase();\n if (VIDEO_EXT.has(ext)) return \"video\";\n if (IMAGE_EXT.has(ext)) return \"image\";\n if (AUDIO_EXT.has(ext)) return \"audio\";\n return \"unknown\";\n}\n\nfunction colorLabel(input: {\n isHdr: boolean;\n hdrTransfer: MediaHdrTransfer | null;\n colorPrimaries: string;\n colorSpace: string;\n colorTransfer: string;\n}): string {\n if (input.isHdr) {\n if (input.hdrTransfer === \"pq\") return \"HDR PQ\";\n if (input.hdrTransfer === \"hlg\") return \"HDR HLG\";\n return \"HDR\";\n }\n if (\n input.colorPrimaries.includes(\"bt709\") ||\n input.colorSpace.includes(\"bt709\") ||\n input.colorTransfer.includes(\"bt709\")\n ) {\n return \"SDR Rec.709\";\n }\n return \"SDR/unknown\";\n}\n\n// Conservative alpha-bearing pix_fmt list: yuva* (yuva420p, yuva444p10le...),\n// rgba/argb/bgra/abgr (packed RGB+alpha), gbrap* (planar GBR+alpha, ProRes\n// 4444 decodes to these), ya* (gray+alpha). Prefix match keeps bit-depth /\n// endianness suffixes covered.\nconst ALPHA_PIX_FMT_RE = /^(?:yuva|rgba|argb|bgra|abgr|gbrap|ya)/;\n\n/** True when an ffprobe `pix_fmt` carries an alpha component. */\nexport function pixelFormatHasAlpha(pixFmt: string | undefined): boolean {\n return pixFmt !== undefined && ALPHA_PIX_FMT_RE.test(pixFmt.toLowerCase());\n}\n\nexport function classifyMediaColor(stream: FfprobeStream | null | undefined): MediaColorMetadata {\n const colorPrimaries = lower(stream?.color_primaries);\n const colorSpace = lower(stream?.color_space);\n const colorTransfer = lower(stream?.color_transfer);\n const isHdr =\n colorPrimaries.includes(\"bt2020\") ||\n colorSpace.includes(\"bt2020\") ||\n colorTransfer === \"smpte2084\" ||\n colorTransfer === \"arib-std-b67\";\n const hdrTransfer: MediaHdrTransfer | null = isHdr\n ? colorTransfer === \"smpte2084\"\n ? \"pq\"\n : colorTransfer === \"arib-std-b67\"\n ? \"hlg\"\n : \"unknown\"\n : null;\n\n return {\n dynamicRange: stream ? (isHdr ? \"hdr\" : \"sdr\") : \"unknown\",\n hdrTransfer,\n label: stream\n ? colorLabel({ isHdr, hdrTransfer, colorPrimaries, colorSpace, colorTransfer })\n : \"Unknown\",\n isHdr,\n codecName: stream?.codec_name,\n profile: stream?.profile,\n pixelFormat: stream?.pix_fmt,\n colorSpace: stream?.color_space,\n colorTransfer: stream?.color_transfer,\n colorPrimaries: stream?.color_primaries,\n bitsPerRawSample: stream?.bits_per_raw_sample,\n };\n}\n\nexport async function probeMediaMetadata(\n filePath: string,\n runner: FfprobeRunner = execFileRunner,\n): Promise<MediaMetadata> {\n const kind = inferKindFromPath(filePath);\n if (kind === \"audio\" || kind === \"unknown\") {\n return { kind, color: classifyMediaColor(null) };\n }\n\n // The default runner degrades a missing ffprobe to \"unavailable\" without\n // spawning; injected runners own execution and receive the normal command.\n const ffprobePath =\n findFfBinary(\"ffprobe\", { configuredMustExist: true }) ??\n (runner === execFileRunner ? undefined : \"ffprobe\");\n if (!ffprobePath) {\n return { kind, color: classifyMediaColor(null), probeError: \"ffprobe unavailable\" };\n }\n\n const result = await runner(\n ffprobePath,\n [\n \"-v\",\n \"error\",\n \"-show_entries\",\n \"stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample:stream_disposition=attached_pic\",\n \"-of\",\n \"json\",\n \"--\",\n filePath,\n ],\n { timeout: 15_000, maxBuffer: 1024 * 1024 },\n );\n\n if (result.error?.code === \"ENOENT\") {\n return { kind, color: classifyMediaColor(null), probeError: \"ffprobe unavailable\" };\n }\n if (result.status !== 0) {\n return { kind, color: classifyMediaColor(null), probeError: \"ffprobe failed\" };\n }\n\n try {\n const parsed = JSON.parse(String(result.stdout || \"{}\")) as { streams?: FfprobeStream[] };\n const stream = parsed.streams?.find((item) => {\n if (kind === \"image\") return item.codec_type === \"video\";\n return item.codec_type === kind && item.disposition?.attached_pic !== 1;\n });\n return { kind, color: classifyMediaColor(stream) };\n } catch {\n return { kind, color: classifyMediaColor(null), probeError: \"ffprobe returned invalid json\" };\n }\n}\n"],"mappings":";AAAA,SAAS,YAAY,gBAAgB;AACrC,SAAS,UAAU,SAAS,WAAW;AACvC,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACTP,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAuB7B,IAAM,iBAAgC,CAAC,SAAS,MAAM,YACpD,IAAI,QAA0B,CAAC,mBAAmB;AAChD;AAAA,IACE;AAAA,IACA;AAAA,IACA,EAAE,SAAS,SAAS,SAAS,WAAW,SAAS,WAAW,aAAa,KAAK;AAAA,IAC9E,CAAC,OAAO,QAAQ,WAAW;AACzB,UAAI,SAAS,MAAM,SAAS,UAAU;AACpC,uBAAe,EAAE,QAAQ,MAAM,QAAQ,IAAI,QAAQ,IAAI,MAAM,CAAC;AAC9D;AAAA,MACF;AACA,UAAI,OAAO;AAGT,cAAM,SAAS,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAC7D,uBAAe,EAAE,QAAQ,QAAQ,UAAU,IAAI,QAAQ,UAAU,GAAG,CAAC;AACrE;AAAA,MACF;AACA,qBAAe,EAAE,QAAQ,GAAG,QAAQ,UAAU,IAAI,QAAQ,UAAU,GAAG,CAAC;AAAA,IAC1E;AAAA,EACF;AACF,CAAC;AAqCH,IAAM,YAAY,oBAAI,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,YAAY,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,SAAS,OAAO,CAAC;AACrE,IAAM,YAAY,oBAAI,IAAI,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAElE,SAAS,MAAM,OAAmC;AAChD,SAAO,OAAO,YAAY,KAAK;AACjC;AAEA,SAAS,kBAAkB,MAAqC;AAC9D,QAAM,MAAM,QAAQ,IAAI,EAAE,YAAY;AACtC,MAAI,UAAU,IAAI,GAAG,EAAG,QAAO;AAC/B,MAAI,UAAU,IAAI,GAAG,EAAG,QAAO;AAC/B,MAAI,UAAU,IAAI,GAAG,EAAG,QAAO;AAC/B,SAAO;AACT;AAEA,SAAS,WAAW,OAMT;AACT,MAAI,MAAM,OAAO;AACf,QAAI,MAAM,gBAAgB,KAAM,QAAO;AACvC,QAAI,MAAM,gBAAgB,MAAO,QAAO;AACxC,WAAO;AAAA,EACT;AACA,MACE,MAAM,eAAe,SAAS,OAAO,KACrC,MAAM,WAAW,SAAS,OAAO,KACjC,MAAM,cAAc,SAAS,OAAO,GACpC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMA,IAAM,mBAAmB;AAGlB,SAAS,oBAAoB,QAAqC;AACvE,SAAO,WAAW,UAAa,iBAAiB,KAAK,OAAO,YAAY,CAAC;AAC3E;AAEO,SAAS,mBAAmB,QAA8D;AAC/F,QAAM,iBAAiB,MAAM,QAAQ,eAAe;AACpD,QAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,QAAM,gBAAgB,MAAM,QAAQ,cAAc;AAClD,QAAM,QACJ,eAAe,SAAS,QAAQ,KAChC,WAAW,SAAS,QAAQ,KAC5B,kBAAkB,eAClB,kBAAkB;AACpB,QAAM,cAAuC,QACzC,kBAAkB,cAChB,OACA,kBAAkB,iBAChB,QACA,YACJ;AAEJ,SAAO;AAAA,IACL,cAAc,SAAU,QAAQ,QAAQ,QAAS;AAAA,IACjD;AAAA,IACA,OAAO,SACH,WAAW,EAAE,OAAO,aAAa,gBAAgB,YAAY,cAAc,CAAC,IAC5E;AAAA,IACJ;AAAA,IACA,WAAW,QAAQ;AAAA,IACnB,SAAS,QAAQ;AAAA,IACjB,aAAa,QAAQ;AAAA,IACrB,YAAY,QAAQ;AAAA,IACpB,eAAe,QAAQ;AAAA,IACvB,gBAAgB,QAAQ;AAAA,IACxB,kBAAkB,QAAQ;AAAA,EAC5B;AACF;AAEA,eAAsB,mBACpB,UACA,SAAwB,gBACA;AACxB,QAAM,OAAO,kBAAkB,QAAQ;AACvC,MAAI,SAAS,WAAW,SAAS,WAAW;AAC1C,WAAO,EAAE,MAAM,OAAO,mBAAmB,IAAI,EAAE;AAAA,EACjD;AAIA,QAAM,cACJ,aAAa,WAAW,EAAE,qBAAqB,KAAK,CAAC,MACpD,WAAW,iBAAiB,SAAY;AAC3C,MAAI,CAAC,aAAa;AAChB,WAAO,EAAE,MAAM,OAAO,mBAAmB,IAAI,GAAG,YAAY,sBAAsB;AAAA,EACpF;AAEA,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,EAAE,SAAS,MAAQ,WAAW,OAAO,KAAK;AAAA,EAC5C;AAEA,MAAI,OAAO,OAAO,SAAS,UAAU;AACnC,WAAO,EAAE,MAAM,OAAO,mBAAmB,IAAI,GAAG,YAAY,sBAAsB;AAAA,EACpF;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,MAAM,OAAO,mBAAmB,IAAI,GAAG,YAAY,iBAAiB;AAAA,EAC/E;AAEA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO,OAAO,UAAU,IAAI,CAAC;AACvD,UAAM,SAAS,OAAO,SAAS,KAAK,CAAC,SAAS;AAC5C,UAAI,SAAS,QAAS,QAAO,KAAK,eAAe;AACjD,aAAO,KAAK,eAAe,QAAQ,KAAK,aAAa,iBAAiB;AAAA,IACxE,CAAC;AACD,WAAO,EAAE,MAAM,OAAO,mBAAmB,MAAM,EAAE;AAAA,EACnD,QAAQ;AACN,WAAO,EAAE,MAAM,OAAO,mBAAmB,IAAI,GAAG,YAAY,gCAAgC;AAAA,EAC9F;AACF;;;AD9KO,IAAM,yBAA8D;AAAA;AAAA;AAAA;AAAA,EAIzE,MAAM,EAAE,oBAAoB,wCAAwC,SAAS,KAAK;AAAA,EAClF,QAAQ,EAAE,oBAAoB,MAAM,SAAS,KAAK;AAAA;AAAA;AAAA,EAGlD,KAAK,EAAE,oBAAoB,qCAAqC,SAAS,MAAM;AAAA,EAC/E,KAAK,EAAE,oBAAoB,sCAAsC,SAAS,MAAM;AAClF;AAIA,SAAS,kBAAkB,WAAoD;AAC7E,SAAO,OAAO,OAAO,wBAAwB,SAAS,IAClD,uBAAuB,SAAS,IAChC;AACN;AAIO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,kBAAkB,MAAM,SAAS,GAAG,YAAY;AACzD;AAKA,IAAM,cAAc,EAAE,mBAAmB,GAAG,eAAe,EAAE;AAGtD,SAAS,mBAAyE;AACvF,SAAO,EAAE,GAAG,YAAY;AAC1B;AAEA,SAAS,gBAAgB,OAA8C;AACrE,MAAI;AACF,YAAQ,OAAO;AAAA,MACb,6BAA6B,KAAK,UAAU,EAAE,OAAO,GAAG,YAAY,CAAC,CAAC;AAAA;AAAA,IACxE;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,2BAAoC;AAC3C,QAAM,QAAQ,QAAQ,IAAI;AAC1B,SAAO,UAAU,OAAO,UAAU;AACpC;AAMA,IAAI,uBAAuB;AAapB,SAAS,qBAA2B;AACzC,cAAY;AACZ,MAAI,CAAC,sBAAsB;AACzB,2BAAuB;AACvB,YAAQ,KAAK,QAAQ,MAAM,gBAAgB,SAAS,CAAC;AAAA,EACvD;AACA,MAAI,yBAAyB,EAAG,iBAAgB,mBAAmB;AACrE;AAMO,SAAS,qBAA2B;AACzC,cAAY;AACd;AAKO,IAAM,uBAGT;AAAA,EACF,MAAM,EAAE,WAAW,QAAQ,aAAa,YAAY;AAAA,EACpD,KAAK,EAAE,WAAW,SAAS,aAAa,aAAa;AACvD;AAEO,SAAS,eAAe,OAAsC;AACnE,SAAO,OAAO,OAAO,sBAAsB,KAAK;AAClD;AAEO,SAAS,sBAAsB,OAA6C;AACjF,SAAO,UAAU,UAAU,eAAe,KAAK;AACjD;AAEO,SAAS,gBAAgB,OAAsC;AACpE,SAAO,MAAM,WAAW,QAAQ;AAClC;AAEO,SAAS,2BACd,SACA,OACqB;AACrB,QAAM,WAAW,gBAAgB,KAAK;AACtC,SAAO,YAAY,UAAU,YAAY,WAAW,WAAW;AACjE;AASO,SAAS,4BAA4B,OAAsD;AAChG,MAAI,CAAC,MAAO,QAAO,EAAE,UAAU,OAAO,QAAQ,gBAAgB;AAC9D,MAAI,CAAC,MAAM,eAAgB,QAAO,EAAE,UAAU,OAAO,QAAQ,qBAAqB;AAClF,SAAO,EAAE,UAAU,KAAK;AAC1B;AAEA,SAAS,cAAc,WAAmB,UAAoC;AAC5E,QAAM,UAAU,kBAAkB,SAAS;AAC3C,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,YAAY;AAAA,IAC5B,oBAAoB,SAAS,sBAAsB;AAAA,IACnD;AAAA,EACF;AACF;AAQA,eAAsB,gBACpB,UACA,QACiC;AACjC,QAAM,WAAW,SACb,MAAM,mBAAmB,UAAU,MAAM,IACzC,MAAM,mBAAmB,QAAQ;AACrC,MAAI,SAAS,SAAS,WAAW,SAAS,WAAY,QAAO;AAC7D,QAAM,YAAY,SAAS,MAAM;AACjC,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,cAAc,WAAW,oBAAoB,SAAS,MAAM,WAAW,CAAC;AACjF;AAYO,SAAS,6BAAmD;AACjE,SAAO,oBAAI,IAAI;AACjB;AAMA,IAAM,oBAA0C,oBAAI,IAAI;AACxD,IAAM,0BAA0B;AAEhC,SAAS,oBACP,OACA,UACA,QACM;AACN,MAAI,CAAC,MAAM,IAAI,QAAQ,KAAK,MAAM,QAAQ,yBAAyB;AACjE,UAAM,SAAS,MAAM,KAAK,EAAE,KAAK,EAAE;AACnC,QAAI,OAAQ,OAAM,OAAO,MAAM;AAAA,EACjC;AAEA,QAAM,OAAO,QAAQ;AACrB,QAAM,IAAI,UAAU,MAAM;AAC5B;AAEA,eAAe,sBACb,UACA,OACA,QACiC;AACjC,MAAI;AACJ,MAAI;AACF,WAAO,SAAS,QAAQ;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,MAAI,UAAU,OAAO,YAAY,KAAK,WAAW,OAAO,SAAS,KAAK,MAAM;AAC1E,wBAAoB,OAAO,UAAU,MAAM;AAC3C,WAAO,OAAO;AAAA,EAChB;AACA,QAAM,QAAQ,MAAM,gBAAgB,UAAU,MAAM;AACpD,sBAAoB,OAAO,UAAU,EAAE,SAAS,KAAK,SAAS,MAAM,KAAK,MAAM,MAAM,CAAC;AACtF,SAAO;AACT;AAYA,IAAM,eAAe;AAarB,SAAS,0BACP,YACA,KAC+D;AAC/D,QAAM,cAAc,QAAQ,UAAU;AACtC,QAAM,eAAe,4BAA4B,aAAa,GAAG,EAAE;AAAA,IAAK,CAAC,cACvE,WAAW,SAAS;AAAA,EACtB;AACA,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,eAAe,SAAS,aAAa,YAAY,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AAC5E,SAAO,EAAE,cAAc,sBAAsB,IAAI,YAAY,GAAG;AAClE;AAOA,SAAS,wBACP,YACA,aACqB;AACrB,QAAM,aAAa,oBAAI,IAAoB;AAE3C,aAAW,EAAE,MAAM,YAAY,KAAK,aAAa;AAC/C,UAAM,YAAY,uBAAuB,IAAI;AAC7C,UAAM,KAAK,IAAI,OAAO,aAAa,QAAQ,aAAa,KAAK;AAC7D,QAAI;AACJ,YAAQ,QAAQ,GAAG,KAAK,SAAS,OAAO,MAAM;AAC5C,YAAM,SAAS,MAAM,CAAC,KAAK;AAE3B,UAAI,6BAA6B,MAAM,EAAG;AAC1C,YAAM,MAAM,cAAc,MAAM;AAChC,UAAI,CAAC,OAAO,oBAAoB,GAAG,EAAG;AACtC,YAAM,kBAAkB,cAAc,iBAAiB,aAAa,GAAG,IAAI;AAC3E,YAAM,WAAW,0BAA0B,YAAY,eAAe;AACtE,UAAI,CAAC,SAAU;AACf,iBAAW,IAAI,SAAS,cAAc,SAAS,oBAAoB;AAAA,IACrE;AAAA,EACF;AAEA,SAAO;AACT;AAIA,IAAM,oBAAoB;AAe1B,eAAsB,yBACpB,YACA,aACA,UAA2C,CAAC,GACpB;AACxB,QAAM,aAAa,wBAAwB,YAAY,WAAW;AAClE,MAAI,WAAW,SAAS,EAAG,QAAO,CAAC;AAEnC,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,UAAU,CAAC,GAAG,WAAW,QAAQ,CAAC;AACxC,QAAM,QAAQ,IAAI,MAA8B,QAAQ,MAAM,EAAE,KAAK,IAAI;AACzE,MAAI,YAAY;AAChB,QAAM,cAAc,KAAK,IAAI,mBAAmB,QAAQ,MAAM;AAC9D,QAAM,QAAQ;AAAA,IACZ,MAAM,KAAK,EAAE,QAAQ,YAAY,GAAG,YAAY;AAC9C,aAAO,YAAY,QAAQ,QAAQ;AACjC,cAAM,QAAQ;AACd,cAAM,QAAQ,QAAQ,KAAK;AAC3B,YAAI,CAAC,MAAO;AACZ,cAAM,KAAK,IAAI,MAAM,sBAAsB,MAAM,CAAC,GAAG,OAAO,QAAQ,MAAM;AAAA,MAC5E;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,MAAqB,CAAC;AAC5B,UAAQ,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,UAAU;AACvC,UAAM,aAAa,MAAM,KAAK;AAC9B,QAAI,YAAY,eAAgB,KAAI,QAAQ,IAAI;AAAA,EAClD,CAAC;AACD,SAAO;AACT;","names":[]}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROXY_PARAMS_VERSION,
|
|
3
3
|
resolveProxy
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BVSPLTYT.js";
|
|
5
5
|
import {
|
|
6
6
|
createMediaCodecProbeCache,
|
|
7
7
|
proxyVariantFor,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
recordProxyPrewarm,
|
|
9
|
+
scanProjectMediaCodecMap,
|
|
10
|
+
shouldPrewarmProxy
|
|
11
|
+
} from "./chunk-QJ73CZFQ.js";
|
|
10
12
|
|
|
11
13
|
// src/helpers/mediaProxyPreview.ts
|
|
12
14
|
import { resolve } from "path";
|
|
@@ -39,7 +41,8 @@ async function injectMediaCodecMapIntoHtml(html, projectDir, htmlSources, probeC
|
|
|
39
41
|
}
|
|
40
42
|
if (Object.keys(map).length === 0) return html;
|
|
41
43
|
for (const [rootRelativePathname, facts] of Object.entries(map)) {
|
|
42
|
-
if (!facts
|
|
44
|
+
if (!shouldPrewarmProxy(facts)) continue;
|
|
45
|
+
recordProxyPrewarm();
|
|
43
46
|
resolveProxy(
|
|
44
47
|
projectDir,
|
|
45
48
|
resolve(projectDir, rootRelativePathname.replace(/^\/+/, "")),
|
|
@@ -63,4 +66,4 @@ export {
|
|
|
63
66
|
injectMediaCodecMapIntoHtml,
|
|
64
67
|
injectMediaCodecMap
|
|
65
68
|
};
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
69
|
+
//# sourceMappingURL=chunk-QN7UPP3B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/helpers/mediaProxyPreview.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport type { StudioApiAdapter } from \"../types.js\";\nimport {\n createMediaCodecProbeCache,\n proxyVariantFor,\n recordProxyPrewarm,\n scanProjectMediaCodecMap,\n shouldPrewarmProxy,\n type HtmlSourceLike,\n type MediaCodecMap,\n type MediaCodecProbeCache,\n} from \"./mediaCodecMap.js\";\nimport { resolveProxy, PROXY_PARAMS_VERSION } from \"./proxyTranscoder.js\";\n\n/**\n * Transparent-media-proxy wiring shared by `routes/preview.ts`\n * (docs/plans/2026-07-14-002-feat-transparent-media-proxies-plan.md, unit U3).\n * Split out of the route module to keep it under the repo's 600-line file cap.\n */\n\n/**\n * Preview-route-local adapter surface for the auto-proxy feature. Both\n * fields are optional so any existing `StudioApiAdapter` value remains\n * structurally assignable without editing the shared interface:\n * `autoProxy` defaults to true (on) when omitted — a later unit wires the\n * CLI `--no-proxy` flag / `hyperframes.json` setting through it;\n * `mediaCodecProbeCache` lets a host share one probe cache across\n * preview/play/static-server surfaces instead of each constructing its own.\n */\nexport type PreviewApiAdapter = StudioApiAdapter & {\n autoProxy?: boolean;\n mediaCodecProbeCache?: MediaCodecProbeCache;\n};\n\nexport function isAutoProxyEnabled(adapter: PreviewApiAdapter): boolean {\n return adapter.autoProxy !== false;\n}\n\n/** One probe cache per server instance — construct once in `registerPreviewRoutes`\n * and reuse across every request so the mtime-cache benefit in\n * `scanProjectMediaCodecMap` actually applies. A host that wants to share the\n * cache across other surfaces (play, static project server) can pass its own\n * via `adapter.mediaCodecProbeCache`. */\nexport function resolvePreviewMediaCodecProbeCache(\n adapter: PreviewApiAdapter,\n): MediaCodecProbeCache {\n return adapter.mediaCodecProbeCache ?? createMediaCodecProbeCache();\n}\n\n/**\n * ETag salt for `?hf-proxy=` asset requests, mirroring `variablesEtagSalt` in\n * preview.ts: salted by the raw param value plus the transcoder's params\n * version, so a future proxy-recipe change (which bumps `PROXY_PARAMS_VERSION`)\n * or a different proxy variant invalidates cached 304s without needing to\n * touch the proxy file itself.\n */\nexport function proxyEtagSalt(raw: string | undefined): string {\n if (raw === undefined) return \"\";\n return `:proxy:${raw}:${PROXY_PARAMS_VERSION}`;\n}\n\n// Mirrors `injectScriptTagIntoHead` in routes/preview.ts (kept local rather\n// than imported to avoid a helpers → routes dependency edge for one\n// two-line utility).\nfunction injectScriptTagIntoHead(html: string, scriptTag: string): string {\n if (html.includes(\"</head>\")) return html.replace(\"</head>\", `${scriptTag}\\n</head>`);\n return `${scriptTag}\\n${html}`;\n}\n\n/**\n * Injects `window.__HF_MEDIA_CODEC_MAP__` (the U1 codec-facts scan) into\n * served composition HTML, and fire-and-forget pre-warms `resolveProxy` for\n * every entry whose codec no browser decodes, so an element's proactive swap\n * usually hits a warm cache (KTD: protects the per-origin connection budget\n * under held responses). Conditionally hostile codecs are injected but NOT\n * pre-warmed: the requesting browser usually plays them, and the transcode\n * runs concurrently with its first layout. No second concurrency limiter here\n * — the transcoder's own global bound throttles both pre-warm and\n * element-triggered calls. Pre-warm failures are swallowed; an actual\n * `?hf-proxy=` request surfaces them as a 502 and transcodes lazily.\n * Alpha-bearing entries pre-warm their VP8/WebM variant.\n *\n * The single shared implementation for every auto-proxy surface — the studio\n * preview route (via `injectMediaCodecMap` below) and the CLI's composition /\n * static project servers (via the `./media-proxy-preview` subpath export).\n * Empty maps leave HTML untouched, preserving the normal no-hostile-media\n * preview path. On-demand proxy requests enforce the same eligibility gate.\n */\nexport async function injectMediaCodecMapIntoHtml(\n html: string,\n projectDir: string,\n htmlSources: HtmlSourceLike[],\n probeCache?: MediaCodecProbeCache,\n): Promise<string> {\n let map: MediaCodecMap;\n try {\n map = await scanProjectMediaCodecMap(\n projectDir,\n htmlSources,\n probeCache ? { cache: probeCache } : {},\n );\n } catch {\n // Best-effort: a scan failure must never block serving the page.\n return html;\n }\n if (Object.keys(map).length === 0) return html;\n for (const [rootRelativePathname, facts] of Object.entries(map)) {\n if (!shouldPrewarmProxy(facts)) continue;\n recordProxyPrewarm();\n resolveProxy(\n projectDir,\n resolve(projectDir, rootRelativePathname.replace(/^\\/+/, \"\")),\n proxyVariantFor(facts),\n ).catch(() => {\n // Swallowed: the pre-warm is best-effort. A real `?hf-proxy=` request\n // for this asset re-attempts the transcode and reports failure (502).\n });\n }\n // <-escape prevents a src path containing \"</script>\" from breaking out of\n // the injected tag, mirroring injectPreviewVariables in helpers/previewVariables.ts.\n const json = JSON.stringify(map)\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n const tag = `<script data-hf-media-codec-map>window.__HF_MEDIA_CODEC_MAP__=${json};</script>`;\n return injectScriptTagIntoHead(html, tag);\n}\n\n/**\n * Adapter-aware wrapper used by the studio preview routes: skipped entirely\n * (no scan, no injection) when auto-proxy is off for this adapter.\n */\nexport async function injectMediaCodecMap(\n html: string,\n adapter: PreviewApiAdapter,\n projectDir: string,\n compSrcPath: string,\n probeCache: MediaCodecProbeCache,\n): Promise<string> {\n if (!isAutoProxyEnabled(adapter)) return html;\n return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache);\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,eAAe;AAkCjB,SAAS,mBAAmB,SAAqC;AACtE,SAAO,QAAQ,cAAc;AAC/B;AAOO,SAAS,mCACd,SACsB;AACtB,SAAO,QAAQ,wBAAwB,2BAA2B;AACpE;AASO,SAAS,cAAc,KAAiC;AAC7D,MAAI,QAAQ,OAAW,QAAO;AAC9B,SAAO,UAAU,GAAG,IAAI,oBAAoB;AAC9C;AAKA,SAAS,wBAAwB,MAAc,WAA2B;AACxE,MAAI,KAAK,SAAS,SAAS,EAAG,QAAO,KAAK,QAAQ,WAAW,GAAG,SAAS;AAAA,QAAW;AACpF,SAAO,GAAG,SAAS;AAAA,EAAK,IAAI;AAC9B;AAqBA,eAAsB,4BACpB,MACA,YACA,aACA,YACiB;AACjB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA,aAAa,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,IACxC;AAAA,EACF,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,KAAK,GAAG,EAAE,WAAW,EAAG,QAAO;AAC1C,aAAW,CAAC,sBAAsB,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/D,QAAI,CAAC,mBAAmB,KAAK,EAAG;AAChC,uBAAmB;AACnB;AAAA,MACE;AAAA,MACA,QAAQ,YAAY,qBAAqB,QAAQ,QAAQ,EAAE,CAAC;AAAA,MAC5D,gBAAgB,KAAK;AAAA,IACvB,EAAE,MAAM,MAAM;AAAA,IAGd,CAAC;AAAA,EACH;AAGA,QAAM,OAAO,KAAK,UAAU,GAAG,EAC5B,QAAQ,MAAM,SAAS,EACvB,QAAQ,WAAW,SAAS,EAC5B,QAAQ,WAAW,SAAS;AAC/B,QAAM,MAAM,iEAAiE,IAAI;AACjF,SAAO,wBAAwB,MAAM,GAAG;AAC1C;AAMA,eAAsB,oBACpB,MACA,SACA,YACA,aACA,YACiB;AACjB,MAAI,CAAC,mBAAmB,OAAO,EAAG,QAAO;AACzC,SAAO,4BAA4B,MAAM,YAAY,CAAC,EAAE,MAAM,YAAY,CAAC,GAAG,UAAU;AAC1F;","names":[]}
|
|
@@ -34,6 +34,18 @@ interface AssetCodecFacts {
|
|
|
34
34
|
}
|
|
35
35
|
/** Server-root-relative URL pathname -> that asset's codec facts. */
|
|
36
36
|
type MediaCodecMap = Record<string, AssetCodecFacts>;
|
|
37
|
+
interface BrowserHostileCodec {
|
|
38
|
+
/** Coarse `canPlayType()` input; `null` when no representative mime exists
|
|
39
|
+
* (ProRes: browsers never decode it, so the runtime always proxies rather
|
|
40
|
+
* than probing `canPlayType`). */
|
|
41
|
+
representativeMime: string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the server may transcode before any browser asks. True only where
|
|
44
|
+
* no cross-platform decode exists, so some client is sure to need the
|
|
45
|
+
* substitute; false where the first `?hf-proxy=` request can do it lazily.
|
|
46
|
+
*/
|
|
47
|
+
prewarm: boolean;
|
|
48
|
+
}
|
|
37
49
|
/**
|
|
38
50
|
* Browser-hostile codec table v1. One exported constant so extending it is a
|
|
39
51
|
* one-line change. `ffprobe` cannot emit exact RFC 6381 codec strings, so
|
|
@@ -41,7 +53,32 @@ type MediaCodecMap = Record<string, AssetCodecFacts>;
|
|
|
41
53
|
* positive costs one proxy transcode, never correctness; a false negative is
|
|
42
54
|
* rescued by the runtime's reactive zero-videoWidth swap).
|
|
43
55
|
*/
|
|
44
|
-
declare const BROWSER_HOSTILE_CODECS: Record<string,
|
|
56
|
+
declare const BROWSER_HOSTILE_CODECS: Record<string, BrowserHostileCodec>;
|
|
57
|
+
/** The pre-warm gate: true only for codecs with no cross-platform browser
|
|
58
|
+
* decode, so some client will ask. See `BrowserHostileCodec.prewarm`. */
|
|
59
|
+
declare function shouldPrewarmProxy(facts: AssetCodecFacts): boolean;
|
|
60
|
+
/** Snapshot of this process's pre-warm demand counters. */
|
|
61
|
+
declare function mediaProxyDemand(): {
|
|
62
|
+
prewarmsRequested: number;
|
|
63
|
+
proxyRequests: number;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* One proxy asked for before any browser wanted it. "Requested", not
|
|
67
|
+
* "started": a warm cache makes `resolveProxy` a no-op and this counter cannot
|
|
68
|
+
* see that, so it is an upper bound on transcodes, not a measure of CPU. The
|
|
69
|
+
* number it does answer exactly is the one that decides policy — a nonzero
|
|
70
|
+
* count beside `proxyRequests: 0` means nothing ever redeemed the pre-warm.
|
|
71
|
+
*
|
|
72
|
+
* The per-asset line is debug-only: a composition with fifty hostile clips
|
|
73
|
+
* would otherwise print fifty JSON lines into a clack-formatted terminal on
|
|
74
|
+
* every re-render. The exit summary carries the same numbers unconditionally.
|
|
75
|
+
*/
|
|
76
|
+
declare function recordProxyPrewarm(): void;
|
|
77
|
+
/** One proxy resolved for a browser that asked, counted on the path that calls
|
|
78
|
+
* `resolveProxy` so it shares a unit with `prewarmsRequested`. A 304 does not
|
|
79
|
+
* count; an unconditional Range refill still does, so read it as zero versus
|
|
80
|
+
* nonzero. Never logged per event, only in the exit summary. */
|
|
81
|
+
declare function recordProxyRequest(): void;
|
|
45
82
|
type ProxyVariant = "h264" | "vp8";
|
|
46
83
|
type ProxyVariantRequest = ProxyVariant | "auto";
|
|
47
84
|
declare const PROXY_VARIANT_CONFIG: Record<ProxyVariant, {
|
|
@@ -97,4 +134,4 @@ interface ScanProjectMediaCodecMapOptions {
|
|
|
97
134
|
*/
|
|
98
135
|
declare function scanProjectMediaCodecMap(projectDir: string, htmlSources: HtmlSourceLike[], options?: ScanProjectMediaCodecMapOptions): Promise<MediaCodecMap>;
|
|
99
136
|
|
|
100
|
-
export { type AssetCodecFacts, BROWSER_HOSTILE_CODECS, type HtmlSourceLike, type MediaCodecMap, type MediaCodecProbeCache, type MediaProxyEligibility, type MediaProxyIneligibilityReason, PROXY_VARIANT_CONFIG, type ProxyVariant, type ProxyVariantRequest, type ScanProjectMediaCodecMapOptions, createMediaCodecProbeCache, decideMediaProxyEligibility, isProxyVariant, isProxyVariantRequest, probeAssetCodec, proxyVariantFor, resolveProxyVariantRequest, scanProjectMediaCodecMap };
|
|
137
|
+
export { type AssetCodecFacts, BROWSER_HOSTILE_CODECS, type BrowserHostileCodec, type HtmlSourceLike, type MediaCodecMap, type MediaCodecProbeCache, type MediaProxyEligibility, type MediaProxyIneligibilityReason, PROXY_VARIANT_CONFIG, type ProxyVariant, type ProxyVariantRequest, type ScanProjectMediaCodecMapOptions, createMediaCodecProbeCache, decideMediaProxyEligibility, isProxyVariant, isProxyVariantRequest, mediaProxyDemand, probeAssetCodec, proxyVariantFor, recordProxyPrewarm, recordProxyRequest, resolveProxyVariantRequest, scanProjectMediaCodecMap, shouldPrewarmProxy };
|
|
@@ -5,11 +5,15 @@ import {
|
|
|
5
5
|
decideMediaProxyEligibility,
|
|
6
6
|
isProxyVariant,
|
|
7
7
|
isProxyVariantRequest,
|
|
8
|
+
mediaProxyDemand,
|
|
8
9
|
probeAssetCodec,
|
|
9
10
|
proxyVariantFor,
|
|
11
|
+
recordProxyPrewarm,
|
|
12
|
+
recordProxyRequest,
|
|
10
13
|
resolveProxyVariantRequest,
|
|
11
|
-
scanProjectMediaCodecMap
|
|
12
|
-
|
|
14
|
+
scanProjectMediaCodecMap,
|
|
15
|
+
shouldPrewarmProxy
|
|
16
|
+
} from "../chunk-QJ73CZFQ.js";
|
|
13
17
|
export {
|
|
14
18
|
BROWSER_HOSTILE_CODECS,
|
|
15
19
|
PROXY_VARIANT_CONFIG,
|
|
@@ -17,9 +21,13 @@ export {
|
|
|
17
21
|
decideMediaProxyEligibility,
|
|
18
22
|
isProxyVariant,
|
|
19
23
|
isProxyVariantRequest,
|
|
24
|
+
mediaProxyDemand,
|
|
20
25
|
probeAssetCodec,
|
|
21
26
|
proxyVariantFor,
|
|
27
|
+
recordProxyPrewarm,
|
|
28
|
+
recordProxyRequest,
|
|
22
29
|
resolveProxyVariantRequest,
|
|
23
|
-
scanProjectMediaCodecMap
|
|
30
|
+
scanProjectMediaCodecMap,
|
|
31
|
+
shouldPrewarmProxy
|
|
24
32
|
};
|
|
25
33
|
//# sourceMappingURL=mediaCodecMap.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { P as PreviewApiAdapter, i as injectMediaCodecMap, e as injectMediaCodecMapIntoHtml, f as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-
|
|
1
|
+
export { P as PreviewApiAdapter, i as injectMediaCodecMap, e as injectMediaCodecMapIntoHtml, f as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-BzPAj--m.js';
|
|
2
2
|
import './mediaCodecMap.js';
|
|
3
3
|
import '@hyperframes/core';
|
|
4
4
|
import '@hyperframes/parsers';
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
isAutoProxyEnabled,
|
|
5
5
|
proxyEtagSalt,
|
|
6
6
|
resolvePreviewMediaCodecProbeCache
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-QN7UPP3B.js";
|
|
8
|
+
import "../chunk-BVSPLTYT.js";
|
|
9
|
+
import "../chunk-QJ73CZFQ.js";
|
|
10
10
|
export {
|
|
11
11
|
injectMediaCodecMap,
|
|
12
12
|
injectMediaCodecMapIntoHtml,
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
getProxyCachePath,
|
|
12
12
|
resolveProxy,
|
|
13
13
|
waitForProxy
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-BVSPLTYT.js";
|
|
15
|
+
import "../chunk-QJ73CZFQ.js";
|
|
16
16
|
export {
|
|
17
17
|
DEFAULT_PROXY_WAIT_TIMEOUT_MS,
|
|
18
18
|
FfmpegMissingFilterError,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { S as StudioApiAdapter, M as MediaProcessingJobState } from './mediaProxyPreview-
|
|
3
|
-
export { L as LintResult, P as PreviewApiAdapter, R as RenderJobState, a as ResolvedProject, b as StudioSelectionResponse, c as StudioSelectionSnapshot, d as StudioSelectionTextField } from './mediaProxyPreview-
|
|
2
|
+
import { S as StudioApiAdapter, M as MediaProcessingJobState } from './mediaProxyPreview-BzPAj--m.js';
|
|
3
|
+
export { L as LintResult, P as PreviewApiAdapter, R as RenderJobState, a as ResolvedProject, b as StudioSelectionResponse, c as StudioSelectionSnapshot, d as StudioSelectionTextField } from './mediaProxyPreview-BzPAj--m.js';
|
|
4
4
|
export { ScreenshotClip, getElementScreenshotClip } from './helpers/screenshotClip.js';
|
|
5
5
|
export { STUDIO_MANUAL_EDITS_PATH, StudioManualEditsRenderScriptOptions, createStudioManualEditsRenderBodyScript, createStudioPositionSeekReapplyScript } from './helpers/manualEditsRenderScript.js';
|
|
6
6
|
export { STUDIO_MOTION_PATH, StudioMotionRenderScriptOptions, createStudioMotionRenderBodyScript } from './helpers/studioMotionRenderScript.js';
|
|
@@ -55,8 +55,22 @@ interface FileWriteReceipt {
|
|
|
55
55
|
}
|
|
56
56
|
/** Strong content version used as both the JSON version and HTTP ETag. */
|
|
57
57
|
declare function fileContentVersion(content: string | Uint8Array): string;
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Attach one API write's identity to the watcher echo for its exact bytes.
|
|
60
|
+
*
|
|
61
|
+
* Reading is non-destructive: one watcher event fans out to every open SSE
|
|
62
|
+
* subscriber, and a receipt removed by the first reader leaves the rest seeing
|
|
63
|
+
* an unlabelled change and reloading the preview on Studio's own edit. Only the
|
|
64
|
+
* TTL removes a receipt.
|
|
65
|
+
*/
|
|
66
|
+
declare function identifyFileWrite(absPath: string, expectedVersion: string): FileWriteReceipt | null;
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated Renamed to {@link identifyFileWrite}, which despite this alias's
|
|
69
|
+
* name does NOT consume the receipt: one watcher event fans out to every SSE
|
|
70
|
+
* subscriber, so a destructive read left all but the first reloading on Studio's
|
|
71
|
+
* own write. Kept for one release; call `identifyFileWrite` instead.
|
|
72
|
+
*/
|
|
73
|
+
declare const consumeFileWriteReceipt: typeof identifyFileWrite;
|
|
60
74
|
|
|
61
75
|
/**
|
|
62
76
|
* Build a standalone HTML page for a sub-composition.
|
|
@@ -118,4 +132,4 @@ type BackgroundRemovalRender = (options: {
|
|
|
118
132
|
}>;
|
|
119
133
|
declare function createBackgroundRemovalJob(opts: BackgroundRemovalJobOptions, render: BackgroundRemovalRender): MediaProcessingJobState;
|
|
120
134
|
|
|
121
|
-
export { type BackgroundRemovalRender, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, buildSubCompositionHtml, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, fileContentVersion, getMimeType, thumbnailDeviceScaleFactor, walkDir };
|
|
135
|
+
export { type BackgroundRemovalRender, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, buildSubCompositionHtml, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, fileContentVersion, getMimeType, identifyFileWrite, thumbnailDeviceScaleFactor, walkDir };
|
package/dist/index.js
CHANGED
|
@@ -19,20 +19,21 @@ import {
|
|
|
19
19
|
isAutoProxyEnabled,
|
|
20
20
|
proxyEtagSalt,
|
|
21
21
|
resolvePreviewMediaCodecProbeCache
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-QN7UPP3B.js";
|
|
23
23
|
import {
|
|
24
24
|
ProxyCapacityError,
|
|
25
25
|
ProxyTranscodeError,
|
|
26
26
|
resolveProxy
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-BVSPLTYT.js";
|
|
28
28
|
import {
|
|
29
29
|
PROXY_VARIANT_CONFIG,
|
|
30
30
|
decideMediaProxyEligibility,
|
|
31
31
|
isProxyVariantRequest,
|
|
32
32
|
probeAssetCodec,
|
|
33
33
|
probeMediaMetadata,
|
|
34
|
+
recordProxyRequest,
|
|
34
35
|
resolveProxyVariantRequest
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-QJ73CZFQ.js";
|
|
36
37
|
import {
|
|
37
38
|
STUDIO_MANUAL_EDITS_PATH,
|
|
38
39
|
createStudioManualEditsRenderBodyScript,
|
|
@@ -669,19 +670,22 @@ function recordFileWriteReceipt(absPath, receipt) {
|
|
|
669
670
|
current.push({ ...receipt, recordedAt: now });
|
|
670
671
|
receipts.set(absPath, current);
|
|
671
672
|
}
|
|
672
|
-
function
|
|
673
|
+
function identifyFileWrite(absPath, expectedVersion) {
|
|
673
674
|
const now = Date.now();
|
|
674
675
|
const current = (receipts.get(absPath) ?? []).filter(
|
|
675
676
|
(entry) => now - entry.recordedAt < RECEIPT_TTL_MS
|
|
676
677
|
);
|
|
677
|
-
const receiptIndex = current.findIndex((entry) => entry.version === expectedVersion);
|
|
678
|
-
const receipt = receiptIndex === -1 ? null : current.splice(receiptIndex, 1)[0] ?? null;
|
|
679
678
|
if (current.length > 0) receipts.set(absPath, current);
|
|
680
679
|
else receipts.delete(absPath);
|
|
680
|
+
let receipt;
|
|
681
|
+
for (let i = current.length - 1; i >= 0 && !receipt; i -= 1) {
|
|
682
|
+
if (current[i]?.version === expectedVersion) receipt = current[i];
|
|
683
|
+
}
|
|
681
684
|
if (!receipt) return null;
|
|
682
685
|
const { path, version, writeToken } = receipt;
|
|
683
686
|
return { path, version, writeToken };
|
|
684
687
|
}
|
|
688
|
+
var consumeFileWriteReceipt = identifyFileWrite;
|
|
685
689
|
|
|
686
690
|
// src/routes/files.ts
|
|
687
691
|
import { classifyPropertyGroup } from "@hyperframes/parsers/gsap-constants";
|
|
@@ -3761,6 +3765,7 @@ ${runtimeTag}`;
|
|
|
3761
3765
|
let servedPath = file;
|
|
3762
3766
|
let servedContentType = contentType;
|
|
3763
3767
|
if (proxyVariant !== void 0) {
|
|
3768
|
+
recordProxyRequest();
|
|
3764
3769
|
try {
|
|
3765
3770
|
servedPath = await resolveProxy(project.dir, file, proxyVariant);
|
|
3766
3771
|
} catch (err) {
|
|
@@ -5030,6 +5035,7 @@ export {
|
|
|
5030
5035
|
fileContentVersion,
|
|
5031
5036
|
getElementScreenshotClip,
|
|
5032
5037
|
getMimeType,
|
|
5038
|
+
identifyFileWrite,
|
|
5033
5039
|
isSafePath,
|
|
5034
5040
|
thumbnailDeviceScaleFactor,
|
|
5035
5041
|
walkDir
|