From dcf54c1250431f5a4a2848f7c07781ef5eebacaf Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 31 Mar 2026 04:03:52 +0200 Subject: feat: migrate time utilities from typescript to rust WASM --- apps/web/src/lib/time.ts | 225 ------------------------- apps/web/src/lib/timeline/bookmarks.ts | 298 ++++++++++++++++----------------- 2 files changed, 149 insertions(+), 374 deletions(-) delete mode 100644 apps/web/src/lib/time.ts (limited to 'apps/web/src/lib') diff --git a/apps/web/src/lib/time.ts b/apps/web/src/lib/time.ts deleted file mode 100644 index aff42bee..00000000 --- a/apps/web/src/lib/time.ts +++ /dev/null @@ -1,225 +0,0 @@ -export type TTimeCode = - | "MM:SS" - | "HH:MM:SS" - | "HH:MM:SS:CS" - | "HH:MM:SS:FF"; - -export function roundToFrame({ - time, - fps, -}: { - time: number; - fps: number; -}): number { - return Math.round(time * fps) / fps; -} - -export function formatTimeCode({ - timeInSeconds, - format = "HH:MM:SS:CS", - fps, -}: { - timeInSeconds: number; - format?: TTimeCode; - fps?: number; -}): string { - const hours = Math.floor(timeInSeconds / 3600); - const minutes = Math.floor((timeInSeconds % 3600) / 60); - const seconds = Math.floor(timeInSeconds % 60); - const centiseconds = Math.floor((timeInSeconds % 1) * 100); - const frames = fps ? Math.floor((timeInSeconds % 1) * fps) : 0; - - switch (format) { - case "MM:SS": - return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; - case "HH:MM:SS": - return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; - case "HH:MM:SS:CS": - return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}:${centiseconds.toString().padStart(2, "0")}`; - case "HH:MM:SS:FF": - if (!fps) throw new Error("FPS is required for HH:MM:SS:FF format"); - return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}:${frames.toString().padStart(2, "0")}`; - } -} - -export function parseTimeCode({ - timeCode, - format = "HH:MM:SS:CS", - fps, -}: { - timeCode: string; - format?: TTimeCode; - fps: number; -}): number | null { - if (!timeCode || typeof timeCode !== "string") return null; - - const cleanTimeCode = timeCode.trim(); - - try { - switch (format) { - case "MM:SS": { - const parts = cleanTimeCode.split(":"); - if (parts.length !== 2) return null; - const [minutes, seconds] = parts.map((part) => parseInt(part, 10)); - if (Number.isNaN(minutes) || Number.isNaN(seconds)) return null; - if (minutes < 0 || seconds < 0 || seconds >= 60) return null; - return minutes * 60 + seconds; - } - - case "HH:MM:SS": { - const parts = cleanTimeCode.split(":"); - if (parts.length !== 3) return null; - const [hours, minutes, seconds] = parts.map((part) => - parseInt(part, 10), - ); - if ( - Number.isNaN(hours) || - Number.isNaN(minutes) || - Number.isNaN(seconds) - ) - return null; - if ( - hours < 0 || - minutes < 0 || - seconds < 0 || - minutes >= 60 || - seconds >= 60 - ) - return null; - return hours * 3600 + minutes * 60 + seconds; - } - - case "HH:MM:SS:CS": { - const parts = cleanTimeCode.split(":"); - if (parts.length !== 4) return null; - const [hours, minutes, seconds, centiseconds] = parts.map((part) => - parseInt(part, 10), - ); - if ( - Number.isNaN(hours) || - Number.isNaN(minutes) || - Number.isNaN(seconds) || - Number.isNaN(centiseconds) - ) - return null; - if ( - hours < 0 || - minutes < 0 || - seconds < 0 || - centiseconds < 0 || - minutes >= 60 || - seconds >= 60 || - centiseconds >= 100 - ) - return null; - return hours * 3600 + minutes * 60 + seconds + centiseconds / 100; - } - - case "HH:MM:SS:FF": { - const parts = cleanTimeCode.split(":"); - if (parts.length !== 4) return null; - const [hours, minutes, seconds, frames] = parts.map((part) => - parseInt(part, 10), - ); - if ( - Number.isNaN(hours) || - Number.isNaN(minutes) || - Number.isNaN(seconds) || - Number.isNaN(frames) - ) - return null; - if ( - hours < 0 || - minutes < 0 || - seconds < 0 || - frames < 0 || - minutes >= 60 || - seconds >= 60 || - frames >= fps - ) - return null; - return hours * 3600 + minutes * 60 + seconds + frames / fps; - } - } - } catch { - return null; - } -} - -export function guessTimeCodeFormat({ - timeCode, -}: { - timeCode: string; -}): TTimeCode | null { - if (!timeCode || typeof timeCode !== "string") return null; - - const numbers = timeCode.split(":"); - - if (!numbers.every((n) => !Number.isNaN(Number(n)))) return null; - - if (numbers.length === 2) return "MM:SS"; - if (numbers.length === 3) return "HH:MM:SS"; - // todo: how to tell frames apart from cs? - if (numbers.length === 4) return "HH:MM:SS:FF"; - - return null; -} - -export function timeToFrame({ - time, - fps, -}: { - time: number; - fps: number; -}): number { - return Math.round(time * fps); -} - -export function frameToTime({ - frame, - fps, -}: { - frame: number; - fps: number; -}): number { - return frame / fps; -} - -export function snapTimeToFrame({ - time, - fps, -}: { - time: number; - fps: number; -}): number { - if (fps <= 0) return time; - const frame = timeToFrame({ time, fps }); - return frameToTime({ frame, fps }); -} - -export function getSnappedSeekTime({ - rawTime, - duration, - fps, -}: { - rawTime: number; - duration: number; - fps: number; -}): number { - const snappedTime = snapTimeToFrame({ time: rawTime, fps }); - const lastFrame = getLastFrameTime({ duration, fps }); - return Math.max(0, Math.min(lastFrame, snappedTime)); -} - -export function getLastFrameTime({ - duration, - fps, -}: { - duration: number; - fps: number; -}): number { - if (duration <= 0) return 0; - if (fps <= 0) return duration; - const frameOffset = 1 / fps; - return Math.max(0, duration - frameOffset); -} diff --git a/apps/web/src/lib/timeline/bookmarks.ts b/apps/web/src/lib/timeline/bookmarks.ts index 31bf73af..70d1b5ee 100644 --- a/apps/web/src/lib/timeline/bookmarks.ts +++ b/apps/web/src/lib/timeline/bookmarks.ts @@ -1,149 +1,149 @@ -import type { Bookmark } from "@/lib/timeline"; -import { roundToFrame } from "@/lib/time"; - -export const BOOKMARK_TIME_EPSILON = 0.001; - -function bookmarkTimeEqual({ - bookmarkTime, - frameTime, -}: { - bookmarkTime: number; - frameTime: number; -}): boolean { - return Math.abs(bookmarkTime - frameTime) < BOOKMARK_TIME_EPSILON; -} - -export function findBookmarkIndex({ - bookmarks, - frameTime, -}: { - bookmarks: Bookmark[]; - frameTime: number; -}): number { - return bookmarks.findIndex((bookmark) => - bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }), - ); -} - -export function isBookmarkAtTime({ - bookmarks, - frameTime, -}: { - bookmarks: Bookmark[]; - frameTime: number; -}): boolean { - return bookmarks.some((bookmark) => - bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }), - ); -} - -export function toggleBookmarkInArray({ - bookmarks, - frameTime, -}: { - bookmarks: Bookmark[]; - frameTime: number; -}): Bookmark[] { - const bookmarkIndex = findBookmarkIndex({ bookmarks, frameTime }); - - if (bookmarkIndex !== -1) { - return bookmarks.filter((_, index) => index !== bookmarkIndex); - } - - const newBookmarks = [...bookmarks, { time: frameTime }]; - return newBookmarks.slice().sort((a, b) => a.time - b.time); -} - -export function removeBookmarkFromArray({ - bookmarks, - frameTime, -}: { - bookmarks: Bookmark[]; - frameTime: number; -}): Bookmark[] { - return bookmarks.filter( - (bookmark) => - !bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }), - ); -} - -export function updateBookmarkInArray({ - bookmarks, - frameTime, - updates, -}: { - bookmarks: Bookmark[]; - frameTime: number; - updates: Partial>; -}): Bookmark[] { - const index = findBookmarkIndex({ bookmarks, frameTime }); - if (index === -1) { - return bookmarks; - } - - const updated = { ...bookmarks[index], ...updates }; - const result = [...bookmarks]; - result[index] = updated; - return result; -} - -export function moveBookmarkInArray({ - bookmarks, - fromTime, - toTime, -}: { - bookmarks: Bookmark[]; - fromTime: number; - toTime: number; -}): Bookmark[] { - const index = findBookmarkIndex({ bookmarks, frameTime: fromTime }); - if (index === -1) { - return bookmarks; - } - - const updated = { ...bookmarks[index], time: toTime }; - const result = [...bookmarks]; - result[index] = updated; - return result.slice().sort((a, b) => a.time - b.time); -} - -export function getFrameTime({ - time, - fps, -}: { - time: number; - fps: number; -}): number { - return roundToFrame({ time, fps }); -} - -export function getBookmarkAtTime({ - bookmarks, - frameTime, -}: { - bookmarks: Bookmark[]; - frameTime: number; -}): Bookmark | null { - const index = findBookmarkIndex({ bookmarks, frameTime }); - return index === -1 ? null : bookmarks[index]; -} - -export function getBookmarksActiveAtTime({ - bookmarks, - time, -}: { - bookmarks: Bookmark[]; - time: number; -}): Bookmark[] { - return bookmarks.filter((bookmark) => { - const start = bookmark.time; - const end = - bookmark.duration != null && bookmark.duration > 0 - ? start + bookmark.duration - : start; - return ( - time >= start - BOOKMARK_TIME_EPSILON && - time <= end + BOOKMARK_TIME_EPSILON - ); - }); -} +import type { Bookmark } from "@/lib/timeline"; +import { roundToFrame } from "opencut-wasm"; + +export const BOOKMARK_TIME_EPSILON = 0.001; + +function bookmarkTimeEqual({ + bookmarkTime, + frameTime, +}: { + bookmarkTime: number; + frameTime: number; +}): boolean { + return Math.abs(bookmarkTime - frameTime) < BOOKMARK_TIME_EPSILON; +} + +export function findBookmarkIndex({ + bookmarks, + frameTime, +}: { + bookmarks: Bookmark[]; + frameTime: number; +}): number { + return bookmarks.findIndex((bookmark) => + bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }), + ); +} + +export function isBookmarkAtTime({ + bookmarks, + frameTime, +}: { + bookmarks: Bookmark[]; + frameTime: number; +}): boolean { + return bookmarks.some((bookmark) => + bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }), + ); +} + +export function toggleBookmarkInArray({ + bookmarks, + frameTime, +}: { + bookmarks: Bookmark[]; + frameTime: number; +}): Bookmark[] { + const bookmarkIndex = findBookmarkIndex({ bookmarks, frameTime }); + + if (bookmarkIndex !== -1) { + return bookmarks.filter((_, index) => index !== bookmarkIndex); + } + + const newBookmarks = [...bookmarks, { time: frameTime }]; + return newBookmarks.slice().sort((a, b) => a.time - b.time); +} + +export function removeBookmarkFromArray({ + bookmarks, + frameTime, +}: { + bookmarks: Bookmark[]; + frameTime: number; +}): Bookmark[] { + return bookmarks.filter( + (bookmark) => + !bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }), + ); +} + +export function updateBookmarkInArray({ + bookmarks, + frameTime, + updates, +}: { + bookmarks: Bookmark[]; + frameTime: number; + updates: Partial>; +}): Bookmark[] { + const index = findBookmarkIndex({ bookmarks, frameTime }); + if (index === -1) { + return bookmarks; + } + + const updated = { ...bookmarks[index], ...updates }; + const result = [...bookmarks]; + result[index] = updated; + return result; +} + +export function moveBookmarkInArray({ + bookmarks, + fromTime, + toTime, +}: { + bookmarks: Bookmark[]; + fromTime: number; + toTime: number; +}): Bookmark[] { + const index = findBookmarkIndex({ bookmarks, frameTime: fromTime }); + if (index === -1) { + return bookmarks; + } + + const updated = { ...bookmarks[index], time: toTime }; + const result = [...bookmarks]; + result[index] = updated; + return result.slice().sort((a, b) => a.time - b.time); +} + +export function getFrameTime({ + time, + fps, +}: { + time: number; + fps: number; +}): number { + return roundToFrame({ time, fps }); +} + +export function getBookmarkAtTime({ + bookmarks, + frameTime, +}: { + bookmarks: Bookmark[]; + frameTime: number; +}): Bookmark | null { + const index = findBookmarkIndex({ bookmarks, frameTime }); + return index === -1 ? null : bookmarks[index]; +} + +export function getBookmarksActiveAtTime({ + bookmarks, + time, +}: { + bookmarks: Bookmark[]; + time: number; +}): Bookmark[] { + return bookmarks.filter((bookmark) => { + const start = bookmark.time; + const end = + bookmark.duration != null && bookmark.duration > 0 + ? start + bookmark.duration + : start; + return ( + time >= start - BOOKMARK_TIME_EPSILON && + time <= end + BOOKMARK_TIME_EPSILON + ); + }); +} -- cgit v1.3.1