mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: migrate time utilities from typescript to rust WASM
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user