summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/commands/timeline/element
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-18 01:40:18 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-18 01:40:18 +0200
commit011f51051c9750706978d6c89ce84dd15560c335 (patch)
tree2e7b23ebebb798934d6cb6aeace855f31699b633 /apps/web/src/lib/commands/timeline/element
parentc46d28891fee62ca2d3132da876af6411a41d9da (diff)
downloadopencut-011f51051c9750706978d6c89ce84dd15560c335.tar.gz
feat: clip volume line and timeline waveform improvements
Diffstat (limited to 'apps/web/src/lib/commands/timeline/element')
-rw-r--r--apps/web/src/lib/commands/timeline/element/insert-element.ts1
-rw-r--r--apps/web/src/lib/commands/timeline/element/split-elements.ts122
-rw-r--r--apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts48
3 files changed, 92 insertions, 79 deletions
diff --git a/apps/web/src/lib/commands/timeline/element/insert-element.ts b/apps/web/src/lib/commands/timeline/element/insert-element.ts
index 0e806d6e..ed05b8c6 100644
--- a/apps/web/src/lib/commands/timeline/element/insert-element.ts
+++ b/apps/web/src/lib/commands/timeline/element/insert-element.ts
@@ -3,7 +3,6 @@ import { EditorCore } from "@/core";
import type {
CreateTimelineElement,
SceneTracks,
- TimelineTrack,
TimelineElement,
TrackType,
} from "@/lib/timeline";
diff --git a/apps/web/src/lib/commands/timeline/element/split-elements.ts b/apps/web/src/lib/commands/timeline/element/split-elements.ts
index 9799f7e8..0f2495f3 100644
--- a/apps/web/src/lib/commands/timeline/element/split-elements.ts
+++ b/apps/web/src/lib/commands/timeline/element/split-elements.ts
@@ -37,7 +37,9 @@ export class SplitElementsCommand extends Command {
this.savedState = editor.scenes.getActiveScene().tracks;
this.rightSideElements = [];
- const splitTrack = <TTrack extends { id: string; elements: TimelineElement[] }>(
+ const splitTrack = <
+ TTrack extends { id: string; elements: TimelineElement[] },
+ >(
track: TTrack,
): TTrack => {
const elementsToSplit = this.elements.filter(
@@ -48,7 +50,7 @@ export class SplitElementsCommand extends Command {
return track;
}
- let elements = track.elements.flatMap((element) => {
+ const elements = track.elements.flatMap((element) => {
const shouldSplit = elementsToSplit.some(
(target) => target.elementId === element.id,
);
@@ -87,67 +89,67 @@ export class SplitElementsCommand extends Command {
splitTime: relativeTime,
shouldIncludeSplitBoundary: true,
});
+ let splitResult: TimelineElement[];
if (this.retainSide === "left") {
- return [
- {
- ...element,
- duration: leftVisibleDuration,
- trimEnd: element.trimEnd + rightSourceSpan,
- name: `${element.name} (left)`,
- animations: leftAnimations,
- ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
- },
- ];
- }
-
- if (this.retainSide === "right") {
- const newId = generateUUID();
- this.rightSideElements.push({
- trackId: track.id,
- elementId: newId,
- });
- return [
- {
- ...element,
- id: newId,
- startTime: this.splitTime,
- duration: rightVisibleDuration,
- trimStart: element.trimStart + leftSourceSpan,
- name: `${element.name} (right)`,
- animations: rightAnimations,
- ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
- },
- ];
- }
-
- // "both" - split into two pieces
- const secondElementId = generateUUID();
- this.rightSideElements.push({
- trackId: track.id,
- elementId: secondElementId,
- });
+ splitResult = [
+ {
+ ...element,
+ duration: leftVisibleDuration,
+ trimEnd: element.trimEnd + rightSourceSpan,
+ name: `${element.name} (left)`,
+ animations: leftAnimations,
+ ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
+ },
+ ];
+ } else if (this.retainSide === "right") {
+ const newId = generateUUID();
+ this.rightSideElements.push({
+ trackId: track.id,
+ elementId: newId,
+ });
+ splitResult = [
+ {
+ ...element,
+ id: newId,
+ startTime: this.splitTime,
+ duration: rightVisibleDuration,
+ trimStart: element.trimStart + leftSourceSpan,
+ name: `${element.name} (right)`,
+ animations: rightAnimations,
+ ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
+ },
+ ];
+ } else {
+ // "both" - split into two pieces
+ const secondElementId = generateUUID();
+ this.rightSideElements.push({
+ trackId: track.id,
+ elementId: secondElementId,
+ });
+ splitResult = [
+ {
+ ...element,
+ duration: leftVisibleDuration,
+ trimEnd: element.trimEnd + rightSourceSpan,
+ name: `${element.name} (left)`,
+ animations: leftAnimations,
+ ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
+ },
+ {
+ ...element,
+ id: secondElementId,
+ startTime: this.splitTime,
+ duration: rightVisibleDuration,
+ trimStart: element.trimStart + leftSourceSpan,
+ name: `${element.name} (right)`,
+ animations: rightAnimations,
+ ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
+ },
+ ];
+ }
- return [
- {
- ...element,
- duration: leftVisibleDuration,
- trimEnd: element.trimEnd + rightSourceSpan,
- name: `${element.name} (left)`,
- animations: leftAnimations,
- ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
- },
- {
- ...element,
- id: secondElementId,
- startTime: this.splitTime,
- duration: rightVisibleDuration,
- trimStart: element.trimStart + leftSourceSpan,
- name: `${element.name} (right)`,
- animations: rightAnimations,
- ...(retimeRef !== undefined ? { retime: retimeRef } : {}),
- },
- ];
+ return splitResult;
});
return { ...track, elements } as TTrack;
diff --git a/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts b/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts
index 36b3b7a7..7300f540 100644
--- a/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts
+++ b/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts
@@ -5,10 +5,12 @@ import {
canExtractSourceAudio,
isSourceAudioSeparated,
} from "@/lib/timeline/audio-separation";
-import { buildEmptyTrack } from "@/lib/timeline/placement";
+import {
+ applyPlacement,
+ resolveTrackPlacement,
+} from "@/lib/timeline/placement";
import { updateElementInSceneTracks } from "@/lib/timeline/track-element-update";
import type {
- AudioTrack,
SceneTracks,
TimelineElement,
VideoElement,
@@ -35,9 +37,7 @@ export class ToggleSourceAudioSeparationCommand extends Command {
...this.savedState.overlay,
this.savedState.main,
...this.savedState.audio,
- ].find(
- (track) => track.id === this.params.trackId,
- );
+ ].find((track) => track.id === this.params.trackId);
if (!sourceTrack) {
return;
}
@@ -61,8 +61,7 @@ export class ToggleSourceAudioSeparationCommand extends Command {
return;
}
- const mediaAsset = editor
- .media
+ const mediaAsset = editor.media
.getAssets()
.find((asset) => asset.id === videoElement.mediaId);
if (!canExtractSourceAudio(videoElement, mediaAsset)) {
@@ -78,20 +77,32 @@ export class ToggleSourceAudioSeparationCommand extends Command {
}),
id: generateUUID(),
};
- const newAudioTrack = {
- ...buildEmptyTrack({
- id: generateUUID(),
- type: "audio",
- }),
+ const placementResult = resolveTrackPlacement({
+ tracks: this.savedState,
+ trackType: "audio",
+ timeSpans: [
+ {
+ startTime: separatedAudioElement.startTime,
+ duration: separatedAudioElement.duration,
+ },
+ ],
+ strategy: { type: "firstAvailable" },
+ });
+ if (!placementResult) {
+ return;
+ }
+ const appliedPlacement = applyPlacement({
+ tracks: this.savedState,
+ placementResult,
elements: [separatedAudioElement],
- } as AudioTrack;
+ });
+ if (!appliedPlacement) {
+ return;
+ }
editor.timeline.updateTracks(
updateSourceAudioEnabled({
- tracks: {
- ...this.savedState,
- audio: [...this.savedState.audio, newAudioTrack],
- },
+ tracks: appliedPlacement.updatedTracks,
trackId: this.params.trackId,
elementId: this.params.elementId,
isSourceAudioEnabled: false,
@@ -124,7 +135,8 @@ function updateSourceAudioEnabled({
tracks,
trackId,
elementId,
- elementPredicate: (element): element is VideoElement => element.type === "video",
+ elementPredicate: (element): element is VideoElement =>
+ element.type === "video",
update: (element) => ({
...element,
isSourceAudioEnabled,