| | import { v4 as uuidv4 } from "uuid" |
| |
|
| | import { Video, VideoShot } from "../types.mts" |
| |
|
| | import { generateVideo } from "../production/generateVideo.mts" |
| | import { upscaleVideo } from "../production/upscaleVideo.mts" |
| | import { interpolateVideo } from "../production/interpolateVideo.mts" |
| | import { postInterpolation } from "../production/postInterpolation.mts" |
| | import { generateAudio } from "../production/generateAudio.mts" |
| | import { addAudioToVideo } from "../production/addAudioToVideo.mts" |
| |
|
| | import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts" |
| | import { copyVideoFromTmpToPending } from "../utils/copyVideoFromTmpToPending.mts" |
| |
|
| | import { saveAndCheckIfNeedToStop } from "./saveAndCheckIfNeedToStop.mts" |
| | import { enrichVideoSpecsUsingLLM } from "../llm/enrichVideoSpecsUsingLLM.mts" |
| | import { updateShotPreview } from "./updateShotPreview.mts" |
| |
|
| | export const processVideo = async (video: Video) => { |
| |
|
| | |
| | if (["pause", "completed", "abort", "delete"].includes(video.status)) { return } |
| |
|
| | console.log(`processing video video ${video.id}`) |
| |
|
| | |
| |
|
| | let nbTotalSteps = 2 |
| |
|
| | for (const shot of video.shots) { |
| | nbTotalSteps += shot.nbTotalSteps |
| | } |
| |
|
| | let nbCompletedSteps = 0 |
| |
|
| | if (!video.hasGeneratedSpecs) { |
| | try { |
| | await enrichVideoSpecsUsingLLM(video) |
| | } catch (err) { |
| | console.error(`LLM error: ${err}`) |
| | video.error = `LLM error: ${err}` |
| | video.status = "delete" |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } |
| |
|
| | nbCompletedSteps++ |
| | video.hasGeneratedSpecs = true |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| |
|
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } |
| |
|
| |
|
| | for (const shot of video.shots) { |
| | nbCompletedSteps += shot.nbCompletedSteps |
| |
|
| | |
| | if (shot.completed) { |
| | continue |
| | } |
| |
|
| | console.log(`need to complete shot ${shot.id}`) |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | const nbFramesForBaseModel = 24 |
| |
|
| |
|
| | if (!shot.hasGeneratedPreview) { |
| | console.log("generating a preview of the final result..") |
| | let generatedPreviewVideoUrl = "" |
| | try { |
| | generatedPreviewVideoUrl = await generateVideo(shot.shotPrompt, { |
| | seed: shot.seed, |
| | nbFrames: nbFramesForBaseModel, |
| | nbSteps: 10, |
| | }) |
| |
|
| | console.log("downloading preview video..") |
| |
|
| | |
| | await downloadFileToTmp(generatedPreviewVideoUrl, shot.fileName) |
| |
|
| | await copyVideoFromTmpToPending(shot.fileName) |
| |
|
| | shot.hasGeneratedPreview = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| | |
| | await updateShotPreview(video, shot) |
| | |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } catch (err) { |
| | console.error(`failed to generate preview for shot ${shot.id} (${err})`) |
| | |
| | video.error = `failed to generate preview for shot ${shot.id} (will try again later)` |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| |
|
| | |
| | return |
| | } |
| |
|
| | } |
| |
|
| | const notAllShotsHavePreview = video.shots.some(s => !s.hasGeneratedPreview) |
| |
|
| | if (notAllShotsHavePreview) { |
| | console.log(`step 2 isn't unlocked yet, because not all videos have generated preview`) |
| | continue |
| | } |
| |
|
| | if (!shot.hasGeneratedVideo) { |
| | console.log("generating primordial pixel soup (raw video)..") |
| | let generatedVideoUrl = "" |
| |
|
| |
|
| | const nbFramesForBaseModel = 24 |
| |
|
| | try { |
| | generatedVideoUrl = await generateVideo(shot.shotPrompt, { |
| | seed: shot.seed, |
| | nbFrames: nbFramesForBaseModel, |
| | nbSteps: shot.steps, |
| | }) |
| |
|
| | console.log("downloading video..") |
| |
|
| | await downloadFileToTmp(generatedVideoUrl, shot.fileName) |
| |
|
| | await copyVideoFromTmpToPending(shot.fileName) |
| |
|
| | shot.hasGeneratedVideo = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| |
|
| | await updateShotPreview(video, shot) |
| |
|
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } catch (err) { |
| | console.error(`failed to generate shot ${shot.id} (${err})`) |
| | |
| | video.error = `failed to generate shot ${shot.id} (will try again later)` |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| |
|
| | break |
| | } |
| |
|
| | } |
| |
|
| | if (!shot.hasUpscaledVideo) { |
| | console.log("upscaling video..") |
| | try { |
| | await upscaleVideo(shot.fileName, shot.shotPrompt) |
| |
|
| | shot.hasUpscaledVideo = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| | |
| | await updateShotPreview(video, shot) |
| | |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| |
|
| | } catch (err) { |
| | console.error(`failed to upscale shot ${shot.id} (${err})`) |
| | |
| | video.error = `failed to upscale shot ${shot.id} (will try again later)` |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| |
|
| | break |
| | } |
| | } |
| |
|
| | if (!shot.hasInterpolatedVideo) { |
| | console.log("interpolating video..") |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const interpolationSteps = 2 |
| | const interpolatedFramesPerSecond = 30 |
| | console.log('creating slow-mo video (910x512 @ 30 FPS)') |
| | try { |
| | await interpolateVideo( |
| | shot.fileName, |
| | interpolationSteps, |
| | interpolatedFramesPerSecond |
| | ) |
| |
|
| | shot.hasInterpolatedVideo = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| |
|
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | console.log('performing final scaling (1280x720 @ 30 FPS)') |
| |
|
| | try { |
| | await postInterpolation(shot.fileName, shot.durationMs, shot.fps, shot.noiseAmount) |
| | |
| | shot.hasPostProcessedVideo = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| |
|
| | await updateShotPreview(video, shot) |
| | |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } catch (err) { |
| | throw err |
| | } |
| | } catch (err) { |
| | console.error(`failed to interpolate and post-process shot ${shot.id} (${err})`) |
| | |
| | video.error = `failed to interpolate and shot ${shot.id} (will try again later)` |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | break |
| | } |
| | } |
| |
|
| |
|
| | let foregroundAudioFileName = `${video.ownerId}_${video.id}_${shot.id}_${uuidv4()}.m4a` |
| |
|
| | if (!shot.hasGeneratedForegroundAudio) { |
| | if (shot.foregroundAudioPrompt) { |
| | console.log("generating foreground audio..") |
| | |
| | try { |
| | await generateAudio(shot.foregroundAudioPrompt, foregroundAudioFileName) |
| |
|
| | shot.hasGeneratedForegroundAudio = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| |
|
| | await addAudioToVideo(shot.fileName, foregroundAudioFileName) |
| |
|
| | await updateShotPreview(video, shot) |
| |
|
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| |
|
| | } catch (err) { |
| | console.error(`failed to generate foreground audio for ${shot.id} (${err})`) |
| | |
| | video.error = `failed to generate foreground audio ${shot.id} (will try again later)` |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | break |
| | } |
| | } else { |
| | shot.hasGeneratedForegroundAudio = true |
| | shot.nbCompletedSteps++ |
| | nbCompletedSteps++ |
| | shot.progressPercent = Math.round((shot.nbCompletedSteps / shot.nbTotalSteps) * 100) |
| | video.progressPercent = Math.round((nbCompletedSteps / nbTotalSteps) * 100) |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } |
| | } |
| |
|
| | shot.completed = true |
| | shot.completedAt = new Date().toISOString() |
| | shot.progressPercent = 100 |
| |
|
| | video.nbCompletedShots++ |
| |
|
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } |
| |
|
| | console.log(`end of the loop:`) |
| | console.log(`nb completed shots: ${video.nbCompletedShots}`) |
| | console.log(`len of the shot array: ${video.shots.length}`) |
| | |
| | |
| |
|
| |
|
| | if (video.nbCompletedShots === video.shots.length) { |
| | console.log(`we have finished each individual shot!`) |
| |
|
| | if (!video.hasAssembledVideo) { |
| | video.hasAssembledVideo = true |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | nbCompletedSteps++ |
| | video.completed = true |
| | if (await saveAndCheckIfNeedToStop(video)) { return } |
| | } |
| | } |