import type { Composition, Transforms } from "../shared/composition.js";
/**
 * Build the comma-chained ffmpeg `-vf` filter expression for a clip's crop +
 * flip transforms. Returns "" when neither crop nor flip is present (no-op).
 *
 * Order is crop FIRST (so flip mirrors the cropped frame, not the full source —
 * matching the preview, where clip-path crops the same element CSS transform
 * mirrors). crop is normalised; we multiply by source dims and round to whole
 * pixels (ffmpeg crop wants integer pixel coords).
 *
 * Examples (W=1080, H=1920):
 *   {}                                  → ""
 *   { flipH:true }                      → "hflip"
 *   { flipV:true }                      → "vflip"
 *   { flipH:true, flipV:true }          → "hflip,vflip"
 *   { crop:{x:.1,y:.2,w:.5,h:.6} }      → "crop=540:1152:108:384"
 *   { crop:{x:0,y:0,w:.5,h:1}, flipH }  → "crop=540:1920:0:0,hflip"
 */
export declare function transformsToFilterChain(t: Transforms, width: number, height: number): string;
/** The subset of a VideoClip the time-warp pass reads. */
export interface TimeWarp {
    reverse?: boolean;
    freezeAtSec?: number;
    /** clip play in-point in the SOURCE (seconds), so reverse trims to [in,out]. */
    inSec?: number;
    /** clip play out-point in the SOURCE (seconds); also the freeze pad length. */
    outSec?: number;
}
/**
 * Build the ffmpeg VIDEO `-vf` chain for a clip's time-warp.
 *   - freeze (precedence) → `trim=start=F,setpts=PTS-STARTPTS,tpad=stop_mode=clone:stop_duration=D`
 *       grab the single frame at F and clone-hold it for the clip's duration D.
 *   - reverse → `trim=start=IN:end=OUT,setpts=PTS-STARTPTS,reverse`
 *       trim to the user-selected [in,out] SPAN of the source FIRST, reset PTS,
 *       THEN reverse — so the reversed material is the clip's own span, not the
 *       whole source's tail (review-fix high: bare `reverse` reversed the entire
 *       source MP4, and applyTimeWarpPrePass then rewrote the clip to in:0/
 *       out:outSec, so Remotion played the source's LAST outSec reversed — the
 *       wrong footage for any trimmed clip). When inSec/outSec are absent
 *       (whole-source reverse) it degrades to bare `reverse`.
 *   - neither → "" (no-op).
 * Freeze takes precedence over reverse (a held still has no direction).
 */
export declare function timeWarpVideoFilterChain(w: TimeWarp, fps: number, outSec: number): string;
/**
 * Build the ffmpeg AUDIO `-af` chain for a clip's time-warp.
 *   - reverse → `atrim=start=IN:end=OUT,asetpts=PTS-STARTPTS,areverse` (audio
 *       played backwards, trimmed to the SAME [in,out] span as the video so the
 *       reversed audio stays in lock-step; degrades to bare `areverse` when the
 *       span isn't known).
 *   - freeze → "" (a held still has no moving audio; the pass silences it -an).
 *   - neither → "".
 */
export declare function timeWarpAudioFilterChain(w: TimeWarp): string;
/**
 * Cache filename for a clip's time-warp pre-pass output. Params are hashed into
 * the name (mirrors transformsCacheName) so a changed warp → new name → re-render,
 * same warp → cache HIT.
 */
export declare function timeWarpCacheName(clipId: string, w: TimeWarp): string;
/**
 * Build the ffmpeg argv for a single clip's time-warp pass.
 *   ffmpeg -y -loglevel error -i {in} -vf {vChain} [-af {aChain} | -an] {out}
 * When the audio chain is empty (freeze) we pass `-an` so the held still is
 * silenced rather than carrying stale audio.
 */
export declare function buildTimeWarpFilterArgs(input: string, output: string, vChain: string, aChain: string): string[];
/**
 * Spawn ffmpeg for the time-warp pass. Mirrors runTransformsPass.
 */
export declare function runTimeWarpPass(input: string, output: string, vChain: string, aChain: string, signal?: AbortSignal): Promise<void>;
/**
 * Cache filename for a clip's crop+flip pre-pass output. The crop/flip PARAMS
 * are hashed into the name (review fix high — the old `clip-{id}-cropflip.mp4`
 * keyed only on the clip id, so changing the crop region re-served a stale file
 * forever). A short content hash of the normalised crop+flip means: same params
 * → same name (cache HIT), any param change → new name → automatic re-render,
 * with the old file naturally orphaned. Mirrors speed-ramp's parameterised name.
 */
export declare function transformsCacheName(clipId: string, t: Transforms): string;
/**
 * ffprobe a video's real pixel dimensions (review fix high). crop is normalised
 * fractions OF THE SOURCE, so the ffmpeg crop= pixel coords must be computed
 * from the source's own width/height — not the canvas. Returns the first video
 * stream's coded width/height. Mirrors probeAudio in audio/peaks.ts.
 */
export declare function probeVideoDimensions(srcPath: string, signal?: AbortSignal): Promise<{
    width: number;
    height: number;
}>;
/**
 * Build the ffmpeg argv for a single clip's crop+flip pre-pass.
 *
 *   ffmpeg -y -loglevel error -i {input} -vf "{chain}" -c:a copy {output}
 *
 * Audio is stream-copied untouched (crop/flip is a video-only transform).
 */
export declare function buildTransformsFilterArgs(input: string, output: string, chain: string): string[];
/**
 * Spawn ffmpeg, collect stderr, reject on non-zero or abort.
 * Mirrors runSpeedRampPass (speed-ramp-ffmpeg.ts).
 */
export declare function runTransformsPass(input: string, output: string, chain: string, signal?: AbortSignal): Promise<void>;
/**
 * Pre-Remotion stage. For each VideoClip with a crop and/or flip, runs the
 * ffmpeg crop/flip pass and rewrites clip.src to point at the cached output,
 * then clears the consumed transform fields so Remotion does NOT crop/mirror a
 * second time (the on-disk frame is already transformed). Clips with neither
 * crop nor flip are left untouched.
 *
 * The crop's pixel basis is the SOURCE video's real dims (ffprobed per clip —
 * review fix high), NOT comp.width/height. crop is normalised fractions of the
 * source frame; using the canvas dims cropped the wrong region whenever the
 * source AR ≠ canvas AR (and could overrun the source → ffmpeg abort).
 *
 * Caching: output goes to `{workDir}/clip-{id}-cropflip-{hash}.mp4` where the
 * hash encodes the crop/flip PARAMS (review fix high — the old id-only name
 * re-served a stale crop after the region changed). Same params → cache HIT,
 * any change → new name → re-render.
 *
 * `probeDims` is injectable so render-pipeline tests don't need a real ffprobe;
 * production passes the default probeVideoDimensions.
 *
 * Never mutates the input composition; returns a deep-cloned comp with affected
 * clips rewritten. Mirrors applySpeedRampPrePass.
 */
export declare function applyTransformsPrePass(comp: Composition, workDir: string, signal?: AbortSignal, probeDims?: (src: string, signal?: AbortSignal) => Promise<{
    width: number;
    height: number;
}>): Promise<Composition>;
/**
 * S19 (US 29/30) — pre-Remotion time-warp stage. For each VideoClip carrying
 * `reverse` and/or `freezeAtSec`, run the ffmpeg reverse/areverse or trim+tpad
 * freeze pass, rewrite clip.src to the cached output, and STRIP the consumed
 * time-domain fields so Remotion does NOT re-apply them (the on-disk frames are
 * already warped). Clips with neither are left untouched.
 *
 * This is the EXPORT consumption of the same `reverse`/`freezeAtSec` fields the
 * preview reads — but with a deliberate asymmetry: freeze IS WYSIWYG (preview
 * holds the same frame), reverse is EXPORT-ONLY (the preview can't play a
 * <video> backwards, so it shows an explicit "export-only" placeholder; here is
 * where the reverse actually happens). Runs BEFORE the crop/flip pre-pass so a
 * clip can be both reversed AND cropped (crop layers on the warped output).
 *
 * Never mutates the input composition; returns a deep-cloned comp with affected
 * clips rewritten. Mirrors applyTransformsPrePass.
 */
export declare function applyTimeWarpPrePass(comp: Composition, workDir: string, signal?: AbortSignal, runWarp?: (input: string, output: string, vChain: string, aChain: string, signal?: AbortSignal) => Promise<void>): Promise<Composition>;
//# sourceMappingURL=transforms-ffmpeg.d.ts.map