/**
 * HTML Compiler for Producer
 *
 * Two-phase compilation that guarantees every media element has data-end:
 * 1. Static pass via core's compileTimingAttrs() (data-start + data-duration → data-end)
 * 2. ffprobe resolution for elements without data-duration
 *
 * Also handles sub-compositions referenced via data-composition-src,
 * recursively extracting nested media from sub-sub-compositions.
 */
import { type ResolvedDuration, type UnresolvedElement } from "@hyperframes/core";
import { type VideoElement, type ImageElement, type AudioElement, type AudioVolumeKeyframe } from "@hyperframes/engine";
import type { Page } from "puppeteer-core";
export interface CompiledComposition {
    html: string;
    subCompositions: Map<string, string>;
    videos: VideoElement[];
    audios: AudioElement[];
    images: ImageElement[];
    unresolvedCompositions: UnresolvedElement[];
    /** Assets that resolve outside projectDir. Keys are the path used in HTML, values are absolute filesystem paths. */
    externalAssets: Map<string, string>;
    width: number;
    height: number;
    staticDuration: number;
    renderModeHints: RenderModeHints;
    hasShaderTransitions: boolean;
}
export type RenderModeHintCode = "iframe" | "requestAnimationFrame" | "htmlInCanvas";
export interface RenderModeHint {
    code: RenderModeHintCode;
    message: string;
}
export interface RenderModeHints {
    recommendScreenshot: boolean;
    reasons: RenderModeHint[];
}
export declare function detectRenderModeHints(html: string): RenderModeHints;
export declare function detectShaderTransitionUsage(html: string): boolean;
/**
 * Download external CDN scripts and inline them into the HTML so rendering
 * works without network access (Docker, CI, restricted environments).
 */
export declare function inlineExternalScripts(html: string): Promise<string>;
/**
 * Scan compiled HTML for asset references that resolve outside projectDir.
 * For each, map the normalized in-HTML path to the real filesystem path so
 * the orchestrator can copy them into the compiled output directory.
 *
 * Handles: src/href attributes, CSS url(), inline style url().
 */
export declare function collectExternalAssets(html: string, projectDir: string): {
    html: string;
    externalAssets: Map<string, string>;
};
/**
 * Download any remote `src` URLs on `<video>` and `<audio>` elements into a
 * local subdirectory of `downloadDir`, rewrite the HTML src attributes to
 * relative paths, and return the updated HTML along with a map of
 * `{ relativePath → absoluteLocalPath }` for callers to add to `externalAssets`.
 *
 * Skips URLs that fail to download (warns and preserves the original URL so
 * the browser can still attempt the remote fetch as a fallback).
 *
 * Why: remote S3 sources require Chrome to buffer every video file over the
 * network before `readyState >= 2` (HAVE_CURRENT_DATA). With 10+ large clips
 * this reliably exhausts `pageReadyTimeout`, producing blank black frames for
 * every clip. Localising the sources before the file server starts eliminates
 * the race entirely and keeps the render hermetic.
 */
/** @internal exported for unit testing only */
export declare function localizeRemoteMediaSources(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/**
 * Download any remote `src` URLs on `<img>` elements into a local subdirectory
 * of `downloadDir`, rewrite the HTML src attributes to relative paths, and
 * return a `{ relativePath → absoluteLocalPath }` map for the orchestrator.
 *
 * Why: a composition with remote S3 `<img src>` URLs reaches Chrome unchanged;
 * the readiness check can pass before the image is fully decoded, *and* Chrome
 * may evict decoded pixels mid-render under memory pressure and re-fetch from
 * the remote origin. Either path produces blank-frame flicker. Localising the
 * sources before render eliminates both races — once the file is local,
 * Chrome's image cache is bounded by fast disk reads, not S3 latency, so a
 * mid-render re-fetch lands within a frame instead of flickering. This is the
 * primary fix; frameCapture's `pollImagesReady` is the defense-in-depth layer.
 *
 * Scope: only `<img src>` is localised here. Remote `srcset`,
 * `<picture><source>`, SVG `<image href>`, and CSS `background-image: url()`
 * outside `@font-face` are NOT covered — agent-pipeline compositions emit
 * plain `<img src>`, but those are open follow-ups if other shapes appear.
 *
 * This bites agent-pipeline-generated compositions (astral / daphne /
 * hyperion `multi-v2` outputs) which render directly without going through
 * `hyperframes publish`'s archive-time localize step.
 */
/** @internal exported for unit testing only */
export declare function localizeRemoteImageSources(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/** @internal exported for unit testing only */
export declare function localizeRemoteFontFaces(html: string, downloadDir: string): Promise<{
    html: string;
    remoteMediaAssets: Map<string, string>;
}>;
/**
 * Optional behavior toggles for {@link compileForRender}. All fields are
 * additive; omitting `options` preserves the in-process renderer's defaults.
 */
export interface CompileForRenderOptions {
    /**
     * Threaded through to {@link injectDeterministicFontFaces}. When `true`,
     * any external font fetch failure throws `FontFetchError` instead of
     * silently falling back to system fonts. Distributed `plan()` sets this
     * to `true` so font availability is part of the planDir's content-addressed
     * hash and fetch failures surface as typed non-retryable errors. Default
     * `false` preserves the in-process behavior.
     */
    failClosedFontFetch?: boolean;
    /**
     * When `true`, fonts not resolved by the bundled alias map or Google Fonts
     * are located on the local filesystem, compressed to woff2, and embedded.
     * Default `true` for local renders. Distributed callers pass `false` to
     * prevent host-specific font capture from leaking into the planDir.
     */
    allowSystemFontCapture?: boolean;
    /**
     * Optional persistent cache directory for prep-time animated GIF → WebM
     * transcodes. When omitted, the render's downloadDir is used.
     */
    animatedGifCacheDir?: string;
    /** FFmpeg timeout for animated GIF transcodes. */
    ffmpegProcessTimeout?: number;
}
/**
 * Compile an HTML composition project into a single self-contained HTML string
 * with all media metadata resolved.
 */
export declare function compileForRender(projectDir: string, htmlPath: string, downloadDir: string, options?: CompileForRenderOptions): Promise<CompiledComposition>;
/**
 * Discover media elements from the browser DOM after JavaScript has run.
 * This catches videos/audios whose `src` is set dynamically via JS
 * (e.g. `document.getElementById("pip-video").src = URL`), which the
 * static regex parsers miss because the HTML has `src=""`.
 */
export interface BrowserMediaElement {
    id: string;
    tagName: "video" | "audio";
    src: string;
    start: number;
    end: number;
    duration: number;
    mediaStart: number;
    loop: boolean;
    hasAudio: boolean;
    volume: number;
}
export interface BrowserAudioVolumeAutomation {
    id: string;
    keyframes: AudioVolumeKeyframe[];
}
export declare function discoverMediaFromBrowser(page: Page): Promise<BrowserMediaElement[]>;
export declare function discoverAudioVolumeAutomationFromTimeline(page: Page, audioIds: string[], compositionDuration: number, sampleFps: number): Promise<BrowserAudioVolumeAutomation[]>;
export interface VideoVisibilityWindow {
    videoId: string;
    visibleStart: number;
    visibleEnd: number;
}
/**
 * Seek the GSAP timeline to discover when each video's parent scene is visible.
 * Only processes videos with the data-hf-auto-start sentinel (auto-injected timing).
 */
export declare function discoverVideoVisibilityFromTimeline(page: Page, compositionDuration: number): Promise<VideoVisibilityWindow[]>;
/**
 * Resolve composition durations via Puppeteer by querying window.__timelines.
 * The page must already have the interceptor loaded and timelines registered.
 */
export declare function resolveCompositionDurations(page: Page, unresolved: UnresolvedElement[]): Promise<ResolvedDuration[]>;
/**
 * Re-compile after composition durations are resolved.
 * Injects durations into the HTML and re-parses sub-composition media with proper bounds.
 */
export declare function recompileWithResolutions(compiled: CompiledComposition, resolutions: ResolvedDuration[], projectDir: string, downloadDir: string): Promise<CompiledComposition>;
//# sourceMappingURL=htmlCompiler.d.ts.map