diff --git a/apps/web/actions/video/trigger-instant-recording-processing.ts b/apps/web/actions/video/trigger-instant-recording-processing.ts new file mode 100644 index 00000000000..ce117d11a40 --- /dev/null +++ b/apps/web/actions/video/trigger-instant-recording-processing.ts @@ -0,0 +1,39 @@ +"use server"; + +import { db } from "@cap/database"; +import { getCurrentUser } from "@cap/database/auth/session"; +import { videos } from "@cap/database/schema"; +import type { Video } from "@cap/web-domain"; +import { eq } from "drizzle-orm"; +import { startVideoProcessingWorkflow } from "@/lib/video-processing"; + +export async function triggerInstantRecordingProcessing({ + videoId, +}: { + videoId: Video.VideoId; +}): Promise<{ success: boolean }> { + const user = await getCurrentUser(); + if (!user) throw new Error("Unauthorized"); + + const [video] = await db() + .select() + .from(videos) + .where(eq(videos.id, videoId)); + + if (!video) throw new Error("Video not found"); + if (video.ownerId !== user.id) throw new Error("Unauthorized"); + + const rawFileKey = `${user.id}/${videoId}/result.mp4`; + + await startVideoProcessingWorkflow({ + videoId, + userId: user.id, + rawFileKey, + bucketId: video.bucket ?? null, + processingMessage: "Starting video processing...", + startFailureMessage: "Video uploaded, but processing could not start.", + mode: "singlepart", + }); + + return { success: true }; +} diff --git a/apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/useWebRecorder.ts b/apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/useWebRecorder.ts index 2016cc8d010..8774920189f 100644 --- a/apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/useWebRecorder.ts +++ b/apps/web/app/(org)/dashboard/caps/components/web-recorder-dialog/useWebRecorder.ts @@ -6,6 +6,7 @@ import { Cause, Exit, Option } from "effect"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; +import { triggerInstantRecordingProcessing } from "@/actions/video/trigger-instant-recording-processing"; import { createVideoAndGetUploadUrl } from "@/actions/video/upload"; import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime"; import { ThumbnailRequest } from "@/lib/Requests/ThumbnailRequest"; @@ -1298,6 +1299,17 @@ export const useWebRecorder = ({ setUploadStatus, ); + try { + await triggerInstantRecordingProcessing({ + videoId: creationResult.id, + }); + } catch (processingError) { + console.error("Failed to start video processing", processingError); + toast.warning( + "Recording uploaded. Processing did not start yet, but the original recording is available.", + ); + } + if (thumbnailBlob) { try { const screenshotData = await createVideoAndGetUploadUrl({ diff --git a/apps/web/workflows/process-video.ts b/apps/web/workflows/process-video.ts index c646ce24230..ada94322303 100644 --- a/apps/web/workflows/process-video.ts +++ b/apps/web/workflows/process-video.ts @@ -48,7 +48,11 @@ export async function processVideoWorkflow( ); await saveMetadataAndComplete(videoId, result.metadata); - await cleanupRawUpload(videoId, rawFileKey); + + const outputKey = `${userId}/${videoId}/result.mp4`; + if (rawFileKey !== outputKey) { + await cleanupRawUpload(videoId, rawFileKey); + } return { success: true,