diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz | |
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts')
| -rw-r--r-- | apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts b/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts new file mode 100644 index 00000000..36d4cd87 --- /dev/null +++ b/apps/web/src/commands/timeline/element/toggle-source-audio-separation.ts @@ -0,0 +1,145 @@ +import { EditorCore } from "@/core"; +import { Command, type CommandResult } from "@/commands/base-command"; +import { + buildSeparatedAudioElement, + canExtractSourceAudio, + isSourceAudioSeparated, +} from "@/timeline/audio-separation"; +import { + applyPlacement, + resolveTrackPlacement, +} from "@/timeline/placement"; +import { updateElementInSceneTracks } from "@/timeline/track-element-update"; +import type { + SceneTracks, + TimelineElement, + VideoElement, +} from "@/timeline/types"; +import { generateUUID } from "@/utils/id"; + +export class ToggleSourceAudioSeparationCommand extends Command { + private savedState: SceneTracks | null = null; + + constructor( + private readonly params: { + trackId: string; + elementId: string; + }, + ) { + super(); + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + + const sourceTrack = [ + ...this.savedState.overlay, + this.savedState.main, + ...this.savedState.audio, + ].find((track) => track.id === this.params.trackId); + if (!sourceTrack) { + return; + } + const sourceElement = sourceTrack.elements.find( + (element) => element.id === this.params.elementId, + ) as TimelineElement | undefined; + if (!sourceElement || sourceElement.type !== "video") { + return; + } + const videoElement: VideoElement = sourceElement; + + if (isSourceAudioSeparated({ element: videoElement })) { + editor.timeline.updateTracks( + updateSourceAudioEnabled({ + tracks: this.savedState, + trackId: this.params.trackId, + elementId: this.params.elementId, + isSourceAudioEnabled: true, + }), + ); + return; + } + + const mediaAsset = editor.media + .getAssets() + .find((asset) => asset.id === videoElement.mediaId); + if (!canExtractSourceAudio(videoElement, mediaAsset)) { + return; + } + if (videoElement.duration <= 0) { + return; + } + + const separatedAudioElement = { + ...buildSeparatedAudioElement({ + sourceElement: videoElement, + }), + id: generateUUID(), + }; + 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], + }); + if (!appliedPlacement) { + return; + } + + editor.timeline.updateTracks( + updateSourceAudioEnabled({ + tracks: appliedPlacement.updatedTracks, + trackId: this.params.trackId, + elementId: this.params.elementId, + isSourceAudioEnabled: false, + }), + ); + } + + undo(): void { + if (!this.savedState) { + return; + } + + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } +} + +function updateSourceAudioEnabled({ + tracks, + trackId, + elementId, + isSourceAudioEnabled, +}: { + tracks: SceneTracks; + trackId: string; + elementId: string; + isSourceAudioEnabled: boolean; +}): SceneTracks { + return updateElementInSceneTracks({ + tracks, + trackId, + elementId, + elementPredicate: (element): element is VideoElement => + element.type === "video", + update: (element) => ({ + ...element, + isSourceAudioEnabled, + }), + }); +} |
