Time, speed, snapping, and trimming
Frame and time conversion, speed maps, snap targets, and edge trims.
Types
TimeMap
type TimeMap = z.infer<typeof timeMapSchema>A time map as inferred from timeMapSchema, mapping element-local output time to a source offset relative to trimStartMs.
TimeMappedElement
interface TimeMappedElement {
startMs: number
durationMs: number
trimStartMs: number
timeMap?: TimeMap | undefined
reversed?: boolean | undefined
}The timing fields of a media clip that the speed helpers read.
MediaClip
type MediaClip = VideoElement | AudioElement | MulticamElementAn element that plays a window over source media. Video, audio, and multicam share trimStartMs, timeMap, reversed, volume, muted, and fades, so trims, splits, slips, and speed changes treat all three the same way.
EdgeTrimRange
interface EdgeTrimRange {
minDeltaMs: number
maxDeltaMs: number
}The most negative and most positive deltaMs an edge trim accepts.
TrimEdge
type TrimEdge = 'start' | 'end'The clip boundary an edge trim moves, 'start' or 'end'.
CollectSnapTargetsOptions
interface CollectSnapTargetsOptions {
playheadMs?: number
excludeElementIds?: ReadonlySet<string>
}Options for collectSnapTargets, a playhead time to include and element ids to leave out.
SnapClipResult
interface SnapClipResult extends SnapResult {
edge: 'start' | 'end' | null
}A SnapResult plus which clip edge snapped, or null.
SnapOptions
interface SnapOptions {
enabled?: boolean
fps?: number
}Options for snapping, whether magnets are enabled and an fps for frame quantization when nothing snaps.
SnapResult
interface SnapResult {
ms: number
guideMs: number | null
target: SnapTarget | null
}The snapped time, the guide line time to draw or null, and the target that won or null.
SnapTarget
interface SnapTarget {
timeMs: number
kind: SnapTargetKind
}A time that attracts editing gestures, with its kind.
SnapTargetKind
type SnapTargetKind = 'origin' | 'clip-start' | 'clip-end' | 'marker' | 'playhead'What a snap target represents, timeline zero, a clip start or end, a marker, or the playhead.
Schemas
timeMapSchema
const timeMapSchema = z
.array(keyframeSchema)
.min(2)
.refine(
(frames) => frames.every((k, i) => i === 0 || k.timeMs > frames[i - 1]!.timeMs),
'timeMap keyframes must be strictly increasing in time',
)
.refine(
(frames) => frames.every((k) => k.value >= 0),
'timeMap values are source offsets and must be >= 0',
)
.refine(
(frames) => frames.every((k, i) => i === 0 || k.value >= frames[i - 1]!.value),
'timeMap values must be non-decreasing (reverse playback is not supported)',
)Zod schema for a time map, at least two keyframes with strictly increasing times and non-negative, non-decreasing source offsets.
Functions
getAverageSpeed
function getAverageSpeed(element: { durationMs: number; timeMap?: TimeMap | undefined }): numberReturns the source span divided by the output duration, or 1 without a time map.
getLocalTimeMs
function getLocalTimeMs(element: TimeMappedElement, sourceMs: number): numberReturns the element-local output time at which a source time plays, the inverse of getSourceTimeMs. It follows the time map and reversed, clamps to the clip, and resolves a freeze to its first local moment.
getMediaSourceDurationMs
function getMediaSourceDurationMs(project: Project, clip: MediaClip): number | undefinedReturns how much source media a clip can play from source time 0. For video and audio it is the asset duration. For a multicam it is the shortest coverage over the sources whose asset duration is known, each asset durationMs minus the source offsetMs. It returns undefined when no duration is known.
getSourceSpanMs
function getSourceSpanMs(element: { durationMs: number; timeMap?: TimeMap | undefined }): numberReturns the source duration the element consumes, which is durationMs without a time map and the last keyframe value with one.
getSourceTimeMs
function getSourceTimeMs(element: TimeMappedElement, localMs: number): numberReturns the source media time that plays at an element-local output time, following the time map when present and running the span backward when reversed is set.
getSpeedAt
function getSpeedAt(element: TimeMappedElement, localMs: number, windowMs = 8): numberReturns the instantaneous speed at an element-local time by finite difference over windowMs, which is 0 inside a freeze and 1 without a time map.
isMediaClip
function isMediaClip(element: TimelineElement): element is MediaClipReturns true for video, audio, and multicam elements.
makeConstantSpeedMap
function makeConstantSpeedMap(durationMs: number, speed: number): TimeMapReturns a two-keyframe time map that plays a clip of durationMs at a constant speed.
splitTimeMap
function splitTimeMap(timeMap: TimeMap, offsetMs: number): { left: TimeMap; right: TimeMap }Splits a time map at an element-local offset into maps for both halves, pinning the boundary value on each side and rebasing the right half to start at 0.
frameToMs
function frameToMs(frame: number, fps: number): numberReturns the integer millisecond timestamp of a frame, which is the time export samples for that frame.
msPerFrame
function msPerFrame(fps: number): numberReturns the length of one frame in fractional milliseconds.
msToFrame
function msToFrame(timeMs: number, fps: number): numberReturns the frame index nearest to timeMs.
quantizeMsToFrame
function quantizeMsToFrame(timeMs: number, fps: number, mode: 'round' | 'floor' | 'ceil' = 'round'): numberRounds, floors, or ceils a timestamp to a frame boundary and returns it in integer milliseconds.
applyEdgeTrim
function applyEdgeTrim(
element: TimelineElement,
edge: TrimEdge,
deltaMs: number,
): TimelineElementMoves one edge of an element by deltaMs while its content stays in place, shrinking through the split machinery and growing by revealing more source, and throws CommandError when the result is too short, starts before 0, or is unsupported.
getEdgeTrimRange
function getEdgeTrimRange(
project: Project,
element: TimelineElement,
edge: TrimEdge,
): EdgeTrimRangeReturns the deltaMs range applyEdgeTrim accepts for an element and edge, bounded by the minimum duration, timeline zero, and the available media, with infinite bounds where media is unbounded.
collectSnapTargets
function collectSnapTargets(
project: Project,
options: CollectSnapTargetsOptions = {},
): SnapTarget[]Returns every snap target in the project sorted by time, timeline zero, markers, clip edges, and the playhead when given.
nearestSnapTarget
function nearestSnapTarget(
timeMs: number,
targets: readonly SnapTarget[],
thresholdMs: number,
): SnapTarget | nullReturns the target nearest to timeMs within thresholdMs, preferring the earlier one on ties, or null.
snapClip
function snapClip(
startMs: number,
durationMs: number,
targets: readonly SnapTarget[],
thresholdMs: number,
options: SnapOptions = {},
): SnapClipResultSnaps whichever clip edge is closer to a target and shifts the clip so that edge lands on it, and otherwise returns the start quantized to a frame when an fps is given.
snapTime
function snapTime(
candidateMs: number,
targets: readonly SnapTarget[],
thresholdMs: number,
options: SnapOptions = {},
): SnapResultSnaps a time to the nearest target within thresholdMs, and otherwise returns the time quantized to a frame when an fps is given.