Spaces:
Sleeping
Sleeping
| import { FPS, fpsToNumber } from "./enums"; | |
| export interface TimeCode { | |
| h: number; | |
| m: number; | |
| s: number; | |
| f: number; | |
| fps: FPS; | |
| locked: boolean; | |
| negative: boolean; | |
| bypass: boolean; | |
| autospot: boolean; | |
| autospotApproved: boolean; | |
| } | |
| export function createTimeCode(partial?: Partial<TimeCode>): TimeCode { | |
| return { | |
| h: 0, m: 0, s: 0, f: 0, | |
| fps: FPS.PAL, | |
| locked: false, negative: false, bypass: false, | |
| autospot: false, autospotApproved: false, | |
| ...partial, | |
| }; | |
| } | |
| export function isTimeCodeZero(tc: TimeCode): boolean { | |
| return tc.h === 0 && tc.m === 0 && tc.s === 0 && tc.f === 0; | |
| } | |
| export function timeCodeToFrames(tc: TimeCode): number { | |
| const fps = Math.round(fpsToNumber(tc.fps)); | |
| if (tc.fps === FPS.NTSC_DF) { | |
| // Drop-frame calculation | |
| const totalMinutes = tc.h * 60 + tc.m; | |
| const dropFrames = 2; | |
| const frames = | |
| tc.h * 107892 + // 30 * 60 * 59.94 rounded | |
| tc.m * 1798 + | |
| tc.s * 30 + | |
| tc.f - | |
| dropFrames * (totalMinutes - Math.floor(totalMinutes / 10)); | |
| return tc.negative ? -frames : frames; | |
| } | |
| const frames = tc.h * fps * 3600 + tc.m * fps * 60 + tc.s * fps + tc.f; | |
| return tc.negative ? -frames : frames; | |
| } | |
| export function framesToTimeCode(totalFrames: number, fps: FPS): TimeCode { | |
| const negative = totalFrames < 0; | |
| let frames = Math.abs(totalFrames); | |
| const fpsNum = Math.round(fpsToNumber(fps)); | |
| if (fps === FPS.NTSC_DF) { | |
| const dropFrames = 2; | |
| const framesPerMin = 1798; // 30 * 60 - 2 | |
| const framesPer10Min = 17982; // 10 * 1798 + 2 | |
| const d = Math.floor(frames / framesPer10Min); | |
| let m = frames % framesPer10Min; | |
| if (m < 2) { | |
| m = 0; | |
| } else { | |
| m = Math.floor((m - 2) / framesPerMin) + 1; | |
| } | |
| const totalMinutes = d * 10 + m; | |
| const adjustedFrames = frames + dropFrames * (totalMinutes - Math.floor(totalMinutes / 10)); | |
| const h = Math.floor(adjustedFrames / (fpsNum * 3600)); | |
| const remainder1 = adjustedFrames % (fpsNum * 3600); | |
| const min = Math.floor(remainder1 / (fpsNum * 60)); | |
| const remainder2 = remainder1 % (fpsNum * 60); | |
| const s = Math.floor(remainder2 / fpsNum); | |
| const f = remainder2 % fpsNum; | |
| return createTimeCode({ h, m: min, s, f, fps, negative }); | |
| } | |
| const h = Math.floor(frames / (fpsNum * 3600)); | |
| frames %= fpsNum * 3600; | |
| const m = Math.floor(frames / (fpsNum * 60)); | |
| frames %= fpsNum * 60; | |
| const s = Math.floor(frames / fpsNum); | |
| const f = frames % fpsNum; | |
| return createTimeCode({ h, m, s, f, fps, negative }); | |
| } | |
| export function timeCodeToMilliseconds(tc: TimeCode): number { | |
| const fpsNum = fpsToNumber(tc.fps); | |
| const ms = (tc.h * 3600 + tc.m * 60 + tc.s) * 1000 + Math.round((tc.f / fpsNum) * 1000); | |
| return tc.negative ? -ms : ms; | |
| } | |
| export function millisecondsToTimeCode(ms: number, fps: FPS): TimeCode { | |
| const negative = ms < 0; | |
| ms = Math.abs(ms); | |
| const fpsNum = fpsToNumber(fps); | |
| const totalSeconds = ms / 1000; | |
| const h = Math.floor(totalSeconds / 3600); | |
| const m = Math.floor((totalSeconds % 3600) / 60); | |
| const s = Math.floor(totalSeconds % 60); | |
| const remainingMs = ms % 1000; | |
| const f = Math.round((remainingMs / 1000) * fpsNum); | |
| return createTimeCode({ h, m, s, f: Math.min(f, Math.round(fpsNum) - 1), fps, negative }); | |
| } | |
| export function timeCodeToString(tc: TimeCode, separator = ":"): string { | |
| const pad2 = (n: number) => n.toString().padStart(2, "0"); | |
| const sign = tc.negative ? "-" : ""; | |
| const frameSep = tc.fps === FPS.NTSC_DF ? ";" : separator; | |
| return `${sign}${pad2(tc.h)}:${pad2(tc.m)}:${pad2(tc.s)}${frameSep}${pad2(tc.f)}`; | |
| } | |
| export function timeCodeToSrtString(tc: TimeCode): string { | |
| const pad2 = (n: number) => n.toString().padStart(2, "0"); | |
| const pad3 = (n: number) => n.toString().padStart(3, "0"); | |
| const ms = Math.round((tc.f / fpsToNumber(tc.fps)) * 1000); | |
| return `${pad2(tc.h)}:${pad2(tc.m)}:${pad2(tc.s)},${pad3(ms)}`; | |
| } | |
| export function parseSrtTimeCode(str: string, fps: FPS): TimeCode { | |
| // Format: HH:MM:SS,mmm | |
| const match = str.trim().match(/^(\d{1,2}):(\d{2}):(\d{2})[,.](\d{3})$/); | |
| if (!match) return createTimeCode({ fps }); | |
| const h = parseInt(match[1]); | |
| const m = parseInt(match[2]); | |
| const s = parseInt(match[3]); | |
| const ms = parseInt(match[4]); | |
| const f = Math.round((ms / 1000) * fpsToNumber(fps)); | |
| return createTimeCode({ h, m, s, f: Math.min(f, Math.round(fpsToNumber(fps)) - 1), fps }); | |
| } | |
| export function addTimeCodes(a: TimeCode, b: TimeCode): TimeCode { | |
| const framesA = timeCodeToFrames(a); | |
| const framesB = timeCodeToFrames(b); | |
| return framesToTimeCode(framesA + framesB, a.fps); | |
| } | |
| export function subtractTimeCodes(a: TimeCode, b: TimeCode): TimeCode { | |
| const framesA = timeCodeToFrames(a); | |
| const framesB = timeCodeToFrames(b); | |
| return framesToTimeCode(framesA - framesB, a.fps); | |
| } | |
| export function compareTimeCodes(a: TimeCode, b: TimeCode): number { | |
| return timeCodeToFrames(a) - timeCodeToFrames(b); | |
| } | |
| export function durationMs(tcIn: TimeCode, tcOut: TimeCode): number { | |
| return timeCodeToMilliseconds(tcOut) - timeCodeToMilliseconds(tcIn); | |
| } | |