import { type BinItem, type MediaClip, type TimelineClip, type TimelineProject, type TimelineTrack } from '../types/timeline.js'; export type OpFailReason = 'locked' | 'occupied' | 'not-contiguous' | 'no-target' | 'no-audio' | 'invalid'; export type OpResult = { ok: true; } | { ok: false; reason: OpFailReason; }; type IdGen = () => string; export declare function clipById(project: TimelineProject, id: string): TimelineClip | undefined; export declare function trackById(project: TimelineProject, id: string): TimelineTrack | undefined; export declare function isClipLocked(project: TimelineProject, clip: TimelineClip): boolean; export declare function trackClipsSorted(project: TimelineProject, trackId: string): TimelineClip[]; export declare function spanFree(project: TimelineProject, trackId: string, startF: number, endF: number, exclude: ReadonlySet): boolean; export declare function linkedPartner(project: TimelineProject, clip: TimelineClip): MediaClip | null; /** Split the given clips at a frame. Locked/non-spanning clips are skipped. */ export declare function splitClips(project: TimelineProject, clipIds: string[], atF: number, idgen?: IdGen): { ok: true; newIds: string[]; }; /** ⇧S: razor every unlocked clip under the playhead on every unlocked track. */ export declare function splitAllAt(project: TimelineProject, atF: number, idgen?: IdGen): { ok: true; newIds: string[]; }; /** * Make [startF, endF) free on a track by trimming/splitting/removing * overlapped clips (standard NLE overwrite). Fails on locked victims. */ export declare function resolveOverwrite(project: TimelineProject, trackId: string, startF: number, endF: number, exclude: ReadonlySet, idgen?: IdGen): OpResult; /** * Open a gap: split clips spanning atF (all unlocked tracks), then shift * everything starting at/after atF right by lengthF. Fails if a locked clip * spans the point or sits in the shifted region. */ export declare function insertGap(project: TimelineProject, atF: number, lengthF: number, exclude: ReadonlySet, idgen?: IdGen): OpResult; export type ClipMove = { clipId: string; startF: number; trackId: string; }; /** * Commit a drag: move clips to new positions/tracks, resolving collisions by * mode. 'overwrite' trims what's underneath (per target track); 'insert' * opens a gap at the landing span across all tracks first. All-or-nothing. */ export declare function placeClips(project: TimelineProject, moves: ClipMove[], mode: 'overwrite' | 'insert', idgen?: IdGen): OpResult; /** * Remove the selection and close the vacated span across ALL tracks. * Aborts if the selection span contains unselected clips, or if any clip * that must shift (or straddles the span end) is locked. */ export declare function rippleDelete(project: TimelineProject, clipIds: string[]): OpResult; export type Gap = { trackId: string; startF: number; endF: number; }; /** The gap (empty span) on a track containing the given frame, if any. */ export declare function gapAt(project: TimelineProject, trackId: string, frame: number): Gap | null; /** * Close a gap. Single-track: shift that track's suffix left by the gap * length. All-tracks: shift every clip starting at/after the gap end on all * tracks, clamped so nothing collides with stationary clips; aborts if a * locked clip must shift or no room exists. */ export declare function closeGap(project: TimelineProject, gap: Gap, allTracks: boolean): OpResult; /** Range ripple delete: clear [inF, outF) on all tracks, then close it. */ export declare function rangeDelete(project: TimelineProject, inF: number, outF: number, idgen?: IdGen): OpResult; export type TrimResult = OpResult & { appliedF?: number; }; /** * Set a clip edge to an absolute frame, clamped to: min duration, source * bounds (media), and adjacent clips on the same track (no-overlap * invariant). Optionally propagates the same delta to a linked partner — * if the partner can't apply the full delta, both clamp together. */ export declare function trimClip(project: TimelineProject, clipId: string, edge: 'left' | 'right', targetF: number, propagateLink?: boolean): TrimResult; /** * Roll the edit point between two exactly-adjacent clips on one track: the * left clip's out and right clip's in move together; total length constant. * Returns the applied (clamped) delta. */ export declare function rollEdit(project: TimelineProject, leftId: string, rightId: string, deltaF: number): TrimResult; /** * Slip: slide the source content under a fixed timeline window. Only for * media with known source length longer than the clip. */ export declare function slipEdit(project: TimelineProject, clipId: string, deltaF: number): TrimResult; export type PasteResult = { ok: true; newIds: string[]; } | { ok: false; reason: OpFailReason; }; /** * Paste clipboard clips with their lead edge at targetF. Group ids and link * ids are re-minted (pairs stay paired, but distinct from the source). */ export declare function pasteClips(project: TimelineProject, payload: TimelineClip[], targetF: number, idgen?: IdGen): PasteResult; /** * Deep-copy clips with fresh ids; group ids and (complete) link pairs are * re-minted so the copies stay grouped/linked together but independently of * the source. Does NOT insert into a project. */ export declare function cloneClipsPayload(clips: TimelineClip[], idgen?: IdGen): TimelineClip[]; /** ⌘D: copies placed immediately after the selection's end, same tracks. */ export declare function duplicateClips(project: TimelineProject, clipIds: string[], idgen?: IdGen): PasteResult; /** * Detach a video clip's audio into a real audio clip on the nearest track * (after the source, by index) with a free span — or a new track. The pair * shares a fresh linkId; the video plays muted from then on. */ export declare function detachAudio(project: TimelineProject, clipId: string, idgen?: IdGen): OpResult & { audioClipId?: string; }; export declare function unlinkClip(project: TimelineProject, clipId: string): OpResult; /** * Swap a media clip's source keeping position/duration/fades; clamps the * source window when the new media is shorter. */ export declare function replaceClipSource(project: TimelineProject, clipId: string, item: BinItem, fps: number): OpResult & { clamped?: boolean; }; /** Pack a track's clips back-to-back from frame 0 (CapCut main-track mode). */ export declare function magneticPack(project: TimelineProject, trackId: string): void; export {};