/** * Pure EDL time mapping: source frames -> output-timeline seconds. * * Crossfades OVERLAP the previous clip, so every clip after a crossfade * starts (and ends) earlier on the output timeline than a cut-only chain * would place it. All render math (xfade offsets, sound delays, total * duration) derives from this module so it can be unit-tested without ffmpeg. */ import type { EdlClip, TimelineSound } from './timeline-types.js'; export declare const DEFAULT_CROSSFADE_SEC = 0.25; export declare const DEFAULT_TAIL_PAD_FRAMES = 30; export declare const MIN_SPEED = 0.25; export declare const MAX_SPEED = 2; export interface ClipTiming { index: number; in: number; out: number; speed: number; /** Resolved transition INTO this clip; first clip is always 'cut'. */ transition: 'cut' | 'crossfade'; /** Seconds; 0 for cuts. Also the xfade offset shortfall vs a plain concat. */ transitionDuration: number; /** Seconds on the output timeline where this clip's first frame lands. */ outputStart: number; /** Seconds this clip occupies on the output timeline (source frames / fps / speed). */ outputDuration: number; label?: string; /** Per-clip UI-overlay override; undefined means "follow the global --ui flag". */ ui?: boolean; } export interface ClipTimelineResult { timings: ClipTiming[]; totalDuration: number; warnings: string[]; } export declare function computeClipTimeline(clips: EdlClip[], fps: number): ClipTimelineResult; /** * Output-timeline seconds where a source frame inside `timing` lands. Frames * before the clip's first frame (sound tail pad) clamp to the clip start. */ export declare function sourceFrameToOutputSec(timing: ClipTiming, sourceFrame: number, fps: number): number; /** * Output time at which a clip stops being the only source on screen. A * crossfade INTO the next clip begins at that clip's outputStart — before this * clip's last frame — so anything that must not double up (UI overlays) hands * over there rather than at this clip's own end. */ export declare function exclusiveOutputEnd(timings: readonly ClipTiming[], index: number): number; export interface PlacedSound { sound: TimelineSound; clipIndex: number; /** Seconds on the output timeline where this sound instance starts. */ outputTimeSec: number; } /** * Place timeline sounds onto the output timeline. A clip picks up sounds with * frame in [in - tailPadFrames, out); sounds from the tail pad (before the * clip's first frame) clamp to the clip's output start. The same sound can be * placed once per clip whose source range covers it. */ export declare function placeSounds(timings: ClipTiming[], sounds: TimelineSound[], fps: number, tailPadFrames?: number): PlacedSound[];