diff --git a/src/components/video-editor/editorPreferences.test.ts b/src/components/video-editor/editorPreferences.test.ts index 009aff45..848240a2 100644 --- a/src/components/video-editor/editorPreferences.test.ts +++ b/src/components/video-editor/editorPreferences.test.ts @@ -78,8 +78,17 @@ describe("editorPreferences", () => { backgroundBlur: 3.5, zoomMotionBlur: DEFAULT_EDITOR_PREFERENCES.zoomMotionBlur, connectZooms: DEFAULT_EDITOR_PREFERENCES.connectZooms, + zoomInDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomInDurationMs, + zoomInOverlapMs: DEFAULT_EDITOR_PREFERENCES.zoomInOverlapMs, + zoomOutDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomOutDurationMs, + connectedZoomGapMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomGapMs, + connectedZoomDurationMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomDurationMs, + zoomInEasing: DEFAULT_EDITOR_PREFERENCES.zoomInEasing, + zoomOutEasing: DEFAULT_EDITOR_PREFERENCES.zoomOutEasing, + connectedZoomEasing: DEFAULT_EDITOR_PREFERENCES.connectedZoomEasing, showCursor: false, loopCursor: DEFAULT_EDITOR_PREFERENCES.loopCursor, + cursorStyle: DEFAULT_EDITOR_PREFERENCES.cursorStyle, cursorSize: DEFAULT_EDITOR_PREFERENCES.cursorSize, cursorSmoothing: DEFAULT_EDITOR_PREFERENCES.cursorSmoothing, cursorMotionBlur: DEFAULT_EDITOR_PREFERENCES.cursorMotionBlur, @@ -98,6 +107,8 @@ describe("editorPreferences", () => { customAspectWidth: "21", customAspectHeight: "9", customWallpapers: ["data:image/jpeg;base64,abc"], + whisperExecutablePath: DEFAULT_EDITOR_PREFERENCES.whisperExecutablePath, + whisperModelPath: DEFAULT_EDITOR_PREFERENCES.whisperModelPath, }); }); @@ -133,8 +144,17 @@ describe("editorPreferences", () => { backgroundBlur: DEFAULT_EDITOR_PREFERENCES.backgroundBlur, zoomMotionBlur: DEFAULT_EDITOR_PREFERENCES.zoomMotionBlur, connectZooms: DEFAULT_EDITOR_PREFERENCES.connectZooms, + zoomInDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomInDurationMs, + zoomInOverlapMs: DEFAULT_EDITOR_PREFERENCES.zoomInOverlapMs, + zoomOutDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomOutDurationMs, + connectedZoomGapMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomGapMs, + connectedZoomDurationMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomDurationMs, + zoomInEasing: DEFAULT_EDITOR_PREFERENCES.zoomInEasing, + zoomOutEasing: DEFAULT_EDITOR_PREFERENCES.zoomOutEasing, + connectedZoomEasing: DEFAULT_EDITOR_PREFERENCES.connectedZoomEasing, showCursor: DEFAULT_EDITOR_PREFERENCES.showCursor, loopCursor: DEFAULT_EDITOR_PREFERENCES.loopCursor, + cursorStyle: DEFAULT_EDITOR_PREFERENCES.cursorStyle, cursorSize: DEFAULT_EDITOR_PREFERENCES.cursorSize, cursorSmoothing: DEFAULT_EDITOR_PREFERENCES.cursorSmoothing, cursorMotionBlur: DEFAULT_EDITOR_PREFERENCES.cursorMotionBlur, @@ -152,6 +172,8 @@ describe("editorPreferences", () => { customAspectWidth: "21", customAspectHeight: "9", customWallpapers: DEFAULT_EDITOR_PREFERENCES.customWallpapers, + whisperExecutablePath: DEFAULT_EDITOR_PREFERENCES.whisperExecutablePath, + whisperModelPath: DEFAULT_EDITOR_PREFERENCES.whisperModelPath, }); }); @@ -165,8 +187,17 @@ describe("editorPreferences", () => { backgroundBlur: 1.5, zoomMotionBlur: 0.75, connectZooms: false, + zoomInDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomInDurationMs, + zoomInOverlapMs: DEFAULT_EDITOR_PREFERENCES.zoomInOverlapMs, + zoomOutDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomOutDurationMs, + connectedZoomGapMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomGapMs, + connectedZoomDurationMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomDurationMs, + zoomInEasing: DEFAULT_EDITOR_PREFERENCES.zoomInEasing, + zoomOutEasing: DEFAULT_EDITOR_PREFERENCES.zoomOutEasing, + connectedZoomEasing: DEFAULT_EDITOR_PREFERENCES.connectedZoomEasing, showCursor: false, loopCursor: true, + cursorStyle: "figma", cursorSize: 3, cursorSmoothing: 1.25, cursorMotionBlur: 0.5, @@ -192,8 +223,17 @@ describe("editorPreferences", () => { backgroundBlur: 1.5, zoomMotionBlur: 0.75, connectZooms: false, + zoomInDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomInDurationMs, + zoomInOverlapMs: DEFAULT_EDITOR_PREFERENCES.zoomInOverlapMs, + zoomOutDurationMs: DEFAULT_EDITOR_PREFERENCES.zoomOutDurationMs, + connectedZoomGapMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomGapMs, + connectedZoomDurationMs: DEFAULT_EDITOR_PREFERENCES.connectedZoomDurationMs, + zoomInEasing: DEFAULT_EDITOR_PREFERENCES.zoomInEasing, + zoomOutEasing: DEFAULT_EDITOR_PREFERENCES.zoomOutEasing, + connectedZoomEasing: DEFAULT_EDITOR_PREFERENCES.connectedZoomEasing, showCursor: false, loopCursor: true, + cursorStyle: "figma", cursorSize: 3, cursorSmoothing: 1.25, cursorMotionBlur: 0.5, @@ -212,6 +252,8 @@ describe("editorPreferences", () => { customAspectWidth: "4", customAspectHeight: "5", customWallpapers: ["data:image/jpeg;base64,abc"], + whisperExecutablePath: DEFAULT_EDITOR_PREFERENCES.whisperExecutablePath, + whisperModelPath: DEFAULT_EDITOR_PREFERENCES.whisperModelPath, }); }); }); diff --git a/src/components/video-editor/editorPreferences.ts b/src/components/video-editor/editorPreferences.ts index 12d6d058..6523de7b 100644 --- a/src/components/video-editor/editorPreferences.ts +++ b/src/components/video-editor/editorPreferences.ts @@ -7,8 +7,17 @@ type PersistedEditorControls = Pick< | "backgroundBlur" | "zoomMotionBlur" | "connectZooms" + | "zoomInDurationMs" + | "zoomInOverlapMs" + | "zoomOutDurationMs" + | "connectedZoomGapMs" + | "connectedZoomDurationMs" + | "zoomInEasing" + | "zoomOutEasing" + | "connectedZoomEasing" | "showCursor" | "loopCursor" + | "cursorStyle" | "cursorSize" | "cursorSmoothing" | "cursorMotionBlur" @@ -32,6 +41,8 @@ export interface EditorPreferences extends PersistedEditorControls { customAspectWidth: string; customAspectHeight: string; customWallpapers: string[]; + whisperExecutablePath: string | null; + whisperModelPath: string | null; } export const EDITOR_PREFERENCES_STORAGE_KEY = "recordly.editor.preferences"; @@ -44,8 +55,17 @@ export const DEFAULT_EDITOR_PREFERENCES: EditorPreferences = { backgroundBlur: DEFAULT_EDITOR_CONTROLS.backgroundBlur, zoomMotionBlur: DEFAULT_EDITOR_CONTROLS.zoomMotionBlur, connectZooms: DEFAULT_EDITOR_CONTROLS.connectZooms, + zoomInDurationMs: DEFAULT_EDITOR_CONTROLS.zoomInDurationMs, + zoomInOverlapMs: DEFAULT_EDITOR_CONTROLS.zoomInOverlapMs, + zoomOutDurationMs: DEFAULT_EDITOR_CONTROLS.zoomOutDurationMs, + connectedZoomGapMs: DEFAULT_EDITOR_CONTROLS.connectedZoomGapMs, + connectedZoomDurationMs: DEFAULT_EDITOR_CONTROLS.connectedZoomDurationMs, + zoomInEasing: DEFAULT_EDITOR_CONTROLS.zoomInEasing, + zoomOutEasing: DEFAULT_EDITOR_CONTROLS.zoomOutEasing, + connectedZoomEasing: DEFAULT_EDITOR_CONTROLS.connectedZoomEasing, showCursor: DEFAULT_EDITOR_CONTROLS.showCursor, loopCursor: DEFAULT_EDITOR_CONTROLS.loopCursor, + cursorStyle: DEFAULT_EDITOR_CONTROLS.cursorStyle, cursorSize: DEFAULT_EDITOR_CONTROLS.cursorSize, cursorSmoothing: DEFAULT_EDITOR_CONTROLS.cursorSmoothing, cursorMotionBlur: DEFAULT_EDITOR_CONTROLS.cursorMotionBlur, @@ -64,6 +84,8 @@ export const DEFAULT_EDITOR_PREFERENCES: EditorPreferences = { customAspectWidth: "16", customAspectHeight: "9", customWallpapers: [], + whisperExecutablePath: null, + whisperModelPath: null, }; function normalizePositiveIntegerString(value: unknown, fallback: string): string { @@ -89,6 +111,15 @@ function normalizeCustomWallpapers(value: unknown, fallback: string[]): string[] ); } +function normalizeNullablePath(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + function normalizeEditorControls( raw: Partial, fallback: EditorPreferences, @@ -99,8 +130,19 @@ function normalizeEditorControls( backgroundBlur: raw.backgroundBlur ?? fallback.backgroundBlur, zoomMotionBlur: raw.zoomMotionBlur ?? fallback.zoomMotionBlur, connectZooms: raw.connectZooms ?? fallback.connectZooms, + zoomInDurationMs: raw.zoomInDurationMs ?? fallback.zoomInDurationMs, + zoomInOverlapMs: raw.zoomInOverlapMs ?? fallback.zoomInOverlapMs, + zoomOutDurationMs: raw.zoomOutDurationMs ?? fallback.zoomOutDurationMs, + connectedZoomGapMs: raw.connectedZoomGapMs ?? fallback.connectedZoomGapMs, + connectedZoomDurationMs: + raw.connectedZoomDurationMs ?? fallback.connectedZoomDurationMs, + zoomInEasing: raw.zoomInEasing ?? fallback.zoomInEasing, + zoomOutEasing: raw.zoomOutEasing ?? fallback.zoomOutEasing, + connectedZoomEasing: + raw.connectedZoomEasing ?? fallback.connectedZoomEasing, showCursor: raw.showCursor ?? fallback.showCursor, loopCursor: raw.loopCursor ?? fallback.loopCursor, + cursorStyle: raw.cursorStyle ?? fallback.cursorStyle, cursorSize: raw.cursorSize ?? fallback.cursorSize, cursorSmoothing: raw.cursorSmoothing ?? fallback.cursorSmoothing, cursorMotionBlur: raw.cursorMotionBlur ?? fallback.cursorMotionBlur, @@ -127,8 +169,17 @@ function normalizeEditorControls( backgroundBlur: normalized.backgroundBlur, zoomMotionBlur: normalized.zoomMotionBlur, connectZooms: normalized.connectZooms, + zoomInDurationMs: normalized.zoomInDurationMs, + zoomInOverlapMs: normalized.zoomInOverlapMs, + zoomOutDurationMs: normalized.zoomOutDurationMs, + connectedZoomGapMs: normalized.connectedZoomGapMs, + connectedZoomDurationMs: normalized.connectedZoomDurationMs, + zoomInEasing: normalized.zoomInEasing, + zoomOutEasing: normalized.zoomOutEasing, + connectedZoomEasing: normalized.connectedZoomEasing, showCursor: normalized.showCursor, loopCursor: normalized.loopCursor, + cursorStyle: normalized.cursorStyle, cursorSize: normalized.cursorSize, cursorSmoothing: normalized.cursorSmoothing, cursorMotionBlur: normalized.cursorMotionBlur, @@ -165,6 +216,10 @@ export function normalizeEditorPreferences( fallback.customAspectHeight, ), customWallpapers: normalizeCustomWallpapers(raw.customWallpapers, fallback.customWallpapers), + whisperExecutablePath: + normalizeNullablePath(raw.whisperExecutablePath) ?? fallback.whisperExecutablePath, + whisperModelPath: + normalizeNullablePath(raw.whisperModelPath) ?? fallback.whisperModelPath, }; } diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index e7d90232..7720b975 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -2,9 +2,16 @@ import type { ExportFormat, ExportQuality, GifFrameRate, GifSizePreset } from "@ import { DEFAULT_WALLPAPER_PATH } from "@/lib/wallpapers"; import { ASPECT_RATIOS, type AspectRatio, isCustomAspectRatio } from "@/utils/aspectRatioUtils"; import { + type AutoCaptionAnimation, + type AutoCaptionSettings, + type CaptionCue, + type CaptionCueWord, type AnnotationRegion, type AudioRegion, type CropRegion, + type CursorStyle, + DEFAULT_AUTO_CAPTION_SETTINGS, + getDefaultCaptionFontFamily, DEFAULT_ANNOTATION_POSITION, DEFAULT_ANNOTATION_SIZE, DEFAULT_ANNOTATION_STYLE, @@ -13,8 +20,12 @@ import { DEFAULT_CURSOR_CLICK_BOUNCE_DURATION, DEFAULT_CURSOR_MOTION_BLUR, DEFAULT_CURSOR_SIZE, + DEFAULT_CURSOR_STYLE, DEFAULT_CURSOR_SMOOTHING, DEFAULT_CURSOR_SWAY, + DEFAULT_CONNECTED_ZOOM_DURATION_MS, + DEFAULT_CONNECTED_ZOOM_EASING, + DEFAULT_CONNECTED_ZOOM_GAP_MS, DEFAULT_WEBCAM_CORNER_RADIUS, DEFAULT_WEBCAM_MARGIN, DEFAULT_WEBCAM_OVERLAY, @@ -27,10 +38,16 @@ import { DEFAULT_FIGURE_DATA, DEFAULT_PLAYBACK_SPEED, DEFAULT_ZOOM_DEPTH, + DEFAULT_ZOOM_IN_DURATION_MS, + DEFAULT_ZOOM_IN_EASING, + DEFAULT_ZOOM_IN_OVERLAP_MS, DEFAULT_ZOOM_MOTION_BLUR, + DEFAULT_ZOOM_OUT_DURATION_MS, + DEFAULT_ZOOM_OUT_EASING, type SpeedRegion, type TrimRegion, type WebcamOverlaySettings, + type ZoomTransitionEasing, type ZoomRegion, } from "./types"; @@ -42,8 +59,17 @@ export interface ProjectEditorState { backgroundBlur: number; zoomMotionBlur: number; connectZooms: boolean; + zoomInDurationMs: number; + zoomInOverlapMs: number; + zoomOutDurationMs: number; + connectedZoomGapMs: number; + connectedZoomDurationMs: number; + zoomInEasing: ZoomTransitionEasing; + zoomOutEasing: ZoomTransitionEasing; + connectedZoomEasing: ZoomTransitionEasing; showCursor: boolean; loopCursor: boolean; + cursorStyle: CursorStyle; cursorSize: number; cursorSmoothing: number; cursorMotionBlur: number; @@ -58,6 +84,8 @@ export interface ProjectEditorState { speedRegions: SpeedRegion[]; annotationRegions: AnnotationRegion[]; audioRegions: AudioRegion[]; + autoCaptions: CaptionCue[]; + autoCaptionSettings: AutoCaptionSettings; webcam: WebcamOverlaySettings; aspectRatio: AspectRatio; exportQuality: ExportQuality; @@ -81,6 +109,28 @@ function clamp(value: number, min: number, max: number) { return Math.min(max, Math.max(min, value)); } +function normalizeZoomTransitionEasing( + value: unknown, + fallback: ZoomTransitionEasing, +): ZoomTransitionEasing { + return value === "recordly" || + value === "glide" || + value === "smooth" || + value === "snappy" || + value === "linear" + ? value + : fallback; +} + +function normalizeAutoCaptionAnimation( + value: unknown, + fallback: AutoCaptionAnimation, +): AutoCaptionAnimation { + return value === "none" || value === "fade" || value === "rise" || value === "pop" + ? value + : fallback; +} + function isFileUrl(value: string): boolean { return /^file:\/\//i.test(value); } @@ -187,6 +237,21 @@ export function normalizeProjectEditor(editor: Partial): Pro : legacyShowBlur ? 2 : 0; + const normalizedZoomInDurationMs = isFiniteNumber(editor.zoomInDurationMs) + ? clamp(editor.zoomInDurationMs, 60, 4000) + : DEFAULT_ZOOM_IN_DURATION_MS; + const normalizedZoomInOverlapMs = isFiniteNumber(editor.zoomInOverlapMs) + ? clamp(editor.zoomInOverlapMs, 0, normalizedZoomInDurationMs) + : DEFAULT_ZOOM_IN_OVERLAP_MS; + const normalizedZoomOutDurationMs = isFiniteNumber(editor.zoomOutDurationMs) + ? clamp(editor.zoomOutDurationMs, 60, 4000) + : DEFAULT_ZOOM_OUT_DURATION_MS; + const normalizedConnectedZoomGapMs = isFiniteNumber(editor.connectedZoomGapMs) + ? clamp(editor.connectedZoomGapMs, 0, 5000) + : DEFAULT_CONNECTED_ZOOM_GAP_MS; + const normalizedConnectedZoomDurationMs = isFiniteNumber(editor.connectedZoomDurationMs) + ? clamp(editor.connectedZoomDurationMs, 60, 4000) + : DEFAULT_CONNECTED_ZOOM_DURATION_MS; const normalizedZoomRegions: ZoomRegion[] = Array.isArray(editor.zoomRegions) ? editor.zoomRegions @@ -342,6 +407,95 @@ export function normalizeProjectEditor(editor: Partial): Pro }) : []; + const normalizedAutoCaptions: CaptionCue[] = Array.isArray( + (editor as Partial).autoCaptions, + ) + ? ((editor as Partial).autoCaptions as CaptionCue[]) + .filter((cue): cue is CaptionCue => Boolean(cue && typeof cue.id === "string")) + .map((cue) => { + const rawStart = isFiniteNumber(cue.startMs) ? Math.round(cue.startMs) : 0; + const rawEnd = isFiniteNumber(cue.endMs) ? Math.round(cue.endMs) : rawStart + 1000; + const startMs = Math.max(0, Math.min(rawStart, rawEnd)); + const endMs = Math.max(startMs + 1, rawEnd); + const words: CaptionCueWord[] | undefined = Array.isArray(cue.words) + ? cue.words + .filter( + (word): word is CaptionCueWord => Boolean(word && typeof word.text === "string"), + ) + .map((word) => { + const rawWordStart = isFiniteNumber(word.startMs) ? Math.round(word.startMs) : startMs; + const rawWordEnd = isFiniteNumber(word.endMs) ? Math.round(word.endMs) : rawWordStart + 1; + const normalizedWordStart = clamp(rawWordStart, startMs, endMs - 1); + const normalizedWordEnd = clamp(rawWordEnd, normalizedWordStart + 1, endMs); + + return { + text: word.text.trim(), + startMs: normalizedWordStart, + endMs: normalizedWordEnd, + ...(word.leadingSpace ? { leadingSpace: true } : {}), + }; + }) + .filter((word) => word.text.length > 0) + : undefined; + + return { + id: cue.id, + startMs, + endMs, + text: typeof cue.text === "string" ? cue.text.trim() : "", + ...(words && words.length > 0 ? { words } : {}), + }; + }) + .filter((cue) => cue.text.length > 0) + : []; + + const rawAutoCaptionSettings: Partial = + editor.autoCaptionSettings && typeof editor.autoCaptionSettings === "object" + ? (editor.autoCaptionSettings as Partial) + : {}; + const normalizedAutoCaptionSettings: AutoCaptionSettings = { + enabled: + typeof rawAutoCaptionSettings.enabled === "boolean" + ? rawAutoCaptionSettings.enabled + : DEFAULT_AUTO_CAPTION_SETTINGS.enabled, + language: + typeof rawAutoCaptionSettings.language === "string" && rawAutoCaptionSettings.language.trim() + ? rawAutoCaptionSettings.language.trim() + : DEFAULT_AUTO_CAPTION_SETTINGS.language, + fontFamily: + getDefaultCaptionFontFamily(), + fontSize: isFiniteNumber(rawAutoCaptionSettings.fontSize) + ? clamp(rawAutoCaptionSettings.fontSize, 16, 72) + : DEFAULT_AUTO_CAPTION_SETTINGS.fontSize, + bottomOffset: isFiniteNumber(rawAutoCaptionSettings.bottomOffset) + ? clamp(rawAutoCaptionSettings.bottomOffset, 0, 30) + : DEFAULT_AUTO_CAPTION_SETTINGS.bottomOffset, + maxWidth: isFiniteNumber(rawAutoCaptionSettings.maxWidth) + ? clamp(rawAutoCaptionSettings.maxWidth, 40, 95) + : DEFAULT_AUTO_CAPTION_SETTINGS.maxWidth, + maxRows: isFiniteNumber(rawAutoCaptionSettings.maxRows) + ? clamp(Math.round(rawAutoCaptionSettings.maxRows), 1, 4) + : DEFAULT_AUTO_CAPTION_SETTINGS.maxRows, + animationStyle: normalizeAutoCaptionAnimation( + rawAutoCaptionSettings.animationStyle, + DEFAULT_AUTO_CAPTION_SETTINGS.animationStyle, + ), + boxRadius: isFiniteNumber(rawAutoCaptionSettings.boxRadius) + ? clamp(rawAutoCaptionSettings.boxRadius, 0, 40) + : DEFAULT_AUTO_CAPTION_SETTINGS.boxRadius, + textColor: + typeof rawAutoCaptionSettings.textColor === "string" && rawAutoCaptionSettings.textColor.trim() + ? rawAutoCaptionSettings.textColor + : DEFAULT_AUTO_CAPTION_SETTINGS.textColor, + inactiveTextColor: + typeof rawAutoCaptionSettings.inactiveTextColor === "string" && rawAutoCaptionSettings.inactiveTextColor.trim() + ? rawAutoCaptionSettings.inactiveTextColor + : DEFAULT_AUTO_CAPTION_SETTINGS.inactiveTextColor, + backgroundOpacity: isFiniteNumber(rawAutoCaptionSettings.backgroundOpacity) + ? clamp(rawAutoCaptionSettings.backgroundOpacity, 0, 1) + : DEFAULT_AUTO_CAPTION_SETTINGS.backgroundOpacity, + }; + const rawCropX = isFiniteNumber(editor.cropRegion?.x) ? editor.cropRegion.x : DEFAULT_CROP_REGION.x; @@ -374,8 +528,26 @@ export function normalizeProjectEditor(editor: Partial): Pro backgroundBlur: normalizedBackgroundBlur, zoomMotionBlur: normalizedZoomMotionBlur, connectZooms: typeof editor.connectZooms === "boolean" ? editor.connectZooms : true, + zoomInDurationMs: normalizedZoomInDurationMs, + zoomInOverlapMs: normalizedZoomInOverlapMs, + zoomOutDurationMs: normalizedZoomOutDurationMs, + connectedZoomGapMs: normalizedConnectedZoomGapMs, + connectedZoomDurationMs: normalizedConnectedZoomDurationMs, + zoomInEasing: normalizeZoomTransitionEasing(editor.zoomInEasing, DEFAULT_ZOOM_IN_EASING), + zoomOutEasing: normalizeZoomTransitionEasing(editor.zoomOutEasing, DEFAULT_ZOOM_OUT_EASING), + connectedZoomEasing: normalizeZoomTransitionEasing( + editor.connectedZoomEasing, + DEFAULT_CONNECTED_ZOOM_EASING, + ), showCursor: typeof editor.showCursor === "boolean" ? editor.showCursor : true, loopCursor: typeof editor.loopCursor === "boolean" ? editor.loopCursor : false, + cursorStyle: + editor.cursorStyle === "dot" || + editor.cursorStyle === "figma" || + editor.cursorStyle === "mono" || + editor.cursorStyle === "tahoe" + ? editor.cursorStyle + : DEFAULT_CURSOR_STYLE, cursorSize: isFiniteNumber(editor.cursorSize) ? clamp(editor.cursorSize, 0.5, 10) : DEFAULT_CURSOR_SIZE, @@ -395,7 +567,7 @@ export function normalizeProjectEditor(editor: Partial): Pro ? clamp((editor as Partial).cursorSway as number, 0, 2) : DEFAULT_CURSOR_SWAY, borderRadius: typeof editor.borderRadius === "number" ? editor.borderRadius : 12.5, - padding: isFiniteNumber(editor.padding) ? clamp(editor.padding, 0, 100) : 50, + padding: isFiniteNumber(editor.padding) ? clamp(editor.padding, 0, 100) : 20, cropRegion: { x: cropX, y: cropY, @@ -407,6 +579,8 @@ export function normalizeProjectEditor(editor: Partial): Pro speedRegions: normalizedSpeedRegions, annotationRegions: normalizedAnnotationRegions, audioRegions: normalizedAudioRegions, + autoCaptions: normalizedAutoCaptions, + autoCaptionSettings: normalizedAutoCaptionSettings, webcam: { enabled: typeof webcam.enabled === "boolean" ? webcam.enabled : DEFAULT_WEBCAM_OVERLAY.enabled,