Initial commit
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Frame-accurate utilities for VFX review.
|
||||
* All frame calculations use 0-based frame numbers
|
||||
* unless documented otherwise.
|
||||
*/
|
||||
|
||||
/** Convert frame number to video currentTime in seconds */
|
||||
export function frameToTime(frame: number, fps: number): number {
|
||||
return frame / fps;
|
||||
}
|
||||
|
||||
/** Convert video currentTime to frame number (0-based) */
|
||||
export function timeToFrame(time: number, fps: number): number {
|
||||
return Math.floor(time * fps);
|
||||
}
|
||||
|
||||
/** Format a frame number as a timecode string: MM:SS:FF */
|
||||
export function frameToTimecode(frame: number, fps: number): string {
|
||||
const totalSeconds = frame / fps;
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(totalSeconds % 60);
|
||||
const frames = frame % Math.round(fps);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}:${String(frames).padStart(2, "0")}`;
|
||||
}
|
||||
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}:${String(frames).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
/** Format a raw time (seconds) as MM:SS.mmm */
|
||||
export function timeToDisplay(time: number): string {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
const ms = Math.floor((time % 1) * 1000);
|
||||
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${String(ms).padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
/** Given duration and fps, compute total frame count */
|
||||
export function durationToFrameCount(duration: number, fps: number): number {
|
||||
return Math.floor(duration * fps);
|
||||
}
|
||||
|
||||
/** Clamp a frame number to [0, totalFrames - 1] */
|
||||
export function clampFrame(frame: number, totalFrames: number): number {
|
||||
return Math.max(0, Math.min(totalFrames - 1, frame));
|
||||
}
|
||||
|
||||
/** Step a single frame forward or backward */
|
||||
export function stepFrame(
|
||||
currentFrame: number,
|
||||
delta: number,
|
||||
totalFrames: number
|
||||
): number {
|
||||
return clampFrame(currentFrame + delta, totalFrames);
|
||||
}
|
||||
|
||||
/** Convert a timeline position (0-1) to frame number */
|
||||
export function positionToFrame(position: number, totalFrames: number): number {
|
||||
return clampFrame(Math.floor(position * totalFrames), totalFrames);
|
||||
}
|
||||
|
||||
/** Convert a frame number to timeline position (0-1) */
|
||||
export function frameToPosition(frame: number, totalFrames: number): number {
|
||||
if (totalFrames === 0) return 0;
|
||||
return frame / totalFrames;
|
||||
}
|
||||
|
||||
/** Common VFX frame rates */
|
||||
export const COMMON_FPS = [23.976, 24, 25, 29.97, 30, 48, 50, 59.94, 60] as const;
|
||||
|
||||
export type CommonFPS = (typeof COMMON_FPS)[number];
|
||||
|
||||
/** Human-readable fps label */
|
||||
export function fpsLabel(fps: number): string {
|
||||
if (fps === 23.976) return "23.976";
|
||||
return String(fps);
|
||||
}
|
||||
Reference in New Issue
Block a user