mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
feat(editor): add ClipRegion type, persistence, and keyboard shortcuts
This commit is contained in:
@@ -47,6 +47,7 @@ import {
|
||||
getDefaultCaptionFontFamily,
|
||||
type SpeedRegion,
|
||||
type TrimRegion,
|
||||
type ClipRegion,
|
||||
type WebcamOverlaySettings,
|
||||
type ZoomRegion,
|
||||
type ZoomTransitionEasing,
|
||||
@@ -82,6 +83,7 @@ export interface ProjectEditorState {
|
||||
cropRegion: CropRegion;
|
||||
zoomRegions: ZoomRegion[];
|
||||
trimRegions: TrimRegion[];
|
||||
clipRegions: ClipRegion[];
|
||||
speedRegions: SpeedRegion[];
|
||||
annotationRegions: AnnotationRegion[];
|
||||
audioRegions: AudioRegion[];
|
||||
@@ -292,6 +294,23 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
})
|
||||
: [];
|
||||
|
||||
const normalizedClipRegions: ClipRegion[] = Array.isArray((editor as any).clipRegions)
|
||||
? ((editor as any).clipRegions as ClipRegion[])
|
||||
.filter((region): region is ClipRegion => Boolean(region && typeof region.id === "string"))
|
||||
.map((region) => {
|
||||
const rawStart = isFiniteNumber(region.startMs) ? Math.round(region.startMs) : 0;
|
||||
const rawEnd = isFiniteNumber(region.endMs) ? Math.round(region.endMs) : rawStart + 1000;
|
||||
const startMs = Math.max(0, Math.min(rawStart, rawEnd));
|
||||
const endMs = Math.max(startMs + 1, rawEnd);
|
||||
return {
|
||||
id: region.id,
|
||||
startMs,
|
||||
endMs,
|
||||
speed: isFiniteNumber((region as any).speed) ? (region as any).speed : 1,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
const normalizedSpeedRegions: SpeedRegion[] = Array.isArray(editor.speedRegions)
|
||||
? editor.speedRegions
|
||||
.filter((region): region is SpeedRegion => Boolean(region && typeof region.id === "string"))
|
||||
@@ -590,6 +609,7 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
},
|
||||
zoomRegions: normalizedZoomRegions,
|
||||
trimRegions: normalizedTrimRegions,
|
||||
clipRegions: normalizedClipRegions,
|
||||
speedRegions: normalizedSpeedRegions,
|
||||
annotationRegions: normalizedAnnotationRegions,
|
||||
audioRegions: normalizedAudioRegions,
|
||||
|
||||
@@ -127,6 +127,51 @@ export interface TrimRegion {
|
||||
endMs: number;
|
||||
}
|
||||
|
||||
export interface ClipRegion {
|
||||
id: string;
|
||||
startMs: number;
|
||||
endMs: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
/** Convert clip regions (kept segments) to trim regions (gaps to remove). */
|
||||
export function clipsToTrims(clips: ClipRegion[], totalDurationMs: number): TrimRegion[] {
|
||||
if (clips.length === 0) return [];
|
||||
const sorted = [...clips].sort((a, b) => a.startMs - b.startMs);
|
||||
const trims: TrimRegion[] = [];
|
||||
let cursor = 0;
|
||||
let trimId = 1;
|
||||
for (const clip of sorted) {
|
||||
if (clip.startMs > cursor) {
|
||||
trims.push({ id: `trim-gap-${trimId++}`, startMs: cursor, endMs: clip.startMs });
|
||||
}
|
||||
cursor = clip.endMs;
|
||||
}
|
||||
if (cursor < totalDurationMs) {
|
||||
trims.push({ id: `trim-gap-${trimId++}`, startMs: cursor, endMs: totalDurationMs });
|
||||
}
|
||||
return trims;
|
||||
}
|
||||
|
||||
/** Convert legacy trim regions to clip regions (complement). */
|
||||
export function trimsToClips(trims: TrimRegion[], totalDurationMs: number): ClipRegion[] {
|
||||
if (trims.length === 0) return [{ id: "clip-1", startMs: 0, endMs: totalDurationMs, speed: 1 }];
|
||||
const sorted = [...trims].sort((a, b) => a.startMs - b.startMs);
|
||||
const clips: ClipRegion[] = [];
|
||||
let cursor = 0;
|
||||
let clipId = 1;
|
||||
for (const trim of sorted) {
|
||||
if (trim.startMs > cursor) {
|
||||
clips.push({ id: `clip-${clipId++}`, startMs: cursor, endMs: trim.startMs, speed: 1 });
|
||||
}
|
||||
cursor = trim.endMs;
|
||||
}
|
||||
if (cursor < totalDurationMs) {
|
||||
clips.push({ id: `clip-${clipId++}`, startMs: cursor, endMs: totalDurationMs, speed: 1 });
|
||||
}
|
||||
return clips;
|
||||
}
|
||||
|
||||
export type AnnotationType = "text" | "image" | "figure";
|
||||
|
||||
export type ArrowDirection =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const SHORTCUT_ACTIONS = [
|
||||
'addZoom',
|
||||
'addTrim',
|
||||
'splitClip',
|
||||
'addSpeed',
|
||||
'addAnnotation',
|
||||
'addKeyframe',
|
||||
@@ -68,6 +69,7 @@ export function findConflict(
|
||||
export const DEFAULT_SHORTCUTS: ShortcutsConfig = {
|
||||
addZoom: { key: 'z' },
|
||||
addTrim: { key: 't' },
|
||||
splitClip: { key: 'c' },
|
||||
addSpeed: { key: 's' },
|
||||
addAnnotation: { key: 'a' },
|
||||
addKeyframe: { key: 'f' },
|
||||
@@ -78,6 +80,7 @@ export const DEFAULT_SHORTCUTS: ShortcutsConfig = {
|
||||
export const SHORTCUT_LABELS: Record<ShortcutAction, string> = {
|
||||
addZoom: 'Add Zoom',
|
||||
addTrim: 'Add Trim',
|
||||
splitClip: 'Split Clip',
|
||||
addSpeed: 'Add Speed',
|
||||
addAnnotation: 'Add Annotation',
|
||||
addKeyframe: 'Add Keyframe',
|
||||
|
||||
Reference in New Issue
Block a user