Spaces:
Paused
Paused
Julian Bilcke
commited on
Commit
Β·
652f343
1
Parent(s):
3ecab68
working on the VideoChain queue system
Browse files- database/completed/README.md +0 -0
- database/pending/README.md +1 -0
- package-lock.json +0 -0
- package.json +6 -2
- src/database/constants.mts +3 -0
- src/database/getCompletedTasks.mts +9 -0
- src/database/getPendingTasks.mts +9 -0
- src/database/getTask.mts +23 -0
- src/database/readTask.mts +12 -0
- src/database/readTasks.mts +39 -0
- src/database/saveCompletedTask.mts +13 -0
- src/database/savePendingTask.mts +11 -0
- src/index.mts +96 -85
- src/main.mts +17 -0
- src/services/processTask.mts +5 -0
- src/services/requestToTask.mts +115 -0
- src/tests/checkStatus.mts +14 -0
- src/tests/config.mts +3 -0
- src/{test.mts β tests/downloadVideo.mts} +6 -12
- src/{test2.mts β tests/stuff.mts} +1 -1
- src/tests/submitVideo.mts +23 -0
- src/types.mts +209 -41
- src/utils/getValidNumber.mts +10 -0
- src/utils/getValidResolution.mts +15 -0
database/completed/README.md
ADDED
|
File without changes
|
database/pending/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Completed tasks go here
|
package-lock.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
CHANGED
|
@@ -5,8 +5,10 @@
|
|
| 5 |
"main": "src/index.mts",
|
| 6 |
"scripts": {
|
| 7 |
"start": "node --loader ts-node/esm src/index.mts",
|
| 8 |
-
"test": "node --loader ts-node/esm src/
|
| 9 |
-
"
|
|
|
|
|
|
|
| 10 |
"docker": "npm run docker:build && npm run docker:run",
|
| 11 |
"docker:build": "docker build -t videochain-api .",
|
| 12 |
"docker:run": "docker run -it -p 7860:7860 videochain-api"
|
|
@@ -17,8 +19,10 @@
|
|
| 17 |
"@gradio/client": "^0.1.4",
|
| 18 |
"@huggingface/inference": "^2.6.1",
|
| 19 |
"@types/express": "^4.17.17",
|
|
|
|
| 20 |
"@types/uuid": "^9.0.2",
|
| 21 |
"express": "^4.18.2",
|
|
|
|
| 22 |
"fluent-ffmpeg": "^2.1.2",
|
| 23 |
"fs-extra": "^11.1.1",
|
| 24 |
"node-fetch": "^3.3.1",
|
|
|
|
| 5 |
"main": "src/index.mts",
|
| 6 |
"scripts": {
|
| 7 |
"start": "node --loader ts-node/esm src/index.mts",
|
| 8 |
+
"test:submitVideo": "node --loader ts-node/esm src/tests/submitVideo.mts",
|
| 9 |
+
"test:checkStatus": "node --loader ts-node/esm src/tests/checkStatus.mts",
|
| 10 |
+
"test:downloadVideo": "node --loader ts-node/esm src/tests/downloadVideo.mts",
|
| 11 |
+
"test:stuff": "node --loader ts-node/esm src/stuff.mts",
|
| 12 |
"docker": "npm run docker:build && npm run docker:run",
|
| 13 |
"docker:build": "docker build -t videochain-api .",
|
| 14 |
"docker:run": "docker run -it -p 7860:7860 videochain-api"
|
|
|
|
| 19 |
"@gradio/client": "^0.1.4",
|
| 20 |
"@huggingface/inference": "^2.6.1",
|
| 21 |
"@types/express": "^4.17.17",
|
| 22 |
+
"@types/ffmpeg-concat": "^1.1.2",
|
| 23 |
"@types/uuid": "^9.0.2",
|
| 24 |
"express": "^4.18.2",
|
| 25 |
+
"ffmpeg-concat": "^1.3.0",
|
| 26 |
"fluent-ffmpeg": "^2.1.2",
|
| 27 |
"fs-extra": "^11.1.1",
|
| 28 |
"node-fetch": "^3.3.1",
|
src/database/constants.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
export const pendingTasksDirFilePath = './database/pending/'
|
| 3 |
+
export const completedTasksDirFilePath = './database/completed/'
|
src/database/getCompletedTasks.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { VideoTask } from "../types.mts"
|
| 2 |
+
import { completedTasksDirFilePath } from "./constants.mts"
|
| 3 |
+
import { readTasks } from "./readTasks.mts"
|
| 4 |
+
|
| 5 |
+
export const getCompletedTasks = async (): Promise<VideoTask[]> => {
|
| 6 |
+
const completedTasks = await readTasks(completedTasksDirFilePath)
|
| 7 |
+
|
| 8 |
+
return completedTasks
|
| 9 |
+
}
|
src/database/getPendingTasks.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { VideoTask } from "../types.mts"
|
| 2 |
+
import { pendingTasksDirFilePath } from "./constants.mts"
|
| 3 |
+
import { readTasks } from "./readTasks.mts"
|
| 4 |
+
|
| 5 |
+
export const getPendingTasks = async (): Promise<VideoTask[]> => {
|
| 6 |
+
const pendingTasks = await readTasks(pendingTasksDirFilePath)
|
| 7 |
+
|
| 8 |
+
return pendingTasks
|
| 9 |
+
}
|
src/database/getTask.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from "node:path"
|
| 2 |
+
|
| 3 |
+
import { completedTasksDirFilePath, pendingTasksDirFilePath } from "./constants.mts"
|
| 4 |
+
import { readTask } from "./readTask.mts"
|
| 5 |
+
|
| 6 |
+
export const getTask = async (id: string) => {
|
| 7 |
+
const taskFileName = `${id}.json`
|
| 8 |
+
|
| 9 |
+
const completedTaskFilePath = path.join(completedTasksDirFilePath, taskFileName)
|
| 10 |
+
const pendingTaskFilePath = path.join(pendingTasksDirFilePath, taskFileName)
|
| 11 |
+
|
| 12 |
+
try {
|
| 13 |
+
const completedTask = await readTask(completedTaskFilePath)
|
| 14 |
+
return completedTask
|
| 15 |
+
} catch (err) {
|
| 16 |
+
try {
|
| 17 |
+
const pendingTask = await readTask(pendingTaskFilePath)
|
| 18 |
+
return pendingTask
|
| 19 |
+
} catch (err) {
|
| 20 |
+
throw new Error(`couldn't find task ${id}`)
|
| 21 |
+
}
|
| 22 |
+
}
|
| 23 |
+
}
|
src/database/readTask.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { promises as fs } from "node:fs"
|
| 2 |
+
import path from "node:path"
|
| 3 |
+
|
| 4 |
+
import { VideoTask } from "../types.mts"
|
| 5 |
+
|
| 6 |
+
export const readTask = async (taskFilePath: string): Promise<VideoTask> => {
|
| 7 |
+
const task = JSON.parse(
|
| 8 |
+
await fs.readFile(taskFilePath, 'utf8')
|
| 9 |
+
) as VideoTask
|
| 10 |
+
|
| 11 |
+
return task
|
| 12 |
+
}
|
src/database/readTasks.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import path from "node:path"
|
| 2 |
+
import { promises as fs } from "node:fs"
|
| 3 |
+
|
| 4 |
+
import { VideoTask } from "../types.mts"
|
| 5 |
+
import { readTask } from "./readTask.mts"
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
export const readTasks = async (taskDirFilePath: string): Promise<VideoTask[]> => {
|
| 9 |
+
|
| 10 |
+
let tasksFiles: string[] = []
|
| 11 |
+
try {
|
| 12 |
+
const filesInDir = await fs.readdir(taskDirFilePath)
|
| 13 |
+
|
| 14 |
+
// we only keep valid files (in UUID.json format)
|
| 15 |
+
tasksFiles = filesInDir.filter(fileName => fileName.match(/[a-z0-9\-]\.json/i))
|
| 16 |
+
} catch (err) {
|
| 17 |
+
console.log(`failed to read tasks: ${err}`)
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
const tasks: VideoTask[] = []
|
| 21 |
+
|
| 22 |
+
for (const taskFileName in tasksFiles) {
|
| 23 |
+
const taskFilePath = path.join(taskDirFilePath, taskFileName)
|
| 24 |
+
try {
|
| 25 |
+
const task = await readTask(taskFilePath)
|
| 26 |
+
tasks.push(task)
|
| 27 |
+
} catch (parsingErr) {
|
| 28 |
+
console.log(`failed to read ${taskFileName}: ${parsingErr}`)
|
| 29 |
+
console.log(`deleting corrupted file ${taskFileName}`)
|
| 30 |
+
try {
|
| 31 |
+
await fs.unlink(taskFilePath)
|
| 32 |
+
} catch (unlinkErr) {
|
| 33 |
+
console.log(`failed to unlink ${taskFileName}: ${unlinkErr}`)
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
return tasks
|
| 39 |
+
}
|
src/database/saveCompletedTask.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { promises as fs } from "node:fs"
|
| 2 |
+
import path from "path"
|
| 3 |
+
|
| 4 |
+
import { VideoTask } from "../types.mts"
|
| 5 |
+
import { completedTasksDirFilePath, pendingTasksDirFilePath } from "./constants.mts"
|
| 6 |
+
|
| 7 |
+
export const saveCompletedTask = async (task: VideoTask) => {
|
| 8 |
+
const fileName = `${task.id}.json`
|
| 9 |
+
const pendingFilePath = path.join(pendingTasksDirFilePath, fileName)
|
| 10 |
+
const completedFilePath = path.join(completedTasksDirFilePath, fileName)
|
| 11 |
+
await fs.writeFile(completedFilePath, JSON.stringify(task, null, 2), "utf8")
|
| 12 |
+
await fs.unlink(pendingFilePath)
|
| 13 |
+
}
|
src/database/savePendingTask.mts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { promises as fs } from "node:fs"
|
| 2 |
+
import path from "path"
|
| 3 |
+
|
| 4 |
+
import { VideoTask } from "../types.mts"
|
| 5 |
+
import { pendingTasksDirFilePath } from "./constants.mts"
|
| 6 |
+
|
| 7 |
+
export const savePendingTask = async (task: VideoTask) => {
|
| 8 |
+
const fileName = `${task.id}.json`
|
| 9 |
+
const filePath = path.join(pendingTasksDirFilePath, fileName)
|
| 10 |
+
await fs.writeFile(filePath, JSON.stringify(task, null, 2), "utf8")
|
| 11 |
+
}
|
src/index.mts
CHANGED
|
@@ -1,112 +1,123 @@
|
|
| 1 |
-
import { promises as fs } from "fs"
|
| 2 |
|
| 3 |
import express from "express"
|
| 4 |
|
| 5 |
-
import {
|
| 6 |
-
import {
|
| 7 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
const app = express()
|
| 10 |
const port = 7860
|
| 11 |
|
| 12 |
app.use(express.json())
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
const
|
| 18 |
-
|
| 19 |
-
const token = `${query.token || ""}`
|
| 20 |
if (token !== process.env.VS_SECRET_ACCESS_TOKEN) {
|
| 21 |
console.log("couldn't find access token in the query")
|
| 22 |
-
res.
|
|
|
|
| 23 |
res.end()
|
| 24 |
return
|
| 25 |
}
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
res.end()
|
| 31 |
return
|
| 32 |
}
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
const upscale = `${query.upscale || "true"}` === "true"
|
| 51 |
-
const interpolate = `${query.upscale || "true"}` === "true"
|
| 52 |
-
const noise = `${query.noise || "true"}` === "true"
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
const stepsStr = Number(`${query.steps || defaultSteps}`)
|
| 63 |
-
const maybeSteps = Number(stepsStr)
|
| 64 |
-
const nbSteps = Math.min(60, Math.max(1, isNaN(maybeSteps) || !isFinite(maybeSteps) ? defaultSteps : maybeSteps))
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
const actorVoicePrompt = `${query.actorVoicePrompt || ""}`
|
| 80 |
-
|
| 81 |
-
const actorDialoguePrompt = `${query.actorDialoguePrompt || ""}`
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
const { filePath } = await generateShot({
|
| 85 |
-
seed,
|
| 86 |
-
actorPrompt,
|
| 87 |
-
shotPrompt,
|
| 88 |
-
backgroundAudioPrompt,
|
| 89 |
-
foregroundAudioPrompt,
|
| 90 |
-
actorDialoguePrompt,
|
| 91 |
-
actorVoicePrompt,
|
| 92 |
-
duration,
|
| 93 |
-
nbFrames,
|
| 94 |
-
resolution,
|
| 95 |
-
nbSteps,
|
| 96 |
-
upscale,
|
| 97 |
-
interpolate,
|
| 98 |
-
noise,
|
| 99 |
-
})
|
| 100 |
-
|
| 101 |
-
console.log(`generated video in ${filePath}`)
|
| 102 |
-
|
| 103 |
-
console.log("returning result to user..")
|
| 104 |
-
|
| 105 |
-
const buffer = await fs.readFile(filePath)
|
| 106 |
-
|
| 107 |
-
res.setHeader("Content-Type", "media/mp4")
|
| 108 |
-
res.setHeader("Content-Length", buffer.length)
|
| 109 |
-
res.end(buffer)
|
| 110 |
})
|
| 111 |
|
| 112 |
app.listen(port, () => { console.log(`Open http://localhost:${port}`) })
|
|
|
|
| 1 |
+
import { createReadStream, promises as fs } from "fs"
|
| 2 |
|
| 3 |
import express from "express"
|
| 4 |
|
| 5 |
+
import { VideoTask, VideoSequenceRequest } from "./types.mts"
|
| 6 |
+
import { requestToTask } from "./services/requestToTask.mts"
|
| 7 |
+
import { savePendingTask } from "./database/savePendingTask.mts"
|
| 8 |
+
import { getTask } from "./database/getTask.mts"
|
| 9 |
+
import { main } from "./main.mts"
|
| 10 |
+
|
| 11 |
+
main()
|
| 12 |
|
| 13 |
const app = express()
|
| 14 |
const port = 7860
|
| 15 |
|
| 16 |
app.use(express.json())
|
| 17 |
|
| 18 |
+
app.post("/", async (req, res) => {
|
| 19 |
+
const request = req.body as VideoSequenceRequest
|
| 20 |
+
|
| 21 |
+
const token = `${request.token || ""}`
|
|
|
|
|
|
|
| 22 |
if (token !== process.env.VS_SECRET_ACCESS_TOKEN) {
|
| 23 |
console.log("couldn't find access token in the query")
|
| 24 |
+
res.status(401)
|
| 25 |
+
res.write(JSON.stringify({ error: "invalid token" }))
|
| 26 |
res.end()
|
| 27 |
return
|
| 28 |
}
|
| 29 |
|
| 30 |
+
let task: VideoTask = null
|
| 31 |
+
|
| 32 |
+
console.log(`creating task from request..`)
|
| 33 |
+
try {
|
| 34 |
+
task = await requestToTask(request)
|
| 35 |
+
} catch (err) {
|
| 36 |
+
console.error(`failed to create task: ${task}`)
|
| 37 |
+
res.status(400)
|
| 38 |
+
res.write(JSON.stringify({ error: "query seems to be malformed" }))
|
| 39 |
res.end()
|
| 40 |
return
|
| 41 |
}
|
| 42 |
|
| 43 |
+
console.log(`saving task ${task.id}`)
|
| 44 |
+
try {
|
| 45 |
+
await savePendingTask(task)
|
| 46 |
+
res.status(200)
|
| 47 |
+
res.write(JSON.stringify(task))
|
| 48 |
+
res.end()
|
| 49 |
+
} catch (err) {
|
| 50 |
+
console.error(err)
|
| 51 |
+
res.status(500)
|
| 52 |
+
res.write(JSON.stringify({ error: "couldn't save the task" }))
|
| 53 |
+
res.end()
|
| 54 |
+
}
|
| 55 |
+
})
|
| 56 |
|
| 57 |
+
app.get("/:id", async (req, res) => {
|
| 58 |
+
try {
|
| 59 |
+
const task = await getTask(req.params.id)
|
| 60 |
+
delete task.finalFilePath
|
| 61 |
+
delete task.tmpFilePath
|
| 62 |
+
res.status(200)
|
| 63 |
+
res.write(JSON.stringify(task))
|
| 64 |
+
res.end()
|
| 65 |
+
} catch (err) {
|
| 66 |
+
console.error(err)
|
| 67 |
+
res.status(404)
|
| 68 |
+
res.write(JSON.stringify({ error: "couldn't find this task" }))
|
| 69 |
+
res.end()
|
| 70 |
+
}
|
| 71 |
+
})
|
| 72 |
|
| 73 |
+
app.get("/video/:id\.mp4", async (req, res) => {
|
| 74 |
+
if (!req.params.id) {
|
| 75 |
+
res.status(400)
|
| 76 |
+
res.write(JSON.stringify({ error: "please provide a valid video id" }))
|
| 77 |
+
res.end()
|
| 78 |
+
return
|
| 79 |
+
}
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
let task: VideoTask = null
|
| 82 |
+
|
| 83 |
+
try {
|
| 84 |
+
task = await getTask(req.params.id)
|
| 85 |
+
console.log("returning result to user..")
|
| 86 |
+
|
| 87 |
+
const filePath = task.finalFilePath || task.tmpFilePath || ''
|
| 88 |
+
if (!filePath) {
|
| 89 |
+
res.status(400)
|
| 90 |
+
res.write(JSON.stringify({ error: "video exists, but cannot be previewed yet" }))
|
| 91 |
+
res.end()
|
| 92 |
+
return
|
| 93 |
+
}
|
| 94 |
+
} catch (err) {
|
| 95 |
+
res.status(404)
|
| 96 |
+
res.write(JSON.stringify({ error: "this video doesn't exist" }))
|
| 97 |
+
res.end()
|
| 98 |
+
return
|
| 99 |
+
}
|
| 100 |
|
| 101 |
+
// file path exists, let's try to read it
|
| 102 |
+
try {
|
| 103 |
+
// do we need this?
|
| 104 |
+
// res.status(200)
|
| 105 |
+
// res.setHeader("Content-Type", "media/mp4")
|
| 106 |
+
console.log(`creating a video read stream from ${filePath}`)
|
| 107 |
+
const stream = createReadStream(filePath)
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
+
stream.on('close', () => {
|
| 110 |
+
console.log(`finished streaming the video`)
|
| 111 |
+
res.end()
|
| 112 |
+
})
|
| 113 |
+
|
| 114 |
+
stream.pipe(res)
|
| 115 |
+
} catch (err) {
|
| 116 |
+
console.error(`failed to read the video file at ${filePath}: ${err}`)
|
| 117 |
+
res.status(500)
|
| 118 |
+
res.write(JSON.stringify({ error: "failed to read the video file" }))
|
| 119 |
+
res.end()
|
| 120 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
})
|
| 122 |
|
| 123 |
app.listen(port, () => { console.log(`Open http://localhost:${port}`) })
|
src/main.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getPendingTasks } from "./database/getPendingTasks.mts"
|
| 2 |
+
|
| 3 |
+
export const main = async () => {
|
| 4 |
+
const tasks = await getPendingTasks()
|
| 5 |
+
if (!tasks.length) {
|
| 6 |
+
setTimeout(() => {
|
| 7 |
+
main()
|
| 8 |
+
}, 500)
|
| 9 |
+
return
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
console.log(`there are ${tasks.length} pending tasks`)
|
| 13 |
+
|
| 14 |
+
setTimeout(() => {
|
| 15 |
+
main()
|
| 16 |
+
}, 1000)
|
| 17 |
+
}
|
src/services/processTask.mts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { VideoTask } from "../types.mts";
|
| 2 |
+
|
| 3 |
+
export const processTask = async (task: VideoTask) => {
|
| 4 |
+
|
| 5 |
+
}
|
src/services/requestToTask.mts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { v4 as uuidv4 } from "uuid"
|
| 2 |
+
|
| 3 |
+
// convert a request (which might be invalid)
|
| 4 |
+
|
| 5 |
+
import { VideoSequenceRequest, VideoTask } from "../types.mts"
|
| 6 |
+
import { generateSeed } from "./generateSeed.mts"
|
| 7 |
+
import { getValidNumber } from "../utils/getValidNumber.mts"
|
| 8 |
+
import { getValidResolution } from "../utils/getValidResolution.mts"
|
| 9 |
+
|
| 10 |
+
// into a valid task
|
| 11 |
+
export const requestToTask = async (request: VideoSequenceRequest): Promise<VideoTask> => {
|
| 12 |
+
|
| 13 |
+
const task: VideoTask = {
|
| 14 |
+
// ------------ VideoSequenceMeta -------------
|
| 15 |
+
id: uuidv4(),
|
| 16 |
+
|
| 17 |
+
// describe the whole movie
|
| 18 |
+
videoPrompt: `${request.sequence.videoPrompt || ''}`,
|
| 19 |
+
|
| 20 |
+
// describe the background audio (crowd, birds, wind, sea etc..)
|
| 21 |
+
backgroundAudioPrompt: `${request.sequence.backgroundAudioPrompt || ''}`,
|
| 22 |
+
|
| 23 |
+
// describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
|
| 24 |
+
foregroundAudioPrompt: `${request.sequence.foregroundAudioPrompt || ''}`,
|
| 25 |
+
|
| 26 |
+
// describe the main actor visible in the shot (optional)
|
| 27 |
+
actorPrompt: `${request.sequence.actorPrompt || ''}`,
|
| 28 |
+
|
| 29 |
+
// describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
|
| 30 |
+
actorVoicePrompt: `${request.sequence.actorVoicePrompt || ''}`,
|
| 31 |
+
|
| 32 |
+
// describe the main actor dialogue line
|
| 33 |
+
actorDialoguePrompt: `${request.sequence.actorDialoguePrompt || ''}`,
|
| 34 |
+
|
| 35 |
+
seed: getValidNumber(request.sequence.seed, 0, 4294967295, generateSeed()),
|
| 36 |
+
|
| 37 |
+
upscale: request.sequence.upscale === true,
|
| 38 |
+
|
| 39 |
+
noise: request.sequence.noise === true,
|
| 40 |
+
|
| 41 |
+
steps: getValidNumber(request.sequence.steps, 1, 60, 35),
|
| 42 |
+
|
| 43 |
+
fps: getValidNumber(request.sequence.fps, 8, 60, 24),
|
| 44 |
+
|
| 45 |
+
resolution: getValidResolution(request.sequence.resolution),
|
| 46 |
+
|
| 47 |
+
outroTransition: 'staticfade',
|
| 48 |
+
outroDurationMs: 3000,
|
| 49 |
+
|
| 50 |
+
// ---------- VideoSequenceData ---------
|
| 51 |
+
nbCompletedShots: 0,
|
| 52 |
+
nbTotalShots: 0,
|
| 53 |
+
progressPercent: 0,
|
| 54 |
+
completedAt: null,
|
| 55 |
+
completed: false,
|
| 56 |
+
error: '',
|
| 57 |
+
tmpFilePath: '',
|
| 58 |
+
finalFilePath: '',
|
| 59 |
+
|
| 60 |
+
// ------- the VideoShot -----
|
| 61 |
+
|
| 62 |
+
shots: [],
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
// optional background audio prompt
|
| 67 |
+
const backgroundAudioPrompt = `${query.backgroundAudioPrompt || ""}`
|
| 68 |
+
|
| 69 |
+
// optional foreground audio prompt
|
| 70 |
+
const foregroundAudioPrompt = `${query.foregroundAudioPrompt || ""}`
|
| 71 |
+
|
| 72 |
+
// optional seed
|
| 73 |
+
const defaultSeed = generateSeed()
|
| 74 |
+
const seedStr = getValidNumber(Number(`${query.seed || defaultSeed}`)
|
| 75 |
+
const maybeSeed = Number(seedStr)
|
| 76 |
+
const seed = isNaN(maybeSeed) || ! isFinite(maybeSeed) ? defaultSeed : maybeSeed
|
| 77 |
+
|
| 78 |
+
// in production we want those ON by default
|
| 79 |
+
const upscale = `${query.upscale || "true"}` === "true"
|
| 80 |
+
const interpolate = `${query.upscale || "true"}` === "true"
|
| 81 |
+
const noise = `${query.noise || "true"}` === "true"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
const defaultDuration = 3
|
| 85 |
+
const maxDuration = 5
|
| 86 |
+
const durationStr = Number(`${query.duration || defaultDuration}`)
|
| 87 |
+
const maybeDuration = Number(durationStr)
|
| 88 |
+
const duration = Math.min(maxDuration, Math.max(1, isNaN(maybeDuration) || !isFinite(maybeDuration) ? defaultDuration : maybeDuration))
|
| 89 |
+
|
| 90 |
+
const defaultSteps = 35
|
| 91 |
+
const stepsStr = Number(`${query.steps || defaultSteps}`)
|
| 92 |
+
const maybeSteps = Number(stepsStr)
|
| 93 |
+
const nbSteps = Math.min(60, Math.max(1, isNaN(maybeSteps) || !isFinite(maybeSteps) ? defaultSteps : maybeSteps))
|
| 94 |
+
|
| 95 |
+
// const frames per second
|
| 96 |
+
const defaultFps = 24
|
| 97 |
+
const fpsStr = Number(`${query.fps || defaultFps}`)
|
| 98 |
+
const maybeFps = Number(fpsStr)
|
| 99 |
+
const nbFrames = Math.min(60, Math.max(8, isNaN(maybeFps) || !isFinite(maybeFps) ? defaultFps : maybeFps))
|
| 100 |
+
|
| 101 |
+
const defaultResolution = 576
|
| 102 |
+
const resolutionStr = Number(`${query.resolution || defaultResolution}`)
|
| 103 |
+
const maybeResolution = Number(resolutionStr)
|
| 104 |
+
const resolution = Math.min(1080, Math.max(256, isNaN(maybeResolution) || !isFinite(maybeResolution) ? defaultResolution : maybeResolution))
|
| 105 |
+
|
| 106 |
+
const actorPrompt = `${query.actorPrompt || ""}`
|
| 107 |
+
|
| 108 |
+
const actorVoicePrompt = `${query.actorVoicePrompt || ""}`
|
| 109 |
+
|
| 110 |
+
const actorDialoguePrompt = `${query.actorDialoguePrompt || ""}`
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
return task
|
| 115 |
+
}
|
src/tests/checkStatus.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { videoId, server } from "./config.mts"
|
| 2 |
+
|
| 3 |
+
console.log(`checking status of video ${videoId}`)
|
| 4 |
+
const response = await fetch(`${server}/${videoId}`, {
|
| 5 |
+
method: "GET",
|
| 6 |
+
headers: {
|
| 7 |
+
"Accept": "application/json",
|
| 8 |
+
}
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
console.log('response:', response)
|
| 12 |
+
const task = await response.json()
|
| 13 |
+
|
| 14 |
+
console.log("task:", JSON.stringify(task, null, 2))
|
src/tests/config.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const server = "http://localhost:7860"
|
| 2 |
+
|
| 3 |
+
export const videoId = "9a9009a5-b960-49a2-bed2-baf0423cc907"
|
src/{test.mts β tests/downloadVideo.mts}
RENAMED
|
@@ -1,23 +1,17 @@
|
|
| 1 |
import { promises as fs } from "node:fs"
|
| 2 |
|
|
|
|
| 3 |
|
| 4 |
-
console.log(
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
"Accept": "application/json",
|
| 9 |
-
"Content-Type": "application/json"
|
| 10 |
-
},
|
| 11 |
-
body: JSON.stringify({
|
| 12 |
-
token: process.env.VS_SECRET_ACCESS_TOKEN,
|
| 13 |
-
shotPrompt: "video of a dancing cat"
|
| 14 |
-
})
|
| 15 |
});
|
| 16 |
|
| 17 |
console.log('response:', response)
|
| 18 |
const buffer = await (response as any).buffer()
|
| 19 |
|
| 20 |
-
fs.writeFile(
|
| 21 |
|
| 22 |
// if called from an API, we ΓΉight want to use streams instead:
|
| 23 |
// https://stackoverflow.com/questions/15713424/how-can-i-download-a-video-mp4-file-using-node-js
|
|
|
|
| 1 |
import { promises as fs } from "node:fs"
|
| 2 |
|
| 3 |
+
import { videoId, server } from "./config.mts"
|
| 4 |
|
| 5 |
+
console.log(`trying to download video ${videoId}`)
|
| 6 |
+
|
| 7 |
+
const response = await fetch(`${server}/${videoId}.mp4`, {
|
| 8 |
+
method: "GET",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
});
|
| 10 |
|
| 11 |
console.log('response:', response)
|
| 12 |
const buffer = await (response as any).buffer()
|
| 13 |
|
| 14 |
+
fs.writeFile(`./${videoId}.mp4`, buffer)
|
| 15 |
|
| 16 |
// if called from an API, we ΓΉight want to use streams instead:
|
| 17 |
// https://stackoverflow.com/questions/15713424/how-can-i-download-a-video-mp4-file-using-node-js
|
src/{test2.mts β tests/stuff.mts}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { generateAudio } from "
|
| 2 |
|
| 3 |
|
| 4 |
console.log('generating background audio..')
|
|
|
|
| 1 |
+
import { generateAudio } from "../services/generateAudio.mts"
|
| 2 |
|
| 3 |
|
| 4 |
console.log('generating background audio..')
|
src/tests/submitVideo.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { server, videoId } from "./config.mts"
|
| 2 |
+
|
| 3 |
+
console.log('submitting a new video..')
|
| 4 |
+
const response = await fetch(`${server}/`, {
|
| 5 |
+
method: "POST",
|
| 6 |
+
headers: {
|
| 7 |
+
"Accept": "application/json",
|
| 8 |
+
"Content-Type": "application/json"
|
| 9 |
+
},
|
| 10 |
+
body: JSON.stringify({
|
| 11 |
+
token: process.env.VS_SECRET_ACCESS_TOKEN,
|
| 12 |
+
sequence: {
|
| 13 |
+
id: videoId,
|
| 14 |
+
},
|
| 15 |
+
shots: []
|
| 16 |
+
})
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
console.log('response:', response)
|
| 21 |
+
const task = await response.json()
|
| 22 |
+
|
| 23 |
+
console.log("task:", JSON.stringify(task, null, 2))
|
src/types.mts
CHANGED
|
@@ -1,65 +1,233 @@
|
|
| 1 |
-
export
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
|
|
|
|
| 28 |
|
| 29 |
-
export interface
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
// describe the background audio (crowd, birds, wind, sea etc..)
|
| 35 |
-
backgroundAudioPrompt
|
| 36 |
|
| 37 |
// describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
|
| 38 |
-
foregroundAudioPrompt
|
| 39 |
|
| 40 |
// describe the main actor visible in the shot (optional)
|
| 41 |
-
actorPrompt
|
| 42 |
|
| 43 |
// describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
|
| 44 |
-
actorVoicePrompt
|
| 45 |
|
| 46 |
// describe the main actor dialogue line
|
| 47 |
-
actorDialoguePrompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
upscale?: boolean
|
| 51 |
|
| 52 |
-
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
fps?: number // 8, 12, 24, 30, 60
|
| 58 |
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
-
export
|
| 63 |
-
|
| 64 |
-
query: ShotQuery
|
| 65 |
}
|
|
|
|
| 1 |
+
export type VideoTransition =
|
| 2 |
+
| 'dissolve'
|
| 3 |
+
| 'bookflip'
|
| 4 |
+
| 'bounce'
|
| 5 |
+
| 'bowtiehorizontal'
|
| 6 |
+
| 'bowtievertical'
|
| 7 |
+
| 'bowtiewithparameter'
|
| 8 |
+
| 'butterflywavescrawler'
|
| 9 |
+
| 'circlecrop'
|
| 10 |
+
| 'colourdistance'
|
| 11 |
+
| 'crazyparametricfun'
|
| 12 |
+
| 'crosszoom'
|
| 13 |
+
| 'directional'
|
| 14 |
+
| 'directionalscaled'
|
| 15 |
+
| 'doomscreentransition'
|
| 16 |
+
| 'dreamy'
|
| 17 |
+
| 'dreamyzoom'
|
| 18 |
+
| 'edgetransition'
|
| 19 |
+
| 'filmburn'
|
| 20 |
+
| 'filmburnglitchdisplace'
|
| 21 |
+
| 'glitchmemories'
|
| 22 |
+
| 'gridflip'
|
| 23 |
+
| 'horizontalclose'
|
| 24 |
+
| 'horizontalopen'
|
| 25 |
+
| 'invertedpagecurl'
|
| 26 |
+
| 'leftright'
|
| 27 |
+
| 'linearblur'
|
| 28 |
+
| 'mosaic'
|
| 29 |
+
| 'overexposure'
|
| 30 |
+
| 'polkadotscurtain'
|
| 31 |
+
| 'radial'
|
| 32 |
+
| 'rectangle'
|
| 33 |
+
| 'rectanglecrop'
|
| 34 |
+
| 'rolls'
|
| 35 |
+
| 'rotatescalevanish'
|
| 36 |
+
| 'simplezoom'
|
| 37 |
+
| 'simplezoomout'
|
| 38 |
+
| 'slides'
|
| 39 |
+
| 'staticfade'
|
| 40 |
+
| 'stereoviewer'
|
| 41 |
+
| 'swirl'
|
| 42 |
+
| 'tvstatic'
|
| 43 |
+
| 'topbottom'
|
| 44 |
+
| 'verticalclose'
|
| 45 |
+
| 'verticalopen'
|
| 46 |
+
| 'waterdrop'
|
| 47 |
+
| 'waterdropzoomincircles'
|
| 48 |
+
| 'zoomleftwipe'
|
| 49 |
+
| 'zoomrigthwipe'
|
| 50 |
+
| 'angular'
|
| 51 |
+
| 'burn'
|
| 52 |
+
| 'cannabisleaf'
|
| 53 |
+
| 'circle'
|
| 54 |
+
| 'circleopen'
|
| 55 |
+
| 'colorphase'
|
| 56 |
+
| 'coordfromin'
|
| 57 |
+
| 'crosshatch'
|
| 58 |
+
| 'crosswarp'
|
| 59 |
+
| 'cube'
|
| 60 |
+
| 'directionaleasing'
|
| 61 |
+
| 'directionalwarp'
|
| 62 |
+
| 'directionalwipe'
|
| 63 |
+
| 'displacement'
|
| 64 |
+
| 'doorway'
|
| 65 |
+
| 'fade'
|
| 66 |
+
| 'fadecolor'
|
| 67 |
+
| 'fadegrayscale'
|
| 68 |
+
| 'flyeye'
|
| 69 |
+
| 'heart'
|
| 70 |
+
| 'hexagonalize'
|
| 71 |
+
| 'kaleidoscope'
|
| 72 |
+
| 'luma'
|
| 73 |
+
| 'luminance_melt'
|
| 74 |
+
| 'morph'
|
| 75 |
+
| 'mosaic_transition'
|
| 76 |
+
| 'multiply_blend'
|
| 77 |
+
| 'perlin'
|
| 78 |
+
| 'pinwheel'
|
| 79 |
+
| 'pixelize'
|
| 80 |
+
| 'polar_function'
|
| 81 |
+
| 'powerkaleido'
|
| 82 |
+
| 'randomnoisex'
|
| 83 |
+
| 'randomsquares'
|
| 84 |
+
| 'ripple'
|
| 85 |
+
| 'rotatetransition'
|
| 86 |
+
| 'rotate_scale_fade'
|
| 87 |
+
| 'scalein'
|
| 88 |
+
| 'squareswire'
|
| 89 |
+
| 'squeeze'
|
| 90 |
+
| 'static_wipe'
|
| 91 |
+
| 'swap'
|
| 92 |
+
| 'tangentmotionblur'
|
| 93 |
+
| 'undulatingburnout'
|
| 94 |
+
| 'wind'
|
| 95 |
+
| 'windowblinds'
|
| 96 |
+
| 'windowslice'
|
| 97 |
+
| 'wipedown'
|
| 98 |
+
| 'wipeleft'
|
| 99 |
+
| 'wiperight'
|
| 100 |
+
| 'wipeup'
|
| 101 |
+
| 'x_axistranslation'
|
| 102 |
|
| 103 |
+
|
| 104 |
+
export interface VideoShotMeta {
|
| 105 |
+
// must be unique
|
| 106 |
+
id: string
|
| 107 |
+
|
| 108 |
+
shotPrompt: string
|
| 109 |
+
// inputVideo?: string
|
| 110 |
+
|
| 111 |
+
// describe the background audio (crowd, birds, wind, sea etc..)
|
| 112 |
+
backgroundAudioPrompt: string
|
| 113 |
+
|
| 114 |
+
// describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
|
| 115 |
+
foregroundAudioPrompt: string
|
| 116 |
+
|
| 117 |
+
// describe the main actor visible in the shot (optional)
|
| 118 |
+
actorPrompt: string
|
| 119 |
+
|
| 120 |
+
// describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
|
| 121 |
+
actorVoicePrompt: string
|
| 122 |
+
|
| 123 |
+
// describe the main actor dialogue line
|
| 124 |
+
actorDialoguePrompt: string
|
| 125 |
+
|
| 126 |
+
seed: number
|
| 127 |
+
upscale: boolean
|
| 128 |
+
|
| 129 |
+
noise: boolean // add movie noise
|
| 130 |
+
|
| 131 |
+
durationMs: number // in milliseconds
|
| 132 |
+
steps: number
|
| 133 |
+
|
| 134 |
+
fps: number // 8, 12, 24, 30, 60
|
| 135 |
+
|
| 136 |
+
resolution: number // 256, 512, 576, 720, 1080
|
| 137 |
+
|
| 138 |
+
introTransition: VideoTransition
|
| 139 |
+
introDurationMs: number // in milliseconds
|
| 140 |
+
|
| 141 |
+
// for internal use
|
| 142 |
+
hasGeneratedVideo: boolean
|
| 143 |
+
hasUpscaledVideo: boolean
|
| 144 |
+
hasGeneratedBackgroundAudio: boolean
|
| 145 |
+
hasGeneratedForegroundAudio: boolean
|
| 146 |
+
hasGeneratedActor: boolean
|
| 147 |
+
hasInterpolatedVideo: boolean
|
| 148 |
+
hasAddedAudio: boolean
|
| 149 |
+
hasPostProcessedVideo: boolean
|
| 150 |
}
|
| 151 |
|
| 152 |
+
|
| 153 |
+
export interface VideoShotData {
|
| 154 |
+
hasGeneratedVideo: boolean
|
| 155 |
+
hasUpscaledVideo: boolean
|
| 156 |
+
hasGeneratedBackgroundAudio: boolean
|
| 157 |
+
hasGeneratedForegroundAudio: boolean
|
| 158 |
+
hasGeneratedActor: boolean
|
| 159 |
+
hasInterpolatedVideo: boolean
|
| 160 |
+
hasAddedAudio: boolean
|
| 161 |
+
hasPostProcessedVideo: boolean
|
| 162 |
+
nbCompletedSteps: number
|
| 163 |
+
nbTotalSteps: number
|
| 164 |
+
progressPercent: number
|
| 165 |
+
completedAt: string
|
| 166 |
+
completed: boolean
|
| 167 |
+
error: string
|
| 168 |
+
tmpFilePath: string
|
| 169 |
+
finalFilePath: string
|
| 170 |
}
|
| 171 |
|
| 172 |
+
export type VideoShot = VideoShotMeta & VideoShotData
|
| 173 |
|
| 174 |
+
export interface VideoSequenceMeta {
|
| 175 |
+
// must be unique
|
| 176 |
+
id: string
|
| 177 |
+
|
| 178 |
+
// describe the whole movie
|
| 179 |
+
videoPrompt: string
|
| 180 |
|
| 181 |
// describe the background audio (crowd, birds, wind, sea etc..)
|
| 182 |
+
backgroundAudioPrompt: string
|
| 183 |
|
| 184 |
// describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
|
| 185 |
+
foregroundAudioPrompt: string
|
| 186 |
|
| 187 |
// describe the main actor visible in the shot (optional)
|
| 188 |
+
actorPrompt: string
|
| 189 |
|
| 190 |
// describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
|
| 191 |
+
actorVoicePrompt: string
|
| 192 |
|
| 193 |
// describe the main actor dialogue line
|
| 194 |
+
actorDialoguePrompt: string
|
| 195 |
+
|
| 196 |
+
seed: number
|
| 197 |
+
upscale: boolean
|
| 198 |
+
|
| 199 |
+
noise: boolean // add movie noise
|
| 200 |
|
| 201 |
+
steps: number
|
|
|
|
| 202 |
|
| 203 |
+
fps: number // 8, 12, 24, 30, 60
|
| 204 |
|
| 205 |
+
resolution: string // 256, 512, 576, 720, 1080
|
| 206 |
+
|
| 207 |
+
outroTransition: VideoTransition
|
| 208 |
+
outroDurationMs: number
|
| 209 |
+
}
|
| 210 |
|
|
|
|
| 211 |
|
| 212 |
+
export interface VideoSequenceData {
|
| 213 |
+
nbCompletedShots: number
|
| 214 |
+
nbTotalShots: number
|
| 215 |
+
progressPercent: number
|
| 216 |
+
completedAt: string
|
| 217 |
+
completed: boolean
|
| 218 |
+
error: string
|
| 219 |
+
tmpFilePath: string
|
| 220 |
+
finalFilePath: string
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
export type VideoSequence = VideoSequenceMeta & VideoSequenceData
|
| 224 |
+
|
| 225 |
+
export type VideoSequenceRequest = {
|
| 226 |
+
token: string
|
| 227 |
+
sequence: VideoSequenceMeta
|
| 228 |
+
shots: VideoShotMeta[]
|
| 229 |
}
|
| 230 |
|
| 231 |
+
export type VideoTask = VideoSequence & {
|
| 232 |
+
shots: VideoShot[]
|
|
|
|
| 233 |
}
|
src/utils/getValidNumber.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const getValidNumber = (something: any, minValue: number, maxValue: number, defaultValue: number) => {
|
| 2 |
+
const strValue = `${something || defaultValue}`
|
| 3 |
+
const numValue = Number(strValue)
|
| 4 |
+
const isValid = !isNaN(numValue) && isFinite(numValue)
|
| 5 |
+
if (!isValid) {
|
| 6 |
+
return defaultValue
|
| 7 |
+
}
|
| 8 |
+
return Math.max(minValue, Math.min(maxValue, numValue))
|
| 9 |
+
|
| 10 |
+
}
|
src/utils/getValidResolution.mts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getValidNumber } from "./getValidNumber.mts"
|
| 2 |
+
|
| 3 |
+
export const getValidResolution = (something: any) => {
|
| 4 |
+
const strValue = `${something || ''}`
|
| 5 |
+
const chunks = strValue.split('x')
|
| 6 |
+
if (chunks.length < 2) {
|
| 7 |
+
throw new Error('Invalid resolution (should be written like "1280x720" etc)')
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
const [widthStr, heightStr] = chunks
|
| 11 |
+
const width = getValidNumber(widthStr, 256, 1280, 1280)
|
| 12 |
+
const height = getValidNumber(widthStr, 256, 720, 720)
|
| 13 |
+
|
| 14 |
+
return `${width}x${height}`
|
| 15 |
+
}
|