@hyperframes/studio-server 0.8.145 → 0.8.147
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-DG2VOIGR.js → chunk-3RUFN2CK.js} +1 -2
- package/dist/{chunk-RN33UALN.js → chunk-EJC7TX3O.js} +1 -2
- package/dist/{chunk-4ETS2LXI.js → chunk-L3HJVTQE.js} +0 -1
- package/dist/{chunk-DTKK7TKL.js → chunk-M62HDRLX.js} +2 -3
- package/dist/{chunk-IMG2GSY6.js → chunk-RGC6DKVM.js} +0 -1
- package/dist/{chunk-6XMC64FJ.js → chunk-SVQFE44C.js} +0 -1
- package/dist/{chunk-X62ASOGO.js → chunk-UEEXAVOA.js} +0 -1
- package/dist/{chunk-32IH7WBY.js → chunk-VMT4OC5B.js} +2 -3
- package/dist/{chunk-2VZGTTEG.js → chunk-XKBQOQFZ.js} +42 -8
- package/dist/{chunk-K3H6HTVC.js → chunk-Y7V6FJHS.js} +41 -16
- package/dist/{chunk-JJ4N3PXT.js → chunk-Z5UJMFO4.js} +1 -2
- package/dist/{chunk-WE5JQ6V7.js → chunk-ZNRTEXK2.js} +2 -3
- package/dist/helpers/draftMarkers.js +0 -1
- package/dist/helpers/finiteMutation.js +1 -2
- package/dist/helpers/hfIdPersist.js +4 -5
- package/dist/helpers/loudness.js +4 -5
- package/dist/helpers/manualEditsRenderScript.js +1 -2
- package/dist/helpers/mediaCodecMap.js +4 -5
- package/dist/helpers/mediaProxyPreview.d.ts +2 -2
- package/dist/helpers/mediaProxyPreview.js +6 -7
- package/dist/helpers/proxyTranscoder.d.ts +13 -1
- package/dist/helpers/proxyTranscoder.js +9 -6
- package/dist/helpers/safePath.js +3 -4
- package/dist/helpers/screenshotClip.js +1 -2
- package/dist/helpers/sourceMutation.d.ts +0 -1
- package/dist/helpers/sourceMutation.js +1 -2
- package/dist/helpers/studioMotionRenderScript.js +1 -2
- package/dist/history/index.d.ts +2 -2
- package/dist/history/index.js +8 -7
- package/dist/index.d.ts +3 -3
- package/dist/index.js +15 -15
- package/dist/{mediaProxyPreview-uWqly-sz.d.ts → mediaProxyPreview-BR7yXz2M.d.ts} +1 -1
- package/dist/{projectHistory-Cm8Mdmmv.d.ts → projectHistory-CyTVYz2y.d.ts} +9 -1
- package/package.json +3 -3
- package/dist/chunk-2VZGTTEG.js.map +0 -1
- package/dist/chunk-32IH7WBY.js.map +0 -1
- package/dist/chunk-4ETS2LXI.js.map +0 -1
- package/dist/chunk-6XMC64FJ.js.map +0 -1
- package/dist/chunk-DG2VOIGR.js.map +0 -1
- package/dist/chunk-DTKK7TKL.js.map +0 -1
- package/dist/chunk-IMG2GSY6.js.map +0 -1
- package/dist/chunk-JJ4N3PXT.js.map +0 -1
- package/dist/chunk-K3H6HTVC.js.map +0 -1
- package/dist/chunk-RN33UALN.js.map +0 -1
- package/dist/chunk-WE5JQ6V7.js.map +0 -1
- package/dist/chunk-X62ASOGO.js.map +0 -1
- package/dist/helpers/draftMarkers.js.map +0 -1
- package/dist/helpers/finiteMutation.js.map +0 -1
- package/dist/helpers/hfIdPersist.js.map +0 -1
- package/dist/helpers/loudness.js.map +0 -1
- package/dist/helpers/manualEditsRenderScript.js.map +0 -1
- package/dist/helpers/mediaCodecMap.js.map +0 -1
- package/dist/helpers/mediaProxyPreview.js.map +0 -1
- package/dist/helpers/proxyTranscoder.js.map +0 -1
- package/dist/helpers/safePath.js.map +0 -1
- package/dist/helpers/screenshotClip.js.map +0 -1
- package/dist/helpers/sourceMutation.js.map +0 -1
- package/dist/helpers/studioMotionRenderScript.js.map +0 -1
- package/dist/history/index.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-
|
|
3
|
-
export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-
|
|
4
|
-
export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryWho, g as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, h as ProjectHistoryOptions, U as UNDO_MODES,
|
|
2
|
+
import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-BR7yXz2M.js';
|
|
3
|
+
export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-BR7yXz2M.js';
|
|
4
|
+
export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryWho, g as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, h as ProjectHistoryOptions, i as ProjectHistoryView, U as UNDO_MODES, j as UndoMode, o as openProjectHistory, r as readProjectHistory } from './projectHistory-CyTVYz2y.js';
|
|
5
5
|
export { HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, historyCache, listProjectHistories, pruneGoneProjectHistories } from './history/index.js';
|
|
6
6
|
export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
|
|
7
7
|
export { walkDir } from './helpers/safePath.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findUnsafeDomPatchValues,
|
|
3
3
|
findUnsafeMutationValues
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UEEXAVOA.js";
|
|
5
5
|
import {
|
|
6
6
|
dedupeClonedCompositionId,
|
|
7
7
|
findTargetElement,
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
splitElementInHtml,
|
|
18
18
|
unwrapElementsFromHtml,
|
|
19
19
|
wrapElementsInHtml
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-Z5UJMFO4.js";
|
|
21
21
|
import {
|
|
22
22
|
isCompositionSource,
|
|
23
23
|
stampProjectHfIds
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-3RUFN2CK.js";
|
|
25
25
|
import {
|
|
26
26
|
DEFAULT_HISTORY_ROOT,
|
|
27
27
|
DELETED_VERSION,
|
|
@@ -43,15 +43,16 @@ import {
|
|
|
43
43
|
listProjectHistories,
|
|
44
44
|
openProjectHistory,
|
|
45
45
|
pruneGoneProjectHistories,
|
|
46
|
+
readProjectHistory,
|
|
46
47
|
recordFileWriteReceipt,
|
|
47
48
|
resolveProjectAndSignature,
|
|
48
49
|
resolveProjectSignature,
|
|
49
50
|
settledFileTag
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-Y7V6FJHS.js";
|
|
51
52
|
import {
|
|
52
53
|
clearElementScreenshotIsolation,
|
|
53
54
|
getElementScreenshotClip
|
|
54
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-RGC6DKVM.js";
|
|
55
56
|
import {
|
|
56
57
|
DEFAULT_TARGET_LUFS,
|
|
57
58
|
absoluteLoudnessPlan,
|
|
@@ -61,13 +62,13 @@ import {
|
|
|
61
62
|
requestedProjectPath,
|
|
62
63
|
requiredFfmpeg,
|
|
63
64
|
resolveLocalAudioPath
|
|
64
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-VMT4OC5B.js";
|
|
65
66
|
import {
|
|
66
67
|
injectMediaCodecMap,
|
|
67
68
|
isAutoProxyEnabled,
|
|
68
69
|
proxyEtagSalt,
|
|
69
70
|
resolvePreviewMediaCodecProbeCache
|
|
70
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-ZNRTEXK2.js";
|
|
71
72
|
import {
|
|
72
73
|
CACHE_DIR_NAME,
|
|
73
74
|
PROXY_PENDING_RETRY_AFTER_SECONDS,
|
|
@@ -78,7 +79,7 @@ import {
|
|
|
78
79
|
proxyActivityMark,
|
|
79
80
|
resolveProxy,
|
|
80
81
|
waitForProxy
|
|
81
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-XKBQOQFZ.js";
|
|
82
83
|
import {
|
|
83
84
|
PROXY_VARIANT_CONFIG,
|
|
84
85
|
decideMediaProxyEligibility,
|
|
@@ -87,7 +88,7 @@ import {
|
|
|
87
88
|
probeMediaMetadata,
|
|
88
89
|
recordProxyRequest,
|
|
89
90
|
resolveProxyVariantRequest
|
|
90
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-EJC7TX3O.js";
|
|
91
92
|
import {
|
|
92
93
|
folderGone,
|
|
93
94
|
isInHiddenOrVendorDir,
|
|
@@ -101,16 +102,16 @@ import {
|
|
|
101
102
|
realpath,
|
|
102
103
|
resolveWithinProject,
|
|
103
104
|
walkDir
|
|
104
|
-
} from "./chunk-
|
|
105
|
+
} from "./chunk-M62HDRLX.js";
|
|
105
106
|
import {
|
|
106
107
|
STUDIO_MANUAL_EDITS_PATH,
|
|
107
108
|
createStudioManualEditsRenderBodyScript,
|
|
108
109
|
createStudioPositionSeekReapplyScript
|
|
109
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-SVQFE44C.js";
|
|
110
111
|
import {
|
|
111
112
|
STUDIO_MOTION_PATH,
|
|
112
113
|
createStudioMotionRenderBodyScript
|
|
113
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-L3HJVTQE.js";
|
|
114
115
|
|
|
115
116
|
// src/createStudioApi.ts
|
|
116
117
|
import { Hono } from "hono";
|
|
@@ -2839,7 +2840,6 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
|
|
|
2839
2840
|
duration: cut.elementDuration,
|
|
2840
2841
|
playbackStart: cut.playbackStart,
|
|
2841
2842
|
playbackRate: cut.playbackRate,
|
|
2842
|
-
stampPlaybackStart: cut.isComposition,
|
|
2843
2843
|
track: cut.track
|
|
2844
2844
|
});
|
|
2845
2845
|
if (!split.matched || !split.newId) {
|
|
@@ -5224,7 +5224,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
5224
5224
|
);
|
|
5225
5225
|
if (!buffer) {
|
|
5226
5226
|
return c.json(
|
|
5227
|
-
{ error: "Thumbnail generation failed
|
|
5227
|
+
{ error: "Thumbnail generation failed. Chrome browser may not be available" },
|
|
5228
5228
|
500
|
|
5229
5229
|
);
|
|
5230
5230
|
}
|
|
@@ -6714,6 +6714,7 @@ export {
|
|
|
6714
6714
|
parseSourceDocument,
|
|
6715
6715
|
patchElementInHtml,
|
|
6716
6716
|
pruneGoneProjectHistories,
|
|
6717
|
+
readProjectHistory,
|
|
6717
6718
|
removeElementFromHtml,
|
|
6718
6719
|
requestSubPath,
|
|
6719
6720
|
settledFileTag,
|
|
@@ -6722,4 +6723,3 @@ export {
|
|
|
6722
6723
|
thumbnailDeviceScaleFactor,
|
|
6723
6724
|
walkDir
|
|
6724
6725
|
};
|
|
6725
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -2,7 +2,7 @@ import * as _hyperframes_core from '@hyperframes/core';
|
|
|
2
2
|
import { RegistryItem, PreviewProxyBox } from '@hyperframes/core';
|
|
3
3
|
import { CanvasResolution } from '@hyperframes/parsers';
|
|
4
4
|
import { BundleOptions } from '@hyperframes/core/compiler';
|
|
5
|
-
import { P as ProjectHistory } from './projectHistory-
|
|
5
|
+
import { P as ProjectHistory } from './projectHistory-CyTVYz2y.js';
|
|
6
6
|
import { MediaCodecProbeCache, HtmlSourceLike } from './helpers/mediaCodecMap.js';
|
|
7
7
|
|
|
8
8
|
/** Resolved info about a single project. */
|
|
@@ -135,5 +135,13 @@ declare class HistoryClosedError extends Error {
|
|
|
135
135
|
}
|
|
136
136
|
/** Opens a project's history: every write to its files becomes an entry that can be undone or restored. */
|
|
137
137
|
declare function openProjectHistory(options: ProjectHistoryOptions): Promise<ProjectHistory>;
|
|
138
|
+
/** A project's history as its log stands now, read without the owner lock: it reads while another process owns it. */
|
|
139
|
+
interface ProjectHistoryView {
|
|
140
|
+
list(): HistoryListItem[];
|
|
141
|
+
peek(point: string): Record<string, string> | null;
|
|
142
|
+
readBlob(hash: string): Promise<Buffer>;
|
|
143
|
+
}
|
|
144
|
+
/** Reads only: takes no lock and files, prunes or writes nothing; an owner's half-appended last line is skipped. */
|
|
145
|
+
declare function readProjectHistory(options: Pick<ProjectHistoryOptions, "projectDir" | "historyRoot">): ProjectHistoryView;
|
|
138
146
|
|
|
139
|
-
export { type ClosedWindow as C, DEFAULT_HISTORY_ROOT as D, HistoryClosedError as H, MAX_WINDOW_IDLE_MS as M, type ProjectHistory as P, START as S, UNDO_MODES as U, type HistoryEntry as a, type HistoryEntrySide as b, type HistoryFileChange as c, type HistoryListItem as d, type HistoryResult as e, type HistoryWho as f, type HistoryWindow as g, type ProjectHistoryOptions as h, type
|
|
147
|
+
export { type ClosedWindow as C, DEFAULT_HISTORY_ROOT as D, HistoryClosedError as H, MAX_WINDOW_IDLE_MS as M, type ProjectHistory as P, START as S, UNDO_MODES as U, type HistoryEntry as a, type HistoryEntrySide as b, type HistoryFileChange as c, type HistoryListItem as d, type HistoryResult as e, type HistoryWho as f, type HistoryWindow as g, type ProjectHistoryOptions as h, type ProjectHistoryView as i, type UndoMode as j, openProjectHistory as o, readProjectHistory as r };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio-server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.147",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"postcss": "^8.5.8",
|
|
82
82
|
"postcss-selector-parser": "^7.1.2",
|
|
83
83
|
"sharp": "^0.35.0",
|
|
84
|
-
"@hyperframes/
|
|
85
|
-
"@hyperframes/
|
|
84
|
+
"@hyperframes/core": "0.8.147",
|
|
85
|
+
"@hyperframes/parsers": "0.8.147"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@types/node": "^25.0.10",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/proxyTranscoder.ts","../src/helpers/proxyCache.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, renameSync, statSync, unlinkSync, utimesSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport {\n formatPreviewProxyBox,\n hdrToSdrToneMapFilter,\n type PreviewProxyBox,\n} from \"@hyperframes/core\";\nimport { findFfBinary } from \"@hyperframes/parsers/ff-binaries\";\nimport { probeFirstFrameColour, probeMediaMetadata } from \"./mediaMetadata.js\";\nimport { cleanupProxyCache } from \"./proxyCache.js\";\nimport { PROXY_VARIANT_CONFIG, type ProxyVariant } from \"./mediaCodecMap.js\";\nimport { mkdirWithinProject, realpath, realProjectRoot } from \"./safePath.js\";\n\n/**\n * Transcodes browser-hostile local video sources (HEVC, ProRes, ...) into a\n * cached, seekable authoring proxy. Consumed by the preview/play/static\n * project routes (U3/U4) to serve a `?hf-proxy=` request; never used on\n * the render path (render always sees the original file).\n *\n * A started ffmpeg child is never killed for one caller: every caller of a cache\n * key shares it (in-flight dedupe below). A caller's `signal` only detaches that\n * caller, and drops the copy while it is still queued with no caller left.\n */\n\nexport const PROXY_PARAMS_VERSION = \"v5\";\n\nexport const CACHE_DIR_NAME = \".transcode-cache\";\n\nfunction boundedEnvInteger(name: string, fallback: number, min: number, max: number): number {\n const raw = process.env[name]?.trim();\n if (!raw) return fallback;\n const parsed = Number(raw);\n return Number.isSafeInteger(parsed) && parsed >= min && parsed <= max ? parsed : fallback;\n}\n\n// ffmpeg is internally multithreaded, so two concurrent proxy encodes already\n// saturate a typical laptop. Operators of shared/large machines may tune the\n// bounded values without patching the package; invalid values fail safe.\nconst MAX_CONCURRENT_TRANSCODES = boundedEnvInteger(\"HYPERFRAMES_PROXY_MAX_CONCURRENCY\", 2, 1, 16);\nconst MAX_QUEUED_TRANSCODES = boundedEnvInteger(\"HYPERFRAMES_PROXY_MAX_QUEUE\", 8, 0, 256);\n\nconst STDERR_TAIL_MAX_CHARS = 4000;\nexport const TRANSCODE_TIMEOUT_MS = 15 * 60 * 1000;\nconst FAILURE_CACHE_TTL_MS = 60 * 1000;\nexport const PROXY_PENDING_RETRY_AFTER_SECONDS = 2;\nconst ENVIRONMENT_FAILURE_TTL_MS = 5 * PROXY_PENDING_RETRY_AFTER_SECONDS * 1000;\nconst MAX_FAILURE_CACHE_ENTRIES = 128;\nexport const DEFAULT_PROXY_WAIT_TIMEOUT_MS = 2 * 60 * 1000;\n\nexport class ProxyTranscodeError extends Error {\n readonly exitCode: number | null;\n readonly stderrTail: string;\n\n constructor(message: string, exitCode: number | null, stderrTail: string) {\n super(message);\n this.name = \"ProxyTranscodeError\";\n this.exitCode = exitCode;\n this.stderrTail = stderrTail;\n }\n}\n\n/** \"ffmpeg isn't installed\" — an environment condition, not a per-source\n * failure, so the negative cache below keeps it only briefly\n * (installing ffmpeg mid-session must recover without a server restart). */\nclass FfmpegUnavailableError extends ProxyTranscodeError {\n constructor() {\n super(\"ffmpeg binary not found\", null, \"\");\n }\n}\n\nexport class FfmpegMissingFilterError extends ProxyTranscodeError {\n constructor() {\n super(\n \"HDR proxying requires ffmpeg zscale/tonemap filters (libzimg); install an ffmpeg build with libzimg support\",\n null,\n \"\",\n );\n this.name = \"FfmpegMissingFilterError\";\n }\n}\n\nexport class ProxyCapacityError extends ProxyTranscodeError {\n constructor() {\n super(\"media proxy queue is full; retry shortly\", null, \"\");\n this.name = \"ProxyCapacityError\";\n }\n}\n\nexport class ProxySourceOutsideProjectError extends ProxyTranscodeError {\n constructor() {\n super(\"media proxy source must be addressed through the project\", null, \"\");\n this.name = \"ProxySourceOutsideProjectError\";\n }\n}\n\nexport class ProxyWaitTimeoutError extends ProxyTranscodeError {\n constructor(timeoutMs: number) {\n super(`media proxy did not become ready within ${timeoutMs}ms`, null, \"\");\n this.name = \"ProxyWaitTimeoutError\";\n }\n}\n\n/** Bounds one caller's wait without cancelling the shared in-flight ffmpeg\n * job. Other preview/publish callers still receive the completed cache entry. */\nexport async function waitForProxy<T>(\n promise: Promise<T>,\n timeoutMs = DEFAULT_PROXY_WAIT_TIMEOUT_MS,\n): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new ProxyWaitTimeoutError(timeoutMs)), timeoutMs);\n timer.unref();\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * Cache key inputs per the plan: project-relative source path (portable across\n * checkouts), canonical source identity (so retargeted external symlinks do\n * not reuse a proxy), mtime and file size (mtime alone can collide on\n * same-second re-exports on coarse-timestamp filesystems; size catches nearly\n * all such cases at zero cost), and a params version token so changing the\n * ffmpeg recipe below invalidates every cached proxy cleanly.\n */\ntype CanonicalProxySource = {\n projectDir: string;\n sourcePath: string;\n relativePath: string;\n cacheIdentity: string;\n};\n\nfunction canonicalizeProxySource(\n projectDir: string,\n absoluteSourcePath: string,\n): CanonicalProxySource {\n const requestedProjectDir = resolve(projectDir);\n const requestedSourcePath = resolve(absoluteSourcePath);\n const requestedRelativePath = relative(requestedProjectDir, requestedSourcePath);\n if (\n requestedRelativePath === \"..\" ||\n requestedRelativePath.startsWith(`..${sep}`) ||\n isAbsolute(requestedRelativePath)\n ) {\n throw new ProxySourceOutsideProjectError();\n }\n\n const canonicalProjectDir = realProjectRoot(projectDir);\n const canonicalSourcePath = realpath(absoluteSourcePath);\n const canonicalRelativePath = relative(canonicalProjectDir, canonicalSourcePath);\n const sourceIsInsideCanonicalProject =\n canonicalRelativePath !== \"..\" &&\n !canonicalRelativePath.startsWith(`..${sep}`) &&\n !isAbsolute(canonicalRelativePath);\n return {\n projectDir: canonicalProjectDir,\n sourcePath: canonicalSourcePath,\n relativePath: requestedRelativePath.normalize(\"NFC\"),\n // An external target needs a stable identity in addition to its project-local\n // symlink path, otherwise retargeting the link can reuse an unrelated proxy.\n cacheIdentity: (sourceIsInsideCanonicalProject\n ? canonicalRelativePath\n : canonicalSourcePath\n ).normalize(\"NFC\"),\n };\n}\n\nfunction buildProxyCacheKey(\n source: CanonicalProxySource,\n variant: ProxyVariant,\n box: PreviewProxyBox | undefined,\n): string {\n const stat = statSync(source.sourcePath);\n const size = box ? `\\0${formatPreviewProxyBox(box)}` : \"\";\n return createHash(\"sha256\")\n .update(\n `${source.relativePath}\\0${source.cacheIdentity}\\0${stat.mtimeMs}\\0${stat.size}\\0${PROXY_PARAMS_VERSION}\\0${variant}${size}`,\n )\n .digest(\"hex\");\n}\n\nfunction getCanonicalProxyCachePath(\n source: CanonicalProxySource,\n variant: ProxyVariant,\n box?: PreviewProxyBox,\n): string {\n const key = buildProxyCacheKey(source, variant, box);\n return join(\n source.projectDir,\n CACHE_DIR_NAME,\n `${key}${PROXY_VARIANT_CONFIG[variant].extension}`,\n );\n}\n\n/**\n * Computes the absolute path a proxy for this source would live at, without\n * transcoding anything, so a host can check cache state. Pass the request's\n * preview box (`hf-proxy-box`) to name a boxed copy.\n */\nexport function getProxyCachePath(\n projectDir: string,\n absoluteSourcePath: string,\n variant: ProxyVariant = \"h264\",\n box?: PreviewProxyBox,\n): string {\n return getCanonicalProxyCachePath(\n canonicalizeProxySource(projectDir, absoluteSourcePath),\n variant,\n box,\n );\n}\n\n// --- global concurrency limiter -------------------------------------------\n// ponytail: a counter + one wait queue is the whole semaphore. Priority copies\n// queue ahead of the rest and are never refused for a full queue, so a few\n// priority asks (the copy a viewer is waiting on) can exceed the cap.\n\n/** One copy per cache key, shared by every caller asking for it. */\ninterface ProxyJob {\n cachePath: string;\n priority: boolean;\n /** Callers still waiting; one without a signal never leaves, so its copy is always made. */\n callers: number;\n /** Set while the job waits for a slot. */\n queued?: { start: () => void; drop: () => void };\n}\n\nclass ProxyDroppedError extends ProxyTranscodeError {\n constructor() {\n super(\"media proxy copy dropped: every caller left before it started\", null, \"\");\n this.name = \"ProxyDroppedError\";\n }\n}\n\nlet activeTranscodes = 0;\nconst waitQueue: ProxyJob[] = [];\n\nfunction enqueue(job: ProxyJob): void {\n const firstNormal = job.priority ? waitQueue.findIndex((queued) => !queued.priority) : -1;\n if (firstNormal === -1) waitQueue.push(job);\n else waitQueue.splice(firstNormal, 0, job);\n}\n\nfunction dequeue(job: ProxyJob): void {\n const index = waitQueue.indexOf(job);\n if (index !== -1) waitQueue.splice(index, 1);\n}\n\nfunction acquireSlot(job: ProxyJob): Promise<void> {\n if (activeTranscodes < MAX_CONCURRENT_TRANSCODES) {\n activeTranscodes++;\n return Promise.resolve();\n }\n return new Promise((resolveSlot, reject) => {\n job.queued = {\n start: () => {\n job.queued = undefined;\n activeTranscodes++;\n resolveSlot();\n },\n drop: () => {\n job.queued = undefined;\n dequeue(job);\n forget(job);\n reject(new ProxyDroppedError());\n },\n };\n enqueue(job);\n });\n}\n\nfunction queueIsFull(): boolean {\n return activeTranscodes >= MAX_CONCURRENT_TRANSCODES && waitQueue.length >= MAX_QUEUED_TRANSCODES;\n}\n\nfunction releaseSlot(): void {\n activeTranscodes--;\n waitQueue.shift()?.queued?.start();\n}\n\nfunction prioritize(job: ProxyJob): void {\n if (job.priority) return;\n job.priority = true;\n if (!job.queued) return;\n dequeue(job);\n enqueue(job);\n}\n\nfunction leave(job: ProxyJob): void {\n job.callers--;\n if (job.callers === 0) job.queued?.drop();\n}\n\n// --- per-key in-flight dedupe ----------------------------------------------\n\nconst inFlight = new Map<string, { promise: Promise<string>; job: ProxyJob }>();\n\nfunction forget(job: ProxyJob): void {\n if (inFlight.get(job.cachePath)?.job === job) inFlight.delete(job.cachePath);\n}\n\nfunction maintainProxyCache(cacheDir: string): void {\n try {\n cleanupProxyCache(cacheDir, { protectedPaths: new Set(inFlight.keys()) });\n } catch (error) {\n // Cache maintenance must never turn a playable preview into an error.\n console.warn(\n `[media-proxy] cache cleanup failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\nfunction markCacheEntryUsed(cachePath: string): void {\n try {\n const now = new Date();\n utimesSync(cachePath, now, now);\n } catch {\n // A concurrent cleanup may have removed a stale entry after existsSync;\n // the normal miss path below will recreate it on the next request.\n }\n}\n\n// --- negative cache ---------------------------------------------------------\n// A source that failed to transcode fails again identically until the file\n// changes (the cache key embeds mtime+size, so a re-export invalidates this\n// naturally). Remembering the failure per key means repeated `?hf-proxy=`\n// requests for a broken asset rethrow instantly instead of respawning ffmpeg\n// on every retry the browser makes.\ninterface RememberedFailure {\n error: unknown;\n expiresAt: number;\n}\n\nconst failedTranscodes = new Map<string, RememberedFailure>();\n\nlet hdrFilterCheck: { ffmpegPath: string; promise: Promise<void> } | undefined;\n\nfunction ensureHdrFilters(ffmpegPath: string): Promise<void> {\n if (hdrFilterCheck?.ffmpegPath === ffmpegPath) return hdrFilterCheck.promise;\n const promise = new Promise<void>((resolveCheck, rejectCheck) => {\n const proc = spawn(ffmpegPath, [\"-hide_banner\", \"-filters\"], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsHide: true,\n });\n let stdout = \"\";\n proc.stdout?.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n proc.on(\"error\", () => rejectCheck(new FfmpegMissingFilterError()));\n proc.on(\"close\", (code) => {\n if (code !== 0 || !/\\bzscale\\b/.test(stdout) || !/\\btonemap\\b/.test(stdout)) {\n rejectCheck(new FfmpegMissingFilterError());\n } else {\n resolveCheck();\n }\n });\n });\n hdrFilterCheck = { ffmpegPath, promise };\n return promise;\n}\n\nfunction rememberFailure(cachePath: string, error: unknown): void {\n const ttlMs =\n error instanceof FfmpegUnavailableError || error instanceof FfmpegMissingFilterError\n ? ENVIRONMENT_FAILURE_TTL_MS\n : FAILURE_CACHE_TTL_MS;\n failedTranscodes.delete(cachePath);\n failedTranscodes.set(cachePath, { error, expiresAt: Date.now() + ttlMs });\n while (failedTranscodes.size > MAX_FAILURE_CACHE_ENTRIES) {\n const oldest = failedTranscodes.keys().next().value;\n if (oldest === undefined) break;\n failedTranscodes.delete(oldest);\n }\n}\n\n/** Test hook: forget remembered transcode failures (module state persists\n * across tests that don't reload the module). */\nexport function clearFailedTranscodesForTest(): void {\n failedTranscodes.clear();\n}\n\n/** Even dimensions; with a box, the source shrinks until its tighter side fills the box, never grows. */\nfunction proxyScaleFilter(box?: PreviewProxyBox): string {\n if (!box) return \"scale=trunc(iw/2)*2:trunc(ih/2)*2\";\n const factor = `min(1\\\\,max(${box.width}/iw\\\\,${box.height}/ih))`;\n const side = (d: string) => `min(trunc(${d}/2)*2\\\\,ceil(${d}*${factor}/2)*2)`;\n return `scale=${side(\"iw\")}:${side(\"ih\")}`;\n}\n\nasync function runFfmpeg(\n sourcePath: string,\n outputPath: string,\n variant: ProxyVariant,\n box: PreviewProxyBox | undefined,\n): Promise<void> {\n const metadata = await probeMediaMetadata(sourcePath);\n const ffmpegPath = findFfBinary(\"ffmpeg\", { configuredMustExist: true });\n if (!ffmpegPath) {\n throw new FfmpegUnavailableError();\n }\n const keepsAlpha = variant === \"vp8\";\n const { hdrTransfer } = metadata.color;\n const toneMap = (hdrTransfer === \"pq\" || hdrTransfer === \"hlg\") && !keepsAlpha;\n if (toneMap) await ensureHdrFilters(ffmpegPath);\n const firstFrame = toneMap ? await probeFirstFrameColour(sourcePath) : {};\n const evenScale = proxyScaleFilter(box);\n const pixelFormat = keepsAlpha ? \"yuva420p\" : \"yuv420p\";\n // The tone map ends in RGB; older ffmpeg (seen on 5.1) converts it with BT.601 unless the matrix is named.\n const videoFilter = toneMap\n ? [\n hdrToSdrToneMapFilter(metadata.color, firstFrame),\n `${evenScale}:out_color_matrix=bt709:out_range=tv`,\n `format=${pixelFormat}`,\n ].join(\",\")\n : [evenScale, `format=${pixelFormat}`].join(\",\");\n\n return new Promise((resolvePromise, reject) => {\n const commonArgs = [\"-y\", \"-i\", sourcePath, \"-vf\", videoFilter];\n const h264Args = [\n \"-c:v\",\n \"libx264\",\n \"-profile:v\",\n \"high\",\n \"-pix_fmt\",\n \"yuv420p\",\n \"-colorspace\",\n \"bt709\",\n \"-color_primaries\",\n \"bt709\",\n \"-color_trc\",\n \"bt709\",\n \"-crf\",\n \"18\",\n \"-preset\",\n \"veryfast\",\n \"-c:a\",\n \"aac\",\n \"-movflags\",\n \"+faststart\",\n ];\n const vp8Args = [\n \"-c:v\",\n \"libvpx\",\n \"-b:v\",\n \"0\",\n \"-crf\",\n \"23\",\n \"-deadline\",\n \"good\",\n \"-pix_fmt\",\n \"yuva420p\",\n \"-colorspace\",\n \"bt709\",\n \"-color_primaries\",\n \"bt709\",\n \"-color_trc\",\n \"bt709\",\n \"-cpu-used\",\n \"4\",\n \"-auto-alt-ref\",\n \"0\",\n \"-metadata:s:v:0\",\n \"alpha_mode=1\",\n \"-ac\",\n \"2\",\n \"-c:a\",\n \"libopus\",\n ];\n const args = [...commonArgs, ...(variant === \"vp8\" ? vp8Args : h264Args), outputPath];\n\n // Hard ceiling so a hung ffmpeg can never permanently occupy one of the\n // global transcode slots: the child is killed and the slot released via\n // the caller's finally. Generous because long assets transcode at\n // roughly real time; a healthy encode of any authoring asset fits.\n const proc = spawn(ffmpegPath, args, {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n timeout: TRANSCODE_TIMEOUT_MS,\n killSignal: \"SIGKILL\",\n windowsHide: true,\n });\n let stderrTail = \"\";\n proc.stderr?.on(\"data\", (chunk: Buffer) => {\n stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_CHARS);\n });\n proc.on(\"error\", (err) => {\n reject(new ProxyTranscodeError(`failed to spawn ffmpeg: ${err.message}`, null, stderrTail));\n });\n proc.on(\"close\", (code, signal) => {\n if (code === 0) {\n resolvePromise();\n } else if (signal) {\n reject(\n new ProxyTranscodeError(\n `ffmpeg killed by ${signal} (timeout ${TRANSCODE_TIMEOUT_MS}ms or external kill)`,\n null,\n stderrTail,\n ),\n );\n } else {\n reject(new ProxyTranscodeError(`ffmpeg exited with code ${code}`, code, stderrTail));\n }\n });\n });\n}\n\nasync function transcodeToCache(\n job: ProxyJob,\n projectDir: string,\n absoluteSourcePath: string,\n cachePath: string,\n variant: ProxyVariant,\n box: PreviewProxyBox | undefined,\n): Promise<string> {\n await acquireSlot(job);\n try {\n // Another caller may have finished (or a pre-warm beat us) while queued.\n if (existsSync(cachePath)) return cachePath;\n\n const cacheDir = dirname(cachePath);\n mkdirWithinProject(projectDir, cacheDir);\n const tempPath = join(cacheDir, `.tmp-${randomUUID()}-${basename(cachePath)}`);\n try {\n await runFfmpeg(absoluteSourcePath, tempPath, variant, box);\n renameSync(tempPath, cachePath);\n maintainProxyCache(cacheDir);\n return cachePath;\n } finally {\n // No partial files: if anything above threw, remove whatever ffmpeg\n // may have partially written under the temp name.\n if (existsSync(tempPath)) unlinkSync(tempPath);\n }\n } finally {\n releaseSlot();\n }\n}\n\nlet proxyActivity = 0;\n\n/** Null while this project has a copy in progress; otherwise a mark that moves when any copy finishes or is refused. */\nexport function proxyActivityMark(projectDir: string): string | null {\n if (!existsSync(projectDir)) return String(proxyActivity);\n const cacheDir = join(realpath(projectDir), CACHE_DIR_NAME) + sep;\n for (const cachePath of inFlight.keys()) if (cachePath.startsWith(cacheDir)) return null;\n return String(proxyActivity);\n}\n\n/**\n * Resolves the cached proxy variant for `absoluteSourcePath`, transcoding it at\n * most once per cache key. Concurrent calls for the same key (including a\n * pre-warm call racing an element-triggered one) share one ffmpeg child and\n * one promise; calls for different keys queue through the global concurrency\n * limiter above. Throws `ProxyTranscodeError` on failure (missing ffmpeg or a\n * nonzero exit) — callers (route handlers) decide how to surface that (502).\n * A `box` makes a smaller preview-only copy with its own cache entry; without\n * one the copy keeps the source size (CLI play, static servers, publish).\n * `priority` moves the copy ahead of every normal queued copy; a `signal`\n * lets this caller leave (see the detachment note at the top of this file).\n */\nexport async function resolveProxy(\n projectDir: string,\n absoluteSourcePath: string,\n variant: ProxyVariant = \"h264\",\n box?: PreviewProxyBox,\n options: ResolveProxyOptions = {},\n): Promise<string> {\n options.signal?.throwIfAborted();\n const source = canonicalizeProxySource(projectDir, absoluteSourcePath);\n const cachePath = getCanonicalProxyCachePath(source, variant, box);\n if (existsSync(cachePath)) {\n markCacheEntryUsed(cachePath);\n maintainProxyCache(dirname(cachePath));\n return cachePath;\n }\n\n const rememberedFailure = failedTranscodes.get(cachePath);\n if (rememberedFailure) {\n if (rememberedFailure.expiresAt > Date.now()) throw rememberedFailure.error;\n failedTranscodes.delete(cachePath);\n }\n\n let entry = inFlight.get(cachePath);\n if (!entry) {\n if (!options.priority && queueIsFull()) {\n proxyActivity += 1;\n throw new ProxyCapacityError();\n }\n const job: ProxyJob = {\n cachePath,\n priority: options.priority === true,\n callers: 0,\n };\n const promise = transcodeToCache(\n job,\n source.projectDir,\n source.sourcePath,\n cachePath,\n variant,\n box,\n )\n .catch((err: unknown) => {\n if (!(err instanceof ProxyDroppedError)) rememberFailure(cachePath, err);\n throw err;\n })\n .finally(() => {\n forget(job);\n proxyActivity += 1;\n });\n entry = { promise, job };\n inFlight.set(cachePath, entry);\n }\n if (options.priority) prioritize(entry.job);\n return joinJob(entry.promise, entry.job, options.signal);\n}\n\nexport interface ResolveProxyOptions {\n priority?: boolean;\n signal?: AbortSignal;\n}\n\nfunction joinJob(promise: Promise<string>, job: ProxyJob, signal?: AbortSignal): Promise<string> {\n job.callers++;\n if (!signal) return promise;\n return new Promise((resolveJoin, rejectJoin) => {\n const onAbort = (): void => {\n leave(job);\n rejectJoin(signal.reason);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n promise\n .finally(() => signal.removeEventListener(\"abort\", onAbort))\n .then(resolveJoin, rejectJoin);\n });\n}\n","import { existsSync, readdirSync, statSync, unlinkSync } from \"node:fs\";\nimport { extname, join } from \"node:path\";\nimport { PROXY_VARIANT_CONFIG } from \"./mediaCodecMap.js\";\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024 * 1024;\nconst DEFAULT_STALE_TEMP_MS = 60 * 60 * 1000;\nconst DEFAULT_MIN_SWEEP_INTERVAL_MS = 5 * 60 * 1000;\nconst PROXY_EXTENSIONS: ReadonlySet<string> = new Set(\n Object.values(PROXY_VARIANT_CONFIG).map(({ extension }) => extension),\n);\nconst TRANSCODER_PROXY_NAME = /^[0-9a-f]{64}\\.\\w+$/;\nconst TRANSCODER_TEMP_NAME = /^\\.tmp-[0-9a-f-]{36}-[0-9a-f]{64}\\.\\w+$/;\n\nexport interface ProxyCacheCleanupOptions {\n maxBytes?: number;\n maxIdleMs?: number;\n staleTempMs?: number;\n minSweepIntervalMs?: number;\n protectedPaths?: ReadonlySet<string>;\n now?: number;\n dryRun?: boolean;\n}\n\nexport interface ProxyCacheCleanupResult {\n removed: string[];\n bytesBefore: number;\n bytesAfter: number;\n skipped: boolean;\n}\n\ninterface CacheEntry {\n path: string;\n size: number;\n modifiedAt: number;\n protected: boolean;\n}\n\nconst lastSweepAt = new Map<string, number>();\n\nfunction positiveEnvNumber(name: string, fallback: number): number {\n const parsed = Number(process.env[name]);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nfunction proxyCacheCleanupDefaults(): Required<\n Pick<ProxyCacheCleanupOptions, \"maxBytes\" | \"maxIdleMs\" | \"staleTempMs\" | \"minSweepIntervalMs\">\n> {\n return {\n maxBytes: positiveEnvNumber(\"HYPERFRAMES_PROXY_CACHE_MAX_BYTES\", DEFAULT_MAX_BYTES),\n maxIdleMs: positiveEnvNumber(\"HYPERFRAMES_PROXY_CACHE_MAX_IDLE_DAYS\", 30) * 24 * 60 * 60 * 1000,\n staleTempMs: positiveEnvNumber(\"HYPERFRAMES_PROXY_CACHE_STALE_TEMP_MS\", DEFAULT_STALE_TEMP_MS),\n minSweepIntervalMs: positiveEnvNumber(\n \"HYPERFRAMES_PROXY_CACHE_SWEEP_INTERVAL_MS\",\n DEFAULT_MIN_SWEEP_INTERVAL_MS,\n ),\n };\n}\n\nfunction shouldSkipSweep(cacheDir: string, now: number, minSweepIntervalMs: number): boolean {\n const previousSweep = lastSweepAt.get(cacheDir);\n if (previousSweep !== undefined && now - previousSweep < minSweepIntervalMs) return true;\n lastSweepAt.set(cacheDir, now);\n return false;\n}\n\nfunction readCacheInventory(\n cacheDir: string,\n protectedPaths: ReadonlySet<string>,\n now: number,\n staleTempMs: number,\n): { entries: CacheEntry[]; staleTemps: CacheEntry[] } {\n const entries: CacheEntry[] = [];\n const staleTemps: CacheEntry[] = [];\n for (const dirent of readdirSync(cacheDir, { withFileTypes: true })) {\n if (!dirent.isFile()) continue;\n const path = join(cacheDir, dirent.name);\n const stat = statSync(path);\n const entry = {\n path,\n size: stat.size,\n modifiedAt: stat.mtimeMs,\n protected: protectedPaths.has(path),\n };\n if (TRANSCODER_TEMP_NAME.test(dirent.name)) {\n if (now - stat.mtimeMs >= staleTempMs) staleTemps.push(entry);\n } else if (\n TRANSCODER_PROXY_NAME.test(dirent.name) &&\n PROXY_EXTENSIONS.has(extname(dirent.name))\n ) {\n entries.push(entry);\n }\n }\n const oldestFirst = (a: CacheEntry, b: CacheEntry): number =>\n a.modifiedAt - b.modifiedAt || a.path.localeCompare(b.path);\n entries.sort(oldestFirst);\n staleTemps.sort(oldestFirst);\n return { entries, staleTemps };\n}\n\nfunction evictCacheEntries(\n entries: CacheEntry[],\n staleTemps: CacheEntry[],\n now: number,\n maxIdleMs: number,\n maxBytes: number,\n dryRun: boolean,\n): Omit<ProxyCacheCleanupResult, \"skipped\"> {\n const bytesBefore = entries.reduce((total, entry) => total + entry.size, 0);\n let bytesAfter = bytesBefore;\n const removed: string[] = [];\n const remove = (entry: CacheEntry, countsTowardBudget: boolean): void => {\n if (!dryRun) unlinkSync(entry.path);\n removed.push(entry.path);\n if (countsTowardBudget) bytesAfter -= entry.size;\n };\n\n for (const entry of staleTemps) remove(entry, false);\n const kept: CacheEntry[] = [];\n for (const entry of entries) {\n if (!entry.protected && now - entry.modifiedAt >= maxIdleMs) remove(entry, true);\n else kept.push(entry);\n }\n for (const entry of kept) {\n if (bytesAfter <= maxBytes) break;\n if (!entry.protected && existsSync(entry.path)) remove(entry, true);\n }\n return { removed, bytesBefore, bytesAfter };\n}\n\n/**\n * Opportunistically bounds a project's transparent-proxy cache. Cleanup is\n * synchronous because callers already perform filesystem bookkeeping on the\n * preview request path, but rate limiting keeps the directory scan off the\n * hot path. Errors intentionally bubble so callers can warn without turning\n * a cache-maintenance failure into a preview failure.\n */\nexport function cleanupProxyCache(\n cacheDir: string,\n options: ProxyCacheCleanupOptions = {},\n): ProxyCacheCleanupResult {\n const defaults = proxyCacheCleanupDefaults();\n const now = options.now ?? Date.now();\n const minSweepIntervalMs = options.minSweepIntervalMs ?? defaults.minSweepIntervalMs;\n if (shouldSkipSweep(cacheDir, now, minSweepIntervalMs)) {\n return { removed: [], bytesBefore: 0, bytesAfter: 0, skipped: true };\n }\n if (!existsSync(cacheDir)) {\n return { removed: [], bytesBefore: 0, bytesAfter: 0, skipped: false };\n }\n\n const maxBytes = options.maxBytes ?? defaults.maxBytes;\n const maxIdleMs = options.maxIdleMs ?? defaults.maxIdleMs;\n const staleTempMs = options.staleTempMs ?? defaults.staleTempMs;\n const protectedPaths = options.protectedPaths ?? new Set<string>();\n const { entries, staleTemps } = readCacheInventory(cacheDir, protectedPaths, now, staleTempMs);\n return {\n ...evictCacheEntries(entries, staleTemps, now, maxIdleMs, maxBytes, options.dryRun ?? false),\n skipped: false,\n };\n}\n"],"mappings":"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","names":["existsSync","statSync","unlinkSync","join","statSync","join","existsSync","unlinkSync"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/loudness.ts","../src/helpers/requestSubPath.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { isAudibleVideoElement } from \"@hyperframes/core/audible-video\";\nimport { formatAudioGain, MAX_AUDIO_GAIN_DB } from \"@hyperframes/core/audio-gain\";\nimport { findFfBinary } from \"@hyperframes/parsers/ff-binaries\";\nimport { readMediaOffsetSeconds } from \"@hyperframes/parsers/media-duration\";\nimport { requestedProjectPath } from \"./requestSubPath.js\";\n\nconst execFileAsync = promisify(execFile);\n\nconst FFMPEG_TIMEOUT_MS = 120_000;\n\ninterface AttributeSpan {\n value: string;\n valueStart: number;\n valueEnd: number;\n}\n\ninterface TagRange {\n start: number;\n end: number;\n}\n\ntype MediaTagName = \"audio\" | \"video\";\n\ninterface MediaTagRange extends TagRange {\n name: MediaTagName;\n}\n\ninterface ParsedStartTag extends TagRange {\n closing: boolean;\n name: string;\n}\n\ninterface ScanResult {\n cursor: number;\n mediaRange: MediaTagRange | null;\n}\n\nexport interface AudioTag {\n tag: MediaTagName;\n id: string;\n src: string;\n volume: number;\n mediaStart: number;\n duration: number | null;\n playbackRate: number;\n start: number;\n end: number;\n volumeAttribute: AttributeSpan | null;\n}\n\nexport interface LoudnessMeasurement {\n integratedLufs: number;\n truePeakDbfs: number;\n}\n\nexport interface NormalizationTrack extends LoudnessMeasurement {\n id: string;\n volume: number;\n}\n\nexport interface AudioNormalizationPlan {\n referenceId: string;\n targetId: string;\n referenceLufs: number;\n targetLufs: number;\n previousVolume: number;\n volume: number;\n gainDb: number;\n changeDb: number;\n projectedLufs: number;\n projectedTruePeakDbfs: number;\n}\n\nfunction authoredNumber(\n raw: string | undefined,\n fallback: number,\n attribute: string,\n id: string,\n strictlyPositive = false,\n): number {\n if (raw === undefined) return fallback;\n const value = Number(raw);\n if (!Number.isFinite(value) || (strictlyPositive ? value <= 0 : value < 0)) {\n throw new Error(`Audio #${id} has an invalid ${attribute}: ${raw}`);\n }\n return value;\n}\n\nfunction attributeFromMatch(\n match: RegExpMatchArray,\n absoluteOffset: number,\n): [string, AttributeSpan] | null {\n if (match.index === undefined) return null;\n const name = match[1]?.toLowerCase();\n const value = matchedAttributeValue(match);\n if (!name || value === undefined) return null;\n\n const whole = match[0];\n const localValueStart = attributeValueStart(whole);\n const valueStart = absoluteOffset + match.index + localValueStart;\n return [name, { value, valueStart, valueEnd: valueStart + value.length }];\n}\n\nfunction matchedAttributeValue(match: RegExpMatchArray): string | undefined {\n return match[2] ?? match[3] ?? match[4];\n}\n\nfunction attributeValueStart(attribute: string): number {\n let cursor = attribute.indexOf(\"=\") + 1;\n while (/\\s/.test(attribute[cursor] ?? \"\")) cursor += 1;\n const quote = attribute[cursor];\n return quote === '\"' || quote === \"'\" ? cursor + 1 : cursor;\n}\n\nfunction attributesInTag(html: string, from: number, to: number): Map<string, AttributeSpan> {\n const tag = html.slice(from, to);\n const nameEnd = tag.search(/\\s|\\/?\\s*>/);\n const attributes = new Map<string, AttributeSpan>();\n if (nameEnd < 0) return attributes;\n\n const pattern = /([^\\s=/>]+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+))/g;\n for (const match of tag.slice(nameEnd).matchAll(pattern)) {\n const attribute = attributeFromMatch(match, from + nameEnd);\n if (attribute) attributes.set(...attribute);\n }\n return attributes;\n}\n\nfunction startTagEnd(html: string, start: number): number {\n let quote = \"\";\n let end = start + 1;\n for (; end < html.length; end += 1) {\n const char = html[end];\n if (quote) {\n if (char === quote) quote = \"\";\n } else if (char === '\"' || char === \"'\") {\n quote = char;\n } else if (char === \">\") {\n return end + 1;\n }\n }\n throw new Error(\"Unterminated HTML start tag\");\n}\n\nfunction commentEnd(html: string, start: number): number {\n const end = html.indexOf(\"-->\", start + 4);\n if (end < 0) throw new Error(\"Unterminated HTML comment\");\n return end + 3;\n}\n\nconst isSpaceAt = (html: string, index: number) => /\\s/.test(html.charAt(index));\nconst isWordAt = (html: string, index: number) => /\\w/.test(html.charAt(index));\n\nfunction skipSpaces(html: string, index: number): number {\n let cursor = index;\n while (cursor < html.length && isSpaceAt(html, cursor)) cursor++;\n return cursor;\n}\n\nfunction parsedStartTag(html: string, start: number): ParsedStartTag | null {\n let cursor = skipSpaces(html, start + 1);\n const closing = html.charAt(cursor) === \"/\";\n if (closing) cursor = skipSpaces(html, cursor + 1);\n const nameStart = cursor;\n if (!/[a-z]/i.test(html.charAt(cursor))) return null;\n while (/[a-z\\d:-]/i.test(html.charAt(cursor + 1))) cursor++;\n let nameEnd = cursor + 1;\n while (nameEnd > nameStart && isWordAt(html, nameEnd - 1) === isWordAt(html, nameEnd)) nameEnd--;\n if (nameEnd === nameStart) return null;\n return {\n start,\n end: startTagEnd(html, start),\n closing,\n name: html.slice(nameStart, nameEnd).toLowerCase(),\n };\n}\n\nfunction rawTextElementEnd(html: string, lower: string, tag: ParsedStartTag): number | null {\n if (tag.closing || (tag.name !== \"script\" && tag.name !== \"style\")) return null;\n const closeStart = lower.indexOf(`</${tag.name}`, tag.end);\n if (closeStart < 0) throw new Error(`Unterminated <${tag.name}> element`);\n return startTagEnd(html, closeStart);\n}\n\nfunction scanMarkup(html: string, lower: string, start: number): ScanResult {\n if (html.startsWith(\"<!--\", start)) {\n return { cursor: commentEnd(html, start), mediaRange: null };\n }\n const tag = parsedStartTag(html, start);\n if (!tag) {\n const marker = html[start + 1];\n const cursor = marker === \"!\" || marker === \"?\" ? startTagEnd(html, start) : start + 1;\n return { cursor, mediaRange: null };\n }\n const rawTextEnd = rawTextElementEnd(html, lower, tag);\n if (rawTextEnd !== null) return { cursor: rawTextEnd, mediaRange: null };\n return { cursor: tag.end, mediaRange: mediaRangeOf(tag) };\n}\n\nfunction mediaRangeOf(tag: ParsedStartTag): MediaTagRange | null {\n if (tag.closing) return null;\n if (tag.name === \"audio\" || tag.name === \"video\") {\n return { start: tag.start, end: tag.end, name: tag.name };\n }\n return null;\n}\n\nfunction mediaTagRanges(html: string): MediaTagRange[] {\n const lower = html.toLowerCase();\n const ranges: MediaTagRange[] = [];\n let cursor = 0;\n while (cursor < html.length) {\n const start = html.indexOf(\"<\", cursor);\n if (start < 0) break;\n const scanned = scanMarkup(html, lower, start);\n if (scanned.mediaRange) ranges.push(scanned.mediaRange);\n cursor = scanned.cursor;\n }\n return ranges;\n}\n\nfunction trimmedAttribute(attributes: Map<string, AttributeSpan>, name: string): string {\n return attributes.get(name)?.value.trim() ?? \"\";\n}\n\n/**\n * How much of the source the composition actually plays.\n *\n * `data-duration` is the explicit trim; absent, `data-end` still bounds the\n * clip's timeline window, and the parser, the runtime and the render mixer all\n * honour it. Measuring the whole file for a `data-end`-trimmed clip reads a\n * loudness the composition never plays — and `--write` then \"corrects\" an\n * already-matched clip by tens of dB.\n */\nfunction authoredDuration(attributes: Map<string, AttributeSpan>, id: string): number | null {\n const raw = attributes.get(\"data-duration\")?.value;\n if (raw !== undefined) return authoredNumber(raw, 0, \"data-duration\", id, true);\n\n const rawEnd = attributes.get(\"data-end\")?.value;\n if (rawEnd === undefined) return null;\n const end = authoredNumber(rawEnd, 0, \"data-end\", id, true);\n const start = authoredNumber(attributes.get(\"data-start\")?.value, 0, \"data-start\", id);\n return end > start ? end - start : null;\n}\n\nfunction requiredAudioIdentity(attributes: Map<string, AttributeSpan>) {\n const id = trimmedAttribute(attributes, \"id\");\n const src = trimmedAttribute(attributes, \"src\");\n if (!id) throw new Error(\"Every normalized <audio> element needs an id\");\n if (!src) throw new Error(`Audio #${id} has no src`);\n return { id, src };\n}\n\nfunction authoredMediaStart(attributes: Map<string, AttributeSpan>, id: string): number {\n return readMediaOffsetSeconds((name) => {\n const raw = attributes.get(name)?.value;\n if (name === \"data-media-start\") authoredNumber(raw, 0, name, id);\n return raw;\n });\n}\n\n/** Boolean attributes carry no `=`, so the value map cannot see them. */\nfunction hasBareAttribute(html: string, range: TagRange, name: string): boolean {\n const unquoted = html.slice(range.start, range.end).replace(/\"[^\"]*\"|'[^']*'/g, '\"\"');\n return new RegExp(`\\\\s${name}(?=[\\\\s/>=])`, \"i\").test(unquoted);\n}\n\nfunction isAudibleVideoTag(\n html: string,\n range: TagRange,\n attributes: Map<string, AttributeSpan>,\n): boolean {\n return isAudibleVideoElement({\n tagName: \"video\",\n hasAttribute: (name) => attributes.has(name) || hasBareAttribute(html, range, name),\n getAttribute: (name) => attributes.get(name)?.value ?? null,\n });\n}\n\n/**\n * A video joins the scan only when it carries sound and can be addressed.\n * Silent footage, and a video no command could name, are skipped rather than\n * failing a run that is about some other clip.\n */\nfunction inScopeVideo(\n html: string,\n range: TagRange,\n attributes: Map<string, AttributeSpan>,\n): boolean {\n return (\n isAudibleVideoTag(html, range, attributes) &&\n trimmedAttribute(attributes, \"id\") !== \"\" &&\n trimmedAttribute(attributes, \"src\") !== \"\"\n );\n}\n\nfunction mediaTagFromRange(html: string, range: MediaTagRange): AudioTag | null {\n const attributes = attributesInTag(html, range.start, range.end);\n if (range.name === \"video\" && !inScopeVideo(html, range, attributes)) return null;\n const { id, src } = requiredAudioIdentity(attributes);\n const volumeAttribute = attributes.get(\"data-volume\") ?? null;\n return {\n tag: range.name,\n id,\n src,\n volume: authoredNumber(volumeAttribute?.value, 1, \"data-volume\", id),\n mediaStart: authoredMediaStart(attributes, id),\n duration: authoredDuration(attributes, id),\n playbackRate: authoredNumber(\n attributes.get(\"data-playback-rate\")?.value,\n 1,\n \"data-playback-rate\",\n id,\n true,\n ),\n start: range.start,\n end: range.end,\n volumeAttribute,\n };\n}\n\n/** Read the authored clips with sound — `<audio>` and audible `<video>` — without reserializing. */\nexport function audioTags(html: string): AudioTag[] {\n const seen = new Set<string>();\n const tags: AudioTag[] = [];\n for (const range of mediaTagRanges(html)) {\n const tag = mediaTagFromRange(html, range);\n if (!tag) continue;\n if (seen.has(tag.id)) throw new Error(`Duplicate audio id \"${tag.id}\"`);\n seen.add(tag.id);\n tags.push(tag);\n }\n return tags;\n}\n\n/** Resolve only a file contained by the project. Remote and traversal sources are not measured. */\nexport function resolveLocalAudioPath(projectDir: string, src: string): string {\n const decoded = requestedProjectPath(projectDir, src);\n if (!decoded || isAbsolute(decoded) || /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i.test(decoded)) {\n throw new Error(`Audio source must be a local project file: ${src}`);\n }\n const root = resolve(projectDir);\n const file = resolve(root, decoded);\n const rel = relative(root, file);\n if (!rel || rel === \"..\" || rel.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`)) {\n throw new Error(`Audio source must be a local project file: ${src}`);\n }\n return file;\n}\n\n/** Parse FFmpeg's final EBU R128 summary, not its per-window log lines. */\nexport function parseEbur128Summary(stderr: string): LoudnessMeasurement {\n const summaryAt = stderr.lastIndexOf(\"Summary:\");\n const summary = summaryAt >= 0 ? stderr.slice(summaryAt) : \"\";\n const integrated = summary.match(/\\bI:\\s*(-?\\d+(?:\\.\\d+)?)\\s+LUFS\\b/);\n const peak = summary.match(/\\bPeak:\\s*(-?\\d+(?:\\.\\d+)?)\\s+dBFS\\b/);\n const integratedLufs = Number(integrated?.[1]);\n const truePeakDbfs = Number(peak?.[1]);\n if (!Number.isFinite(integratedLufs)) {\n throw new Error(\"FFmpeg did not report integrated loudness for this audio stream\");\n }\n if (!Number.isFinite(truePeakDbfs)) {\n throw new Error(\"FFmpeg did not report true peak for this audio stream\");\n }\n return { integratedLufs, truePeakDbfs };\n}\n\nexport function gainDb(volume: number): number {\n return volume > 0 ? 20 * Math.log10(volume) : Number.NEGATIVE_INFINITY;\n}\n\n/** Calculate the target's absolute authored gain while keeping the reference unchanged. */\nexport function audioNormalizationPlan(\n referenceTrack: NormalizationTrack,\n targetTrack: NormalizationTrack,\n): AudioNormalizationPlan {\n if (!(referenceTrack.volume > 0) || !(targetTrack.volume > 0)) {\n throw new Error(\"Muted audio cannot be used for loudness matching\");\n }\n const referenceLufs = referenceTrack.integratedLufs + gainDb(referenceTrack.volume);\n const targetLufs = targetTrack.integratedLufs + gainDb(targetTrack.volume);\n const wantedGainDb = referenceLufs - targetTrack.integratedLufs;\n if (wantedGainDb > MAX_AUDIO_GAIN_DB + 1e-9) {\n throw new Error(\n `Matching #${targetTrack.id} needs ${wantedGainDb.toFixed(1)} dB, beyond the +${MAX_AUDIO_GAIN_DB} dB authoring ceiling. ` +\n `A gap this large belongs in the source file, not the mixer — mixer gain raises the noise floor with the signal. ` +\n `Normalize ${targetTrack.id}'s asset offline (e.g. ffmpeg loudnorm), or lower #${referenceTrack.id} instead.`,\n );\n }\n const volume = 10 ** (wantedGainDb / 20);\n const projectedTruePeakDbfs = targetTrack.truePeakDbfs + wantedGainDb;\n if (projectedTruePeakDbfs > 0) {\n throw new Error(\n `Matching #${targetTrack.id} would clip at +${projectedTruePeakDbfs.toFixed(1)} dBFS; limit or preprocess the source first`,\n );\n }\n return {\n referenceId: referenceTrack.id,\n targetId: targetTrack.id,\n referenceLufs,\n targetLufs,\n previousVolume: targetTrack.volume,\n volume,\n gainDb: wantedGainDb,\n changeDb: wantedGainDb - gainDb(targetTrack.volume),\n projectedLufs: targetTrack.integratedLufs + wantedGainDb,\n projectedTruePeakDbfs,\n };\n}\n\n/** Patch one authored attribute in place so scripts/styles/comments remain byte-stable. */\nexport function updateAudioVolume(html: string, id: string, volume: number): string {\n const tag = audioTags(html).find((candidate) => candidate.id === id);\n if (!tag) throw new Error(`Audio #${id} was not found`);\n const value = formatAudioGain(volume);\n if (tag.volumeAttribute) {\n return (\n html.slice(0, tag.volumeAttribute.valueStart) +\n value +\n html.slice(tag.volumeAttribute.valueEnd)\n );\n }\n\n let insertion = tag.end - 1;\n let tail = insertion - 1;\n while (tail >= tag.start && /\\s/.test(html[tail] ?? \"\")) tail -= 1;\n if (html[tail] === \"/\") {\n insertion = tail;\n while (insertion > tag.start && /\\s/.test(html[insertion - 1] ?? \"\")) insertion -= 1;\n } else {\n while (insertion > tag.start && /\\s/.test(html[insertion - 1] ?? \"\")) insertion -= 1;\n }\n return html.slice(0, insertion) + ` data-volume=\"${value}\"` + html.slice(insertion);\n}\n\n/**\n * FFmpeg arguments that measure exactly the window the composition plays.\n *\n * `-ss` / `-t` go BEFORE `-i`, as INPUT options. After `-i` they bound the\n * output, and with `-f null` there is no real output to bound: ffmpeg keeps\n * feeding the filter graph past the limit, so ebur128 integrates audio the clip\n * never plays. Measured on a 12 s file whose first 4 s — the played window — is\n * -61.8 LUFS: output-side `-t 4` reported -33.8 LUFS, input-side reports -61.8,\n * the same as physically cutting the file first.\n */\nexport function loudnessMeasureArgs(file: string, tag: MeasuredWindow): string[] {\n const args = [\"-hide_banner\", \"-nostats\"];\n if (tag.mediaStart > 0) args.push(\"-ss\", String(tag.mediaStart));\n const sourceSpan = tag.duration === null ? null : tag.duration * (tag.playbackRate ?? 1);\n if (sourceSpan !== null && sourceSpan > 0) args.push(\"-t\", String(sourceSpan));\n args.push(\"-i\", file, \"-map\", \"0:a:0\", \"-vn\", \"-af\", \"ebur128=peak=true\", \"-f\", \"null\", \"-\");\n return args;\n}\n\nexport type MeasuredWindow = Pick<AudioTag, \"mediaStart\" | \"duration\"> &\n Partial<Pick<AudioTag, \"playbackRate\">>;\n\nexport async function measureAudio(\n ffmpegPath: string,\n file: string,\n tag: MeasuredWindow,\n): Promise<LoudnessMeasurement> {\n const result = await execFileAsync(ffmpegPath, loudnessMeasureArgs(file, tag), {\n encoding: \"utf8\",\n maxBuffer: 16 * 1024 * 1024,\n timeout: FFMPEG_TIMEOUT_MS,\n windowsHide: true,\n });\n return parseEbur128Summary(result.stderr);\n}\n\nexport function requiredFfmpeg(): string {\n const ffmpegPath = findFfBinary(\"ffmpeg\", { configuredMustExist: true });\n if (!ffmpegPath) throw new Error(\"FFmpeg is required to measure integrated loudness\");\n return ffmpegPath;\n}\n\nexport const DEFAULT_TARGET_LUFS = -16;\nexport const NORMALIZE_TRUE_PEAK_CEILING_DBFS = -1.5;\n/** EBU R128's absolute gate: a window this quiet has no integrated loudness to match. */\nconst SILENCE_LUFS = -70;\n\nexport type LoudnessLimit = \"gain-ceiling\" | \"true-peak\";\n\nexport interface AbsoluteLoudnessPlan {\n targetId: string;\n targetLufs: number;\n sourceLufs: number;\n previousVolume: number;\n volume: number;\n gainDb: number;\n changeDb: number;\n projectedLufs: number;\n projectedTruePeakDbfs: number;\n limitedBy: LoudnessLimit | null;\n}\n\n/**\n * The authored gain that brings one clip's played window to an absolute\n * loudness. Unlike reference matching it never refuses: past the +12 dB\n * authoring ceiling or the true-peak ceiling it applies the most that fits and\n * names which limit stopped it.\n */\nexport function absoluteLoudnessPlan(\n track: NormalizationTrack,\n targetLufs: number = DEFAULT_TARGET_LUFS,\n): AbsoluteLoudnessPlan {\n if (track.integratedLufs <= SILENCE_LUFS) {\n throw new Error(`#${track.id} is silent in the played window; there is nothing to normalize`);\n }\n const wantedGainDb = targetLufs - track.integratedLufs;\n const peakRoomDb = NORMALIZE_TRUE_PEAK_CEILING_DBFS - track.truePeakDbfs;\n const limits: Array<[LoudnessLimit | null, number]> = [\n [null, wantedGainDb],\n [\"gain-ceiling\", MAX_AUDIO_GAIN_DB],\n [\"true-peak\", peakRoomDb],\n ];\n const [limitedBy, appliedGainDb] = limits.reduce((lowest, candidate) =>\n candidate[1] < lowest[1] - 1e-9 ? candidate : lowest,\n );\n const previousGainDb = track.volume > 0 ? gainDb(track.volume) : 0;\n return {\n targetId: track.id,\n targetLufs,\n sourceLufs: track.integratedLufs,\n previousVolume: track.volume,\n volume: 10 ** (appliedGainDb / 20),\n gainDb: appliedGainDb,\n changeDb: appliedGainDb - previousGainDb,\n projectedLufs: track.integratedLufs + appliedGainDb,\n projectedTruePeakDbfs: track.truePeakDbfs + appliedGainDb,\n limitedBy,\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { decodedUrlPath, decodeWellFormedEscapes } from \"@hyperframes/parsers/asset-paths\";\nimport { resolveWithinProject } from \"./safePath.js\";\nexport { decodeWellFormedEscapes } from \"@hyperframes/parsers/asset-paths\";\n\n// The decoded path after `route` (\"projects/:id/preview\", \"composition\"), cut by segment from the raw URL:\n// Hono's c.req.path leaves %40 %25 %23 %26 %3F encoded, so cutting a decoded prefix out of it misses.\nexport function requestSubPath(url: string, route: string): string {\n const routeSegments = route.split(\"/\");\n const segments = new URL(url).pathname.split(\"/\");\n const start = segments.indexOf(routeSegments[0] ?? \"\");\n return decodeWellFormedEscapes(segments.slice(start + routeSegments.length).join(\"/\"));\n}\n\n// A project file a request names: the literal name when that file exists, otherwise the field read as a URL.\nexport function requestedProjectPath(projectDir: string, field: string): string {\n const trimmed = field.trim().replace(/^[.]\\//, \"\");\n const named = resolveWithinProject(projectDir, trimmed);\n return named && existsSync(named) ? trimmed : decodedUrlPath(trimmed);\n}\n"],"mappings":"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","names":["decodeWellFormedEscapes"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/studioMotionRenderScript.ts"],"sourcesContent":["export interface StudioMotionRenderScriptOptions {\n activeCompositionPath?: string | null;\n}\n\nexport const STUDIO_MOTION_PATH = \".hyperframes/studio-motion.json\";\n\nfunction hasStudioMotionEntries(manifestContent: string): boolean {\n try {\n const parsed = JSON.parse(manifestContent) as { motions?: unknown };\n return Array.isArray(parsed.motions) && parsed.motions.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Builds the render-time Studio motion runtime script, or null when no owned motion exists.\n */\nexport function createStudioMotionRenderBodyScript(\n manifestContent: string,\n options: StudioMotionRenderScriptOptions = {},\n): string | null {\n if (!manifestContent.trim() || !hasStudioMotionEntries(manifestContent)) return null;\n return `(${studioMotionRenderRuntime.toString()})(${JSON.stringify(manifestContent)}, ${JSON.stringify(options.activeCompositionPath ?? null)});`;\n}\n\nfunction studioMotionRenderRuntime(\n manifestContent: string,\n activeCompositionPath: string | null,\n): void {\n const STUDIO_MOTION_TIMELINE_ID = \"studio-motion\";\n const STUDIO_MOTION_ATTR = \"data-hf-studio-motion\";\n const ORIGINAL_TRANSFORM_ATTR = \"data-hf-studio-motion-original-transform\";\n const ORIGINAL_OPACITY_ATTR = \"data-hf-studio-motion-original-opacity\";\n const ORIGINAL_VISIBILITY_ATTR = \"data-hf-studio-motion-original-visibility\";\n\n const objectRecord = (value: unknown): Record<string, unknown> | null =>\n value && typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n\n const finiteNumber = (value: unknown): number | null =>\n typeof value === \"number\" && Number.isFinite(value) ? value : null;\n\n const runtimeWindow = window as Window & {\n gsap?: {\n timeline?: (vars?: Record<string, unknown>) => {\n fromTo?: (\n target: HTMLElement,\n from: Record<string, unknown>,\n to: Record<string, unknown>,\n at: number,\n ) => unknown;\n totalTime?: (time: number, suppressEvents?: boolean) => unknown;\n time?: (time: number) => unknown;\n pause?: () => unknown;\n kill?: () => unknown;\n };\n set?: (target: HTMLElement, vars: Record<string, unknown>) => unknown;\n registerPlugin?: (...plugins: unknown[]) => unknown;\n };\n CustomEase?: { create?: (id: string, data: string) => unknown };\n __player?: { getTime?: () => number };\n __timeline?: { time?: () => number };\n __timelines?: Record<\n string,\n | {\n kill?: () => unknown;\n }\n | undefined\n >;\n __hfStudioMotionApply?: () => number;\n };\n\n const parseMotionValues = (value: unknown): Record<string, number> | null => {\n const record = objectRecord(value);\n if (!record) return null;\n const parsed: Record<string, number> = {};\n for (const key of [\"x\", \"y\", \"scale\", \"rotation\", \"opacity\", \"autoAlpha\"]) {\n const next = finiteNumber(record[key]);\n if (next != null) parsed[key] = next;\n }\n return Object.keys(parsed).length > 0 ? parsed : null;\n };\n\n const parseCustomEase = (value: unknown): { id: string; data: string } | null => {\n const record = objectRecord(value);\n if (!record) return null;\n const id = typeof record.id === \"string\" ? record.id.trim() : \"\";\n const data = typeof record.data === \"string\" ? record.data.trim() : \"\";\n if (!id || !data) return null;\n return { id, data };\n };\n\n const parsedManifest = (() => {\n try {\n return objectRecord(JSON.parse(manifestContent));\n } catch {\n return null;\n }\n })();\n const manifestMotions = Array.isArray(parsedManifest?.motions) ? parsedManifest.motions : [];\n\n const sourceFileForElement = (element: HTMLElement): string => {\n let current: HTMLElement | null = element;\n while (current) {\n const sourceFile =\n current.getAttribute(\"data-composition-file\") ??\n current.getAttribute(\"data-composition-src\");\n if (sourceFile) return sourceFile;\n current = current.parentElement;\n }\n return activeCompositionPath ?? \"index.html\";\n };\n\n const elementMatchesSourceFile = (element: HTMLElement, sourceFile: string): boolean =>\n sourceFileForElement(element) === sourceFile;\n\n const isHTMLElement = (element: Element | null): element is HTMLElement =>\n element instanceof HTMLElement;\n\n const querySelectorCandidates = (selector: string): HTMLElement[] => {\n const className = selector.match(/^\\.([A-Za-z0-9_-]+)$/)?.[1];\n if (className) {\n return Array.from(document.getElementsByTagName(\"*\")).filter(\n (element): element is HTMLElement =>\n isHTMLElement(element) && element.classList.contains(className),\n );\n }\n if (/^[A-Za-z][A-Za-z0-9-]*$/.test(selector)) {\n return Array.from(document.getElementsByTagName(selector)).filter(isHTMLElement);\n }\n return Array.from(document.querySelectorAll(selector)).filter(isHTMLElement);\n };\n\n const resolveTarget = (targetRecord: Record<string, unknown>): HTMLElement | null => {\n const sourceFile = typeof targetRecord.sourceFile === \"string\" ? targetRecord.sourceFile : \"\";\n if (!sourceFile) return null;\n const id = typeof targetRecord.id === \"string\" ? targetRecord.id : \"\";\n if (id) {\n const byId = document.getElementById(id);\n if (isHTMLElement(byId) && elementMatchesSourceFile(byId, sourceFile)) return byId;\n }\n const selector = typeof targetRecord.selector === \"string\" ? targetRecord.selector : \"\";\n if (!selector) return null;\n try {\n const selectorIndex = Math.max(0, Math.floor(finiteNumber(targetRecord.selectorIndex) ?? 0));\n return (\n querySelectorCandidates(selector).filter((element) =>\n elementMatchesSourceFile(element, sourceFile),\n )[selectorIndex] ?? null\n );\n } catch {\n return null;\n }\n };\n\n const restoreElement = (element: HTMLElement): void => {\n runtimeWindow.gsap?.set?.(element, { clearProps: \"transform,opacity,visibility\" });\n element.style.transform = element.getAttribute(ORIGINAL_TRANSFORM_ATTR) ?? \"\";\n element.style.opacity = element.getAttribute(ORIGINAL_OPACITY_ATTR) ?? \"\";\n element.style.visibility = element.getAttribute(ORIGINAL_VISIBILITY_ATTR) ?? \"\";\n element.removeAttribute(STUDIO_MOTION_ATTR);\n element.removeAttribute(ORIGINAL_TRANSFORM_ATTR);\n element.removeAttribute(ORIGINAL_OPACITY_ATTR);\n element.removeAttribute(ORIGINAL_VISIBILITY_ATTR);\n };\n\n const restoreStudioMotionElements = (): void => {\n for (const element of Array.from(document.querySelectorAll(`[${STUDIO_MOTION_ATTR}]`))) {\n if (isHTMLElement(element)) restoreElement(element);\n }\n };\n\n const readCurrentTime = (): number => {\n try {\n const playerTime = runtimeWindow.__player?.getTime?.();\n if (typeof playerTime === \"number\" && Number.isFinite(playerTime)) {\n return Math.max(0, playerTime);\n }\n } catch {\n // fall through\n }\n try {\n const timelineTime = runtimeWindow.__timeline?.time?.();\n if (typeof timelineTime === \"number\" && Number.isFinite(timelineTime)) {\n return Math.max(0, timelineTime);\n }\n } catch {\n // fall through\n }\n return 0;\n };\n\n const resolveEase = (motion: Record<string, unknown>): string => {\n const fallback =\n typeof motion.ease === \"string\" && motion.ease.trim() ? motion.ease.trim() : \"none\";\n const customEase = parseCustomEase(motion.customEase);\n const customEasePlugin = runtimeWindow.CustomEase;\n if (!customEase || typeof customEasePlugin?.create !== \"function\") return fallback;\n try {\n runtimeWindow.gsap?.registerPlugin?.(customEasePlugin);\n customEasePlugin.create(customEase.id, customEase.data);\n return customEase.id;\n } catch {\n return fallback;\n }\n };\n\n const applyManifest = (): number => {\n runtimeWindow.__timelines = runtimeWindow.__timelines ?? {};\n runtimeWindow.__timelines[STUDIO_MOTION_TIMELINE_ID]?.kill?.();\n delete runtimeWindow.__timelines[STUDIO_MOTION_TIMELINE_ID];\n restoreStudioMotionElements();\n const gsap = runtimeWindow.gsap;\n if (!gsap?.timeline || manifestMotions.length === 0) return 0;\n\n const timeline = gsap.timeline({ paused: true, defaults: { overwrite: \"auto\" } });\n let applied = 0;\n for (const motionValue of manifestMotions) {\n const motion = objectRecord(motionValue);\n if (!motion || motion.kind !== \"gsap-motion\") continue;\n const targetRecord = objectRecord(motion.target);\n if (!targetRecord) continue;\n const target = resolveTarget(targetRecord);\n if (!target || typeof timeline.fromTo !== \"function\") continue;\n const start = finiteNumber(motion.start);\n const duration = finiteNumber(motion.duration);\n if (start == null || duration == null || start < 0 || duration <= 0) continue;\n const from = parseMotionValues(motion.from);\n const to = parseMotionValues(motion.to);\n if (!from || !to) continue;\n if (!target.hasAttribute(STUDIO_MOTION_ATTR)) {\n target.setAttribute(ORIGINAL_TRANSFORM_ATTR, target.style.transform);\n target.setAttribute(ORIGINAL_OPACITY_ATTR, target.style.opacity);\n target.setAttribute(ORIGINAL_VISIBILITY_ATTR, target.style.visibility);\n }\n target.setAttribute(STUDIO_MOTION_ATTR, \"true\");\n timeline.fromTo(\n target,\n from,\n { ...to, duration, ease: resolveEase(motion), overwrite: \"auto\", immediateRender: false },\n start,\n );\n applied += 1;\n }\n\n if (applied === 0) {\n timeline.kill?.();\n return 0;\n }\n runtimeWindow.__timelines[STUDIO_MOTION_TIMELINE_ID] = timeline;\n timeline.pause?.();\n const currentTime = readCurrentTime();\n if (typeof timeline.totalTime === \"function\") timeline.totalTime(currentTime, false);\n else timeline.time?.(currentTime);\n return applied;\n };\n\n runtimeWindow.__hfStudioMotionApply = applyManifest;\n applyManifest();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/manualEditsRenderScript.ts"],"sourcesContent":["// fallow-ignore-file code-duplication\nexport interface StudioManualEditsRenderScriptOptions {\n activeCompositionPath?: string | null;\n}\n\nexport const STUDIO_MANUAL_EDITS_PATH = \".hyperframes/studio-manual-edits.json\";\n\nexport function createStudioManualEditsRenderBodyScript(\n manifestContent: string,\n options: StudioManualEditsRenderScriptOptions = {},\n): string | null {\n if (!manifestContent.trim()) return null;\n return `(${studioManualEditsRenderRuntime.toString()})(${JSON.stringify(manifestContent)}, ${JSON.stringify(options.activeCompositionPath ?? null)});`;\n}\n\n/**\n * Returns a self-contained IIFE string that re-applies studio position edits\n * (translate, rotate) after every GSAP seek by querying data attributes baked\n * into the HTML. Works without a JSON manifest — positions are already inlined\n * as CSS custom properties on the elements.\n */\nexport function createStudioPositionSeekReapplyScript(): string {\n return `(${studioPositionSeekReapplyRuntime.toString()})();`;\n}\n\nfunction studioPositionSeekReapplyRuntime(): void {\n const OFFSET_X_PROP = \"--hf-studio-offset-x\";\n const OFFSET_Y_PROP = \"--hf-studio-offset-y\";\n const WIDTH_PROP = \"--hf-studio-width\";\n const HEIGHT_PROP = \"--hf-studio-height\";\n const ROTATION_PROP = \"--hf-studio-rotation\";\n const PATH_OFFSET_ATTR = \"data-hf-studio-path-offset\";\n const BOX_SIZE_ATTR = \"data-hf-studio-box-size\";\n const ROTATION_ATTR = \"data-hf-studio-rotation\";\n const ORIGINAL_TRANSLATE_ATTR = \"data-hf-studio-original-translate\";\n const ORIGINAL_ROTATE_ATTR = \"data-hf-studio-original-rotate\";\n const MOTION_ATTR = \"data-hf-studio-motion\";\n const MOTION_TL_KEY = \"studio-motion\";\n const WRAPPED_PROP = \"__hfStudioPositionSeekReapplyWrapped\";\n\n if (\n !document.querySelector(\"[\" + PATH_OFFSET_ATTR + '=\"true\"]') &&\n !document.querySelector(\"[\" + BOX_SIZE_ATTR + '=\"true\"]') &&\n !document.querySelector(\"[\" + ROTATION_ATTR + '=\"true\"]') &&\n !document.querySelector(\"[\" + MOTION_ATTR + \"]\")\n )\n return;\n\n const splitTopLevelWhitespace = (value: string): string[] => {\n const parts: string[] = [];\n let depth = 0;\n let current = \"\";\n for (const char of value.trim()) {\n if (char === \"(\") depth += 1;\n if (char === \")\") depth = Math.max(0, depth - 1);\n if (/\\s/.test(char) && depth === 0) {\n if (current) parts.push(current);\n current = \"\";\n } else {\n current += char;\n }\n }\n if (current) parts.push(current);\n return parts;\n };\n\n const composeTranslate = (element: HTMLElement, x: string, y: string): string => {\n const original = element.getAttribute(ORIGINAL_TRANSLATE_ATTR)?.trim();\n if (!original || original === \"none\") return x + \" \" + y;\n const parts = splitTopLevelWhitespace(original);\n if (parts.length === 1) return \"calc(\" + parts[0] + \" + \" + x + \") \" + y;\n if (parts.length >= 2) {\n const z = parts.length >= 3 ? \" \" + parts[2] : \"\";\n return \"calc(\" + parts[0] + \" + \" + x + \") calc(\" + parts[1] + \" + \" + y + \")\" + z;\n }\n return x + \" \" + y;\n };\n\n const isSimpleRotateAngle = (value: string): boolean =>\n /^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:deg|rad|turn|grad)$/.test(value.trim());\n\n const composeRotation = (element: HTMLElement, rotationValue: string): string => {\n const original = element.getAttribute(ORIGINAL_ROTATE_ATTR)?.trim();\n if (!original || original === \"none\" || !isSimpleRotateAngle(original)) return rotationValue;\n return \"calc(\" + original + \" + \" + rotationValue + \")\";\n };\n\n let lastSeekTime = 0;\n let cachedMotionKey = \"\";\n\n const finiteNum = (v: unknown): number | null =>\n typeof v === \"number\" && Number.isFinite(v) ? v : null;\n\n const computeMotionKey = (motionEls: NodeListOf<Element>): string => {\n let key = \"\";\n for (let i = 0; i < motionEls.length; i++) {\n const json = (motionEls[i] as HTMLElement).getAttribute?.(MOTION_ATTR);\n if (json) key += (key ? \"\\n\" : \"\") + json;\n }\n return key;\n };\n\n const reapplyMotionTimeline = (): void => {\n const motionEls = document.querySelectorAll(\"[\" + MOTION_ATTR + \"]\");\n if (motionEls.length === 0) {\n cachedMotionKey = \"\";\n return;\n }\n const win = window as Window & {\n gsap?: {\n timeline?: (opts: Record<string, unknown>) => Record<string, unknown>;\n set?: (el: HTMLElement, vars: Record<string, unknown>) => void;\n registerPlugin?: (plugin: unknown) => void;\n };\n CustomEase?: { create?: (id: string, data: string) => void };\n __timelines?: Record<string, Record<string, unknown>>;\n };\n const gsap = win.gsap;\n if (!gsap || typeof gsap.timeline !== \"function\") return;\n win.__timelines = win.__timelines || {};\n\n // Cache the timeline keyed by the concatenated motion JSON strings.\n // On each seek, if the key hasn't changed, just seek the existing timeline\n // instead of rebuilding it (avoids kill+recreate on every frame).\n const motionKey = computeMotionKey(motionEls);\n const existing = win.__timelines[MOTION_TL_KEY];\n if (\n motionKey &&\n motionKey === cachedMotionKey &&\n existing &&\n typeof existing.totalTime === \"function\"\n ) {\n (existing.totalTime as (t: number, s: boolean) => void)(lastSeekTime, false);\n return;\n }\n\n if (existing && typeof existing.kill === \"function\") (existing.kill as () => void)();\n const tl = gsap.timeline({ paused: true, defaults: { overwrite: \"auto\" } });\n const fromTo = tl.fromTo as (\n el: HTMLElement,\n from: Record<string, unknown>,\n to: Record<string, unknown>,\n pos: number,\n ) => void;\n if (typeof fromTo !== \"function\") return;\n let applied = 0;\n for (let i = 0; i < motionEls.length; i++) {\n const el = motionEls[i] as HTMLElement;\n if (!(el instanceof HTMLElement)) continue;\n const json = el.getAttribute(MOTION_ATTR);\n if (!json) continue;\n try {\n const m = JSON.parse(json) as Record<string, unknown>;\n const start = finiteNum(m.start);\n const duration = finiteNum(m.duration);\n if (start == null || duration == null || duration <= 0) continue;\n const ease = typeof m.ease === \"string\" ? m.ease : \"none\";\n const from = (m.from && typeof m.from === \"object\" ? m.from : {}) as Record<\n string,\n unknown\n >;\n const to = (m.to && typeof m.to === \"object\" ? m.to : {}) as Record<string, unknown>;\n const customEase = m.customEase as { id?: string; data?: string } | null | undefined;\n let resolvedEase = ease;\n if (customEase?.id && customEase?.data && win.CustomEase?.create) {\n try {\n gsap.registerPlugin?.(win.CustomEase);\n win.CustomEase.create(customEase.id, customEase.data);\n resolvedEase = customEase.id;\n } catch {\n /* use default ease */\n }\n }\n fromTo.call(\n tl,\n el,\n { ...from },\n { ...to, duration, ease: resolvedEase, overwrite: \"auto\", immediateRender: false },\n start,\n );\n applied += 1;\n } catch {\n /* malformed JSON — skip */\n }\n }\n if (applied === 0) {\n cachedMotionKey = \"\";\n if (typeof (tl as { kill?: () => void }).kill === \"function\")\n (tl as { kill: () => void }).kill();\n return;\n }\n cachedMotionKey = motionKey;\n win.__timelines[MOTION_TL_KEY] = tl;\n if (typeof tl.pause === \"function\") (tl.pause as () => void)();\n if (typeof tl.totalTime === \"function\")\n (tl.totalTime as (t: number, s: boolean) => void)(lastSeekTime, false);\n };\n\n const stripGsapTranslateFromTransform = (el: HTMLElement): void => {\n const transform = el.style.getPropertyValue(\"transform\");\n if (!transform || transform === \"none\") return;\n const win = el.ownerDocument.defaultView as (Window & typeof globalThis) | null;\n const MatrixCtor = (win as unknown as { DOMMatrix?: typeof DOMMatrix })?.DOMMatrix;\n if (!MatrixCtor) return;\n try {\n const m = new MatrixCtor(transform);\n if (m.m41 === 0 && m.m42 === 0) return;\n m.m41 = 0;\n m.m42 = 0;\n if (m.is2D && m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1) {\n el.style.removeProperty(\"transform\");\n } else {\n el.style.setProperty(\"transform\", m.toString());\n }\n } catch {\n /* non-parseable transform — leave as-is */\n }\n };\n\n const reapplyAll = (): void => {\n const offsetEls = document.querySelectorAll(\"[\" + PATH_OFFSET_ATTR + '=\"true\"]');\n for (let i = 0; i < offsetEls.length; i++) {\n const el = offsetEls[i] as HTMLElement;\n if (!(el instanceof HTMLElement)) continue;\n const x = el.style.getPropertyValue(OFFSET_X_PROP);\n const y = el.style.getPropertyValue(OFFSET_Y_PROP);\n if (x || y) {\n el.style.setProperty(\n \"translate\",\n composeTranslate(\n el,\n \"var(\" + OFFSET_X_PROP + \", 0px)\",\n \"var(\" + OFFSET_Y_PROP + \", 0px)\",\n ),\n );\n stripGsapTranslateFromTransform(el);\n }\n }\n const boxSizeEls = document.querySelectorAll(\"[\" + BOX_SIZE_ATTR + '=\"true\"]');\n for (let i = 0; i < boxSizeEls.length; i++) {\n const el = boxSizeEls[i] as HTMLElement;\n if (!(el instanceof HTMLElement)) continue;\n const w = el.style.getPropertyValue(WIDTH_PROP);\n const h = el.style.getPropertyValue(HEIGHT_PROP);\n if (w) el.style.setProperty(\"width\", w);\n if (h) el.style.setProperty(\"height\", h);\n }\n const rotEls = document.querySelectorAll(\"[\" + ROTATION_ATTR + '=\"true\"]');\n for (let i = 0; i < rotEls.length; i++) {\n const el = rotEls[i] as HTMLElement;\n if (!(el instanceof HTMLElement)) continue;\n const rot = el.style.getPropertyValue(ROTATION_PROP);\n if (rot) {\n el.style.setProperty(\"rotate\", composeRotation(el, \"var(\" + ROTATION_PROP + \", 0deg)\"));\n stripGsapTranslateFromTransform(el);\n }\n }\n reapplyMotionTimeline();\n };\n\n const runtimeWindow = window as Window & {\n __hf?: Record<string, unknown>;\n __player?: Record<string, unknown>;\n };\n\n const isWrapped = (fn: (time: number) => unknown): boolean =>\n Boolean((fn as unknown as Record<string, unknown>)[WRAPPED_PROP]);\n\n const markWrapped = (fn: (time: number) => unknown): void => {\n try {\n Object.defineProperty(fn, WRAPPED_PROP, {\n configurable: false,\n enumerable: false,\n value: true,\n });\n } catch {\n try {\n (fn as unknown as Record<string, unknown>)[WRAPPED_PROP] = true;\n } catch {\n /* ignore */\n }\n }\n };\n\n const wrapFn = (get: () => unknown, set: (fn: (time: number) => unknown) => void): boolean => {\n const fn = get();\n if (typeof fn !== \"function\") return false;\n const seek = fn as (time: number) => unknown;\n if (isWrapped(seek)) {\n reapplyAll();\n return true;\n }\n const wrapped = function (this: unknown, time: number): unknown {\n lastSeekTime = typeof time === \"number\" && Number.isFinite(time) ? Math.max(0, time) : 0;\n const result = seek.call(this, time);\n reapplyAll();\n return result;\n };\n markWrapped(wrapped);\n set(wrapped);\n reapplyAll();\n return true;\n };\n\n const wrapSeekFunctions = (): boolean => {\n const a = wrapFn(\n () => runtimeWindow.__hf?.[\"seek\"],\n (fn) => {\n if (runtimeWindow.__hf) runtimeWindow.__hf[\"seek\"] = fn;\n },\n );\n const b = wrapFn(\n () => runtimeWindow.__player?.[\"renderSeek\"],\n (fn) => {\n if (runtimeWindow.__player) runtimeWindow.__player[\"renderSeek\"] = fn;\n },\n );\n return a || b;\n };\n\n const installSeekTrap = (\n obj: Record<string, unknown> | undefined,\n key: string,\n getter: () => unknown,\n setter: (fn: (time: number) => unknown) => void,\n ): void => {\n if (!obj) return;\n try {\n let current = obj[key];\n Object.defineProperty(obj, key, {\n configurable: true,\n enumerable: true,\n get() {\n return current;\n },\n set(value: unknown) {\n current = value;\n if (typeof value === \"function\" && !isWrapped(value as (time: number) => unknown)) {\n wrapFn(getter, setter);\n }\n },\n });\n } catch {\n /* non-configurable — fall back to polling */\n }\n };\n\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", () => reapplyAll(), { once: true });\n } else {\n reapplyAll();\n }\n\n wrapSeekFunctions();\n installSeekTrap(\n runtimeWindow.__hf,\n \"seek\",\n () => runtimeWindow.__hf?.[\"seek\"],\n (fn) => {\n if (runtimeWindow.__hf) runtimeWindow.__hf[\"seek\"] = fn;\n },\n );\n installSeekTrap(\n runtimeWindow.__player as Record<string, unknown> | undefined,\n \"renderSeek\",\n () => runtimeWindow.__player?.[\"renderSeek\"],\n (fn) => {\n if (runtimeWindow.__player) runtimeWindow.__player[\"renderSeek\"] = fn;\n },\n );\n let remaining = 120;\n const interval = setInterval(() => {\n wrapSeekFunctions();\n remaining -= 1;\n if (remaining <= 0) clearInterval(interval);\n }, 50);\n}\n\nfunction studioManualEditsRenderRuntime(\n manifestContent: string,\n activeCompositionPath: string | null,\n): void {\n const OFFSET_X_PROP = \"--hf-studio-offset-x\";\n const OFFSET_Y_PROP = \"--hf-studio-offset-y\";\n const WIDTH_PROP = \"--hf-studio-width\";\n const HEIGHT_PROP = \"--hf-studio-height\";\n const ROTATION_PROP = \"--hf-studio-rotation\";\n const PATH_OFFSET_ATTR = \"data-hf-studio-path-offset\";\n const BOX_SIZE_ATTR = \"data-hf-studio-box-size\";\n const ROTATION_ATTR = \"data-hf-studio-rotation\";\n const ORIGINAL_TRANSLATE_ATTR = \"data-hf-studio-original-translate\";\n const ORIGINAL_ROTATE_ATTR = \"data-hf-studio-original-rotate\";\n const WRAPPED_SEEK_PROP = \"__hfStudioManualEditsWrapped\";\n const ROTATION_TRANSFORM_ORIGIN = \"center center\";\n\n const finiteNumber = (value: unknown): number | null =>\n typeof value === \"number\" && Number.isFinite(value) ? value : null;\n\n const objectRecord = (value: unknown): Record<string, unknown> | null =>\n value && typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n\n const runtimeWindow = window as Window & {\n __hf?: { seek?: (time: number) => unknown };\n __hfStudioManualEditsApply?: () => number;\n __player?: { renderSeek?: (time: number) => unknown };\n };\n\n const parsedManifest = (() => {\n try {\n return objectRecord(JSON.parse(manifestContent));\n } catch {\n return null;\n }\n })();\n const manifestEdits = Array.isArray(parsedManifest?.edits) ? parsedManifest.edits : [];\n if (manifestEdits.length === 0) return;\n\n const sourceFileForElement = (element: HTMLElement): string => {\n let current: HTMLElement | null = element;\n while (current) {\n const sourceFile =\n current.getAttribute(\"data-composition-file\") ??\n current.getAttribute(\"data-composition-src\");\n if (sourceFile) return sourceFile;\n current = current.parentElement;\n }\n return activeCompositionPath ?? \"index.html\";\n };\n\n const elementMatchesSourceFile = (element: HTMLElement, sourceFile: string): boolean =>\n sourceFileForElement(element) === sourceFile;\n\n const styleUsesStudioOffset = (value: string): boolean =>\n value.includes(OFFSET_X_PROP) || value.includes(OFFSET_Y_PROP);\n\n const styleUsesStudioRotation = (value: string): boolean => value.includes(ROTATION_PROP);\n\n const splitTopLevelWhitespace = (value: string): string[] => {\n const parts: string[] = [];\n let depth = 0;\n let current = \"\";\n for (const char of value.trim()) {\n if (char === \"(\") depth += 1;\n if (char === \")\") depth = Math.max(0, depth - 1);\n if (/\\s/.test(char) && depth === 0) {\n if (current) parts.push(current);\n current = \"\";\n } else {\n current += char;\n }\n }\n if (current) parts.push(current);\n return parts;\n };\n\n const composeTranslate = (element: HTMLElement, x: string, y: string): string => {\n const original = element.getAttribute(ORIGINAL_TRANSLATE_ATTR)?.trim();\n if (!original || original === \"none\") return `${x} ${y}`;\n\n const parts = splitTopLevelWhitespace(original);\n if (parts.length === 1) return `calc(${parts[0]} + ${x}) ${y}`;\n if (parts.length === 2) return `calc(${parts[0]} + ${x}) calc(${parts[1]} + ${y})`;\n if (parts.length === 3) {\n return `calc(${parts[0]} + ${x}) calc(${parts[1]} + ${y}) ${parts[2]}`;\n }\n return `${x} ${y}`;\n };\n\n const readStyleOrComputed = (element: HTMLElement, property: string): string => {\n try {\n return (\n element.style.getPropertyValue(property) ||\n getComputedStyle(element).getPropertyValue(property)\n );\n } catch {\n return element.style.getPropertyValue(property);\n }\n };\n\n const readTransformLonghandBase = (\n element: HTMLElement,\n property: \"translate\" | \"rotate\",\n ): string => {\n const value = readStyleOrComputed(element, property).trim();\n return value === \"none\" ? \"\" : value;\n };\n\n const preparePathOffsetBase = (element: HTMLElement): void => {\n const currentTranslate = readTransformLonghandBase(element, \"translate\");\n const hasMarker = element.hasAttribute(PATH_OFFSET_ATTR);\n const wasResetByAnimation = !styleUsesStudioOffset(currentTranslate);\n if (!hasMarker) {\n element.setAttribute(ORIGINAL_TRANSLATE_ATTR, wasResetByAnimation ? currentTranslate : \"\");\n } else if (wasResetByAnimation) {\n element.setAttribute(ORIGINAL_TRANSLATE_ATTR, currentTranslate);\n }\n };\n\n const prepareRotationBase = (element: HTMLElement): void => {\n const currentRotate = readTransformLonghandBase(element, \"rotate\");\n const hasMarker = element.hasAttribute(ROTATION_ATTR);\n const wasResetByAnimation = !styleUsesStudioRotation(currentRotate);\n if (!hasMarker) {\n element.setAttribute(ORIGINAL_ROTATE_ATTR, wasResetByAnimation ? currentRotate : \"\");\n } else if (wasResetByAnimation) {\n element.setAttribute(ORIGINAL_ROTATE_ATTR, currentRotate);\n }\n };\n\n const querySelectorCandidates = (selector: string): HTMLElement[] => {\n const isCandidate = (element: Element): element is HTMLElement =>\n element instanceof HTMLElement;\n\n const className = selector.match(/^\\.([A-Za-z0-9_-]+)$/)?.[1];\n if (className) {\n return Array.from(document.getElementsByTagName(\"*\")).filter(\n (element): element is HTMLElement =>\n isCandidate(element) && element.classList.contains(className),\n );\n }\n\n if (/^[A-Za-z][A-Za-z0-9-]*$/.test(selector)) {\n return Array.from(document.getElementsByTagName(selector)).filter(isCandidate);\n }\n\n return Array.from(document.querySelectorAll(selector)).filter(isCandidate);\n };\n\n const resolveTarget = (edit: Record<string, unknown>): HTMLElement | null => {\n const targetRecord = objectRecord(edit.target);\n if (!targetRecord) return null;\n\n const sourceFile = typeof targetRecord.sourceFile === \"string\" ? targetRecord.sourceFile : \"\";\n if (!sourceFile) return null;\n\n const id = typeof targetRecord.id === \"string\" ? targetRecord.id : \"\";\n if (id) {\n const byId = document.getElementById(id);\n if (byId instanceof HTMLElement && elementMatchesSourceFile(byId, sourceFile)) return byId;\n\n const matchesById = [\n document.documentElement,\n ...Array.from(document.getElementsByTagName(\"*\")),\n ].filter(\n (element): element is HTMLElement =>\n element instanceof HTMLElement &&\n element.id === id &&\n elementMatchesSourceFile(element, sourceFile),\n );\n if (matchesById[0]) return matchesById[0];\n }\n\n const selector = typeof targetRecord.selector === \"string\" ? targetRecord.selector : \"\";\n if (!selector) return null;\n\n try {\n const matches = querySelectorCandidates(selector).filter((element) =>\n elementMatchesSourceFile(element, sourceFile),\n );\n const selectorIndex = finiteNumber(targetRecord.selectorIndex) ?? 0;\n return matches[Math.max(0, Math.floor(selectorIndex))] ?? null;\n } catch {\n return null;\n }\n };\n\n const roundRotationAngle = (angle: number): number => Math.round(angle * 10) / 10;\n\n const isSimpleRotateAngle = (value: string): boolean =>\n /^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:deg|rad|turn|grad)$/.test(value.trim());\n\n const composeRotation = (element: HTMLElement, rotationValue: string): string => {\n const original = element.getAttribute(ORIGINAL_ROTATE_ATTR)?.trim();\n if (!original || original === \"none\" || !isSimpleRotateAngle(original)) {\n return rotationValue;\n }\n return `calc(${original} + ${rotationValue})`;\n };\n\n const applyPathOffset = (element: HTMLElement, edit: Record<string, unknown>): void => {\n const x = finiteNumber(edit.x);\n const y = finiteNumber(edit.y);\n if (x == null || y == null) return;\n preparePathOffsetBase(element);\n element.setAttribute(PATH_OFFSET_ATTR, \"true\");\n element.style.setProperty(OFFSET_X_PROP, `${Math.round(x)}px`);\n element.style.setProperty(OFFSET_Y_PROP, `${Math.round(y)}px`);\n element.style.setProperty(\n \"translate\",\n composeTranslate(element, `var(${OFFSET_X_PROP}, 0px)`, `var(${OFFSET_Y_PROP}, 0px)`),\n );\n };\n\n const readParentFlexBasisPixels = (\n element: HTMLElement,\n size: { width: number; height: number },\n ): number | null => {\n const parent = element.parentElement;\n if (!parent) return null;\n const styles = getComputedStyle(parent);\n if (styles.display !== \"flex\" && styles.display !== \"inline-flex\") return null;\n return Math.round(\n Math.max(1, styles.flexDirection.startsWith(\"column\") ? size.height : size.width),\n );\n };\n\n const applyBoxSize = (element: HTMLElement, edit: Record<string, unknown>): void => {\n const width = finiteNumber(edit.width);\n const height = finiteNumber(edit.height);\n if (width == null || height == null || width <= 0 || height <= 0) return;\n\n const rounded = {\n width: Math.round(Math.max(1, width)),\n height: Math.round(Math.max(1, height)),\n };\n element.setAttribute(BOX_SIZE_ATTR, \"true\");\n element.style.setProperty(WIDTH_PROP, `${rounded.width}px`);\n element.style.setProperty(HEIGHT_PROP, `${rounded.height}px`);\n element.style.setProperty(\"box-sizing\", \"border-box\");\n element.style.setProperty(\"width\", `${rounded.width}px`);\n element.style.setProperty(\"height\", `${rounded.height}px`);\n element.style.setProperty(\"min-width\", \"0px\");\n element.style.setProperty(\"min-height\", \"0px\");\n element.style.setProperty(\"max-width\", \"none\");\n element.style.setProperty(\"max-height\", \"none\");\n\n const flexBasis = readParentFlexBasisPixels(element, rounded);\n if (flexBasis != null) {\n element.style.setProperty(\"flex-basis\", `${flexBasis}px`);\n element.style.setProperty(\"flex-grow\", \"0\");\n element.style.setProperty(\"flex-shrink\", \"0\");\n }\n if (getComputedStyle(element).display === \"inline\") {\n element.style.setProperty(\"display\", \"inline-block\");\n }\n };\n\n const applyRotation = (element: HTMLElement, edit: Record<string, unknown>): void => {\n const angle = finiteNumber(edit.angle);\n if (angle == null) return;\n prepareRotationBase(element);\n element.setAttribute(ROTATION_ATTR, \"true\");\n element.style.setProperty(ROTATION_PROP, `${roundRotationAngle(angle)}deg`);\n element.style.setProperty(\"transform-origin\", ROTATION_TRANSFORM_ORIGIN);\n element.style.setProperty(\"rotate\", composeRotation(element, `var(${ROTATION_PROP}, 0deg)`));\n };\n\n const applyManifest = (): number => {\n let applied = 0;\n for (const edit of manifestEdits) {\n const editRecord = objectRecord(edit);\n if (!editRecord) continue;\n const element = resolveTarget(editRecord);\n if (!element) continue;\n if (editRecord.kind === \"path-offset\") applyPathOffset(element, editRecord);\n if (editRecord.kind === \"box-size\") applyBoxSize(element, editRecord);\n if (editRecord.kind === \"rotation\") applyRotation(element, editRecord);\n applied += 1;\n }\n return applied;\n };\n runtimeWindow.__hfStudioManualEditsApply = applyManifest;\n\n const markWrapped = (fn: (time: number) => unknown): void => {\n try {\n Object.defineProperty(fn, WRAPPED_SEEK_PROP, {\n configurable: false,\n enumerable: false,\n value: true,\n });\n } catch {\n try {\n (fn as unknown as Record<string, unknown>)[WRAPPED_SEEK_PROP] = true;\n } catch {\n // Ignore non-extensible functions.\n }\n }\n };\n\n const isWrapped = (fn: (time: number) => unknown): boolean =>\n Boolean((fn as unknown as Record<string, unknown>)[WRAPPED_SEEK_PROP]);\n\n const wrapFunction = (\n get: () => ((time: number) => unknown) | undefined,\n set: (fn: (time: number) => unknown) => void,\n ): boolean => {\n const fn = get();\n if (!fn) return false;\n const seek = fn as (time: number) => unknown;\n if (isWrapped(seek)) {\n applyManifest();\n return true;\n }\n\n const wrappedSeek = function (this: unknown, time: number): unknown {\n const result = seek.call(this, time);\n applyManifest();\n return result;\n };\n markWrapped(wrappedSeek);\n set(wrappedSeek);\n applyManifest();\n return true;\n };\n\n const wrapSeekFunctions = (): boolean => {\n const wrappedHfSeek = wrapFunction(\n () => runtimeWindow.__hf?.seek,\n (fn) => {\n if (runtimeWindow.__hf) runtimeWindow.__hf.seek = fn;\n },\n );\n const wrappedPlayerRenderSeek = wrapFunction(\n () => runtimeWindow.__player?.renderSeek,\n (fn) => {\n if (runtimeWindow.__player) runtimeWindow.__player.renderSeek = fn;\n },\n );\n return wrappedHfSeek || wrappedPlayerRenderSeek;\n };\n\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", () => applyManifest(), { once: true });\n } else {\n applyManifest();\n }\n\n wrapSeekFunctions();\n let remainingSeekWrapAttempts = 120;\n const seekWrapInterval = setInterval(() => {\n wrapSeekFunctions();\n remainingSeekWrapAttempts -= 1;\n if (remainingSeekWrapAttempts <= 0) clearInterval(seekWrapInterval);\n }, 50);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/hfIdPersist.ts"],"sourcesContent":["import { ensureHfIds } from \"@hyperframes/parsers/hf-ids\";\nimport { createHash } from \"node:crypto\";\nimport { closeSync, constants, fstatSync, openSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { replaceFileAtomically } from \"@hyperframes/core/atomic-file\";\nimport { isInHiddenOrVendorDir, mkdirWithinProject, walkDir } from \"./safePath.js\";\n\nexport const isCompositionSource = (html: string): boolean => /data-composition-id\\s*=/.test(html);\n\nconst STAMP_RECORD = join(\".hyperframes\", \"hf-ids-stamped.json\");\n\nconst contentHash = (text: string): string => createHash(\"sha1\").update(text).digest(\"base64url\");\n\n/** Pins hf-ids into every composition in the project. A host runs it once before it serves or\n * watches the project: an id write during a session reaches Studio as an outside edit and reloads it.\n * A file whose content hash matches the last run's (kept in `.hyperframes`) is not parsed again. */\nexport function stampProjectHfIds(projectDir: string): void {\n const recordPath = join(projectDir, STAMP_RECORD);\n let last: Record<string, string> = {};\n try {\n last = JSON.parse(readFileSync(recordPath, \"utf-8\"));\n } catch {\n // first run, or an unreadable record: parse everything\n }\n const next: Record<string, string> = {};\n for (const file of walkDir(projectDir)) {\n if (!file.endsWith(\".html\") || isInHiddenOrVendorDir(file)) continue;\n const absPath = join(projectDir, file);\n let html: string;\n try {\n html = readFileSync(absPath, \"utf-8\");\n } catch {\n continue; // unreadable now; the preview route stamps it when it is served\n }\n let hash = contentHash(html);\n if (last[file] !== hash && isCompositionSource(html)) {\n const stamped = stampFileHfIds(absPath);\n if (stamped === null) continue;\n hash = contentHash(stamped);\n }\n next[file] = hash;\n }\n try {\n mkdirWithinProject(projectDir, dirname(recordPath));\n writeFileSync(recordPath, JSON.stringify(next));\n } catch {\n // read-only project: the next start parses again\n }\n}\n\nfunction openNoFollow(filePath: string, flags: number): number | null {\n // O_NOFOLLOW is undefined on Windows; opening without it is the platform norm there.\n const noFollow = constants.O_NOFOLLOW ?? 0;\n try {\n return openSync(filePath, flags | noFollow);\n } catch {\n return null;\n }\n}\n\nfunction isUnchanged(filePath: string, expected: string): boolean {\n const fd = openNoFollow(filePath, constants.O_RDONLY);\n if (fd === null) return false;\n try {\n return readFileSync(fd, \"utf-8\") === expected;\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Read `filePath`, mint any missing `data-hf-id`s, write the stamped content\n * back if new ids were added, and return the stamped content. Every open of the path is\n * no-follow (POSIX) and the write renames a sibling temp file over it, so a symlink swapped\n * in at the path is never read or written through (CodeQL js/file-system-race).\n *\n * Falls back to read-only stamping when the file isn't writable (read-only\n * fs, sandbox) — serving stamped content without persisting is still correct;\n * ids are content-keyed so the SDK mints the same ones from the same bytes.\n *\n * Returns null when the file is missing, unreadable, or not a regular file.\n *\n * A write that lands while ids are minted is kept: the file is replaced only if\n * it still holds the bytes that were stamped, else it is left for the next open or save.\n */\nexport function stampFileHfIds(filePath: string): string | null {\n let fd: number | null = openNoFollow(filePath, constants.O_RDWR);\n let writable = true;\n if (fd === null) {\n fd = openNoFollow(filePath, constants.O_RDONLY);\n writable = false;\n }\n if (fd === null) return null;\n try {\n if (!fstatSync(fd).isFile()) return null;\n const html = readFileSync(fd, \"utf-8\");\n const normalized = ensureHfIds(html);\n // Attribute count, not string equality — linkedom serialization normalizes\n // quote style/whitespace even when no ids were minted.\n const idsBefore = (html.match(/\\bdata-hf-id=/g) ?? []).length;\n const idsAfter = (normalized.match(/\\bdata-hf-id=/g) ?? []).length;\n if (writable && idsAfter > idsBefore) {\n const mode = fstatSync(fd).mode;\n closeSync(fd);\n fd = null;\n if (isUnchanged(filePath, html)) replaceFileAtomically(filePath, normalized, mode);\n }\n return normalized;\n } catch (err) {\n console.warn(\"[hyperframes] stampFileHfIds: failed to stamp ids:\", err);\n return null;\n } finally {\n if (fd !== null) closeSync(fd);\n }\n}\n"],"mappings":";;;;;;;AAAA,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,WAAW,WAAW,WAAW,UAAU,cAAc,qBAAqB;AACvF,SAAS,SAAS,YAAY;AAC9B,SAAS,6BAA6B;AAG/B,IAAM,sBAAsB,CAAC,SAA0B,0BAA0B,KAAK,IAAI;AAEjG,IAAM,eAAe,KAAK,gBAAgB,qBAAqB;AAE/D,IAAM,cAAc,CAAC,SAAyB,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,WAAW;AAKzF,SAAS,kBAAkB,YAA0B;AAC1D,QAAM,aAAa,KAAK,YAAY,YAAY;AAChD,MAAI,OAA+B,CAAC;AACpC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAAA,EACrD,QAAQ;AAAA,EAER;AACA,QAAM,OAA+B,CAAC;AACtC,aAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,QAAI,CAAC,KAAK,SAAS,OAAO,KAAK,sBAAsB,IAAI,EAAG;AAC5D,UAAM,UAAU,KAAK,YAAY,IAAI;AACrC,QAAI;AACJ,QAAI;AACF,aAAO,aAAa,SAAS,OAAO;AAAA,IACtC,QAAQ;AACN;AAAA,IACF;AACA,QAAI,OAAO,YAAY,IAAI;AAC3B,QAAI,KAAK,IAAI,MAAM,QAAQ,oBAAoB,IAAI,GAAG;AACpD,YAAM,UAAU,eAAe,OAAO;AACtC,UAAI,YAAY,KAAM;AACtB,aAAO,YAAY,OAAO;AAAA,IAC5B;AACA,SAAK,IAAI,IAAI;AAAA,EACf;AACA,MAAI;AACF,uBAAmB,YAAY,QAAQ,UAAU,CAAC;AAClD,kBAAc,YAAY,KAAK,UAAU,IAAI,CAAC;AAAA,EAChD,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,aAAa,UAAkB,OAA8B;AAEpE,QAAM,WAAW,UAAU,cAAc;AACzC,MAAI;AACF,WAAO,SAAS,UAAU,QAAQ,QAAQ;AAAA,EAC5C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,UAAkB,UAA2B;AAChE,QAAM,KAAK,aAAa,UAAU,UAAU,QAAQ;AACpD,MAAI,OAAO,KAAM,QAAO;AACxB,MAAI;AACF,WAAO,aAAa,IAAI,OAAO,MAAM;AAAA,EACvC,UAAE;AACA,cAAU,EAAE;AAAA,EACd;AACF;AAiBO,SAAS,eAAe,UAAiC;AAC9D,MAAI,KAAoB,aAAa,UAAU,UAAU,MAAM;AAC/D,MAAI,WAAW;AACf,MAAI,OAAO,MAAM;AACf,SAAK,aAAa,UAAU,UAAU,QAAQ;AAC9C,eAAW;AAAA,EACb;AACA,MAAI,OAAO,KAAM,QAAO;AACxB,MAAI;AACF,QAAI,CAAC,UAAU,EAAE,EAAE,OAAO,EAAG,QAAO;AACpC,UAAM,OAAO,aAAa,IAAI,OAAO;AACrC,UAAM,aAAa,YAAY,IAAI;AAGnC,UAAM,aAAa,KAAK,MAAM,gBAAgB,KAAK,CAAC,GAAG;AACvD,UAAM,YAAY,WAAW,MAAM,gBAAgB,KAAK,CAAC,GAAG;AAC5D,QAAI,YAAY,WAAW,WAAW;AACpC,YAAM,OAAO,UAAU,EAAE,EAAE;AAC3B,gBAAU,EAAE;AACZ,WAAK;AACL,UAAI,YAAY,UAAU,IAAI,EAAG,uBAAsB,UAAU,YAAY,IAAI;AAAA,IACnF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,sDAAsD,GAAG;AACtE,WAAO;AAAA,EACT,UAAE;AACA,QAAI,OAAO,KAAM,WAAU,EAAE;AAAA,EAC/B;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/safePath.ts"],"sourcesContent":["import { basename, dirname, isAbsolute, join, relative, sep } from \"node:path\";\nimport { readdirSync, type Dirent } from \"node:fs\";\nimport { realpath, resolveWithinProject as resolveInProject } from \"@hyperframes/core/safe-path\";\nimport { STUDIO_MANUAL_EDITS_PATH } from \"./manualEditsRenderScript.js\";\nimport { STUDIO_MOTION_PATH } from \"./studioMotionRenderScript.js\";\n\n// `isSafePath` lives at the package root so non-studio-api layers (compiler,\n// CLI, engine) can share it without a backwards dependency on studio-api.\n// Re-exported here for back-compat with existing `../helpers/safePath.js` imports.\nexport {\n folderGone,\n isProjectRootMissing,\n isSafePath,\n mkdirWithinProject,\n realpath,\n realProjectRoot,\n} from \"@hyperframes/core/safe-path\";\n\n/** Core's `resolveWithinProject`, closed to the desktop app's private files for every route a request path reaches. */\nexport function resolveWithinProject(base: string, relativePath: string): string | null {\n const resolved = resolveInProject(base, relativePath);\n return resolved && !isPrivateProjectFile(base, resolved) ? resolved : null;\n}\n\n/** The real path; for a path not there (yet, or any more), the nearest existing folder's real path plus the rest. */\nexport function realFilePath(filePath: string): string {\n try {\n return realpath(filePath);\n } catch {\n const dir = dirname(filePath);\n return dir === filePath ? filePath : join(realFilePath(dir), basename(filePath));\n }\n}\n\n// `.hyperframes/` holds the desktop app's and the CLI's own records (a hand-off, chat history, read marks): no route\n// reaches any of it, the folder itself included, but Studio's own files and the app's request pictures.\nconst STUDIO_FILES = new Set([STUDIO_MOTION_PATH, STUDIO_MANUAL_EDITS_PATH]);\nconst STUDIO_FOLDERS = [\".hyperframes/prepared-assets\", \".hyperframes/requests\"];\n\n/** For a project-relative path with `/` separators. */\nexport function isPrivateProjectPath(relPath: string): boolean {\n const path = relPath.toLowerCase();\n return (\n (path === \".hyperframes\" || path.startsWith(\".hyperframes/\")) &&\n !STUDIO_FILES.has(path) &&\n !STUDIO_FOLDERS.some((folder) => path === folder || path.startsWith(`${folder}/`))\n );\n}\n\nexport function isPrivateProjectFile(projectDir: string, filePath: string): boolean {\n const rel = (from: string, to: string) => relative(from, to).split(sep).join(\"/\");\n return [rel(projectDir, filePath), rel(realFilePath(projectDir), realFilePath(filePath))].some(\n isPrivateProjectPath,\n );\n}\n\n/**\n * `resolveWithinProject` with every link inside the project resolved now, so a write to the result lands on the\n * file checked here even if a link on the way is retargeted before the write.\n */\nexport function pinWithinProject(base: string, relativePath: string): string | null {\n const checked = resolveWithinProject(base, relativePath);\n if (!checked) return null;\n const inside = relative(realFilePath(base), realFilePath(checked));\n const escapes = inside === \"..\" || inside.startsWith(`..${sep}`) || isAbsolute(inside);\n return escapes ? null : join(base, inside);\n}\n\nconst IGNORE_DIRS = new Set([\n \".thumbnails\",\n \".transcode-cache\",\n \".waveform-cache\",\n \"node_modules\",\n \".git\",\n]);\n\nfunction shouldIgnoreDir(rel: string): boolean {\n return rel === \".hyperframes/backup\";\n}\n\n/**\n * True when any directory segment of a relative path is a dot-directory or\n * node_modules. Projects that vendor tooling assets under dot-directories\n * (.hyperframes/, .cache/, …) ship example/preset HTML that must not surface\n * as project compositions or studio lint targets (#1384). The file tree is\n * deliberately not filtered — this only gates discovery.\n */\nexport function isInHiddenOrVendorDir(relPath: string): boolean {\n const segments = relPath.split(\"/\");\n return segments.slice(0, -1).some((seg) => seg.startsWith(\".\") || seg === \"node_modules\");\n}\n\nconst UNREADABLE_DIR_CODES = new Set([\"EACCES\", \"EPERM\", \"ENOENT\", \"ENOTDIR\"]);\n\n/** Recursively walk a directory and return relative file paths. A subfolder that is unreadable\n * or removed mid-walk is skipped; any other failure, or an unreadable `dir` itself, throws. */\nexport function walkDir(dir: string, prefix = \"\"): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (!prefix || !code || !UNREADABLE_DIR_CODES.has(code)) throw err;\n return [];\n }\n const files: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (IGNORE_DIRS.has(entry.name) || shouldIgnoreDir(rel)) continue;\n if (entry.isDirectory()) {\n files.push(...walkDir(join(dir, entry.name), rel));\n } else {\n files.push(rel);\n }\n }\n return files;\n}\n"],"mappings":"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","names":["realpath"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/screenshotClip.ts"],"sourcesContent":["export interface ScreenshotClip {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n// Serialised by page.evaluate, so self-contained; fades everything but the element's own branch.\nexport function getElementScreenshotClip(\n selector: string,\n selectorIndex?: number,\n): ScreenshotClip | undefined {\n // Guard against invalid CSS selectors (e.g. `#0` — a digit-leading id from\n // user HTML that upstream producers forgot to CSS.escape). querySelectorAll\n // throws SyntaxError on those, which bubbles out of page.evaluate and fails\n // the whole thumbnail. Returning undefined here falls back to a full-page\n // screenshot, so the user still sees a thumbnail instead of a broken image.\n let matches: HTMLElement[];\n try {\n matches = Array.from(document.querySelectorAll(selector)).filter(\n (el): el is HTMLElement => el instanceof HTMLElement,\n );\n } catch {\n return undefined;\n }\n const safeIndex = Math.max(0, Math.min(matches.length - 1, Math.floor(selectorIndex ?? 0)));\n const el = matches[safeIndex] ?? null;\n if (!(el instanceof HTMLElement)) return undefined;\n // Opacity, not visibility; each prior inline opacity is kept for clearElementScreenshotIsolation.\n const page = window as Window & { __hfThumbnailFaded?: [CSSStyleDeclaration, string, string][] };\n const faded = (page.__hfThumbnailFaded ??= []);\n for (let node: Element = el; node.parentElement; node = node.parentElement) {\n for (const sibling of Array.from(node.parentElement.children)) {\n const style = (sibling as HTMLElement).style;\n if (sibling === node || !style) continue;\n faded.push([style, style.getPropertyValue(\"opacity\"), style.getPropertyPriority(\"opacity\")]);\n style.setProperty(\"opacity\", \"0\", \"important\");\n }\n }\n const rect = el.getBoundingClientRect();\n if (rect.width < 4 || rect.height < 4) return undefined;\n const pad = 8;\n const x = Math.max(0, rect.left - pad);\n const y = Math.max(0, rect.top - pad);\n const maxWidth = window.innerWidth - x;\n const maxHeight = window.innerHeight - y;\n return {\n x,\n y,\n width: Math.max(1, Math.min(rect.width + pad * 2, maxWidth)),\n height: Math.max(1, Math.min(rect.height + pad * 2, maxHeight)),\n };\n}\n\nexport function clearElementScreenshotIsolation(): void {\n const page = window as Window & { __hfThumbnailFaded?: [CSSStyleDeclaration, string, string][] };\n for (const [style, value, priority] of (page.__hfThumbnailFaded ?? []).reverse()) {\n if (value) style.setProperty(\"opacity\", value, priority);\n else style.removeProperty(\"opacity\");\n }\n page.__hfThumbnailFaded = [];\n}\n"],"mappings":";AAQO,SAAS,yBACd,UACA,eAC4B;AAM5B,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,KAAK,SAAS,iBAAiB,QAAQ,CAAC,EAAE;AAAA,MACxD,CAACA,QAA0BA,eAAc;AAAA,IAC3C;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,SAAS,GAAG,KAAK,MAAM,iBAAiB,CAAC,CAAC,CAAC;AAC1F,QAAM,KAAK,QAAQ,SAAS,KAAK;AACjC,MAAI,EAAE,cAAc,aAAc,QAAO;AAEzC,QAAM,OAAO;AACb,QAAM,QAAS,KAAK,uBAAuB,CAAC;AAC5C,WAAS,OAAgB,IAAI,KAAK,eAAe,OAAO,KAAK,eAAe;AAC1E,eAAW,WAAW,MAAM,KAAK,KAAK,cAAc,QAAQ,GAAG;AAC7D,YAAM,QAAS,QAAwB;AACvC,UAAI,YAAY,QAAQ,CAAC,MAAO;AAChC,YAAM,KAAK,CAAC,OAAO,MAAM,iBAAiB,SAAS,GAAG,MAAM,oBAAoB,SAAS,CAAC,CAAC;AAC3F,YAAM,YAAY,WAAW,KAAK,WAAW;AAAA,IAC/C;AAAA,EACF;AACA,QAAM,OAAO,GAAG,sBAAsB;AACtC,MAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,EAAG,QAAO;AAC9C,QAAM,MAAM;AACZ,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,GAAG;AACrC,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG;AACpC,QAAM,WAAW,OAAO,aAAa;AACrC,QAAM,YAAY,OAAO,cAAc;AACvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,MAAM,GAAG,QAAQ,CAAC;AAAA,IAC3D,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,MAAM,GAAG,SAAS,CAAC;AAAA,EAChE;AACF;AAEO,SAAS,kCAAwC;AACtD,QAAM,OAAO;AACb,aAAW,CAAC,OAAO,OAAO,QAAQ,MAAM,KAAK,sBAAsB,CAAC,GAAG,QAAQ,GAAG;AAChF,QAAI,MAAO,OAAM,YAAY,WAAW,OAAO,QAAQ;AAAA,QAClD,OAAM,eAAe,SAAS;AAAA,EACrC;AACA,OAAK,qBAAqB,CAAC;AAC7B;","names":["el"]}
|