import { EditorCore } from "@/core"; import { Command, type CommandResult } from "@/lib/commands/base-command"; import { buildSeparatedAudioElement, canExtractSourceAudio, isSourceAudioSeparated, } from "@/lib/timeline/audio-separation"; import { buildEmptyTrack } from "@/lib/timeline/placement"; import { updateElementInSceneTracks } from "@/lib/timeline/track-element-update"; import type { AudioTrack, SceneTracks, TimelineElement, VideoElement, } from "@/lib/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 newAudioTrack = { ...buildEmptyTrack({ id: generateUUID(), type: "audio", }), elements: [separatedAudioElement], } as AudioTrack; editor.timeline.updateTracks( updateSourceAudioEnabled({ tracks: { ...this.savedState, audio: [...this.savedState.audio, newAudioTrack], }, 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, }), }); }