VideoChain-API / src /scheduler /processVideo.mts
jbilcke-hf's picture
jbilcke-hf HF staff
live test
ccd48b8
raw
history blame
12.2 kB
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) => {
// just a an additional precaution, for consistency and robustness
if (["pause", "completed", "abort", "delete"].includes(video.status)) { return }
console.log(`processing video video ${video.id}`)
// always count 2 more steps: 1 for the LLM, 1 for the final assembly
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
// skip shots completed previously
if (shot.completed) {
continue
}
console.log(`need to complete shot ${shot.id}`)
// currenty we cannot generate too many frames at once,
// otherwise the upscaler will have trouble
// so for now, we fix it to 24 frames
// const nbFramesForBaseModel = Math.min(3, Math.max(1, Math.round(duration))) * 8
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, // for the preview, we only give a rough approximation
})
console.log("downloading preview video..")
// download to /tmp
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})`)
// something is wrong, let's put the whole thing back into the queue
video.error = `failed to generate preview for shot ${shot.id} (will try again later)`
if (await saveAndCheckIfNeedToStop(video)) { return }
// always try to yield whenever possible
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})`)
// something is wrong, let's put the whole thing back into the queue
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})`)
// something is wrong, let's put the whole thing back into the queue
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..")
// ATTENTION 1:
// the interpolation step always create a SLOW MOTION video
// it means it can last a lot longer (eg. 2x, 3x, 4x.. longer)
// than the duration generated by the original video model
// ATTENTION 2:
// the interpolation step generates videos in 910x512!
// ATTENTION 3:
// the interpolation step parameters are currently not passed to the space,
// so changing those two variables below will have no effect!
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)
// note: showing the intermediary result isn't very interesting here
// with our current interpolation settings, the 3 seconds video generated by the model
// become a 7 seconds video, at 30 FPS
// so we want to scale it back to the desired duration length
// also, as a last trick we want to upscale it (without AI) and add some FXs
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})`)
// something is wrong, let's put the whole thing back into the queue
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})`)
// something is wrong, let's put the whole thing back into the queue
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}`)
// now time to check the end game
if (video.nbCompletedShots === video.shots.length) {
console.log(`we have finished each individual shot!`)
if (!video.hasAssembledVideo) {
video.hasAssembledVideo = true
}
/*
console.log(`assembling the final..`)
console.log(`note: this might be redundant..`)
if (!video.hasAssembledVideo) {
video.hasAssembledVideo = true
if (video.shots.length === 1) {
console.log(`we only have one shot, so this gonna be easy`)
video.hasAssembledVideo = true
// the single shot (so, the first) becomes the final movie
await copyVideoFromPendingToCompleted(video.shots[0].fileName, video.fileName)
if (await saveAndCheckIfNeedToStop(video)) { return }
} else {
console.log(`assembling ${video.shots.length} shots together, might take a while`)
try {
await assembleShots(video.shots, video.fileName)
console.log(`finished assembling the ${video.shots.length} shots together!`)
await copyVideoFromPendingToCompleted(video.fileName)
video.hasAssembledVideo = true
if (await saveAndCheckIfNeedToStop(video)) { return }
} catch (err) {
console.error(`failed to assemble the shots together (${err})`)
// something is wrong, let's put the whole thing back into the queue
video.error = `failed to assemble the shots together (will try again later)`
if (await saveAndCheckIfNeedToStop(video)) { return }
}
}
}
*/
nbCompletedSteps++
video.completed = true
if (await saveAndCheckIfNeedToStop(video)) { return }
}
}