@hyperframes/studio 0.8.107 → 0.8.108
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/assets/{hyperframes-player-h4mktSdm.js → hyperframes-player-BakGew45.js} +1 -1
- package/dist/assets/{index-D8GKc2D9.js → index-C5dyUHwk.js} +1 -1
- package/dist/assets/{index-CM1xA_jD.js → index-C842tcVa.js} +165 -165
- package/dist/assets/{index-zoqPmBF6.js → index-CPe7d_wO.js} +1 -1
- package/dist/assets/{index-lrT-gfHa.css → index-ncVJ5IVu.css} +1 -1
- package/dist/chunk-6LUNLTHR.js +611 -0
- package/dist/chunk-6LUNLTHR.js.map +1 -0
- package/dist/{chunk-4OZC426H.js → chunk-AOWVEVEU.js} +3 -503
- package/dist/chunk-AOWVEVEU.js.map +1 -0
- package/dist/{chunk-46XP7J3B.js → chunk-PQFEBX32.js} +5 -3
- package/dist/chunk-PQFEBX32.js.map +1 -0
- package/dist/{domEditingLayers-AQBDFNUC.js → domEditingLayers-JQPQGGT7.js} +3 -3
- package/dist/index.d.ts +11 -2
- package/dist/index.html +2 -2
- package/dist/index.js +12416 -11195
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +8 -8
- package/src/App.tsx +1 -0
- package/src/components/EditorShell.tsx +2 -0
- package/src/components/editor/CropPresetBar.tsx +69 -0
- package/src/components/editor/DomEditCropHandles.test.tsx +44 -0
- package/src/components/editor/DomEditCropHandles.tsx +123 -95
- package/src/components/editor/carveOutput.ts +54 -0
- package/src/components/editor/cropPresetStore.ts +33 -0
- package/src/components/editor/cropPresets.test.ts +41 -0
- package/src/components/editor/cropPresets.ts +24 -0
- package/src/components/editor/domEditingLayers.test.ts +21 -0
- package/src/components/editor/domEditingLayers.ts +2 -0
- package/src/components/editor/dragUndoPaint.test.ts +71 -0
- package/src/components/editor/manualOffsetDrag.ts +11 -0
- package/src/components/editor/mediaAudioEdits.test.ts +59 -0
- package/src/components/editor/mediaAudioEdits.ts +40 -11
- package/src/components/editor/propertyPanelAudioFxGroup.test.tsx +32 -4
- package/src/components/editor/propertyPanelAudioFxGroup.tsx +3 -1
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +7 -3
- package/src/components/editor/useDomEditNudge.ts +17 -12
- package/src/components/editor/useDomEditOverlayGestures.ts +17 -6
- package/src/components/editor/useFxCarve.ts +3 -61
- package/src/components/editor/useFxCarveGrouping.test.ts +13 -0
- package/src/components/editor/useFxCarveGrouping.ts +8 -3
- package/src/components/nle/AudioMeterStrip.test.tsx +53 -0
- package/src/components/nle/AudioMeterStrip.tsx +60 -2
- package/src/components/nle/useTimelineEditCallbacks.ts +4 -0
- package/src/contexts/TimelineEditContext.tsx +1 -0
- package/src/hooks/useEditHistoryActions.paint.test.tsx +41 -0
- package/src/hooks/useEditHistoryActions.ts +13 -4
- package/src/hooks/useElementPicker.test.ts +62 -13
- package/src/hooks/useElementPicker.ts +10 -2
- package/src/hooks/useFreezeFrame.test.ts +56 -0
- package/src/hooks/useFreezeFrame.ts +121 -0
- package/src/hooks/usePersistentEditHistory.test.ts +9 -6
- package/src/hooks/usePersistentEditHistory.ts +1 -1
- package/src/hooks/useRazorSplit.ts +14 -1
- package/src/hooks/useRenderClipContent.test.ts +102 -0
- package/src/hooks/useRenderClipContent.ts +30 -23
- package/src/hooks/useTimelineEditing.ts +2 -2
- package/src/index.ts +1 -0
- package/src/player/components/AudibleVideoClipContent.tsx +29 -0
- package/src/player/components/ClipBadges.test.tsx +82 -0
- package/src/player/components/ClipBadges.tsx +67 -0
- package/src/player/components/ClipContextMenu.tsx +22 -0
- package/src/player/components/ClipPeakMarks.test.tsx +41 -0
- package/src/player/components/ClipPeakMarks.tsx +71 -0
- package/src/player/components/TimelineClip.tsx +2 -0
- package/src/player/components/TimelineFxButton.test.tsx +2 -2
- package/src/player/components/TimelineTrackHeader.test.tsx +37 -0
- package/src/player/components/TimelineTrackHeader.tsx +12 -25
- package/src/player/components/clipMenuAudioItems.test.tsx +125 -0
- package/src/player/components/clipMenuAudioItems.tsx +108 -0
- package/src/player/components/clipMenuDuck.test.ts +117 -0
- package/src/player/components/clipMenuDuck.ts +142 -0
- package/src/player/components/clipMenuNormalize.test.ts +95 -0
- package/src/player/components/clipMenuNormalize.ts +96 -0
- package/src/player/components/clipMenuToolItems.test.tsx +139 -0
- package/src/player/components/clipMenuToolItems.tsx +235 -0
- package/src/player/components/clipPeakRuns.test.ts +38 -0
- package/src/player/components/clipPeakRuns.ts +59 -0
- package/src/player/components/clipToolAttrs.test.ts +161 -0
- package/src/player/components/clipToolAttrs.ts +187 -0
- package/src/player/components/timelineCallbacks.ts +1 -0
- package/src/player/components/useClipToolState.ts +92 -0
- package/src/player/lib/timelineElementHelpers.ts +9 -0
- package/src/timelineChromeExports.test.tsx +7 -0
- package/src/utils/audioMeterMath.test.ts +37 -0
- package/src/utils/audioMeterMath.ts +9 -3
- package/src/utils/exportPeakCeiling.ts +1 -0
- package/src/utils/previewRelativePath.ts +19 -0
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioPendingEdits.test.ts +66 -0
- package/src/utils/studioPendingEdits.ts +45 -11
- package/src/utils/timelineInspector.test.ts +33 -2
- package/src/utils/timelineInspector.ts +16 -4
- package/dist/chunk-46XP7J3B.js.map +0 -1
- package/dist/chunk-4OZC426H.js.map +0 -1
- package/dist/chunk-DI4LCJHL.js +0 -102
- package/dist/chunk-DI4LCJHL.js.map +0 -1
- /package/dist/{domEditingLayers-AQBDFNUC.js.map → domEditingLayers-JQPQGGT7.js.map} +0 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
// src/utils/sourceScopedSelectorIndex.ts
|
|
2
|
+
var activePass = null;
|
|
3
|
+
function withSelectorIndexPass(doc, run) {
|
|
4
|
+
const previous = activePass;
|
|
5
|
+
activePass = { doc, bySelector: /* @__PURE__ */ new Map() };
|
|
6
|
+
try {
|
|
7
|
+
return run();
|
|
8
|
+
} finally {
|
|
9
|
+
activePass = previous;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function buildOccurrences(doc, selector, resolveSourceFile) {
|
|
13
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
14
|
+
const counts = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const candidate of doc.querySelectorAll(selector)) {
|
|
16
|
+
const scope = resolveSourceFile(candidate) ?? "index.html";
|
|
17
|
+
const index = counts.get(scope) ?? 0;
|
|
18
|
+
counts.set(scope, index + 1);
|
|
19
|
+
occurrences.set(candidate, { scope, index });
|
|
20
|
+
}
|
|
21
|
+
return occurrences;
|
|
22
|
+
}
|
|
23
|
+
function getSourceScopedSelectorIndex(doc, el, selector, sourceFile, resolveSourceFile) {
|
|
24
|
+
if (!selector || selector.startsWith("#") || selector.startsWith("[data-composition-id=")) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const pass = activePass?.doc === doc ? activePass : null;
|
|
29
|
+
let occurrences = pass?.bySelector.get(selector);
|
|
30
|
+
if (!occurrences) {
|
|
31
|
+
occurrences = buildOccurrences(doc, selector, resolveSourceFile);
|
|
32
|
+
pass?.bySelector.set(selector, occurrences);
|
|
33
|
+
}
|
|
34
|
+
const hit = occurrences.get(el);
|
|
35
|
+
return hit && hit.scope === (sourceFile ?? "index.html") ? hit.index : void 0;
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/player/lib/playbackAdapter.ts
|
|
42
|
+
function isFinitePositive(value) {
|
|
43
|
+
return Number.isFinite(value) && value > 0;
|
|
44
|
+
}
|
|
45
|
+
function clampTime(time, duration) {
|
|
46
|
+
const safeDuration = Math.max(0, Number.isFinite(duration) ? duration : 0);
|
|
47
|
+
const safeTime = Math.max(0, Number.isFinite(time) ? time : 0);
|
|
48
|
+
return safeDuration > 0 ? Math.min(safeTime, safeDuration) : safeTime;
|
|
49
|
+
}
|
|
50
|
+
function getAdapterDuration(adapter) {
|
|
51
|
+
if (!adapter) return 0;
|
|
52
|
+
try {
|
|
53
|
+
const duration = Number(adapter.getDuration());
|
|
54
|
+
return isFinitePositive(duration) ? duration : 0;
|
|
55
|
+
} catch {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function getDefaultStaticSeekPlaybackClock(win) {
|
|
60
|
+
return {
|
|
61
|
+
now: () => win.performance.now(),
|
|
62
|
+
requestAnimationFrame: (callback) => win.requestAnimationFrame(callback),
|
|
63
|
+
cancelAnimationFrame: (handle) => win.cancelAnimationFrame(handle)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createStaticSeekPlaybackAdapter(player, duration, clock, getPlaybackRate = () => 1) {
|
|
67
|
+
const safeDuration = Math.max(0, Number.isFinite(duration) ? duration : 0);
|
|
68
|
+
let currentTime = clampTime(Number(player.getTime?.() ?? 0), safeDuration);
|
|
69
|
+
let playing = false;
|
|
70
|
+
let rafId = 0;
|
|
71
|
+
let playStartTime = currentTime;
|
|
72
|
+
let playStartNow = clock.now();
|
|
73
|
+
const renderSeek = (time) => {
|
|
74
|
+
currentTime = clampTime(time, safeDuration);
|
|
75
|
+
if (typeof player.renderSeek === "function") {
|
|
76
|
+
player.renderSeek(currentTime);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
player.seek?.(currentTime);
|
|
80
|
+
};
|
|
81
|
+
const stopTicker = () => {
|
|
82
|
+
if (rafId) {
|
|
83
|
+
clock.cancelAnimationFrame(rafId);
|
|
84
|
+
rafId = 0;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const tick = (now) => {
|
|
88
|
+
if (!playing) return;
|
|
89
|
+
const playbackRate = Math.max(0.1, Number(getPlaybackRate()) || 1);
|
|
90
|
+
const elapsed = (now - playStartNow) / 1e3 * playbackRate;
|
|
91
|
+
renderSeek(playStartTime + elapsed);
|
|
92
|
+
if (currentTime >= safeDuration) {
|
|
93
|
+
playing = false;
|
|
94
|
+
rafId = 0;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
rafId = clock.requestAnimationFrame(tick);
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
play: () => {
|
|
101
|
+
if (playing || safeDuration <= 0) return;
|
|
102
|
+
if (currentTime >= safeDuration) renderSeek(0);
|
|
103
|
+
playing = true;
|
|
104
|
+
playStartTime = currentTime;
|
|
105
|
+
playStartNow = clock.now();
|
|
106
|
+
stopTicker();
|
|
107
|
+
rafId = clock.requestAnimationFrame(tick);
|
|
108
|
+
},
|
|
109
|
+
pause: () => {
|
|
110
|
+
playing = false;
|
|
111
|
+
stopTicker();
|
|
112
|
+
},
|
|
113
|
+
seek: (time, options) => {
|
|
114
|
+
renderSeek(time);
|
|
115
|
+
if (options?.keepPlaying) {
|
|
116
|
+
if (playing) {
|
|
117
|
+
playStartTime = currentTime;
|
|
118
|
+
playStartNow = clock.now();
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
playing = false;
|
|
123
|
+
stopTicker();
|
|
124
|
+
},
|
|
125
|
+
getTime: () => currentTime,
|
|
126
|
+
getDuration: () => safeDuration,
|
|
127
|
+
isPlaying: () => playing
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function releaseStaticSeekCache(cache, warned) {
|
|
131
|
+
cache.current?.adapter.pause();
|
|
132
|
+
cache.current = null;
|
|
133
|
+
warned.current = false;
|
|
134
|
+
}
|
|
135
|
+
function resolveStaticSeekFallback(opts) {
|
|
136
|
+
const { cache, warned, bestAdapter, effectiveDuration, docDuration } = opts;
|
|
137
|
+
const cached = cache.current;
|
|
138
|
+
if (cached?.player === bestAdapter && cached.duration === effectiveDuration) {
|
|
139
|
+
return cached.adapter;
|
|
140
|
+
}
|
|
141
|
+
cached?.adapter.pause();
|
|
142
|
+
if (!warned.current) {
|
|
143
|
+
warned.current = true;
|
|
144
|
+
console.warn(
|
|
145
|
+
`[useTimelinePlayer] Selected adapter duration (${getAdapterDuration(bestAdapter)}s) does not cover the document duration (${docDuration}s); falling back to seek-driven playback, which never starts media elements or WebAudio. Audio will not play in preview \u2014 extend the GSAP timeline to cover the declared data-duration.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const adapter = createStaticSeekPlaybackAdapter(
|
|
149
|
+
bestAdapter,
|
|
150
|
+
effectiveDuration,
|
|
151
|
+
opts.clock,
|
|
152
|
+
opts.getPlaybackRate
|
|
153
|
+
);
|
|
154
|
+
cache.current = { player: bestAdapter, duration: effectiveDuration, adapter };
|
|
155
|
+
return adapter;
|
|
156
|
+
}
|
|
157
|
+
function wrapTimeline(tl) {
|
|
158
|
+
return {
|
|
159
|
+
play: () => tl.play(),
|
|
160
|
+
pause: () => tl.pause(),
|
|
161
|
+
seek: (t, options) => {
|
|
162
|
+
const shouldPause = !options?.keepPlaying;
|
|
163
|
+
if (shouldPause) tl.pause();
|
|
164
|
+
tl.seek(t);
|
|
165
|
+
if (shouldPause) tl.pause();
|
|
166
|
+
},
|
|
167
|
+
getTime: () => tl.time(),
|
|
168
|
+
getDuration: () => tl.duration(),
|
|
169
|
+
isPlaying: () => tl.isActive()
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/player/lib/timelineElementHelpers.ts
|
|
174
|
+
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
175
|
+
import { readElementFades } from "@hyperframes/core/audio-fade";
|
|
176
|
+
import {
|
|
177
|
+
clampPlaybackRate,
|
|
178
|
+
readMediaOffsetSeconds
|
|
179
|
+
} from "@hyperframes/parsers/media-duration";
|
|
180
|
+
var LAYER_REVEAL_PRIOR_Z_ATTR = "data-hf-reveal-prior-z";
|
|
181
|
+
var LAYER_REVEAL_PRIOR_POSITION_ATTR = "data-hf-reveal-prior-pos";
|
|
182
|
+
function readLayerRevealPriorZ(el) {
|
|
183
|
+
const raw = el.getAttribute(LAYER_REVEAL_PRIOR_Z_ATTR);
|
|
184
|
+
if (raw == null) return null;
|
|
185
|
+
const n = Number.parseInt(raw, 10);
|
|
186
|
+
return Number.isFinite(n) ? n : null;
|
|
187
|
+
}
|
|
188
|
+
function readTimelineElementZIndex(el) {
|
|
189
|
+
const prior = readLayerRevealPriorZ(el);
|
|
190
|
+
if (prior != null) return prior;
|
|
191
|
+
const html = el;
|
|
192
|
+
const parseZ = (value) => {
|
|
193
|
+
if (value == null || value === "" || value === "auto") return null;
|
|
194
|
+
const n = Number.parseInt(value, 10);
|
|
195
|
+
return Number.isFinite(n) ? n : null;
|
|
196
|
+
};
|
|
197
|
+
const fromInline = parseZ(html.style?.zIndex);
|
|
198
|
+
if (fromInline != null) return fromInline;
|
|
199
|
+
const view = el.ownerDocument?.defaultView;
|
|
200
|
+
if (view?.getComputedStyle) {
|
|
201
|
+
const fromComputed = parseZ(view.getComputedStyle(html).zIndex);
|
|
202
|
+
if (fromComputed != null) return fromComputed;
|
|
203
|
+
}
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
function readDurationAttribute(el) {
|
|
207
|
+
if (!el) return 0;
|
|
208
|
+
const duration = Number.parseFloat(el.getAttribute("data-duration") ?? "") || Number.parseFloat(el.getAttribute("data-hf-authored-duration") ?? "");
|
|
209
|
+
return isFinitePositive(duration) ? duration : 0;
|
|
210
|
+
}
|
|
211
|
+
function isTimelineIgnoredElement(el) {
|
|
212
|
+
if (el.tagName.toLowerCase() === HF_AUDIO_GROUP_TAG) return true;
|
|
213
|
+
return Boolean(
|
|
214
|
+
el.closest(
|
|
215
|
+
[
|
|
216
|
+
"[data-hyperframes-ignore]",
|
|
217
|
+
"[data-hyperframes-picker-ignore]",
|
|
218
|
+
"[data-hf-ignore]",
|
|
219
|
+
"[data-hf-color-grading-canvas]"
|
|
220
|
+
].join(",")
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function furthestClipEndFromDocument(doc) {
|
|
225
|
+
if (!doc) return 0;
|
|
226
|
+
const root = doc.querySelector("[data-composition-id]");
|
|
227
|
+
let maxEnd = 0;
|
|
228
|
+
for (const node of Array.from(doc.querySelectorAll("[data-start]"))) {
|
|
229
|
+
if (node === root || isTimelineIgnoredElement(node)) continue;
|
|
230
|
+
const start = Number.parseFloat(node.getAttribute("data-start") ?? "");
|
|
231
|
+
const duration = readDurationAttribute(node);
|
|
232
|
+
if (!Number.isFinite(start) || start < 0 || duration <= 0) continue;
|
|
233
|
+
maxEnd = Math.max(maxEnd, start + duration);
|
|
234
|
+
}
|
|
235
|
+
return maxEnd;
|
|
236
|
+
}
|
|
237
|
+
function readTimelineDurationFromDocument(doc) {
|
|
238
|
+
if (!doc) return 0;
|
|
239
|
+
const rootDuration = readDurationAttribute(doc.querySelector("[data-composition-id]"));
|
|
240
|
+
if (rootDuration > 0) return rootDuration;
|
|
241
|
+
return furthestClipEndFromDocument(doc);
|
|
242
|
+
}
|
|
243
|
+
function furthestClipEndFromSource(source) {
|
|
244
|
+
if (!source) return 0;
|
|
245
|
+
return furthestClipEndFromDocument(new DOMParser().parseFromString(source, "text/html"));
|
|
246
|
+
}
|
|
247
|
+
function isHtmlElement(el) {
|
|
248
|
+
const HtmlElementCtor = el.ownerDocument.defaultView?.HTMLElement ?? globalThis.HTMLElement;
|
|
249
|
+
return typeof HtmlElementCtor !== "undefined" && el instanceof HtmlElementCtor;
|
|
250
|
+
}
|
|
251
|
+
function isCompositionHost(el) {
|
|
252
|
+
return el.hasAttribute("data-composition-id") || el.hasAttribute("data-composition-src") || el.hasAttribute("data-composition-file");
|
|
253
|
+
}
|
|
254
|
+
function resolveMediaElement(el) {
|
|
255
|
+
const win = el.ownerDocument.defaultView ?? window;
|
|
256
|
+
const MediaElementCtor = win.HTMLMediaElement ?? globalThis.HTMLMediaElement;
|
|
257
|
+
const ImageElementCtor = win.HTMLImageElement ?? globalThis.HTMLImageElement;
|
|
258
|
+
if (el instanceof MediaElementCtor || el instanceof ImageElementCtor) return el;
|
|
259
|
+
if (isCompositionHost(el)) return null;
|
|
260
|
+
const candidate = el.querySelector("video, audio, img");
|
|
261
|
+
return candidate instanceof MediaElementCtor || candidate instanceof ImageElementCtor ? candidate : null;
|
|
262
|
+
}
|
|
263
|
+
function readPlaybackStartAttributes(getAttr) {
|
|
264
|
+
const playbackStartAttr = getAttr("data-playback-start") != null ? "playback-start" : getAttr("data-media-start") != null ? "media-start" : void 0;
|
|
265
|
+
return playbackStartAttr ? { playbackStart: readMediaOffsetSeconds(getAttr), playbackStartAttr } : {};
|
|
266
|
+
}
|
|
267
|
+
function playbackStartAttributeForElement(element) {
|
|
268
|
+
return element.playbackStartAttr === "playback-start" || element.kind === "composition" ? "data-playback-start" : "data-media-start";
|
|
269
|
+
}
|
|
270
|
+
function applyPlaybackMetadataFromElement(entry, el) {
|
|
271
|
+
Object.assign(
|
|
272
|
+
entry,
|
|
273
|
+
readPlaybackStartAttributes((n) => el.getAttribute(n))
|
|
274
|
+
);
|
|
275
|
+
const authoredPlaybackRate = Number.parseFloat(el.getAttribute("data-playback-rate") ?? "");
|
|
276
|
+
if (Number.isFinite(authoredPlaybackRate) && authoredPlaybackRate > 0) {
|
|
277
|
+
entry.playbackRate = clampPlaybackRate(authoredPlaybackRate);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function setOptional(entry, key, value) {
|
|
281
|
+
if (value === void 0) delete entry[key];
|
|
282
|
+
else entry[key] = value;
|
|
283
|
+
}
|
|
284
|
+
function readVolume(el, media) {
|
|
285
|
+
const volume = Number.parseFloat(
|
|
286
|
+
el.getAttribute("data-volume") ?? media.getAttribute("data-volume") ?? ""
|
|
287
|
+
);
|
|
288
|
+
return Number.isFinite(volume) ? volume : void 0;
|
|
289
|
+
}
|
|
290
|
+
function isVideoAudible(opts) {
|
|
291
|
+
if (opts.hasAudioAttr === "true") return true;
|
|
292
|
+
if (opts.hasAudioAttr === "false" || opts.hasAudioAttr === "") return false;
|
|
293
|
+
return opts.tag.toLowerCase() === "video" && !opts.muted;
|
|
294
|
+
}
|
|
295
|
+
function isAudibleVideoNode(el) {
|
|
296
|
+
if (el.tagName.toLowerCase() !== "video" || el.hasAttribute("muted")) return false;
|
|
297
|
+
return isVideoAudible({
|
|
298
|
+
tag: "video",
|
|
299
|
+
hasAudioAttr: el.getAttribute("data-has-audio"),
|
|
300
|
+
muted: false
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function applyAudioMetadataFromElement(entry, el) {
|
|
304
|
+
const media = resolveMediaElement(el) ?? el;
|
|
305
|
+
const muted = el.hasAttribute("muted") || media.hasAttribute("muted");
|
|
306
|
+
const sound = isVideoAudible({
|
|
307
|
+
tag: el.tagName,
|
|
308
|
+
hasAudioAttr: el.getAttribute("data-has-audio"),
|
|
309
|
+
muted
|
|
310
|
+
});
|
|
311
|
+
setOptional(entry, "hasAudio", sound ? true : void 0);
|
|
312
|
+
setOptional(entry, "muted", muted ? true : void 0);
|
|
313
|
+
setOptional(entry, "volume", readVolume(el, media));
|
|
314
|
+
}
|
|
315
|
+
function applyFadeMetadataFromElement(entry, el) {
|
|
316
|
+
const fades = readElementFades(el);
|
|
317
|
+
setOptional(entry, "fadeIn", fades.fadeIn > 0 ? fades.fadeIn : void 0);
|
|
318
|
+
setOptional(entry, "fadeOut", fades.fadeOut > 0 ? fades.fadeOut : void 0);
|
|
319
|
+
}
|
|
320
|
+
function applyMediaMetadataFromElement(entry, el) {
|
|
321
|
+
applyPlaybackMetadataFromElement(entry, el);
|
|
322
|
+
applyAudioMetadataFromElement(entry, el);
|
|
323
|
+
applyFadeMetadataFromElement(entry, el);
|
|
324
|
+
const mediaEl = resolveMediaElement(el);
|
|
325
|
+
if (!mediaEl) return;
|
|
326
|
+
entry.tag = mediaEl.tagName.toLowerCase();
|
|
327
|
+
const src = mediaEl.getAttribute("src");
|
|
328
|
+
if (src) entry.src = src;
|
|
329
|
+
const win = mediaEl.ownerDocument.defaultView ?? window;
|
|
330
|
+
const MediaElementCtor = win.HTMLMediaElement ?? globalThis.HTMLMediaElement;
|
|
331
|
+
if (typeof MediaElementCtor === "undefined" || !(mediaEl instanceof MediaElementCtor)) return;
|
|
332
|
+
const sourceDurationAttr = el.getAttribute("data-source-duration") ?? mediaEl.getAttribute("data-source-duration");
|
|
333
|
+
const sourceDuration = sourceDurationAttr ? parseFloat(sourceDurationAttr) : mediaEl.duration;
|
|
334
|
+
if (Number.isFinite(sourceDuration) && sourceDuration > 0) {
|
|
335
|
+
entry.sourceDuration = sourceDuration;
|
|
336
|
+
}
|
|
337
|
+
const playbackRate = mediaEl.defaultPlaybackRate;
|
|
338
|
+
if (entry.playbackRate == null && Number.isFinite(playbackRate) && playbackRate > 0) {
|
|
339
|
+
entry.playbackRate = clampPlaybackRate(playbackRate);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function getTimelineElementDisplayLabel(input) {
|
|
343
|
+
const label = input.label?.trim();
|
|
344
|
+
if (label) return label;
|
|
345
|
+
const id = input.id?.trim();
|
|
346
|
+
if (id) return id;
|
|
347
|
+
const tag = input.tag?.trim().toLowerCase();
|
|
348
|
+
return tag ? `${tag} clip` : "Timeline clip";
|
|
349
|
+
}
|
|
350
|
+
function getTimelineElementSelector(el) {
|
|
351
|
+
if (isHtmlElement(el) && el.id) return `#${CSS.escape(el.id)}`;
|
|
352
|
+
const compId = el.getAttribute("data-composition-id");
|
|
353
|
+
if (compId) return `[data-composition-id="${CSS.escape(compId)}"]`;
|
|
354
|
+
if (isHtmlElement(el)) {
|
|
355
|
+
const classes = el.className.split(/\s+/).filter(Boolean);
|
|
356
|
+
const firstClass = classes.find((className) => className !== "clip") ?? classes[0];
|
|
357
|
+
if (firstClass) return `.${CSS.escape(firstClass)}`;
|
|
358
|
+
}
|
|
359
|
+
return void 0;
|
|
360
|
+
}
|
|
361
|
+
function getTimelineElementSourceFile(el) {
|
|
362
|
+
const ownerRoot = el.parentElement?.closest("[data-composition-id]");
|
|
363
|
+
return ownerRoot?.getAttribute("data-composition-file") ?? ownerRoot?.getAttribute("data-composition-src") ?? void 0;
|
|
364
|
+
}
|
|
365
|
+
function getTimelineElementSelectorIndex(doc, el, selector) {
|
|
366
|
+
return getSourceScopedSelectorIndex(
|
|
367
|
+
doc,
|
|
368
|
+
el,
|
|
369
|
+
selector,
|
|
370
|
+
getTimelineElementSourceFile(el),
|
|
371
|
+
getTimelineElementSourceFile
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
function buildTimelineElementKey(params) {
|
|
375
|
+
const scope = params.sourceFile ?? "index.html";
|
|
376
|
+
if (params.domId) return `${scope}#${params.domId}`;
|
|
377
|
+
if (params.selector) return `${scope}:${params.selector}:${params.selectorIndex ?? 0}`;
|
|
378
|
+
return `${scope}:${params.id}:${params.fallbackIndex}`;
|
|
379
|
+
}
|
|
380
|
+
function splitTimelineElementKey(key) {
|
|
381
|
+
const hashIndex = key.lastIndexOf("#");
|
|
382
|
+
if (hashIndex < 0) return { sourceFile: null, domId: key };
|
|
383
|
+
return { sourceFile: key.slice(0, hashIndex), domId: key.slice(hashIndex + 1) };
|
|
384
|
+
}
|
|
385
|
+
function buildTimelineElementIdentity(params) {
|
|
386
|
+
const id = params.preferredId?.trim() || buildTimelineElementKey({
|
|
387
|
+
id: params.label,
|
|
388
|
+
fallbackIndex: params.fallbackIndex,
|
|
389
|
+
domId: params.domId,
|
|
390
|
+
selector: params.selector,
|
|
391
|
+
selectorIndex: params.selectorIndex,
|
|
392
|
+
sourceFile: params.sourceFile
|
|
393
|
+
});
|
|
394
|
+
const key = buildTimelineElementKey({
|
|
395
|
+
id,
|
|
396
|
+
fallbackIndex: params.fallbackIndex,
|
|
397
|
+
domId: params.domId,
|
|
398
|
+
selector: params.selector,
|
|
399
|
+
selectorIndex: params.selectorIndex,
|
|
400
|
+
sourceFile: params.sourceFile
|
|
401
|
+
});
|
|
402
|
+
return { id, key };
|
|
403
|
+
}
|
|
404
|
+
function getTimelineElementIdentity(element) {
|
|
405
|
+
return element.key ?? element.id;
|
|
406
|
+
}
|
|
407
|
+
function runtimeAudioId(element) {
|
|
408
|
+
return element.domId || null;
|
|
409
|
+
}
|
|
410
|
+
function deriveTimelineStoreKey(params) {
|
|
411
|
+
if (!params.domId && !params.selector) return void 0;
|
|
412
|
+
return buildTimelineElementKey({ id: "", fallbackIndex: 0, ...params });
|
|
413
|
+
}
|
|
414
|
+
function deriveTimelineStoreKeyForDomId(domId, sourceFile) {
|
|
415
|
+
return deriveTimelineStoreKey({ domId, sourceFile });
|
|
416
|
+
}
|
|
417
|
+
function getTimelineDomNodes(doc) {
|
|
418
|
+
const rootComp = doc.querySelector("[data-composition-id]");
|
|
419
|
+
return Array.from(doc.querySelectorAll("[data-start]")).filter(
|
|
420
|
+
(node) => node !== rootComp && !isTimelineIgnoredElement(node)
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
function numbersNearlyEqual(a, b) {
|
|
424
|
+
return Math.abs(a - b) < 1e-3;
|
|
425
|
+
}
|
|
426
|
+
var MANIFEST_CLIP_ATTRS = [
|
|
427
|
+
["data-start", (clip) => clip.start],
|
|
428
|
+
["data-duration", (clip) => clip.duration],
|
|
429
|
+
["data-track-index", (clip) => clip.track]
|
|
430
|
+
];
|
|
431
|
+
function nodeMatchesClipTag(node, clip) {
|
|
432
|
+
return !clip.tagName || node.tagName.toLowerCase() === clip.tagName.toLowerCase();
|
|
433
|
+
}
|
|
434
|
+
function nodeMatchesManifestClip(node, clip) {
|
|
435
|
+
if (!nodeMatchesClipTag(node, clip)) return false;
|
|
436
|
+
return MANIFEST_CLIP_ATTRS.every(([attr, expected]) => {
|
|
437
|
+
const actual = Number.parseFloat(node.getAttribute(attr) ?? "");
|
|
438
|
+
return !Number.isFinite(actual) || numbersNearlyEqual(actual, expected(clip));
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function nodeInClipScope(node, clip) {
|
|
442
|
+
const scope = clip.compositionAncestors;
|
|
443
|
+
if (!scope) return true;
|
|
444
|
+
const ids = [];
|
|
445
|
+
for (let cursor = node.parentElement; cursor; cursor = cursor.parentElement) {
|
|
446
|
+
const id = cursor.getAttribute("data-composition-id");
|
|
447
|
+
if (id) ids.unshift(id);
|
|
448
|
+
}
|
|
449
|
+
return ids.length === scope.length && ids.every((id, index) => id === scope[index]);
|
|
450
|
+
}
|
|
451
|
+
function findInClipScope(doc, clip, selectors) {
|
|
452
|
+
let lone = null;
|
|
453
|
+
for (const selector of selectors) {
|
|
454
|
+
const nodes = Array.from(doc.querySelectorAll(selector));
|
|
455
|
+
const scoped = nodes.find((node) => nodeInClipScope(node, clip));
|
|
456
|
+
if (scoped) return scoped;
|
|
457
|
+
if (nodes.length === 1) lone ??= nodes[0];
|
|
458
|
+
}
|
|
459
|
+
return lone;
|
|
460
|
+
}
|
|
461
|
+
function previewElementFinder(doc, selector = "[data-hf-id], [id]") {
|
|
462
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
463
|
+
const add = (key, node) => {
|
|
464
|
+
const nodes = byKey.get(key);
|
|
465
|
+
if (nodes) nodes.push(node);
|
|
466
|
+
else byKey.set(key, [node]);
|
|
467
|
+
};
|
|
468
|
+
for (const node of doc.querySelectorAll(selector)) {
|
|
469
|
+
const hfId = node.getAttribute("data-hf-id");
|
|
470
|
+
const id = node.getAttribute("id");
|
|
471
|
+
if (hfId) add(`hf:${hfId}`, node);
|
|
472
|
+
if (id) add(`id:${id}`, node);
|
|
473
|
+
}
|
|
474
|
+
return (target) => {
|
|
475
|
+
const matches = [
|
|
476
|
+
...target.hfId && byKey.get(`hf:${target.hfId}`) || [],
|
|
477
|
+
...byKey.get(`id:${target.domId ?? target.id}`) ?? []
|
|
478
|
+
];
|
|
479
|
+
return matches.find((node) => getTimelineElementSourceFile(node) === target.sourceFile) ?? matches[0] ?? null;
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function findClipElementById(doc, clip) {
|
|
483
|
+
if (!clip.id) return null;
|
|
484
|
+
const first = doc.getElementById(clip.id);
|
|
485
|
+
if (!first || nodeInClipScope(first, clip)) return first;
|
|
486
|
+
return findInClipScope(doc, clip, [`[id="${CSS.escape(clip.id)}"]`]);
|
|
487
|
+
}
|
|
488
|
+
function findTimelineDomNode(doc, clip) {
|
|
489
|
+
if (!clip.id) return null;
|
|
490
|
+
const first = doc.getElementById(clip.id);
|
|
491
|
+
if (first && nodeInClipScope(first, clip)) return first;
|
|
492
|
+
const id = CSS.escape(clip.id);
|
|
493
|
+
const byOtherKeys = [`[data-hf-id="${id}"]`, `[data-composition-id="${id}"]`, `.${id}`];
|
|
494
|
+
return findInClipScope(doc, clip, first ? [`[id="${id}"]`, ...byOtherKeys] : byOtherKeys);
|
|
495
|
+
}
|
|
496
|
+
function findTimelineDomNodeForClip(doc, clip, fallbackIndex, usedNodes = /* @__PURE__ */ new Set(), getCandidates = () => getTimelineDomNodes(doc)) {
|
|
497
|
+
const byIdentity = findTimelineDomNode(doc, clip);
|
|
498
|
+
if (byIdentity && !usedNodes.has(byIdentity) && nodeMatchesClipTag(byIdentity, clip))
|
|
499
|
+
return byIdentity;
|
|
500
|
+
const candidates = getCandidates().filter((node) => !usedNodes.has(node));
|
|
501
|
+
const exact = candidates.find((node) => nodeMatchesManifestClip(node, clip));
|
|
502
|
+
if (exact) return exact;
|
|
503
|
+
const positional = candidates[fallbackIndex];
|
|
504
|
+
return positional && nodeMatchesClipTag(positional, clip) ? positional : null;
|
|
505
|
+
}
|
|
506
|
+
function createTimelineDomNodeResolver(doc) {
|
|
507
|
+
let candidates;
|
|
508
|
+
const usedNodes = /* @__PURE__ */ new Set();
|
|
509
|
+
const getCandidates = () => candidates ??= getTimelineDomNodes(doc);
|
|
510
|
+
return (clip, index) => {
|
|
511
|
+
const node = findTimelineDomNodeForClip(doc, clip, index, usedNodes, getCandidates);
|
|
512
|
+
if (node) usedNodes.add(node);
|
|
513
|
+
return node;
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/utils/projectRouting.ts
|
|
518
|
+
var PROJECT_HASH_PREFIX = "#project/";
|
|
519
|
+
var WINDOWS_DRIVE_PREFIX = /^[a-z]:/i;
|
|
520
|
+
function isValidProjectId(value) {
|
|
521
|
+
return value.length > 0 && value !== "." && value !== ".." && !WINDOWS_DRIVE_PREFIX.test(value) && !value.includes("/") && !value.includes("\\") && !Array.from(value).some((char) => char.charCodeAt(0) < 32);
|
|
522
|
+
}
|
|
523
|
+
function decodeHashProjectId(value) {
|
|
524
|
+
try {
|
|
525
|
+
return decodeURIComponent(value);
|
|
526
|
+
} catch {
|
|
527
|
+
return value;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function normalizeHashParams(params) {
|
|
531
|
+
if (!params) return new URLSearchParams();
|
|
532
|
+
if (params instanceof URLSearchParams) return params;
|
|
533
|
+
const next = new URLSearchParams();
|
|
534
|
+
for (const [key, value] of Object.entries(params)) {
|
|
535
|
+
if (!key || value == null || value === "") continue;
|
|
536
|
+
next.set(key, value);
|
|
537
|
+
}
|
|
538
|
+
return next;
|
|
539
|
+
}
|
|
540
|
+
function encodeProjectId(projectId) {
|
|
541
|
+
if (!isValidProjectId(projectId)) throw new Error("Invalid project ID");
|
|
542
|
+
return encodeURIComponent(projectId);
|
|
543
|
+
}
|
|
544
|
+
function buildProjectHash(projectId, params) {
|
|
545
|
+
const search = normalizeHashParams(params).toString();
|
|
546
|
+
return `${PROJECT_HASH_PREFIX}${encodeProjectId(projectId)}${search ? `?${search}` : ""}`;
|
|
547
|
+
}
|
|
548
|
+
function parseProjectHashRoute(hash) {
|
|
549
|
+
if (!hash.startsWith(PROJECT_HASH_PREFIX)) return null;
|
|
550
|
+
const route = hash.slice(PROJECT_HASH_PREFIX.length);
|
|
551
|
+
const queryIndex = route.indexOf("?");
|
|
552
|
+
const encodedProjectId = queryIndex >= 0 ? route.slice(0, queryIndex) : route;
|
|
553
|
+
if (!encodedProjectId || encodedProjectId.includes("/")) return null;
|
|
554
|
+
const projectId = decodeHashProjectId(encodedProjectId);
|
|
555
|
+
if (!isValidProjectId(projectId)) return null;
|
|
556
|
+
const rawParams = queryIndex >= 0 ? route.slice(queryIndex + 1) : "";
|
|
557
|
+
return {
|
|
558
|
+
projectId,
|
|
559
|
+
params: new URLSearchParams(rawParams)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function parseProjectIdFromHash(hash) {
|
|
563
|
+
return parseProjectHashRoute(hash)?.projectId ?? null;
|
|
564
|
+
}
|
|
565
|
+
function buildProjectApiPath(projectId, suffix = "") {
|
|
566
|
+
const normalizedSuffix = suffix && !suffix.startsWith("/") ? `/${suffix}` : suffix;
|
|
567
|
+
return `/api/projects/${encodeProjectId(projectId)}${normalizedSuffix}`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export {
|
|
571
|
+
buildProjectHash,
|
|
572
|
+
parseProjectHashRoute,
|
|
573
|
+
parseProjectIdFromHash,
|
|
574
|
+
buildProjectApiPath,
|
|
575
|
+
getAdapterDuration,
|
|
576
|
+
getDefaultStaticSeekPlaybackClock,
|
|
577
|
+
releaseStaticSeekCache,
|
|
578
|
+
resolveStaticSeekFallback,
|
|
579
|
+
wrapTimeline,
|
|
580
|
+
withSelectorIndexPass,
|
|
581
|
+
getSourceScopedSelectorIndex,
|
|
582
|
+
LAYER_REVEAL_PRIOR_Z_ATTR,
|
|
583
|
+
LAYER_REVEAL_PRIOR_POSITION_ATTR,
|
|
584
|
+
readLayerRevealPriorZ,
|
|
585
|
+
readTimelineElementZIndex,
|
|
586
|
+
isTimelineIgnoredElement,
|
|
587
|
+
furthestClipEndFromDocument,
|
|
588
|
+
readTimelineDurationFromDocument,
|
|
589
|
+
furthestClipEndFromSource,
|
|
590
|
+
resolveMediaElement,
|
|
591
|
+
readPlaybackStartAttributes,
|
|
592
|
+
playbackStartAttributeForElement,
|
|
593
|
+
isVideoAudible,
|
|
594
|
+
isAudibleVideoNode,
|
|
595
|
+
applyMediaMetadataFromElement,
|
|
596
|
+
getTimelineElementDisplayLabel,
|
|
597
|
+
getTimelineElementSelector,
|
|
598
|
+
getTimelineElementSourceFile,
|
|
599
|
+
getTimelineElementSelectorIndex,
|
|
600
|
+
buildTimelineElementKey,
|
|
601
|
+
splitTimelineElementKey,
|
|
602
|
+
buildTimelineElementIdentity,
|
|
603
|
+
getTimelineElementIdentity,
|
|
604
|
+
runtimeAudioId,
|
|
605
|
+
deriveTimelineStoreKey,
|
|
606
|
+
deriveTimelineStoreKeyForDomId,
|
|
607
|
+
previewElementFinder,
|
|
608
|
+
findClipElementById,
|
|
609
|
+
createTimelineDomNodeResolver
|
|
610
|
+
};
|
|
611
|
+
//# sourceMappingURL=chunk-6LUNLTHR.js.map
|