diff options
Diffstat (limited to 'apps/web/src/lib')
| -rw-r--r-- | apps/web/src/lib/time.ts | 225 | ||||
| -rw-r--r-- | apps/web/src/lib/timeline/bookmarks.ts | 298 |
2 files changed, 149 insertions, 374 deletions
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<Omit<Bookmark, "time">>;
-}): 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<Omit<Bookmark, "time">>; +}): 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 + ); + }); +} |
