diff options
Diffstat (limited to 'apps/web/src/commands/media/add-media-asset.ts')
| -rw-r--r-- | apps/web/src/commands/media/add-media-asset.ts | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/apps/web/src/commands/media/add-media-asset.ts b/apps/web/src/commands/media/add-media-asset.ts new file mode 100644 index 00000000..d22241a2 --- /dev/null +++ b/apps/web/src/commands/media/add-media-asset.ts @@ -0,0 +1,135 @@ +import { Command, type CommandResult } from "@/commands/base-command"; +import { EditorCore } from "@/core"; +import { toast } from "sonner"; +import type { MediaAsset } from "@/media/types"; +import { generateUUID } from "@/utils/id"; +import { storageService } from "@/services/storage/service"; +import type { FrameRate } from "opencut-wasm"; +import { hasMediaId } from "@/timeline/element-utils"; +import { frameRatesEqual, getHighestImportedVideoFps } from "@/fps/utils"; +import { UpdateProjectSettingsCommand } from "@/commands/project"; + +export class AddMediaAssetCommand extends Command { + private assetId: string; + private savedAssets: MediaAsset[] | null = null; + private createdAsset: MediaAsset | null = null; + private previousProjectFps: FrameRate | null = null; + private appliedProjectFps: FrameRate | null = null; + + constructor( + private projectId: string, + private asset: Omit<MediaAsset, "id">, + ) { + super(); + this.assetId = generateUUID(); + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedAssets = [...editor.media.getAssets()]; + + this.createdAsset = { + ...this.asset, + id: this.assetId, + }; + + editor.media.setAssets({ + assets: [...this.savedAssets, this.createdAsset], + }); + this.previousProjectFps = editor.project.getActiveOrNull()?.settings.fps ?? null; + this.appliedProjectFps = editor.project.ratchetFpsForImportedMedia({ + importedAssets: [this.createdAsset], + }); + + storageService + .saveMediaAsset({ + projectId: this.projectId, + mediaAsset: this.createdAsset, + }) + .catch((error) => { + console.error("Failed to save media item:", error); + + const currentAssets = editor.media.getAssets(); + editor.media.setAssets({ + assets: currentAssets.filter((asset) => asset.id !== this.assetId), + }); + + const currentTracks = editor.scenes.getActiveScene().tracks; + const orphanedElements: Array<{ trackId: string; elementId: string }> = + []; + + for (const track of [ + ...currentTracks.overlay, + currentTracks.main, + ...currentTracks.audio, + ]) { + for (const element of track.elements) { + if (hasMediaId(element) && element.mediaId === this.assetId) { + orphanedElements.push({ + trackId: track.id, + elementId: element.id, + }); + } + } + } + + if (orphanedElements.length > 0) { + editor.timeline.deleteElements({ elements: orphanedElements }); + } + + this.restoreProjectFpsAfterFailedSave({ editor }); + + if (storageService.isQuotaExceededError({ error })) { + toast.error("Not enough browser storage", { + description: error instanceof Error ? error.message : undefined, + }); + } + }); + + return undefined; + } + + undo(): void { + if (this.savedAssets) { + const editor = EditorCore.getInstance(); + editor.media.setAssets({ assets: this.savedAssets }); + + if (this.createdAsset) { + storageService + .deleteMediaAsset({ projectId: this.projectId, id: this.assetId }) + .catch((error) => { + console.error("Failed to delete media item on undo:", error); + }); + } + } + } + + getAssetId(): string { + return this.assetId; + } + + private restoreProjectFpsAfterFailedSave({ + editor, + }: { + editor: EditorCore; + }): void { + if (this.previousProjectFps === null || this.appliedProjectFps === null) return; + + const activeProject = editor.project.getActiveOrNull(); + if (!activeProject) return; + if (!this.appliedProjectFps || !frameRatesEqual(activeProject.settings.fps, this.appliedProjectFps)) return; + + const highestRemainingVideoFps = getHighestImportedVideoFps({ + mediaAssets: editor.media.getAssets(), + }); + const appliedFpsFloat = this.appliedProjectFps.numerator / this.appliedProjectFps.denominator; + if ( + highestRemainingVideoFps !== null && + highestRemainingVideoFps >= appliedFpsFloat + ) { + return; + } + + new UpdateProjectSettingsCommand({ fps: this.previousProjectFps }).execute(); + } +} |
