@mevdragon/vidfarm-devcli 0.5.2 → 0.6.0
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/README.md +3 -3
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +1184 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
- package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +15 -0
- package/dist/src/account-pages-legacy.js +9396 -0
- package/dist/src/account-pages.js +61 -0
- package/dist/src/app.js +14378 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/composition-runtime.js +613 -0
- package/dist/src/config.js +166 -0
- package/dist/src/context.js +447 -0
- package/dist/src/dev-app-legacy.js +739 -0
- package/dist/src/dev-app.js +6 -0
- package/dist/src/domain.js +2 -0
- package/dist/src/editor-chat-history.js +82 -0
- package/dist/src/editor-chat.js +449 -0
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/frontend/debug.js +71 -0
- package/dist/src/frontend/flockposter-cache-store.js +124 -0
- package/dist/src/frontend/homepage-client.js +182 -0
- package/dist/src/frontend/homepage-shared.js +4 -0
- package/dist/src/frontend/homepage-store.js +28 -0
- package/dist/src/frontend/homepage-view.js +547 -0
- package/dist/src/frontend/page-runtime-client.js +132 -0
- package/dist/src/frontend/page-runtime-store.js +9 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/template-editor-chat.js +3960 -0
- package/dist/src/help-page.js +346 -0
- package/dist/src/homepage.js +1235 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/index.js +16 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/page-runtime.js +2 -0
- package/dist/src/page-shell.js +1381 -0
- package/dist/src/primitive-context.js +357 -0
- package/dist/src/primitive-registry.js +2436 -0
- package/dist/src/primitive-sdk.js +4 -0
- package/dist/src/primitives/hyperframes-media.js +108 -0
- package/dist/src/react-page-shell.js +35 -0
- package/dist/src/ready-post-schedule-component.js +1540 -0
- package/dist/src/registry.js +296 -0
- package/dist/src/runtime.js +35 -0
- package/dist/src/services/api-call-history.js +249 -0
- package/dist/src/services/auth.js +152 -0
- package/dist/src/services/billing-pricing.js +39 -0
- package/dist/src/services/billing.js +228 -0
- package/dist/src/services/cast.js +127 -0
- package/dist/src/services/chat-threads.js +92 -0
- package/dist/src/services/composition-sanitize.js +124 -0
- package/dist/src/services/composition-watch.js +79 -0
- package/dist/src/services/fork-access.js +93 -0
- package/dist/src/services/fork-manifest.js +42 -0
- package/dist/src/services/ghostcut.js +179 -0
- package/dist/src/services/hyperframes.js +2307 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-logs.js +197 -0
- package/dist/src/services/jobs.js +136 -0
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +766 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/providers.js +2926 -0
- package/dist/src/services/rate-limits.js +262 -0
- package/dist/src/services/serverless-auth.js +382 -0
- package/dist/src/services/serverless-jobs.js +1082 -0
- package/dist/src/services/serverless-provider-keys.js +409 -0
- package/dist/src/services/serverless-records.js +1385 -0
- package/dist/src/services/serverless-template-configs.js +75 -0
- package/dist/src/services/storage.js +383 -0
- package/dist/src/services/template-certification.js +413 -0
- package/dist/src/services/template-loader.js +99 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-sources.js +1017 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/webhooks.js +62 -0
- package/dist/src/template-editor-pages.js +2576 -0
- package/dist/src/template-editor-shell.js +2840 -0
- package/dist/src/template-sdk.js +4 -0
- package/dist/src/worker.js +17 -0
- package/package.json +6 -4
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
- package/src/assets/SELLING_WITH_HOOKS.md +377 -0
- package/src/assets/SELLING_WITH_VSLS.md +606 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
package/dist/src/cli.js
CHANGED
|
@@ -616,7 +616,7 @@ function printServeBanner(input) {
|
|
|
616
616
|
console.log(line);
|
|
617
617
|
console.log(` server ${input.base}`);
|
|
618
618
|
console.log(` data dir ${input.dataDir}`);
|
|
619
|
-
console.log(` render ${DIM}
|
|
619
|
+
console.log(` render ${DIM}local (in-process HyperFrames render — free, no cloud charge)${RESET}`);
|
|
620
620
|
console.log("");
|
|
621
621
|
console.log(` ${DIM}Agents: edit composition files under ${input.dataDir}/storage — the`);
|
|
622
622
|
console.log(` browser live-reloads on save. Multiple forks can be edited at once.${RESET}`);
|
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
// Runtime script injected into composition iframes so the studio editor can
|
|
2
|
+
// see a `__player` adapter and drive playback. The script lives in src/ so the
|
|
3
|
+
// API lambda can inject it into both draft and fork composition HTML before
|
|
4
|
+
// serving it to the editor iframe.
|
|
5
|
+
//
|
|
6
|
+
// IMPORTANT: this body is executed inside the iframe, NOT in the Node API
|
|
7
|
+
// runtime. Keep it browser-only (no node imports, no top-level await).
|
|
8
|
+
export const COMPOSITION_RUNTIME_SCRIPT_BODY = `
|
|
9
|
+
(() => {
|
|
10
|
+
const root = document.querySelector("[data-composition-id]");
|
|
11
|
+
const duration = Number(root?.getAttribute("data-duration") || 0);
|
|
12
|
+
let time = 0;
|
|
13
|
+
let playing = false;
|
|
14
|
+
let playbackRate = 1;
|
|
15
|
+
let previewMuted = false;
|
|
16
|
+
let raf = 0;
|
|
17
|
+
let last = 0;
|
|
18
|
+
const backingVideo = document.querySelector("[data-vf-playback-source]");
|
|
19
|
+
const initialMuted = new WeakMap();
|
|
20
|
+
if (backingVideo instanceof HTMLMediaElement) {
|
|
21
|
+
initialMuted.set(backingVideo, backingVideo.muted || backingVideo.hasAttribute("muted"));
|
|
22
|
+
}
|
|
23
|
+
let clips = [];
|
|
24
|
+
let activeVideoProxyKey = null;
|
|
25
|
+
let activeVideoProxyNode = null;
|
|
26
|
+
let hasVideoProxies = false;
|
|
27
|
+
const activeMedia = new WeakSet();
|
|
28
|
+
const mediaStateKeys = new WeakMap();
|
|
29
|
+
const visibilityStates = new WeakMap();
|
|
30
|
+
|
|
31
|
+
function clampTime(nextTime) {
|
|
32
|
+
const safe = Number(nextTime);
|
|
33
|
+
if (!Number.isFinite(safe)) return 0;
|
|
34
|
+
return Math.max(0, Math.min(duration, safe));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readNumberAttr(node, names, fallback = 0) {
|
|
38
|
+
for (const name of names) {
|
|
39
|
+
const raw = node.getAttribute(name);
|
|
40
|
+
if (raw !== null) {
|
|
41
|
+
const value = Number(raw);
|
|
42
|
+
return Number.isFinite(value) ? value : fallback;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readTiming(clip) {
|
|
49
|
+
const start = readNumberAttr(clip, ["data-start"], 0);
|
|
50
|
+
const clipDuration = readNumberAttr(clip, ["data-duration"], 0);
|
|
51
|
+
const mediaStart = readNumberAttr(clip, ["data-playback-start", "data-media-start"], 0);
|
|
52
|
+
return { start, duration: clipDuration, end: start + clipDuration, mediaStart };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function refreshClips() {
|
|
56
|
+
clips = Array.from(document.querySelectorAll("[data-start]")).filter((node) => node !== root);
|
|
57
|
+
for (const clip of clips) {
|
|
58
|
+
if (clip instanceof HTMLMediaElement && !initialMuted.has(clip)) {
|
|
59
|
+
initialMuted.set(clip, clip.muted || clip.hasAttribute("muted"));
|
|
60
|
+
}
|
|
61
|
+
if (clip instanceof HTMLMediaElement) {
|
|
62
|
+
clip.preload = "auto";
|
|
63
|
+
if (clip.readyState === 0) {
|
|
64
|
+
try { clip.load(); } catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (backingVideo instanceof HTMLMediaElement) {
|
|
69
|
+
backingVideo.preload = "auto";
|
|
70
|
+
if (backingVideo.readyState === 0) {
|
|
71
|
+
try { backingVideo.load(); } catch {}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
activeVideoProxyKey = null;
|
|
75
|
+
activeVideoProxyNode = null;
|
|
76
|
+
hasVideoProxies = clips.some((clip) => clip instanceof Element && clip.getAttribute("data-vf-timeline-proxy") === "video");
|
|
77
|
+
apply(time, { forceSeek: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function applyMediaState(clip) {
|
|
81
|
+
const volume = Number(clip.getAttribute("data-volume") || 1);
|
|
82
|
+
const nextMuted = previewMuted || initialMuted.get(clip) === true;
|
|
83
|
+
const stateKey = playbackRate + "|" + volume + "|" + nextMuted;
|
|
84
|
+
if (mediaStateKeys.get(clip) === stateKey) return;
|
|
85
|
+
mediaStateKeys.set(clip, stateKey);
|
|
86
|
+
if (Number.isFinite(volume)) clip.volume = Math.max(0, Math.min(1, volume));
|
|
87
|
+
clip.muted = nextMuted;
|
|
88
|
+
try { clip.playbackRate = playbackRate; } catch {}
|
|
89
|
+
try { clip.defaultPlaybackRate = playbackRate; } catch {}
|
|
90
|
+
try {
|
|
91
|
+
if ("preservesPitch" in clip) clip.preservesPitch = true;
|
|
92
|
+
if ("mozPreservesPitch" in clip) clip.mozPreservesPitch = true;
|
|
93
|
+
if ("webkitPreservesPitch" in clip) clip.webkitPreservesPitch = true;
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function setVisibility(node, active) {
|
|
98
|
+
const next = active ? "visible" : "hidden";
|
|
99
|
+
if (visibilityStates.get(node) === next) return;
|
|
100
|
+
visibilityStates.set(node, next);
|
|
101
|
+
node.style.visibility = next;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setPlaybackRate(nextRate) {
|
|
105
|
+
const next = Number(nextRate);
|
|
106
|
+
playbackRate = Number.isFinite(next) && next > 0 ? Math.max(0.05, Math.min(8, next)) : 1;
|
|
107
|
+
for (const clip of clips) {
|
|
108
|
+
if (clip instanceof HTMLMediaElement) applyMediaState(clip);
|
|
109
|
+
}
|
|
110
|
+
if (backingVideo instanceof HTMLMediaElement) applyMediaState(backingVideo);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function setMuted(nextMuted) {
|
|
114
|
+
previewMuted = Boolean(nextMuted);
|
|
115
|
+
for (const clip of clips) {
|
|
116
|
+
if (clip instanceof HTMLMediaElement) applyMediaState(clip);
|
|
117
|
+
}
|
|
118
|
+
if (backingVideo instanceof HTMLMediaElement) applyMediaState(backingVideo);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function clipKey(clip) {
|
|
122
|
+
return clip.getAttribute("data-hf-id") || clip.id || clip.getAttribute("data-label") || "";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ensurePlaying(media) {
|
|
126
|
+
if (!playing) {
|
|
127
|
+
if (!media.paused) media.pause();
|
|
128
|
+
activeMedia.delete(media);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!activeMedia.has(media) || media.paused) {
|
|
132
|
+
activeMedia.add(media);
|
|
133
|
+
void media.play().catch(() => {});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function pauseInactiveMedia(media) {
|
|
138
|
+
activeMedia.delete(media);
|
|
139
|
+
if (!media.paused) media.pause();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function seekMedia(media, target, forceSeek, driftLimit) {
|
|
143
|
+
if (!Number.isFinite(target)) return;
|
|
144
|
+
if (forceSeek || Math.abs(media.currentTime - target) > driftLimit) {
|
|
145
|
+
try { media.currentTime = Math.max(0, target); } catch {}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function apply(nextTime, options = {}) {
|
|
150
|
+
const forceSeek = Boolean(options.forceSeek);
|
|
151
|
+
const fromClock = Boolean(options.fromClock);
|
|
152
|
+
time = clampTime(nextTime);
|
|
153
|
+
let activeVideoProxy = null;
|
|
154
|
+
for (const clip of clips) {
|
|
155
|
+
const timing = readTiming(clip);
|
|
156
|
+
const active = time >= timing.start && time < timing.end;
|
|
157
|
+
if (clip instanceof HTMLElement && !(clip instanceof HTMLAudioElement)) {
|
|
158
|
+
if (clip.getAttribute("data-vf-timeline-proxy") === "video") {
|
|
159
|
+
if (active) activeVideoProxy = clip;
|
|
160
|
+
} else {
|
|
161
|
+
setVisibility(clip, active);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (clip instanceof HTMLMediaElement) {
|
|
165
|
+
applyMediaState(clip);
|
|
166
|
+
if (active) {
|
|
167
|
+
const target = Math.max(0, timing.mediaStart + time - timing.start);
|
|
168
|
+
const enteringClip = !activeMedia.has(clip);
|
|
169
|
+
seekMedia(clip, target, forceSeek || enteringClip, playing ? 0.75 : 0.08);
|
|
170
|
+
if (playing) {
|
|
171
|
+
ensurePlaying(clip);
|
|
172
|
+
} else {
|
|
173
|
+
pauseInactiveMedia(clip);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
pauseInactiveMedia(clip);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (backingVideo instanceof HTMLVideoElement) {
|
|
181
|
+
applyMediaState(backingVideo);
|
|
182
|
+
const shouldShowBackingVideo = !hasVideoProxies || Boolean(activeVideoProxy);
|
|
183
|
+
backingVideo.style.visibility = shouldShowBackingVideo ? "visible" : "hidden";
|
|
184
|
+
if (activeVideoProxy) {
|
|
185
|
+
const nextProxyKey = clipKey(activeVideoProxy);
|
|
186
|
+
const proxyChanged = nextProxyKey !== activeVideoProxyKey;
|
|
187
|
+
activeVideoProxyKey = nextProxyKey;
|
|
188
|
+
activeVideoProxyNode = activeVideoProxy;
|
|
189
|
+
const timing = readTiming(activeVideoProxy);
|
|
190
|
+
const target = Math.max(0, timing.mediaStart + time - timing.start);
|
|
191
|
+
const driftLimit = playing ? (fromClock ? 0.75 : 2.0) : 0.08;
|
|
192
|
+
const drifting = !backingVideo.seeking && Math.abs(backingVideo.currentTime - target) > driftLimit;
|
|
193
|
+
if (forceSeek || proxyChanged || !playing || drifting) {
|
|
194
|
+
seekMedia(backingVideo, target, true, 0.08);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
activeVideoProxyKey = null;
|
|
198
|
+
activeVideoProxyNode = null;
|
|
199
|
+
if (!hasVideoProxies && !fromClock) seekMedia(backingVideo, time, forceSeek, playing ? 1.25 : 0.08);
|
|
200
|
+
}
|
|
201
|
+
if (playing && shouldShowBackingVideo) {
|
|
202
|
+
ensurePlaying(backingVideo);
|
|
203
|
+
} else {
|
|
204
|
+
pauseInactiveMedia(backingVideo);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function tick(now) {
|
|
210
|
+
if (!playing) return;
|
|
211
|
+
if (!last) last = now;
|
|
212
|
+
const delta = ((now - last) / 1000) * playbackRate;
|
|
213
|
+
const next = time + delta;
|
|
214
|
+
last = now;
|
|
215
|
+
apply(next >= duration ? duration : next, { fromClock: true });
|
|
216
|
+
if (time >= duration) {
|
|
217
|
+
playing = false;
|
|
218
|
+
for (const clip of clips) {
|
|
219
|
+
if (clip instanceof HTMLMediaElement) pauseInactiveMedia(clip);
|
|
220
|
+
}
|
|
221
|
+
if (backingVideo instanceof HTMLMediaElement) pauseInactiveMedia(backingVideo);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
raf = requestAnimationFrame(tick);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
window.__player = {
|
|
228
|
+
play() {
|
|
229
|
+
if (playing) return;
|
|
230
|
+
if (time >= duration) time = 0;
|
|
231
|
+
playing = true;
|
|
232
|
+
last = 0;
|
|
233
|
+
apply(time, { forceSeek: true });
|
|
234
|
+
cancelAnimationFrame(raf);
|
|
235
|
+
raf = requestAnimationFrame(tick);
|
|
236
|
+
},
|
|
237
|
+
pause() {
|
|
238
|
+
playing = false;
|
|
239
|
+
cancelAnimationFrame(raf);
|
|
240
|
+
for (const clip of clips) {
|
|
241
|
+
if (clip instanceof HTMLMediaElement) pauseInactiveMedia(clip);
|
|
242
|
+
}
|
|
243
|
+
if (backingVideo instanceof HTMLMediaElement) pauseInactiveMedia(backingVideo);
|
|
244
|
+
},
|
|
245
|
+
seek(nextTime, options = {}) {
|
|
246
|
+
const shouldKeepPlaying = playing || options.keepPlaying === true;
|
|
247
|
+
playing = shouldKeepPlaying;
|
|
248
|
+
if (!shouldKeepPlaying) cancelAnimationFrame(raf);
|
|
249
|
+
apply(nextTime, { forceSeek: true });
|
|
250
|
+
if (shouldKeepPlaying) {
|
|
251
|
+
last = 0;
|
|
252
|
+
cancelAnimationFrame(raf);
|
|
253
|
+
raf = requestAnimationFrame(tick);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
getTime() {
|
|
257
|
+
return time;
|
|
258
|
+
},
|
|
259
|
+
getDuration() {
|
|
260
|
+
return duration;
|
|
261
|
+
},
|
|
262
|
+
isPlaying() {
|
|
263
|
+
return playing;
|
|
264
|
+
},
|
|
265
|
+
setPlaybackRate,
|
|
266
|
+
getPlaybackRate() {
|
|
267
|
+
return playbackRate;
|
|
268
|
+
},
|
|
269
|
+
setMuted,
|
|
270
|
+
refreshClips
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
refreshClips();
|
|
274
|
+
if (backingVideo instanceof HTMLMediaElement) {
|
|
275
|
+
backingVideo.addEventListener("loadedmetadata", () => {
|
|
276
|
+
if (!playing) apply(time, { forceSeek: true });
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Cache the parent frame's origin from the first accepted control message
|
|
281
|
+
// and pin all outbound postMessages to it. Falls back to document.referrer's
|
|
282
|
+
// origin so drag/select events still reach the editor before any control
|
|
283
|
+
// message has been received. Without pinning, sending "*" leaks preview
|
|
284
|
+
// frame coordinates and layer ids to any embedding third-party page.
|
|
285
|
+
let trustedParentOrigin = null;
|
|
286
|
+
try {
|
|
287
|
+
if (document.referrer) {
|
|
288
|
+
trustedParentOrigin = new URL(document.referrer).origin;
|
|
289
|
+
}
|
|
290
|
+
} catch {}
|
|
291
|
+
function parentTargetOrigin() {
|
|
292
|
+
return trustedParentOrigin || "*";
|
|
293
|
+
}
|
|
294
|
+
function postToParent(payload) {
|
|
295
|
+
if (!window.parent || window.parent === window) return;
|
|
296
|
+
try {
|
|
297
|
+
window.parent.postMessage(payload, parentTargetOrigin());
|
|
298
|
+
} catch {}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
window.addEventListener("message", (event) => {
|
|
302
|
+
// Only accept messages from the direct parent frame. Blocks sibling
|
|
303
|
+
// iframes and popups that could otherwise spoof play/pause/seek by
|
|
304
|
+
// forging data.source.
|
|
305
|
+
if (event.source !== window.parent) return;
|
|
306
|
+
const data = event.data || {};
|
|
307
|
+
if (data.type !== "control" && data.source !== "hf-parent") return;
|
|
308
|
+
if (event.origin && event.origin !== "null") {
|
|
309
|
+
trustedParentOrigin = event.origin;
|
|
310
|
+
}
|
|
311
|
+
if (data.action === "play") {
|
|
312
|
+
window.__player.play();
|
|
313
|
+
}
|
|
314
|
+
if (data.action === "pause" || data.action === "stop-media") {
|
|
315
|
+
window.__player.pause();
|
|
316
|
+
}
|
|
317
|
+
if (data.action === "seek") {
|
|
318
|
+
const nextTime = Number.isFinite(Number(data.time))
|
|
319
|
+
? Number(data.time)
|
|
320
|
+
: Number.isFinite(Number(data.frame))
|
|
321
|
+
? Number(data.frame) / 30
|
|
322
|
+
: Number.isFinite(Number(data.payload?.time))
|
|
323
|
+
? Number(data.payload.time)
|
|
324
|
+
: time;
|
|
325
|
+
window.__player.seek(nextTime);
|
|
326
|
+
}
|
|
327
|
+
if (data.action === "set-playback-rate") {
|
|
328
|
+
setPlaybackRate(data.playbackRate ?? data.payload?.playbackRate);
|
|
329
|
+
}
|
|
330
|
+
if (data.action === "set-muted") {
|
|
331
|
+
setMuted(data.muted ?? data.payload?.muted);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
function selectTargetFromEvent(event) {
|
|
336
|
+
const handle = event.target instanceof Element
|
|
337
|
+
? event.target.closest("[data-vf-frame-handle]")
|
|
338
|
+
: null;
|
|
339
|
+
const handleTargetId = handle?.getAttribute("data-vf-target-id") || null;
|
|
340
|
+
if (handleTargetId) {
|
|
341
|
+
return document.querySelector("[data-hf-id='" + CSS.escape(handleTargetId) + "'], #" + CSS.escape(handleTargetId));
|
|
342
|
+
}
|
|
343
|
+
return event.target instanceof Element
|
|
344
|
+
? event.target.closest("[data-start]")
|
|
345
|
+
: null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function postSelectedLayer(event) {
|
|
349
|
+
const target = selectTargetFromEvent(event);
|
|
350
|
+
const rawId = target?.getAttribute("data-hf-id") || target?.id || null;
|
|
351
|
+
const id = rawId === "source-video" ? null : rawId;
|
|
352
|
+
postToParent({
|
|
353
|
+
source: "vidfarm-composition",
|
|
354
|
+
action: "select-layer",
|
|
355
|
+
id
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function postPreviewContextMenu(event) {
|
|
360
|
+
event.preventDefault();
|
|
361
|
+
const root = document.querySelector("[data-composition-id]");
|
|
362
|
+
const rect = root instanceof HTMLElement ? root.getBoundingClientRect() : null;
|
|
363
|
+
const target = selectTargetFromEvent(event);
|
|
364
|
+
const rawId = target?.getAttribute("data-hf-id") || target?.id || null;
|
|
365
|
+
const id = rawId === "source-video" ? null : rawId;
|
|
366
|
+
const x = rect ? ((event.clientX - rect.left) / Math.max(1, rect.width)) * 100 : 12;
|
|
367
|
+
const y = rect ? ((event.clientY - rect.top) / Math.max(1, rect.height)) * 100 : 70;
|
|
368
|
+
postToParent({
|
|
369
|
+
source: "vidfarm-composition",
|
|
370
|
+
action: "preview-context-menu",
|
|
371
|
+
id,
|
|
372
|
+
clientX: event.clientX,
|
|
373
|
+
clientY: event.clientY,
|
|
374
|
+
time,
|
|
375
|
+
frame: {
|
|
376
|
+
x: Math.max(0, Math.min(95, x)),
|
|
377
|
+
y: Math.max(0, Math.min(95, y)),
|
|
378
|
+
width: 42,
|
|
379
|
+
height: 12
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function frameFromNode(node) {
|
|
385
|
+
return {
|
|
386
|
+
x: Number.parseFloat(node.style.left || "0") || 0,
|
|
387
|
+
y: Number.parseFloat(node.style.top || "0") || 0,
|
|
388
|
+
width: Number.parseFloat(node.style.width || "100") || 100,
|
|
389
|
+
height: Number.parseFloat(node.style.height || "100") || 100,
|
|
390
|
+
// NaN (not 18) when the wrapper has no inline font-size — text layers keep
|
|
391
|
+
// their real size on inner spans, so fabricating 18 here would make a plain
|
|
392
|
+
// MOVE collapse the text to 18px. Downstream apply/stage all guard
|
|
393
|
+
// Number.isFinite, so an absent size is simply left untouched on a move.
|
|
394
|
+
fontSize: Number.parseFloat(node.style.fontSize)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isVisualFrameTarget(node) {
|
|
399
|
+
if (!(node instanceof HTMLElement) || node instanceof HTMLAudioElement) return false;
|
|
400
|
+
const id = node.getAttribute("data-hf-id") || node.id || "";
|
|
401
|
+
return id !== "source-video";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function isTextFrameTarget(node) {
|
|
405
|
+
return node instanceof HTMLElement &&
|
|
406
|
+
!(node instanceof HTMLAudioElement) &&
|
|
407
|
+
!(node instanceof HTMLVideoElement) &&
|
|
408
|
+
!(node instanceof HTMLImageElement);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function syncRuntimeFrameControls(node) {
|
|
412
|
+
if (!isVisualFrameTarget(node)) return;
|
|
413
|
+
const root = document.querySelector("[data-composition-id]");
|
|
414
|
+
if (!(root instanceof HTMLElement)) return;
|
|
415
|
+
const targetId = node.getAttribute("data-hf-id") || node.id || "";
|
|
416
|
+
if (!targetId) return;
|
|
417
|
+
let ring = document.querySelector("[data-vf-selection-ring='true']");
|
|
418
|
+
if (!(ring instanceof HTMLElement)) {
|
|
419
|
+
ring = document.createElement("div");
|
|
420
|
+
ring.setAttribute("data-vf-selection-ring", "true");
|
|
421
|
+
root.append(ring);
|
|
422
|
+
}
|
|
423
|
+
ring.style.display = "block";
|
|
424
|
+
ring.style.position = "absolute";
|
|
425
|
+
ring.style.left = node.style.left || "0%";
|
|
426
|
+
ring.style.top = node.style.top || "0%";
|
|
427
|
+
ring.style.width = node.style.width || "100%";
|
|
428
|
+
ring.style.height = node.style.height || "100%";
|
|
429
|
+
ring.style.borderRadius = node.style.borderRadius || "0";
|
|
430
|
+
ring.style.zIndex = "9999";
|
|
431
|
+
ring.style.pointerEvents = "auto";
|
|
432
|
+
ring.style.cursor = "grab";
|
|
433
|
+
ring.style.border = "1px dashed #F2CD46";
|
|
434
|
+
ring.style.boxShadow = "0 0 0 2px #118df2";
|
|
435
|
+
ring.setAttribute("data-vf-frame-handle", "move");
|
|
436
|
+
ring.setAttribute("data-vf-target-id", targetId);
|
|
437
|
+
for (const handle of Array.from(ring.querySelectorAll("[data-vf-frame-handle]"))) {
|
|
438
|
+
if (handle !== ring) handle.remove();
|
|
439
|
+
}
|
|
440
|
+
for (const [mode, cssText] of [
|
|
441
|
+
["top-left", "left:-7px;top:-7px;cursor:nwse-resize;"],
|
|
442
|
+
["top", "top:-7px;left:50%;transform:translateX(-50%);cursor:ns-resize;"],
|
|
443
|
+
["top-right", "right:-7px;top:-7px;cursor:nesw-resize;"],
|
|
444
|
+
["right", "right:-7px;top:50%;transform:translateY(-50%);cursor:ew-resize;"],
|
|
445
|
+
["bottom-right", "right:-7px;bottom:-7px;cursor:nwse-resize;"],
|
|
446
|
+
["bottom", "bottom:-7px;left:50%;transform:translateX(-50%);cursor:ns-resize;"],
|
|
447
|
+
["bottom-left", "left:-7px;bottom:-7px;cursor:nesw-resize;"],
|
|
448
|
+
["left", "left:-7px;top:50%;transform:translateY(-50%);cursor:ew-resize;"]
|
|
449
|
+
]) {
|
|
450
|
+
const handle = document.createElement("span");
|
|
451
|
+
handle.setAttribute("data-vf-frame-handle", mode);
|
|
452
|
+
handle.setAttribute("data-vf-target-id", targetId);
|
|
453
|
+
handle.style.cssText = [
|
|
454
|
+
"position:absolute",
|
|
455
|
+
"display:block",
|
|
456
|
+
"width:14px",
|
|
457
|
+
"height:14px",
|
|
458
|
+
"box-sizing:border-box",
|
|
459
|
+
"border-radius:999px",
|
|
460
|
+
"border:2px solid #0b0d0a",
|
|
461
|
+
"background:#F2CD46",
|
|
462
|
+
"box-shadow:0 0 0 2px rgba(242,205,70,0.32),0 1px 4px rgba(0,0,0,0.38)",
|
|
463
|
+
"pointer-events:auto",
|
|
464
|
+
"touch-action:none",
|
|
465
|
+
cssText
|
|
466
|
+
].join(";");
|
|
467
|
+
ring.append(handle);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function applyFrameToNode(node, frame) {
|
|
472
|
+
node.style.left = frame.x + "%";
|
|
473
|
+
node.style.top = frame.y + "%";
|
|
474
|
+
node.style.width = frame.width + "%";
|
|
475
|
+
node.style.height = frame.height + "%";
|
|
476
|
+
if (Number.isFinite(frame.fontSize)) {
|
|
477
|
+
node.style.fontSize = frame.fontSize + "px";
|
|
478
|
+
}
|
|
479
|
+
syncRuntimeFrameControls(node);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function postFrameDraft(node, phase) {
|
|
483
|
+
const id = node.getAttribute("data-hf-id") || node.id || null;
|
|
484
|
+
if (!id) return;
|
|
485
|
+
postToParent({
|
|
486
|
+
source: "vidfarm-composition",
|
|
487
|
+
action: "visual-frame-draft",
|
|
488
|
+
phase,
|
|
489
|
+
id,
|
|
490
|
+
frame: frameFromNode(node)
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let visualFrameDrag = null;
|
|
495
|
+
|
|
496
|
+
function beginVisualFrameDrag(event) {
|
|
497
|
+
if (event.button !== 0) return;
|
|
498
|
+
const handle = event.target instanceof Element
|
|
499
|
+
? event.target.closest("[data-vf-frame-handle]")
|
|
500
|
+
: null;
|
|
501
|
+
const handleTargetId = handle?.getAttribute("data-vf-target-id") || null;
|
|
502
|
+
const target = handleTargetId
|
|
503
|
+
? document.querySelector("[data-hf-id='" + CSS.escape(handleTargetId) + "'], #" + CSS.escape(handleTargetId))
|
|
504
|
+
: event.target instanceof Element
|
|
505
|
+
? event.target.closest("[data-start]")
|
|
506
|
+
: null;
|
|
507
|
+
if (!isVisualFrameTarget(target)) return;
|
|
508
|
+
const root = document.querySelector("[data-composition-id]");
|
|
509
|
+
if (!(root instanceof HTMLElement)) return;
|
|
510
|
+
const rootRect = root.getBoundingClientRect();
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
event.stopPropagation();
|
|
513
|
+
postSelectedLayer(event);
|
|
514
|
+
visualFrameDrag = {
|
|
515
|
+
node: target,
|
|
516
|
+
mode: handle?.getAttribute("data-vf-frame-handle") || "move",
|
|
517
|
+
startX: event.clientX,
|
|
518
|
+
startY: event.clientY,
|
|
519
|
+
rootWidth: Math.max(1, rootRect.width),
|
|
520
|
+
rootHeight: Math.max(1, rootRect.height),
|
|
521
|
+
frame: frameFromNode(target),
|
|
522
|
+
moved: false
|
|
523
|
+
};
|
|
524
|
+
syncRuntimeFrameControls(target);
|
|
525
|
+
target.style.cursor = "grabbing";
|
|
526
|
+
postFrameDraft(target, "start");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function updateVisualFrameDrag(event) {
|
|
530
|
+
if (!visualFrameDrag) return;
|
|
531
|
+
const drag = visualFrameDrag;
|
|
532
|
+
const dx = ((event.clientX - drag.startX) / drag.rootWidth) * 100;
|
|
533
|
+
const dy = ((event.clientY - drag.startY) / drag.rootHeight) * 100;
|
|
534
|
+
if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 2) return;
|
|
535
|
+
drag.moved = true;
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
const next = { ...drag.frame };
|
|
538
|
+
if (drag.mode === "move") {
|
|
539
|
+
next.x = Math.max(0, Math.min(100 - drag.frame.width, drag.frame.x + dx));
|
|
540
|
+
next.y = Math.max(0, Math.min(100 - drag.frame.height, drag.frame.y + dy));
|
|
541
|
+
}
|
|
542
|
+
if (drag.mode === "left" || drag.mode === "top-left" || drag.mode === "bottom-left") {
|
|
543
|
+
const nextX = Math.max(0, Math.min(drag.frame.x + drag.frame.width - 1, drag.frame.x + dx));
|
|
544
|
+
next.x = nextX;
|
|
545
|
+
next.width = Math.max(1, Math.min(100 - nextX, drag.frame.width + (drag.frame.x - nextX)));
|
|
546
|
+
}
|
|
547
|
+
if (drag.mode === "right" || drag.mode === "top-right" || drag.mode === "bottom-right") {
|
|
548
|
+
next.width = Math.max(1, Math.min(100 - drag.frame.x, drag.frame.width + dx));
|
|
549
|
+
}
|
|
550
|
+
if (drag.mode === "top" || drag.mode === "top-left" || drag.mode === "top-right") {
|
|
551
|
+
const nextY = Math.max(0, Math.min(drag.frame.y + drag.frame.height - 1, drag.frame.y + dy));
|
|
552
|
+
next.y = nextY;
|
|
553
|
+
next.height = Math.max(1, Math.min(100 - nextY, drag.frame.height + (drag.frame.y - nextY)));
|
|
554
|
+
}
|
|
555
|
+
if (drag.mode === "bottom" || drag.mode === "bottom-left" || drag.mode === "bottom-right") {
|
|
556
|
+
next.height = Math.max(1, Math.min(100 - drag.frame.y, drag.frame.height + dy));
|
|
557
|
+
}
|
|
558
|
+
if (
|
|
559
|
+
isTextFrameTarget(drag.node) &&
|
|
560
|
+
["top-left", "top-right", "bottom-left", "bottom-right"].includes(drag.mode)
|
|
561
|
+
) {
|
|
562
|
+
const widthScale = next.width / Math.max(1, drag.frame.width);
|
|
563
|
+
const heightScale = next.height / Math.max(1, drag.frame.height);
|
|
564
|
+
const scale = Math.sqrt(Math.max(0.05, widthScale * heightScale));
|
|
565
|
+
next.fontSize = Math.max(8, Math.min(180, (drag.frame.fontSize || 18) * scale));
|
|
566
|
+
}
|
|
567
|
+
applyFrameToNode(drag.node, next);
|
|
568
|
+
postFrameDraft(drag.node, "move");
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function endVisualFrameDrag() {
|
|
572
|
+
if (!visualFrameDrag) return;
|
|
573
|
+
visualFrameDrag.node.style.cursor = "";
|
|
574
|
+
postFrameDraft(visualFrameDrag.node, visualFrameDrag.moved ? "end" : "tap");
|
|
575
|
+
visualFrameDrag = null;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function hoverVisualFrameTarget(event) {
|
|
579
|
+
const handle = event.target instanceof Element
|
|
580
|
+
? event.target.closest("[data-vf-frame-handle]")
|
|
581
|
+
: null;
|
|
582
|
+
const handleTargetId = handle?.getAttribute("data-vf-target-id") || null;
|
|
583
|
+
const target = handleTargetId
|
|
584
|
+
? document.querySelector("[data-hf-id='" + CSS.escape(handleTargetId) + "'], #" + CSS.escape(handleTargetId))
|
|
585
|
+
: event.target instanceof Element
|
|
586
|
+
? event.target.closest("[data-start]")
|
|
587
|
+
: null;
|
|
588
|
+
if (!isVisualFrameTarget(target)) return;
|
|
589
|
+
target.style.cursor = "grab";
|
|
590
|
+
syncRuntimeFrameControls(target);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
document.addEventListener("pointerover", hoverVisualFrameTarget, true);
|
|
594
|
+
document.addEventListener("pointerdown", beginVisualFrameDrag, true);
|
|
595
|
+
document.addEventListener("pointermove", updateVisualFrameDrag, { passive: false });
|
|
596
|
+
document.addEventListener("pointerup", endVisualFrameDrag);
|
|
597
|
+
document.addEventListener("pointercancel", endVisualFrameDrag);
|
|
598
|
+
document.addEventListener("pointerdown", postSelectedLayer);
|
|
599
|
+
document.addEventListener("click", postSelectedLayer);
|
|
600
|
+
document.addEventListener("contextmenu", postPreviewContextMenu, true);
|
|
601
|
+
|
|
602
|
+
try {
|
|
603
|
+
console.info("[hf-runtime] ready", {
|
|
604
|
+
compositionId: root?.getAttribute("data-composition-id"),
|
|
605
|
+
duration,
|
|
606
|
+
clipCount: document.querySelectorAll("[data-start]").length
|
|
607
|
+
});
|
|
608
|
+
} catch {}
|
|
609
|
+
|
|
610
|
+
apply(0);
|
|
611
|
+
})();
|
|
612
|
+
`;
|
|
613
|
+
//# sourceMappingURL=composition-runtime.js.map
|