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 { 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); }