/**
 * Per-clip render-time frame-coverage accounting + threshold fail-loud gate.
 *
 * Sibling to #2474's `hasRuntimeInsertedMedia` probe: that PR guarantees the
 * DISCOVERY of runtime-inserted media (so the browser probe launches and
 * reconciles element identity). This module owns the DELIVERY side —
 * for each authored/discovered video clip on the timeline, did the
 * extractor actually produce enough source-video frames to composite the
 * clip's authored `[data-start,data-end]` window? Any clip whose
 * `capturedFrames / expectedFrames` ratio falls below a configurable
 * threshold aborts the render with a `VideoFrameCoverageError` at extract
 * finalization, BEFORE encode produces an MP4 that silently drops the
 * clip's pixels to black.
 *
 * Two field signals defined the failure surface this exists to close
 * (both `#hyperframes-cli-feedback`, both `check`/`snapshot` pass /
 * final MP4 wrong):
 *
 *   • ts=1784139267 · win32/x64 CLI 0.7.58 156s render, 15 injected
 *     videos: several later-injected video clips render BLANK in the
 *     encoded MP4. Injection-count-scaled — 12 injections fail, 4
 *     succeed (workaround: pre-compose into one base timeline). Points
 *     at injector scheduling / worker-seek saturation / extractor
 *     concurrency. Directly covered here — the per-video capture
 *     shortfall lands with `capturedFrames << expectedFrames`.
 *
 *   • ts=1784144554 · darwin/arm64 CLI 0.7.59 3/10 147-clip
 *     composition (130 word-level caption divs): producer left a
 *     subset of authored `[data-start]` div clips permanently visible
 *     in the rendered MP4 while preview/snapshot showed correct
 *     visibility. Authored-clip-count-scaled — 147 clips fails, 3
 *     succeeds. This case runs through `syncTimedElementVisibility`
 *     at runtime and is NOT source-video-frame-shaped; the coverage
 *     gate cannot directly observe it. We surface an
 *     `authoredTimedClipCount` gauge so a 147-clip composition is
 *     visible in telemetry, and leave the per-tick visibility parity
 *     check as follow-up work (a separate runtime observability
 *     channel is required — the extractor doesn't see div visibility).
 *
 * Threshold: default 0.95 (a 5% capture-frame drop is loud); override
 * via `HF_VIDEO_COVERAGE_THRESHOLD` env; disable entirely by setting
 * the env to `0` or a negative number. A threshold of `1` requires
 * exact coverage (no slack for the ffmpeg ±1-frame boundary rounding
 * that legitimately happens on a 29.97 fps timeline).
 */
import { type FpsInput } from "@hyperframes/core";
import { type ExtractedFrames, type VideoElement } from "@hyperframes/engine";
export interface VideoFrameCoverageReport {
    videoId: string;
    clipStart: number;
    clipEnd: number;
    expectedFrames: number;
    capturedFrames: number;
    /** `capturedFrames / expectedFrames`; `1` when `expectedFrames === 0` (nothing to cover). */
    ratio: number;
}
/**
 * Discriminant-based error so callers cross-module (producer server,
 * distributed worker) can identify a coverage-gate failure without
 * `instanceof` (which is fragile across duplicated module instances,
 * see `DrawElementVerificationError` for the same problem).
 */
export interface VideoFrameCoverageErrorDetails {
    readonly hyperframesVideoFrameCoverageError: true;
    readonly threshold: number;
    readonly worst: VideoFrameCoverageReport;
    readonly failedReports: VideoFrameCoverageReport[];
}
export declare class VideoFrameCoverageError extends Error {
    readonly hyperframesVideoFrameCoverageError: true;
    readonly threshold: number;
    readonly worst: VideoFrameCoverageReport;
    readonly failedReports: VideoFrameCoverageReport[];
    constructor(message: string, details: Omit<VideoFrameCoverageErrorDetails, "hyperframesVideoFrameCoverageError">);
}
export declare function isVideoFrameCoverageError(err: unknown): err is VideoFrameCoverageError;
/**
 * Resolve the coverage threshold from `HF_VIDEO_COVERAGE_THRESHOLD` env.
 *
 * Defaults to `0.95`. Values outside `(0, 1]` disable the gate: `0` or
 * negative → off (return `null`); `>1` clamps to `1`. Non-numeric env
 * values fall back to the default (with a caller-side warning if a log
 * is available).
 */
export declare function resolveVideoCoverageThreshold(envValue?: string | undefined): number | null;
/**
 * Count frames in a clip's authored `[start,end)` window. CFR extraction uses
 * FFmpeg's `-vf fps=<fps>` and rounds to the nearest output-frame boundary;
 * VFR extraction uses `-fps_mode cfr -r <fps>` and rounds up. Missing entries
 * retain the fail-closed `ceil` default. Positive sub-frame clips emit one.
 */
export declare function expectedFramesForClip(start: number, end: number, fps: FpsInput, rounding?: "ceil" | "nearest"): number;
export declare function computeVideoFrameCoverage(videos: readonly VideoElement[], extracted: readonly ExtractedFrames[], fps: FpsInput): VideoFrameCoverageReport[];
/**
 * Throws `VideoFrameCoverageError` when any per-clip ratio is below
 * `threshold`. A `null` threshold disables the gate (env opt-out).
 * A clip with `expectedFrames === 0` (0-duration or non-authored) is
 * unconditionally passing so the gate never fires on a degenerate window.
 */
export declare function assertVideoFrameCoverage(reports: readonly VideoFrameCoverageReport[], threshold: number | null): void;
/**
 * Count authored `[data-start]` clip windows in the compiled HTML.
 *
 * Not a fail-loud gate — a raw counter that lands in
 * `RenderExtractionObservability.authoredTimedClipCount` so a 147-clip
 * composition is queryable in telemetry (the ts=1784144554 field signal
 * shape). Runtime `syncTimedElementVisibility` iterates the same set at
 * render time; counting statically here is a coarse proxy — dynamic
 * script-inserted `[data-start]` divs land in `hasRuntimeInsertedMedia`'s
 * probe path (PR #2474), not this static scan.
 */
export declare function countAuthoredTimedClips(html: string): number;
//# sourceMappingURL=videoFrameCoverage.d.ts.map