@hyperframes/studio-server 0.7.90 → 0.7.93
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-I2USK772.js → chunk-6H3V3WGJ.js} +2 -1
- package/dist/{chunk-I2USK772.js.map → chunk-6H3V3WGJ.js.map} +1 -1
- package/dist/{chunk-JOZUTP73.js → chunk-LHYV3WLZ.js} +3 -3
- package/dist/{chunk-IQAVZUQP.js → chunk-ZPI6QXJH.js} +2 -2
- package/dist/helpers/mediaCodecMap.js +1 -1
- 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 +2 -2
- package/dist/index.js +18 -13
- package/dist/index.js.map +1 -1
- package/dist/{mediaProxyPreview-ChE1ATG4.d.ts → mediaProxyPreview-pJzYIpV9.d.ts} +7 -0
- package/package.json +3 -3
- /package/dist/{chunk-JOZUTP73.js.map → chunk-LHYV3WLZ.js.map} +0 -0
- /package/dist/{chunk-IQAVZUQP.js.map → chunk-ZPI6QXJH.js.map} +0 -0
|
@@ -110,6 +110,7 @@ async function probeMediaMetadata(filePath, runner = execFileRunner) {
|
|
|
110
110
|
"stream=codec_type,codec_name,profile,pix_fmt,color_space,color_transfer,color_primaries,bits_per_raw_sample:stream_disposition=attached_pic",
|
|
111
111
|
"-of",
|
|
112
112
|
"json",
|
|
113
|
+
"--",
|
|
113
114
|
filePath
|
|
114
115
|
],
|
|
115
116
|
{ timeout: 15e3, maxBuffer: 1024 * 1024 }
|
|
@@ -278,4 +279,4 @@ export {
|
|
|
278
279
|
createMediaCodecProbeCache,
|
|
279
280
|
scanProjectMediaCodecMap
|
|
280
281
|
};
|
|
281
|
-
//# sourceMappingURL=chunk-
|
|
282
|
+
//# sourceMappingURL=chunk-6H3V3WGJ.js.map
|
|
@@ -1 +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\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, string | null> = {\n hevc: 'video/mp4; codecs=\"hvc1.1.6.L120.B0\"',\n prores: null,\n av1: 'video/mp4; codecs=\"av01.0.08M.08\"',\n // VP9 is browser-dependent: Chrome generally decodes it while Safari\n // support varies. Treat it as conditional so canPlayType keeps the\n // original where supported and transparently proxies it where unsupported.\n vp9: 'video/webm; codecs=\"vp09.00.10.08\"',\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 isHostile = Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName);\n return {\n codecName,\n browserHostile: isHostile,\n representativeMime: isHostile ? (BROWSER_HOSTILE_CODECS[codecName] ?? null) : 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 },\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 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":"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","names":[]}
|
|
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\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, string | null> = {\n hevc: 'video/mp4; codecs=\"hvc1.1.6.L120.B0\"',\n prores: null,\n av1: 'video/mp4; codecs=\"av01.0.08M.08\"',\n // VP9 is browser-dependent: Chrome generally decodes it while Safari\n // support varies. Treat it as conditional so canPlayType keeps the\n // original where supported and transparently proxies it where unsupported.\n vp9: 'video/webm; codecs=\"vp09.00.10.08\"',\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 isHostile = Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName);\n return {\n codecName,\n browserHostile: isHostile,\n representativeMime: isHostile ? (BROWSER_HOSTILE_CODECS[codecName] ?? null) : 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 },\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":"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","names":[]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROXY_PARAMS_VERSION,
|
|
3
3
|
resolveProxy
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZPI6QXJH.js";
|
|
5
5
|
import {
|
|
6
6
|
createMediaCodecProbeCache,
|
|
7
7
|
proxyVariantFor,
|
|
8
8
|
scanProjectMediaCodecMap
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6H3V3WGJ.js";
|
|
10
10
|
|
|
11
11
|
// src/helpers/mediaProxyPreview.ts
|
|
12
12
|
import { resolve } from "path";
|
|
@@ -63,4 +63,4 @@ export {
|
|
|
63
63
|
injectMediaCodecMapIntoHtml,
|
|
64
64
|
injectMediaCodecMap
|
|
65
65
|
};
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-LHYV3WLZ.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROXY_VARIANT_CONFIG,
|
|
3
3
|
probeMediaMetadata
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6H3V3WGJ.js";
|
|
5
5
|
|
|
6
6
|
// src/helpers/proxyTranscoder.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
@@ -461,4 +461,4 @@ export {
|
|
|
461
461
|
clearFailedTranscodesForTest,
|
|
462
462
|
resolveProxy
|
|
463
463
|
};
|
|
464
|
-
//# sourceMappingURL=chunk-
|
|
464
|
+
//# sourceMappingURL=chunk-ZPI6QXJH.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-pJzYIpV9.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-LHYV3WLZ.js";
|
|
8
|
+
import "../chunk-ZPI6QXJH.js";
|
|
9
|
+
import "../chunk-6H3V3WGJ.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-ZPI6QXJH.js";
|
|
15
|
+
import "../chunk-6H3V3WGJ.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-pJzYIpV9.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-pJzYIpV9.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';
|
package/dist/index.js
CHANGED
|
@@ -19,12 +19,12 @@ import {
|
|
|
19
19
|
isAutoProxyEnabled,
|
|
20
20
|
proxyEtagSalt,
|
|
21
21
|
resolvePreviewMediaCodecProbeCache
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-LHYV3WLZ.js";
|
|
23
23
|
import {
|
|
24
24
|
ProxyCapacityError,
|
|
25
25
|
ProxyTranscodeError,
|
|
26
26
|
resolveProxy
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-ZPI6QXJH.js";
|
|
28
28
|
import {
|
|
29
29
|
PROXY_VARIANT_CONFIG,
|
|
30
30
|
decideMediaProxyEligibility,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
probeAssetCodec,
|
|
33
33
|
probeMediaMetadata,
|
|
34
34
|
resolveProxyVariantRequest
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6H3V3WGJ.js";
|
|
36
36
|
import {
|
|
37
37
|
STUDIO_MANUAL_EDITS_PATH,
|
|
38
38
|
createStudioManualEditsRenderBodyScript,
|
|
@@ -501,6 +501,7 @@ function validateUploadedMedia(filePath, runner = spawnSync) {
|
|
|
501
501
|
"stream=codec_type",
|
|
502
502
|
"-of",
|
|
503
503
|
"json",
|
|
504
|
+
"--",
|
|
504
505
|
filePath
|
|
505
506
|
]);
|
|
506
507
|
if (result.error?.code === "ENOENT") {
|
|
@@ -2999,21 +3000,24 @@ import { stripEmbeddedRuntimeScripts } from "@hyperframes/core/compiler";
|
|
|
2999
3000
|
function isFullHtmlDocument(html) {
|
|
3000
3001
|
return /^\s*(?:<!doctype\s|<html[\s>])/i.test(html);
|
|
3001
3002
|
}
|
|
3002
|
-
function rewriteRelativePaths(root, compPath) {
|
|
3003
|
+
function rewriteRelativePaths(root, compPath, projectDir) {
|
|
3004
|
+
const assetExists = (path) => existsSync5(join7(projectDir, path));
|
|
3003
3005
|
rewriteAssetPaths(
|
|
3004
3006
|
root.querySelectorAll("[src], [href]"),
|
|
3005
3007
|
compPath,
|
|
3006
3008
|
(el, attr) => el.getAttribute(attr),
|
|
3007
|
-
(el, attr, value) => el.setAttribute(attr, value)
|
|
3009
|
+
(el, attr, value) => el.setAttribute(attr, value),
|
|
3010
|
+
assetExists
|
|
3008
3011
|
);
|
|
3009
3012
|
rewriteInlineStyleAssetUrls(
|
|
3010
3013
|
root.querySelectorAll("[style]"),
|
|
3011
3014
|
compPath,
|
|
3012
3015
|
(el) => el.getAttribute("style"),
|
|
3013
|
-
(el, value) => el.setAttribute("style", value)
|
|
3016
|
+
(el, value) => el.setAttribute("style", value),
|
|
3017
|
+
assetExists
|
|
3014
3018
|
);
|
|
3015
3019
|
for (const styleEl of root.querySelectorAll("style")) {
|
|
3016
|
-
styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath);
|
|
3020
|
+
styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath, assetExists);
|
|
3017
3021
|
}
|
|
3018
3022
|
}
|
|
3019
3023
|
function escapeLeadingDigitIdent(id) {
|
|
@@ -3036,11 +3040,11 @@ function fixDigitLeadingIdSelectors(root) {
|
|
|
3036
3040
|
styleEl.textContent = css;
|
|
3037
3041
|
}
|
|
3038
3042
|
}
|
|
3039
|
-
function extractFullDocumentParts(rawHtml, compPath) {
|
|
3043
|
+
function extractFullDocumentParts(rawHtml, compPath, projectDir) {
|
|
3040
3044
|
const { document: doc } = parseHTML3(rawHtml);
|
|
3041
3045
|
const rewriteTargets = [doc.head, doc.body].filter(Boolean);
|
|
3042
3046
|
for (const target of rewriteTargets) {
|
|
3043
|
-
rewriteRelativePaths(target, compPath);
|
|
3047
|
+
rewriteRelativePaths(target, compPath, projectDir);
|
|
3044
3048
|
}
|
|
3045
3049
|
fixDigitLeadingIdSelectors(doc);
|
|
3046
3050
|
const headContent = doc.head?.innerHTML ?? "";
|
|
@@ -3106,12 +3110,12 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
|
|
|
3106
3110
|
const { document: contentDoc } = parseHTML3(
|
|
3107
3111
|
`<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
|
|
3108
3112
|
);
|
|
3109
|
-
rewriteRelativePaths(contentDoc, compPath);
|
|
3113
|
+
rewriteRelativePaths(contentDoc, compPath, projectDir);
|
|
3110
3114
|
fixDigitLeadingIdSelectors(contentDoc);
|
|
3111
3115
|
promoteTemplateCompositionId(rawComp, contentDoc.body);
|
|
3112
3116
|
rewrittenContent = contentDoc.body.innerHTML || templateInner;
|
|
3113
3117
|
} else if (isFullHtmlDocument(rawComp)) {
|
|
3114
|
-
const parts = extractFullDocumentParts(rawComp, compPath);
|
|
3118
|
+
const parts = extractFullDocumentParts(rawComp, compPath, projectDir);
|
|
3115
3119
|
compHeadContent = parts.headContent;
|
|
3116
3120
|
rewrittenContent = parts.bodyContent;
|
|
3117
3121
|
htmlAttrs = parts.htmlAttrs;
|
|
@@ -3120,7 +3124,7 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
|
|
|
3120
3124
|
const { document: contentDoc } = parseHTML3(
|
|
3121
3125
|
`<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
|
|
3122
3126
|
);
|
|
3123
|
-
rewriteRelativePaths(contentDoc, compPath);
|
|
3127
|
+
rewriteRelativePaths(contentDoc, compPath, projectDir);
|
|
3124
3128
|
fixDigitLeadingIdSelectors(contentDoc);
|
|
3125
3129
|
rewrittenContent = contentDoc.body.innerHTML || rawComp;
|
|
3126
3130
|
}
|
|
@@ -3750,7 +3754,8 @@ function registerRenderRoutes(api, adapter) {
|
|
|
3750
3754
|
outputResolution,
|
|
3751
3755
|
composition,
|
|
3752
3756
|
variables,
|
|
3753
|
-
distinctId: typeof body.telemetryDistinctId === "string" ? body.telemetryDistinctId : void 0
|
|
3757
|
+
distinctId: typeof body.telemetryDistinctId === "string" ? body.telemetryDistinctId : void 0,
|
|
3758
|
+
telemetryOptOut: body.telemetryOptOut === true
|
|
3754
3759
|
});
|
|
3755
3760
|
jobState.createdAt = Date.now();
|
|
3756
3761
|
renderJobs.set(jobId, jobState);
|