diff options
| author | Maze <167211895+mazeincoding@users.noreply.github.com> | 2026-01-31 00:20:04 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 00:20:04 +0100 |
| commit | 7bf0984698e43b9aa2dd879f65951318ed410390 (patch) | |
| tree | ccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/constants | |
| parent | 0173db99448d1c4c6415dcf15272e17551b48b04 (diff) | |
| download | opencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz | |
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/constants')
| -rw-r--r-- | apps/web/src/constants/actions.ts | 295 | ||||
| -rw-r--r-- | apps/web/src/constants/editor-constants.ts | 15 | ||||
| -rw-r--r-- | apps/web/src/constants/export-constants.ts | 12 | ||||
| -rw-r--r-- | apps/web/src/constants/font-constants.ts | 122 | ||||
| -rw-r--r-- | apps/web/src/constants/language-constants.ts | 11 | ||||
| -rw-r--r-- | apps/web/src/constants/project-constants.ts | 27 | ||||
| -rw-r--r-- | apps/web/src/constants/site-constants.ts | 66 | ||||
| -rw-r--r-- | apps/web/src/constants/site.ts | 33 | ||||
| -rw-r--r-- | apps/web/src/constants/stickers-constants.ts | 16 | ||||
| -rw-r--r-- | apps/web/src/constants/text-constants.ts | 50 | ||||
| -rw-r--r-- | apps/web/src/constants/timeline-constants.ts | 107 | ||||
| -rw-r--r-- | apps/web/src/constants/timeline-constants.tsx | 72 | ||||
| -rw-r--r-- | apps/web/src/constants/transcription-constants.ts | 58 |
13 files changed, 364 insertions, 520 deletions
diff --git a/apps/web/src/constants/actions.ts b/apps/web/src/constants/actions.ts deleted file mode 100644 index 50e8ae5d..00000000 --- a/apps/web/src/constants/actions.ts +++ /dev/null @@ -1,295 +0,0 @@ -/* An `action` is a unique verb that is associated with certain thing that can be done on OpenCut. - * For example, toggling playback or seeking. - */ - -import { - useEffect, - useRef, - useState, - useCallback, - MutableRefObject, -} from "react"; - -// Simple event emitter for action changes -class ActionEmitter { - private listeners: Array<(actions: Action[]) => void> = []; - - subscribe(listener: (actions: Action[]) => void) { - this.listeners.push(listener); - return () => { - this.listeners = this.listeners.filter((l) => l !== listener); - }; - } - - emit(actions: Action[]) { - this.listeners.forEach((listener) => listener(actions)); - } -} - -const actionEmitter = new ActionEmitter(); - -export type Action = - | "toggle-play" // Toggle play/pause state - | "stop-playback" // Stop playback - | "seek-forward" // Seek forward in playback - | "seek-backward" // Seek backward in playback - | "frame-step-forward" // Step forward by one frame - | "frame-step-backward" // Step backward by one frame - | "jump-forward" // Jump forward by 5 seconds - | "jump-backward" // Jump backward by 5 seconds - | "goto-start" // Go to timeline start - | "goto-end" // Go to timeline end - | "split-element" // Split element at current time - | "delete-selected" // Delete selected elements - | "select-all" // Select all elements - | "duplicate-selected" // Duplicate selected element - | "toggle-snapping" // Toggle snapping - | "undo" // Undo last action - | "redo" // Redo last undone action - | "copy-selected" // Copy selected elements to clipboard - | "paste-selected"; // Paste elements from clipboard at playhead - -/** - * Defines the arguments, if present for a given type that is required to be passed on - * invocation and will be passed to action handlers. - * - * This type is supposed to be an object with the key being one of the actions mentioned above. - * The value to the key can be anything. - * If an action has no argument, you do not need to add it to this type. - * - * NOTE: We can't enforce type checks to make sure the key is Action, you - * will know if you got something wrong if there is a type error in this file - */ -type ActionArgsMap = { - "seek-forward": { seconds: number } | undefined; // Args needed for seeking forward (default: 1) - "seek-backward": { seconds: number } | undefined; // Args needed for seeking backward (default: 1) - "jump-forward": { seconds: number } | undefined; // Args needed for jumping forward (default: 5) - "jump-backward": { seconds: number } | undefined; // Args needed for jumping backward (default: 5) -}; - -type KeysWithValueUndefined<T> = { - [K in keyof T]: undefined extends T[K] ? K : never; -}[keyof T]; - -/** - * Actions which require arguments for their invocation - */ -export type ActionWithArgs = keyof ActionArgsMap; - -/** - * Actions which optionally takes in arguments for their invocation - */ - -export type ActionWithOptionalArgs = - | ActionWithNoArgs - | KeysWithValueUndefined<ActionArgsMap>; - -/** - * Actions which do not require arguments for their invocation - */ -export type ActionWithNoArgs = Exclude<Action, ActionWithArgs>; - -/** - * Resolves the argument type for a given Action - */ -type ArgOfHoppAction<A extends Action> = A extends ActionWithArgs - ? ActionArgsMap[A] - : undefined; - -/** - * Resolves the action function for a given Action, used by action handler function defs - */ -type ActionFunc<A extends Action> = A extends ActionWithArgs - ? (arg: ArgOfHoppAction<A>, trigger?: InvocationTriggers) => void - : (_?: undefined, trigger?: InvocationTriggers) => void; - -type BoundActionList = { - [A in Action]?: Array<ActionFunc<A>>; -}; - -const boundActions: BoundActionList = {}; - -let currentActiveActions: Action[] = []; - -function updateActiveActions() { - const newActions = Object.keys(boundActions) as Action[]; - currentActiveActions = newActions; - actionEmitter.emit(newActions); -} - -export function bindAction<A extends Action>( - action: A, - handler: ActionFunc<A> -) { - if (boundActions[action]) { - boundActions[action]?.push(handler); - } else { - // 'any' assertion because TypeScript doesn't seem to be able to figure out the links. - boundActions[action] = [handler] as any; - } - - updateActiveActions(); -} - -export type InvocationTriggers = "keypress" | "mouseclick"; - -type InvokeActionFunc = { - ( - action: ActionWithOptionalArgs, - args?: undefined, - trigger?: InvocationTriggers - ): void; - <A extends ActionWithArgs>(action: A, args: ActionArgsMap[A]): void; -}; - -/** - * Invokes an action, triggering action handlers if any registered. - * The second and third arguments are optional - * @param action The action to fire - * @param args The argument passed to the action handler. Optional if action has no args required - * @param trigger Optionally supply the trigger that invoked the action (keypress/mouseclick) - */ -export const invokeAction: InvokeActionFunc = <A extends Action>( - action: A, - args?: ArgOfHoppAction<A>, - trigger?: InvocationTriggers -) => { - boundActions[action]?.forEach((handler) => (handler as any)(args, trigger)); -}; - -export function unbindAction<A extends Action>( - action: A, - handler: ActionFunc<A> -) { - // 'any' assertion because TypeScript doesn't seem to be able to figure out the links. - boundActions[action] = boundActions[action]?.filter( - (x) => x !== handler - ) as any; - - if (boundActions[action]?.length === 0) { - delete boundActions[action]; - } - - updateActiveActions(); -} - -/** - * Returns whether a given action is bound at a given time - * - * @param action The action to check - */ -export function isActionBound(action: Action): boolean { - return !!boundActions[action]; -} - -/** - * A React hook that defines a component can handle a given - * Action. The handler will be bound when the component is mounted - * and unbound when the component is unmounted. - * @param action The action to be bound - * @param handler The function to be called when the action is invoked - * @param isActive A ref that indicates whether the action is active - */ -export function useActionHandler<A extends Action>( - action: A, - handler: ActionFunc<A>, - isActive: MutableRefObject<boolean> | boolean | undefined -) { - const handlerRef = useRef(handler); - const [isBound, setIsBound] = useState(false); - - // Update handler ref when handler changes - useEffect(() => { - handlerRef.current = handler; - }, [handler]); - - // Create a stable handler wrapper - const stableHandler = useCallback( - (args: any, trigger?: InvocationTriggers) => { - (handlerRef.current as any)(args, trigger); - }, - [] - ) as ActionFunc<A>; - - useEffect(() => { - const shouldBind = - isActive === undefined || - (typeof isActive === "boolean" ? isActive : isActive.current); - - if (shouldBind && !isBound) { - bindAction(action, stableHandler); - setIsBound(true); - } else if (!shouldBind && isBound) { - unbindAction(action, stableHandler); - setIsBound(false); - } - - return () => { - if (isBound) { - unbindAction(action, stableHandler); - setIsBound(false); - } - }; - }, [action, stableHandler, isActive, isBound]); - - // Handle ref-based isActive changes - useEffect(() => { - if (isActive && typeof isActive === "object" && "current" in isActive) { - // Poll for ref changes - const interval = setInterval(() => { - const shouldBind = isActive.current; - if (shouldBind !== isBound) { - if (shouldBind) { - bindAction(action, stableHandler); - } else { - unbindAction(action, stableHandler); - } - setIsBound(shouldBind); - } - }, 100); - return () => clearInterval(interval); - } - }, [action, stableHandler, isActive, isBound]); -} - -/** - * A React hook that returns the current list of active actions - * and re-renders when the list changes - */ -export function useActiveActions(): Action[] { - const [activeActions, setActiveActions] = useState<Action[]>([]); - - useEffect(() => { - // Set initial value - setActiveActions(currentActiveActions); - - // Subscribe to changes - const unsubscribe = actionEmitter.subscribe(setActiveActions); - return unsubscribe; - }, []); - - return activeActions; -} - -/** - * A React hook that returns whether a specific action is currently bound - * and re-renders when the binding state changes - */ -export function useIsActionBound(action: Action): boolean { - const [isBound, setIsBound] = useState(() => isActionBound(action)); - - useEffect(() => { - const updateBoundState = () => { - setIsBound(isActionBound(action)); - }; - - // Set initial value - updateBoundState(); - - // Subscribe to changes - const unsubscribe = actionEmitter.subscribe(updateBoundState); - return unsubscribe; - }, [action]); - - return isBound; -} diff --git a/apps/web/src/constants/editor-constants.ts b/apps/web/src/constants/editor-constants.ts new file mode 100644 index 00000000..d9570825 --- /dev/null +++ b/apps/web/src/constants/editor-constants.ts @@ -0,0 +1,15 @@ +import type { TPlatformLayout } from "@/types/editor"; + +export const PLATFORM_LAYOUTS: Record<TPlatformLayout, string> = { + tiktok: "TikTok", +}; + +export const PANEL_CONFIG = { + panels: { + tools: 25, + preview: 50, + properties: 25, + mainContent: 70, + timeline: 30, + }, +}; diff --git a/apps/web/src/constants/export-constants.ts b/apps/web/src/constants/export-constants.ts new file mode 100644 index 00000000..4e27aae5 --- /dev/null +++ b/apps/web/src/constants/export-constants.ts @@ -0,0 +1,12 @@ +import type { ExportOptions } from "@/types/export"; + +export const DEFAULT_EXPORT_OPTIONS = { + format: "mp4", + quality: "high", + includeAudio: true, +} satisfies ExportOptions; + +export const EXPORT_MIME_TYPES = { + webm: "video/webm", + mp4: "video/mp4", +} as const; diff --git a/apps/web/src/constants/font-constants.ts b/apps/web/src/constants/font-constants.ts index a1c4e4d5..46f84848 100644 --- a/apps/web/src/constants/font-constants.ts +++ b/apps/web/src/constants/font-constants.ts @@ -1,66 +1,66 @@ export interface FontOption { - value: string; - label: string; - category: "system" | "google" | "custom"; - weights?: number[]; - hasClassName?: boolean; + value: string; + label: string; + category: "system" | "google" | "custom"; + weights?: number[]; + hasClassName?: boolean; } export const FONT_OPTIONS: FontOption[] = [ - // System fonts (always available) - { value: "Arial", label: "Arial", category: "system", hasClassName: false }, - { - value: "Helvetica", - label: "Helvetica", - category: "system", - hasClassName: false, - }, - { - value: "Times New Roman", - label: "Times New Roman", - category: "system", - hasClassName: false, - }, - { - value: "Georgia", - label: "Georgia", - category: "system", - hasClassName: false, - }, + // System fonts (always available) + { value: "Arial", label: "Arial", category: "system", hasClassName: false }, + { + value: "Helvetica", + label: "Helvetica", + category: "system", + hasClassName: false, + }, + { + value: "Times New Roman", + label: "Times New Roman", + category: "system", + hasClassName: false, + }, + { + value: "Georgia", + label: "Georgia", + category: "system", + hasClassName: false, + }, - // Google Fonts (loaded in layout.tsx) - { - value: "Inter", - label: "Inter", - category: "google", - weights: [400, 700], - hasClassName: true, - }, - { - value: "Roboto", - label: "Roboto", - category: "google", - weights: [400, 700], - hasClassName: true, - }, - { - value: "Open Sans", - label: "Open Sans", - category: "google", - hasClassName: true, - }, - { - value: "Playfair Display", - label: "Playfair Display", - category: "google", - hasClassName: true, - }, - { - value: "Comic Neue", - label: "Comic Neue", - category: "google", - hasClassName: false, - }, + // Google Fonts (loaded in layout.tsx) + { + value: "Inter", + label: "Inter", + category: "google", + weights: [400, 700], + hasClassName: true, + }, + { + value: "Roboto", + label: "Roboto", + category: "google", + weights: [400, 700], + hasClassName: true, + }, + { + value: "Open Sans", + label: "Open Sans", + category: "google", + hasClassName: true, + }, + { + value: "Playfair Display", + label: "Playfair Display", + category: "google", + hasClassName: true, + }, + { + value: "Comic Neue", + label: "Comic Neue", + category: "google", + hasClassName: false, + }, ] as const; export const DEFAULT_FONT = "Arial"; @@ -70,10 +70,10 @@ export type FontFamily = (typeof FONT_OPTIONS)[number]["value"]; // Helper functions export const getFontByValue = (value: string): FontOption | undefined => - FONT_OPTIONS.find((font) => font.value === value); + FONT_OPTIONS.find((font) => font.value === value); export const getGoogleFonts = (): FontOption[] => - FONT_OPTIONS.filter((font) => font.category === "google"); + FONT_OPTIONS.filter((font) => font.category === "google"); export const getSystemFonts = (): FontOption[] => - FONT_OPTIONS.filter((font) => font.category === "system"); + FONT_OPTIONS.filter((font) => font.category === "system"); diff --git a/apps/web/src/constants/language-constants.ts b/apps/web/src/constants/language-constants.ts new file mode 100644 index 00000000..3da36661 --- /dev/null +++ b/apps/web/src/constants/language-constants.ts @@ -0,0 +1,11 @@ +export const LANGUAGES = [ + { code: "en", name: "English" }, + { code: "es", name: "Spanish" }, + { code: "it", name: "Italian" }, + { code: "fr", name: "French" }, + { code: "de", name: "German" }, + { code: "pt", name: "Portuguese" }, + { code: "ru", name: "Russian" }, + { code: "ja", name: "Japanese" }, + { code: "zh", name: "Chinese" }, +] as const; diff --git a/apps/web/src/constants/project-constants.ts b/apps/web/src/constants/project-constants.ts new file mode 100644 index 00000000..7e3f06b6 --- /dev/null +++ b/apps/web/src/constants/project-constants.ts @@ -0,0 +1,27 @@ +import type { TCanvasSize } from "@/types/project"; + +export const DEFAULT_CANVAS_PRESETS: TCanvasSize[] = [ + { width: 1920, height: 1080 }, + { width: 1080, height: 1920 }, + { width: 1080, height: 1080 }, + { width: 1440, height: 1080 }, +]; + +export const FPS_PRESETS = [ + { value: "24", label: "24 fps" }, + { value: "25", label: "25 fps" }, + { value: "30", label: "30 fps" }, + { value: "60", label: "60 fps" }, + { value: "120", label: "120 fps" }, +] as const; + +export const BLUR_INTENSITY_PRESETS: { label: string; value: number }[] = [ + { label: "Light", value: 4 }, + { label: "Medium", value: 8 }, + { label: "Heavy", value: 18 }, +] as const; + +export const DEFAULT_CANVAS_SIZE: TCanvasSize = { width: 1920, height: 1080 }; +export const DEFAULT_FPS = 30; +export const DEFAULT_BLUR_INTENSITY = 8; +export const DEFAULT_COLOR = "#000000"; diff --git a/apps/web/src/constants/site-constants.ts b/apps/web/src/constants/site-constants.ts new file mode 100644 index 00000000..ac6cd2cd --- /dev/null +++ b/apps/web/src/constants/site-constants.ts @@ -0,0 +1,66 @@ +import { OcDataBuddyIcon, OcMarbleIcon, } from "@opencut/ui/icons"; + +export const SITE_URL = "https://opencut.app"; + +export const SITE_INFO = { + title: "OpenCut", + description: + "A simple but powerful video editor that gets the job done. In your browser.", + url: SITE_URL, + openGraphImage: "/open-graph/default.jpg", + twitterImage: "/open-graph/default.jpg", + favicon: "/favicon.ico", +}; + +export type ExternalTool = { + name: string; + description: string; + url: string; + icon: React.ElementType; +}; + +export const EXTERNAL_TOOLS: ExternalTool[] = [ + { + name: "Marble", + description: + "Modern headless CMS for content management and the blog for OpenCut", + url: "https://marblecms.com?utm_source=opencut", + icon: OcMarbleIcon, + }, + { + name: "Databuddy", + description: "GDPR compliant analytics and user insights for OpenCut", + url: "https://databuddy.cc?utm_source=opencut", + icon: OcDataBuddyIcon, + }, +]; + +export const DEFAULT_LOGO_URL = "/logos/opencut/svg/logo.svg"; + +export const SOCIAL_LINKS = { + x: "https://x.com/opencutapp", + github: "https://github.com/OpenCut-app/OpenCut", + discord: "https://discord.com/invite/Mu3acKZvCp", +}; + +export type Sponsor = { + name: string; + url: string; + logo: string; + description: string; +}; + +export const SPONSORS: Sponsor[] = [ + { + name: "Fal.ai", + url: "https://fal.ai?utm_source=opencut", + logo: "/logos/others/fal.svg", + description: "Generative image, video, and audio models all in one place.", + }, + { + name: "Vercel", + url: "https://vercel.com?utm_source=opencut", + logo: "/logos/others/vercel.svg", + description: "Platform where we deploy and host OpenCut.", + }, +]; diff --git a/apps/web/src/constants/site.ts b/apps/web/src/constants/site.ts deleted file mode 100644 index 00bd649f..00000000 --- a/apps/web/src/constants/site.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const SITE_URL = "https://opencut.app"; - -export const SITE_INFO = { - title: "OpenCut", - description: - "A simple but powerful video editor that gets the job done. In your browser.", - url: SITE_URL, - openGraphImage: "/open-graph/default.jpg", - twitterImage: "/open-graph/default.jpg", - favicon: "/favicon.ico", -}; - -export const EXTERNAL_TOOLS = [ - { - name: "Marble", - description: - "Modern headless CMS for content management and the blog for OpenCut", - url: "https://marblecms.com?utm_source=opencut", - icon: "MarbleIcon" as const, - }, - { - name: "Vercel", - description: "Platform where we deploy and host OpenCut", - url: "https://vercel.com?utm_source=opencut", - icon: "VercelIcon" as const, - }, - { - name: "Databuddy", - description: "GDPR compliant analytics and user insights for OpenCut", - url: "https://databuddy.cc?utm_source=opencut", - icon: "DataBuddyIcon" as const, - }, -]; diff --git a/apps/web/src/constants/stickers-constants.ts b/apps/web/src/constants/stickers-constants.ts new file mode 100644 index 00000000..3c98d4f7 --- /dev/null +++ b/apps/web/src/constants/stickers-constants.ts @@ -0,0 +1,16 @@ +export const STICKER_CATEGORIES = [ + "all", + "general", + "brands", + "emoji", +] as const; + +export const STICKER_CATEGORY_CONFIG: Record< + (typeof STICKER_CATEGORIES)[number], + string | undefined +> = { + all: undefined, + general: "General", + brands: "Brands / Social", + emoji: "Emoji", +}; diff --git a/apps/web/src/constants/text-constants.ts b/apps/web/src/constants/text-constants.ts index e3fcce38..c8a23655 100644 --- a/apps/web/src/constants/text-constants.ts +++ b/apps/web/src/constants/text-constants.ts @@ -1,27 +1,29 @@ -import { TextElement } from "@/types/timeline";
+import type { TextElement } from "@/types/timeline";
import { TIMELINE_CONSTANTS } from "./timeline-constants";
-export const DEFAULT_TEXT_ELEMENT: Omit<
- TextElement,
- "id"
-> = {
- type: "text",
- name: "Text",
- content: "Default Text",
- fontSize: 48,
- fontFamily: "Arial",
- color: "#ffffff",
- backgroundColor: "transparent",
- textAlign: "center",
- fontWeight: "normal",
- fontStyle: "normal",
- textDecoration: "none",
- x: 0,
- y: 0,
- rotation: 0,
- opacity: 1,
- duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION,
- startTime: 0,
- trimStart: 0,
- trimEnd: 0,
+export const DEFAULT_TEXT_ELEMENT: Omit<TextElement, "id"> = {
+ type: "text",
+ name: "Text",
+ content: "Default Text",
+ fontSize: 48,
+ fontFamily: "Arial",
+ color: "#ffffff",
+ backgroundColor: "transparent",
+ textAlign: "center",
+ fontWeight: "normal",
+ fontStyle: "normal",
+ textDecoration: "none",
+ duration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,
+ startTime: 0,
+ trimStart: 0,
+ trimEnd: 0,
+ transform: {
+ scale: 1,
+ position: {
+ x: 0,
+ y: 0,
+ },
+ rotate: 0,
+ },
+ opacity: 1,
};
diff --git a/apps/web/src/constants/timeline-constants.ts b/apps/web/src/constants/timeline-constants.ts deleted file mode 100644 index 4516775f..00000000 --- a/apps/web/src/constants/timeline-constants.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { TrackType } from "@/types/timeline"; - -// Track color definitions -export const TRACK_COLORS: Record< - TrackType, - { solid: string; background: string; border: string } -> = { - media: { - solid: "bg-blue-500", - background: "", - border: "", - }, - text: { - solid: "bg-[#5DBAA0]", - background: "bg-[#5DBAA0]", - border: "", - }, - audio: { - solid: "bg-green-500", - background: "bg-[#915DBE]", - border: "", - }, -} as const; - -// Utility functions -export function getTrackColors(type: TrackType) { - return TRACK_COLORS[type]; -} - -export function getTrackElementClasses(type: TrackType) { - const colors = getTrackColors(type); - return `${colors.background} ${colors.border}`; -} - -// Track height definitions -export const TRACK_HEIGHTS: Record<TrackType, number> = { - media: 60, - text: 25, - audio: 50, -} as const; - -// Utility function for track heights -export function getTrackHeight(type: TrackType): number { - return TRACK_HEIGHTS[type]; -} - -// Calculate cumulative height up to (but not including) a track index -export function getCumulativeHeightBefore( - tracks: Array<{ type: TrackType }>, - trackIndex: number -): number { - const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1) - return tracks - .slice(0, trackIndex) - .reduce((sum, track) => sum + getTrackHeight(track.type) + GAP, 0); -} - -// Calculate total height of all tracks -export function getTotalTracksHeight( - tracks: Array<{ type: TrackType }> -): number { - const GAP = 4; // 4px gap between tracks (equivalent to Tailwind's gap-1) - const tracksHeight = tracks.reduce( - (sum, track) => sum + getTrackHeight(track.type), - 0 - ); - const gapsHeight = Math.max(0, tracks.length - 1) * GAP; // n-1 gaps for n tracks - return tracksHeight + gapsHeight; -} - -// Other timeline constants -export const TIMELINE_CONSTANTS = { - ELEMENT_MIN_WIDTH: 80, - PIXELS_PER_SECOND: 50, - TRACK_HEIGHT: 60, // Default fallback - DEFAULT_TEXT_DURATION: 5, - DEFAULT_IMAGE_DURATION: 5, - ZOOM_LEVELS: [0.25, 0.5, 1, 1.5, 2, 3, 4], -} as const; - -// FPS presets for project settings -export const FPS_PRESETS = [ - { value: "24", label: "24 fps" }, - { value: "25", label: "25 fps" }, - { value: "30", label: "30 fps" }, - { value: "60", label: "60 fps" }, - { value: "120", label: "120 fps" }, -] as const; - -// Frame snapping utilities -export function timeToFrame(time: number, fps: number): number { - return Math.round(time * fps); -} - -export function frameToTime(frame: number, fps: number): number { - return frame / fps; -} - -export function snapTimeToFrame(time: number, fps: number): number { - if (fps <= 0) return time; // Fallback for invalid FPS - const frame = timeToFrame(time, fps); - return frameToTime(frame, fps); -} - -export function getFrameDuration(fps: number): number { - return 1 / fps; -} diff --git a/apps/web/src/constants/timeline-constants.tsx b/apps/web/src/constants/timeline-constants.tsx new file mode 100644 index 00000000..692ccd65 --- /dev/null +++ b/apps/web/src/constants/timeline-constants.tsx @@ -0,0 +1,72 @@ +import type { TTimelineViewState } from "@/types/project"; +import type { TrackType } from "@/types/timeline"; +import { + Happy01Icon, + MusicNote03Icon, + TextIcon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { OcVideoIcon } from "@opencut/ui/icons"; + +export const TRACK_COLORS: Record<TrackType, { background: string }> = { + video: { + background: "transparent", + }, + text: { + background: "bg-[#5DBAA0]", + }, + audio: { + background: "bg-[#915DBE]", + }, + sticker: { + background: "bg-amber-500", + }, +} as const; + +export const TRACK_HEIGHTS: Record<TrackType, number> = { + video: 60, + text: 25, + audio: 50, + sticker: 50, +} as const; + +export const TRACK_GAP = 4; + +export const TIMELINE_CONSTANTS = { + PIXELS_PER_SECOND: 50, + DEFAULT_ELEMENT_DURATION: 5, + PLAYHEAD_LOOKAHEAD_SECONDS: 30, // padding ahead + PADDING_TOP_PX: 0, + ZOOM_LEVELS: [0.1, 0.25, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 10, 15, 20, 30, 50], + ZOOM_MIN: 0.1, + ZOOM_MAX: 100, + ZOOM_STEP: 0.1, +} as const; + +export const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState = { + zoomLevel: 1, + scrollLeft: 0, + playheadTime: 0, +}; + +export const TRACK_ICONS: Record<TrackType, React.ReactNode> = { + video: <OcVideoIcon className="text-muted-foreground size-4 shrink-0" />, + text: ( + <HugeiconsIcon + icon={TextIcon} + className="text-muted-foreground size-4 shrink-0" + /> + ), + audio: ( + <HugeiconsIcon + icon={MusicNote03Icon} + className="text-muted-foreground size-4 shrink-0" + /> + ), + sticker: ( + <HugeiconsIcon + icon={Happy01Icon} + className="text-muted-foreground size-4 shrink-0" + /> + ), +} as const; diff --git a/apps/web/src/constants/transcription-constants.ts b/apps/web/src/constants/transcription-constants.ts new file mode 100644 index 00000000..3fa914f2 --- /dev/null +++ b/apps/web/src/constants/transcription-constants.ts @@ -0,0 +1,58 @@ +import { LANGUAGES } from "@/constants/language-constants"; +import type { + TranscriptionModel, + TranscriptionModelId, +} from "@/types/transcription"; +import type { LanguageCode } from "@/types/language"; + +const SUPPORTED_TRANSCRIPTION_LANGS: ReadonlyArray<LanguageCode> = [ + "en", + "es", + "it", + "fr", + "de", + "pt", + "ru", + "ja", + "zh", +]; + +export const TRANSCRIPTION_LANGUAGES = LANGUAGES.filter((language) => + SUPPORTED_TRANSCRIPTION_LANGS.includes(language.code), +); + +export const TRANSCRIPTION_MODELS: TranscriptionModel[] = [ + { + id: "whisper-tiny", + name: "Tiny", + huggingFaceId: "onnx-community/whisper-tiny", + description: "Fastest, lower accuracy", + }, + { + id: "whisper-small", + name: "Small", + huggingFaceId: "onnx-community/whisper-small", + description: "Good balance of speed and accuracy", + }, + { + id: "whisper-medium", + name: "Medium", + huggingFaceId: "onnx-community/whisper-medium", + description: "Higher accuracy, slower", + }, + { + id: "whisper-large-v3-turbo", + name: "Large v3 Turbo", + huggingFaceId: "onnx-community/whisper-large-v3-turbo", + description: "Best accuracy, requires WebGPU for good performance", + }, +]; + +export const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId = + "whisper-small"; + +export const DEFAULT_CHUNK_LENGTH_SECONDS = 30; +export const DEFAULT_STRIDE_SECONDS = 5; + +export const DEFAULT_WORDS_PER_CAPTION = 3; +export const MIN_CAPTION_DURATION_SECONDS = 0.8; |
