summaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorDevloperAmanSingh <amansinghneh211205@gmail.com>2025-06-26 23:40:50 +0530
committerDevloperAmanSingh <amansinghneh211205@gmail.com>2025-06-26 23:40:50 +0530
commit3224dd974aa0a4906cae5642e8479a3618c49c03 (patch)
tree622ad913457795bca78a326e2619a48fd87bb606 /apps/web
parentb10bb8ed55330d98cbbb88d7e139b943a9f95f00 (diff)
downloadopencut-3224dd974aa0a4906cae5642e8479a3618c49c03.tar.gz
feat: add clip splitting and audio separation functionality to timeline store
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/src/stores/timeline-store.ts209
1 files changed, 202 insertions, 7 deletions
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
index 3b068d7c..a6da4e25 100644
--- a/apps/web/src/stores/timeline-store.ts
+++ b/apps/web/src/stores/timeline-store.ts
@@ -74,10 +74,28 @@ interface TimelineStore {
) => void;
toggleTrackMute: (trackId: string) => void;
+ // Split operations
+ splitClip: (
+ trackId: string,
+ clipId: string,
+ splitTime: number
+ ) => string | null;
+ splitAndKeepLeft: (
+ trackId: string,
+ clipId: string,
+ splitTime: number
+ ) => void;
+ splitAndKeepRight: (
+ trackId: string,
+ clipId: string,
+ splitTime: number
+ ) => void;
+ separateAudio: (trackId: string, clipId: string) => string | null;
+
// Computed values
getTotalDuration: () => number;
- // New actions
+ // History actions
undo: () => void;
redo: () => void;
pushHistory: () => void;
@@ -91,10 +109,9 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
pushHistory: () => {
const { tracks, history, redoStack } = get();
- // Deep copy tracks
set({
history: [...history, JSON.parse(JSON.stringify(tracks))],
- redoStack: [], // Clear redo stack when new action is performed
+ redoStack: [],
});
},
@@ -105,7 +122,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
set({
tracks: prev,
history: history.slice(0, -1),
- redoStack: [...redoStack, JSON.parse(JSON.stringify(tracks))], // Add current state to redo stack
+ redoStack: [...redoStack, JSON.parse(JSON.stringify(tracks))],
});
},
@@ -115,7 +132,6 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
(c) => c.trackId === trackId && c.clipId === clipId
);
if (multi) {
- // Toggle selection
return exists
? {
selectedClips: state.selectedClips.filter(
@@ -128,6 +144,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}
});
},
+
deselectClip: (trackId, clipId) => {
set((state) => ({
selectedClips: state.selectedClips.filter(
@@ -135,6 +152,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
),
}));
},
+
clearSelectedClips: () => {
set({ selectedClips: [] });
},
@@ -194,7 +212,6 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}
: track
)
- // Remove track if it becomes empty
.filter((track) => track.clips.length > 0),
}));
},
@@ -223,7 +240,6 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}
return track;
})
- // Remove track if it becomes empty
.filter((track) => track.clips.length > 0),
};
});
@@ -270,6 +286,185 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
},
+ splitClip: (trackId, clipId, splitTime) => {
+ const { tracks } = get();
+ const track = tracks.find((t) => t.id === trackId);
+ const clip = track?.clips.find((c) => c.id === clipId);
+
+ if (!clip) return null;
+
+ const effectiveStart = clip.startTime;
+ const effectiveEnd =
+ clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+
+ if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return null;
+
+ get().pushHistory();
+
+ const relativeTime = splitTime - clip.startTime;
+ const firstDuration = relativeTime;
+ const secondDuration =
+ clip.duration - clip.trimStart - clip.trimEnd - relativeTime;
+
+ const secondClipId = crypto.randomUUID();
+
+ set((state) => ({
+ tracks: state.tracks.map((track) =>
+ track.id === trackId
+ ? {
+ ...track,
+ clips: track.clips.flatMap((c) =>
+ c.id === clipId
+ ? [
+ {
+ ...c,
+ trimEnd: c.trimEnd + secondDuration,
+ name: c.name + " (left)",
+ },
+ {
+ ...c,
+ id: secondClipId,
+ startTime: splitTime,
+ trimStart: c.trimStart + firstDuration,
+ name: c.name + " (right)",
+ },
+ ]
+ : [c]
+ ),
+ }
+ : track
+ ),
+ }));
+
+ return secondClipId;
+ },
+
+ splitAndKeepLeft: (trackId, clipId, splitTime) => {
+ const { tracks } = get();
+ const track = tracks.find((t) => t.id === trackId);
+ const clip = track?.clips.find((c) => c.id === clipId);
+
+ if (!clip) return;
+
+ const effectiveStart = clip.startTime;
+ const effectiveEnd =
+ clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+
+ if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return;
+
+ get().pushHistory();
+
+ const relativeTime = splitTime - clip.startTime;
+ const durationToRemove =
+ clip.duration - clip.trimStart - clip.trimEnd - relativeTime;
+
+ set((state) => ({
+ tracks: state.tracks.map((track) =>
+ track.id === trackId
+ ? {
+ ...track,
+ clips: track.clips.map((c) =>
+ c.id === clipId
+ ? {
+ ...c,
+ trimEnd: c.trimEnd + durationToRemove,
+ name: c.name + " (left)",
+ }
+ : c
+ ),
+ }
+ : track
+ ),
+ }));
+ },
+
+ splitAndKeepRight: (trackId, clipId, splitTime) => {
+ const { tracks } = get();
+ const track = tracks.find((t) => t.id === trackId);
+ const clip = track?.clips.find((c) => c.id === clipId);
+
+ if (!clip) return;
+
+ const effectiveStart = clip.startTime;
+ const effectiveEnd =
+ clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+
+ if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return;
+
+ get().pushHistory();
+
+ const relativeTime = splitTime - clip.startTime;
+
+ set((state) => ({
+ tracks: state.tracks.map((track) =>
+ track.id === trackId
+ ? {
+ ...track,
+ clips: track.clips.map((c) =>
+ c.id === clipId
+ ? {
+ ...c,
+ startTime: splitTime,
+ trimStart: c.trimStart + relativeTime,
+ name: c.name + " (right)",
+ }
+ : c
+ ),
+ }
+ : track
+ ),
+ }));
+ },
+
+ separateAudio: (trackId, clipId) => {
+ const { tracks } = get();
+ const track = tracks.find((t) => t.id === trackId);
+ const clip = track?.clips.find((c) => c.id === clipId);
+
+ if (!clip || track?.type !== "video") return null;
+
+ get().pushHistory();
+
+ let audioTrackId = tracks.find((t) => t.type === "audio")?.id;
+
+ if (!audioTrackId) {
+ audioTrackId = crypto.randomUUID();
+ const newAudioTrack: TimelineTrack = {
+ id: audioTrackId,
+ name: "Audio Track",
+ type: "audio",
+ clips: [],
+ muted: false,
+ };
+
+ set((state) => ({
+ tracks: [...state.tracks, newAudioTrack],
+ }));
+ }
+
+ const audioClipId = crypto.randomUUID();
+
+ set((state) => ({
+ tracks: state.tracks.map((track) =>
+ track.id === audioTrackId
+ ? {
+ ...track,
+ clips: [
+ ...track.clips,
+ {
+ ...clip,
+ id: audioClipId,
+ name: clip.name + " (audio)",
+ },
+ ],
+ }
+ : track
+ ),
+ }));
+
+ return audioClipId;
+ },
+
getTotalDuration: () => {
const { tracks } = get();
if (tracks.length === 0) return 0;