Repository navigation
Fork patch: Discover video window from GSAP opacity - #9
valeriangalliat wants to merge 5 commits into
Conversation
061a8ae to
9f4d89b
Compare
|
🤖 AI agent: sync/2026-10-24 (tags moved the same day): rebased onto upstream First pass on this date, onto The range-diff below is against 1: 31e00ab63 = 1: 47872e64f fix(producer): window auto-start video to GSAP opacity, not .scene
2: 64af6d3e6 ! 2: 37fbe2858 fix(producer): keep auto-start file clock when opacity raises start
@@ packages/producer/src/services/render/stages/probeStage.ts: export async functio
+ video.mediaStart += raised;
+ }
log.info(
-- `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s–${win.visibleEnd.toFixed(2)}s`,
-+ `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s–${win.visibleEnd.toFixed(2)}s → ${video.start.toFixed(2)}s–${video.end.toFixed(2)}s`,
+- `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s`,
++ `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s, placed ${video.start.toFixed(2)}s to ${video.end.toFixed(2)}s`,
);
}
}
3: 1f7e83af4 ! 3: 472398cdf fix(producer): stamp opacity out-point onto compiled video duration
@@ Commit message
Capture still used the file-bounded data-duration, so an in-flow overlay
went display:none at source EOF and the underlay showed through.
- ## packages/engine/src/services/screenshotService.ts ##
-@@ packages/engine/src/services/screenshotService.ts: export async function injectVideoFramesBatch(
- img.style.top = `${offsetTop}px`;
- img.style.right = "auto";
- img.style.bottom = "auto";
-- img.style.width = `${offsetWidth}px`;
-- img.style.height = `${offsetHeight}px`;
-+ // display:none (runtime hid a file-bounded leaf) reports a 0 box.
-+ // Keep the last painted overlay size so a short clip can hold.
-+ if (offsetWidth > 0 && offsetHeight > 0) {
-+ img.style.width = `${offsetWidth}px`;
-+ img.style.height = `${offsetHeight}px`;
-+ }
- }
- img.style.objectFit = computedStyle.objectFit;
- img.style.objectPosition = computedStyle.objectPosition;
-
## packages/producer/src/services/render/stages/probeStage.test.ts ##
@@ packages/producer/src/services/render/stages/probeStage.test.ts: function makeProbeInput(overrides: {
};
@@ packages/producer/src/services/render/stages/probeStage.test.ts: describe("runPr
it("does not let visibility pull start earlier than the host-mapped start", async () => {
## packages/producer/src/services/render/stages/probeStage.ts ##
-@@ packages/producer/src/services/render/stages/probeStage.ts: import {
- isTransientBrowserError,
- probeBeginFrameLiveness,
- } from "@hyperframes/engine";
--import { fpsToNumber } from "@hyperframes/core";
-+import { fpsToNumber, MEDIA_RENDER_ID_ATTR } from "@hyperframes/core";
- import { extractMediaSrcMutations } from "@hyperframes/parsers";
- import type { CompiledComposition } from "../../htmlCompiler.js";
- import {
@@ packages/producer/src/services/render/stages/probeStage.ts: export function hasAutoStartVideos(html: string): boolean {
return document.querySelector("video[data-hf-auto-start]") !== null;
}
@@ packages/producer/src/services/render/stages/probeStage.ts: export async functio
+ mediaStart: video.mediaStart,
+ });
log.info(
- `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s–${win.visibleEnd.toFixed(2)}s → ${video.start.toFixed(2)}s–${video.end.toFixed(2)}s`,
+ `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s, placed ${video.start.toFixed(2)}s to ${video.end.toFixed(2)}s`,
);
}
}
4: 061a8aeef ! 4: f87a27157 refactor(core): stamp opacity slot ends through the timing compiler
@@ packages/core/src/compiler/timingCompiler.ts: import {
export interface UnresolvedElement {
@@ packages/core/src/compiler/timingCompiler.ts: export function extractResolvedMedia(html: string): ResolvedMediaElement[] {
- * For each resolution, replaces the declared duration with the clamped value
- * and recomputes data-end accordingly.
- */
+ return resolved;
+ }
+
+export type VideoSlotEnd = {
+ id: string;
+ end: number;
@@ packages/core/src/compiler/timingCompiler.ts: export function extractResolvedMed
+ */
+export function extendVideoSlotEnds(html: string, slots: readonly VideoSlotEnd[]): string {
+ if (slots.length === 0) return html;
-+ const { masked, restore } = maskInertRegions(html);
+ const byId = new Map(slots.map((slot) => [slot.id, slot]));
-+ const next = masked.replace(/<video\b[^>]*>/gi, (tag) => {
++ return replaceOpeningTags(html, ["video"], (tag) => {
+ const id = getAttr(tag, MEDIA_RENDER_ID_ATTR) ?? getAttr(tag, "id");
+ if (!id) return tag;
+ const slot = byId.get(id);
@@ packages/core/src/compiler/timingCompiler.ts: export function extractResolvedMed
+ String(slot.duration),
+ );
+ });
-+ return restore(next);
+}
+
- export function clampDurations(html: string, clamps: ResolvedDuration[]): string {
- for (const { id, duration } of clamps) {
- html = replaceIdTags(html, id, (tag) => {
+ /**
+ * Clamp existing data-duration and data-end on media elements.
+ * For each resolution, replaces the declared duration with the clamped value
## packages/core/src/index.ts ##
@@ packages/core/src/index.ts: export {
@@ packages/core/src/index.ts: export {
export {
- ## packages/engine/src/services/screenshotService.ts ##
-@@ packages/engine/src/services/screenshotService.ts: export async function injectVideoFramesBatch(
- img.style.top = `${offsetTop}px`;
- img.style.right = "auto";
- img.style.bottom = "auto";
-- // display:none (runtime hid a file-bounded leaf) reports a 0 box.
-- // Keep the last painted overlay size so a short clip can hold.
-- if (offsetWidth > 0 && offsetHeight > 0) {
-- img.style.width = `${offsetWidth}px`;
-- img.style.height = `${offsetHeight}px`;
-- }
-+ img.style.width = `${offsetWidth}px`;
-+ img.style.height = `${offsetHeight}px`;
- }
- img.style.objectFit = computedStyle.objectFit;
- img.style.objectPosition = computedStyle.objectPosition;
-
## packages/producer/src/services/render/stages/probeStage.test.ts ##
@@ packages/producer/src/services/render/stages/probeStage.test.ts: function makeProbeInput(overrides: {
};
@@ packages/producer/src/services/render/stages/probeStage.test.ts: function makePr
## packages/producer/src/services/render/stages/probeStage.ts ##
@@ packages/producer/src/services/render/stages/probeStage.ts: import {
- isTransientBrowserError,
- probeBeginFrameLiveness,
} from "@hyperframes/engine";
--import { fpsToNumber, MEDIA_RENDER_ID_ATTR } from "@hyperframes/core";
-+import {
-+ MEDIA_RENDER_ID_ATTR,
+ import {
+ MEDIA_RENDER_ID_ATTR,
+ extendVideoSlotEnds,
-+ fpsToNumber,
+ fpsToNumber,
+ normalizeRateSpec,
+ readMediaStart,
+ shiftRateLane,
+ sourceTimeAt,
+ timeAtSourceTime,
+ type VideoSlotEnd,
-+} from "@hyperframes/core";
+ } from "@hyperframes/core";
import { extractMediaSrcMutations } from "@hyperframes/parsers";
- import type { CompiledComposition } from "../../htmlCompiler.js";
- import {
+ import type { BrowserMediaElement, CompiledComposition } from "../../htmlCompiler.js";
@@ packages/producer/src/services/render/stages/probeStage.ts: export function hasAutoStartVideos(html: string): boolean {
return document.querySelector("video[data-hf-auto-start]") !== null;
}
@@ packages/producer/src/services/render/stages/probeStage.ts: export async functio
+ slotEnds.push({ id: video.id, end: video.end, duration });
+ }
log.info(
- `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s–${win.visibleEnd.toFixed(2)}s → ${video.start.toFixed(2)}s–${video.end.toFixed(2)}s`,
+ `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s, placed ${video.start.toFixed(2)}s to ${video.end.toFixed(2)}s`,
);
}
}
-: --------- > 5: dc420a073 fix(producer): advance a sped-up clip's file clock by the source time it played |
Edit accuracy: accurate 271 (base branch 271), smooth 168 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
Keep-local: visibility discovery sampled .scene (usually 1), so probe injected the file for the whole host window. Seek via __hf.seek so nested GSAP updates, and bind mediaStart to the first visible frame. Never pull start earlier than host-map. Do not upstream (wizard-emit untimed video).
When visibility moves start later and the clip has a static src and no in-point, add the delta to mediaStart so encode keeps the host clock. Empty-src stays bound to the reveal. Host-mapped start is a floor; visibility owns the out-point. Authored mediaStart stays on the reveal.
Capture still used the file-bounded data-duration, so an in-flow overlay went display:none at source EOF and the underlay showed through.
Move the hold write-back onto extendVideoSlotEnds and drop the injector
0-size special case so probe only applies one {id,end,duration} row.
… it played When opacity raises an auto-start clip's start, the encoder's in-point moved by the timeline delta, as if the clip plays at 1x. Integrate the clip's rate over that delta instead, and shift a rate lane to the new start.
9f4d89b to
dc420a0
Compare
🤖 AI agent: Arcade fork patch. Do not merge.
This draft shows
patch/discover-video-opacityagainstmain, the upstream mirror, so the patch has one page for its diff, its revisions and its reason. Arcade's/hyperframes-updaterebases the branch ontomainand cherry-picks it ontoarcade; this PR is not part of that flow. When a rebuild changes the patch, itsgit range-diffagainst the previoussync/<date>/discover-video-opacitytag is posted as a comment.Why
Visibility discovery sampled the opacity of
.scene, which is usually 1, so the probe injected an auto-start video for its host's whole window. The patch reads the window from the video's own GSAP opacity instead:__hf.seekso nested GSAP updates, and bindsmediaStartto the first visible frame. It never moves the start earlier than the host mapping.srcand no in-point, it adds the delta tomediaStartso encode keeps the host clock.display: noneat source EOF.extendVideoSlotEnds).Known follow-up, unverified: the
mediaStart += deltabump ignores playback rate.Upstream
Kept local. The upstream check errors with
media_missing_data_start, this fallback is undocumented, and RFC heygen-com#2057 was not planned.Drop when
Arcade's generator emits timed video (ARC-13573).
Arcade
ARC-14877 · corpus scenes ArcadeHQ/arcade#10036