/** Per-character verdict from the vision client for one scene frame. */ export interface ConsistencyVisionAssessment { /** True iff the on-screen character matches the reference face/hair AND costume/colours. */ match: boolean; /** Human-readable differences (empty when matching). */ issues: string[]; /** True when the vision client suspects an unexpected extra figure in the frame. */ extraFigure?: boolean; } /** Input passed to the vision client for a single (character, scene-frame) pair. */ export interface ConsistencyVisionInput { /** Canonical display name of the character being checked. */ characterName: string; /** Canonical reference (the character's GB reference URL / referenceAssets[0]); may be absent. */ referencePath?: string; /** The character's locked visual descriptor. */ description?: string; /** The character's locked costume/wardrobe (the dhoti-colour anchor). */ costume?: string; /** Path to the extracted scene mid-frame to assess. */ framePath: string; /** Scene index (for context / prompts). */ sceneIndex: number; } /** Injectable vision client — the default reuses the Gemini infra; tests inject a fake. */ export interface ConsistencyVisionClient { assess(input: ConsistencyVisionInput): Promise; } /** Per-frame verdict from the frame-inspection client for one sampled clip frame. */ export interface ConsistencyFrameInspection { /** * Things visible in the sampled frame that are NOT present in the keyframe — * a new object/garment that materialized mid-clip (a floating garland, a * flame, a veil pulled over the head, new cloth/petals). Empty when clean. */ appearingElements: string[]; /** * Anatomy/duplication errors in the sampled frame — a person with more than * two arms/hands, an extra/duplicate limb, or a duplicated character. Empty * when clean. */ anatomyIssues: string[]; } /** Input passed to the frame-inspection client for one sampled clip frame. */ export interface ConsistencyFrameInspectionInput { /** The scene's i2v START IMAGE (keyframe) the frame is compared against. */ keyframePath: string; /** Path to the extracted sampled frame. */ framePath: string; /** Scene index (for context / prompts). */ sceneIndex: number; /** Fraction (0–1) of the clip at which this frame was sampled. */ fraction: number; } /** * Injectable frame-inspection client — compares one sampled frame against the * scene keyframe and reports appearing elements + anatomy issues. The default * reuses the Gemini two-image transport; tests inject a fake per-frame verdict. */ export interface ConsistencyFrameInspectionClient { inspect(input: ConsistencyFrameInspectionInput): Promise; } /** Per-character result inside a scene. */ export interface ConsistencyCharacterResult { name: string; match: boolean; issues: string[]; } /** Per-scene audit result. */ export interface ConsistencySceneResult { sceneIndex: number; /** True when a representative frame was actually extracted + assessed. */ frameChecked: boolean; characters: ConsistencyCharacterResult[]; borderDetected: boolean; extraFigureSuspected: boolean; /** * Elements that materialized mid-clip and were NOT in the scene keyframe, * deduped across the sampled frames and each tagged with the approx frame * fraction (e.g. "t≈0.6: floating garland"). Empty when none / no keyframe. */ appearingElements: string[]; /** * Anatomy/duplication errors (third hand, extra limb, duplicated person), * deduped across the sampled frames and each tagged with the approx frame * fraction (e.g. "t≈0.4: third hand on the figure"). Empty when none. */ anatomyIssues: string[]; /** Number of frames actually sampled across the clip for mid-clip inspection. */ framesSampled: number; } /** The full structured audit report (also persisted to artifacts/consistency-audit.json). */ export interface ConsistencyAuditReport { projectSlug: string; /** * False when any checked character mismatches OR any checked frame has a * border OR any scene has appearing-elements / anatomy issues. */ ok: boolean; generatedAt: string; scenes: ConsistencySceneResult[]; /** * Flat, operator-facing list of every problem found (drift + border + * extra-figure + appearing-element + anatomy). */ findings: string[]; } /** Already-sampled per-edge pixel statistics for the deterministic border check. */ export interface EdgePixelStats { /** Mean luma (0–255) of each edge strip. */ topMeanLuma: number; bottomMeanLuma: number; leftMeanLuma: number; rightMeanLuma: number; /** * Uniformity (0–1) of each edge strip — how flat/featureless it is. A true * letterbox/pillarbox margin is both DARK and near-perfectly uniform; a dark * but textured edge (e.g. a night sky) is dark but NOT uniform. */ topUniformity: number; bottomUniformity: number; leftUniformity: number; rightUniformity: number; } /** * Pure border/letterbox detector over already-sampled edge stats. A border is * detected when EITHER pair of opposite edges (top+bottom, i.e. letterbox, OR * left+right, i.e. pillarbox) are both dark, near-uniform margins. Requiring a * matched PAIR (not a lone edge) avoids flagging a single dark-but-textured edge * like a night sky. */ export declare function detectBorderFromEdgeStats(stats: EdgePixelStats): boolean; /** * True when a Gemini/Google vision key is configured. Used to gate the default * (network) audit — the pipeline integration only runs the audit when a key * exists. Reads the env directly (not the cached key pool) so it stays * deterministic and test-friendly. */ export declare function hasVisionKey(env?: NodeJS.ProcessEnv): boolean; /** * Extract ONE frame from a video to `framePath` (returns it). `fraction` (0–1) * is the position along the clip to sample; it defaults to 0.5 (the midpoint) so * existing single-frame callers/tests are unaffected. */ export type FrameExtractor = (videoPath: string, framePath: string, fraction?: number) => Promise; /** Sample edge-pixel stats from an already-extracted frame image. */ export type EdgeStatsSampler = (framePath: string) => Promise; /** Default number of frames sampled across a clip for mid-clip inspection. */ export declare const DEFAULT_FRAME_SAMPLE_COUNT = 5; export interface AuditProjectConsistencyOptions { /** Vision client (REQUIRED for tests; defaults to the Gemini-backed client). */ visionClient?: ConsistencyVisionClient; /** * Frame-inspection client for the multi-frame appearing-element / anatomy * check (REQUIRED for tests; defaults to the Gemini two-image client). */ frameInspectionClient?: ConsistencyFrameInspectionClient; /** Frame extractor (defaults to the ffmpeg-backed {@link extractMidFrame}). */ frameExtractor?: FrameExtractor; /** Edge-stats sampler (defaults to the ffmpeg-backed {@link sampleEdgePixelStats}). */ edgeStatsSampler?: EdgeStatsSampler; /** * Number of frames to sample evenly across each clip for mid-clip inspection * (default {@link DEFAULT_FRAME_SAMPLE_COUNT}). Clamped to ≥1. */ frameSampleCount?: number; /** Endpoint override forwarded to the default vision/inspection clients. */ endpoint?: string; /** Explicit Gemini key for the default vision/inspection clients (bypasses the pool). */ keyOverride?: string; /** Injectable fetch for the default vision/inspection clients (offline tests). */ fetcher?: typeof fetch; } /** * Find the representative source media for a scene to audit: * - a rendered output video `outputs/scene-.mp4` (preferred), else * - a rendered output image `outputs/scene-.`, else * - undefined (the scene is skipped — not yet rendered). * * Exported for reuse by the motion-artifact QC (`motion-artifact-qc.ts`), * which audits the same rendered outputs for a different defect class. */ export declare function resolveSceneMedia(projectDir: string, sceneIndex: number): { path: string; kind: 'video' | 'image'; } | undefined; /** Minimal asset-manifest shape needed to resolve a scene's i2v start image. */ interface AssetManifestAssets { assets?: Array<{ kind?: string; path?: string; sceneIndex?: number; }>; } /** * Resolve a scene's i2v START IMAGE (keyframe) from the project's asset-manifest: * the first `image` asset whose `sceneIndex` matches. Mirrors how * `buildExecutionPayload` groups manifest assets by scene. Returns an absolute, * on-disk, existing path or undefined (no manifest, no matching image, a remote * URI, or a missing file — all graceful: the scene simply skips mid-clip * inspection). Pure over the parsed manifest + a fs existence check. */ export declare function resolveSceneKeyframe(projectDir: string, manifest: AssetManifestAssets, sceneIndex: number): string | undefined; /** * The K evenly-spaced sample fractions across a clip for mid-clip inspection. * Interior points only (never 0 or 1): `(i + 1) / (k + 1)` for i in [0, k). So * k=5 → 1/6, 2/6, 3/6, 4/6, 5/6 (≈0.17 … 0.83), spreading samples across the * body of the clip rather than clustering at the ends. Always ≥1 sample. */ export declare function sampleFractions(k: number): number[]; /** * Audit a project's rendered scenes for character identity/costume consistency. * * Pure aside from the injected/default frame extractor + edge sampler + vision * client. For each storyboard scene with a rendered output, extracts one * mid-frame, runs the deterministic border check, and asks the vision client to * compare each registered scene character against its locked reference + * descriptor + costume. `ok` is false when any checked character mismatches OR * any checked frame has a border. Scenes without a rendered frame are reported * `frameChecked:false` and never flip `ok`. */ export declare function auditProjectConsistency(projectSlug: string, root?: string, options?: AuditProjectConsistencyOptions): Promise; /** * Default ffmpeg-backed frame extractor: probe the video duration, seek to * `fraction` of the way through (default 0.5 = midpoint), and write a single * PNG. The real-spawn path — NOT used in unit tests (they inject a fake * extractor). `fraction` is clamped to [0, 1). */ export declare function extractMidFrame(videoPath: string, framePath: string, fraction?: number): Promise; /** * Default ffmpeg-backed edge sampler. Crops the four edge strips and reads each * strip's mean luma + a uniformity proxy (1 - normalized luma std-dev) via * ffmpeg's `signalstats` filter. The real-spawn path — NOT used in unit tests * (they inject a fake sampler). Falls back to a "no border" reading on any * probe failure (advisory, never fatal). */ export declare function sampleEdgePixelStats(framePath: string): Promise; export interface DefaultVisionClientOptions { endpoint?: string; keyOverride?: string; fetcher?: typeof fetch; } /** Build the structured per-character audit prompt for the vision client. */ export declare function buildConsistencyAuditPrompt(input: ConsistencyVisionInput): string; /** * The default vision client. Reuses the EXISTING shared Gemini-Vision transport * ({@link classifyImageWithGemini} → {@link fetchGeminiWithPool} key pool + * VCLAW_GEMINI_API_ENDPOINT override) used by the assemble QA vision modules — * no new auth path. It classifies the SCENE FRAME (the reference image is * described in the prompt; the shared transport posts a single image) and maps * the `match|mismatch` verdict + reason into a {@link ConsistencyVisionAssessment}. * A transport `error` verdict degrades to a non-fatal "could not assess" issue * (match:true) so a flaky vision call never falsely fails a good render. */ export declare function createDefaultVisionClient(options?: DefaultVisionClientOptions): ConsistencyVisionClient; /** Build the two-image mid-clip inspection prompt (keyframe FIRST, frame SECOND). */ export declare function buildFrameInspectionPrompt(input: ConsistencyFrameInspectionInput): string; /** * Parse the two-line `appearing:` / `anatomy:` reply into a * {@link ConsistencyFrameInspection}. A `none` (case-insensitive) or empty value * yields an empty list; otherwise the value is split on commas/semicolons and * each non-empty item is trimmed. Lenient: missing lines yield empty lists. */ export declare function parseFrameInspectionReply(text: string): ConsistencyFrameInspection; /** * The default frame-inspection client. Reuses the SAME Gemini infra as the * identity audit — the two-image {@link classifyTwoImagesWithGemini} call over * the {@link fetchGeminiWithPool} key pool + VCLAW_GEMINI_API_ENDPOINT override — * posting the keyframe + the sampled frame in one call. A transport error * degrades to empty lists (advisory) so a flaky vision call never falsely flags * a clean clip. */ export declare function createDefaultFrameInspectionClient(options?: DefaultVisionClientOptions): ConsistencyFrameInspectionClient; export {}; //# sourceMappingURL=consistency-audit.d.ts.map