jbilcke-hf HF staff commited on
Commit
cd0e411
·
1 Parent(s): ba53841

added support for parallelism

Browse files
src/main.mts CHANGED
@@ -7,9 +7,10 @@ export const main = async () => {
7
 
8
  const videos = await getPendingVideos()
9
  if (!videos.length) {
 
10
  setTimeout(() => {
11
  main()
12
- }, 500)
13
  return
14
  }
15
 
@@ -17,12 +18,24 @@ export const main = async () => {
17
 
18
  sortPendingVideosByLeastCompletedFirst(videos)
19
 
20
- for (const video of videos) {
21
- await processVideo(video)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
- console.log(`processed ${videos.length} videos`)
24
 
25
- setTimeout(() => {
26
- main()
27
- }, 1000)
28
  }
 
7
 
8
  const videos = await getPendingVideos()
9
  if (!videos.length) {
10
+ console.log(`no job to process.. going to try in 200 ms`)
11
  setTimeout(() => {
12
  main()
13
+ }, 200)
14
  return
15
  }
16
 
 
18
 
19
  sortPendingVideosByLeastCompletedFirst(videos)
20
 
21
+ let somethingFailed = ""
22
+ await Promise.all(videos.map(async video => {
23
+ try {
24
+ const result = await processVideo(video)
25
+ return result
26
+ } catch (err) {
27
+ somethingFailed = `${err}`
28
+ // a video failed.. no big deal
29
+ return Promise.resolve(somethingFailed)
30
+ }
31
+ }))
32
+
33
+ if (somethingFailed) {
34
+ console.error(`one of the jobs failed: ${somethingFailed}, let's wait 3 seconds`)
35
+ setTimeout(() => { main() }, 3000)
36
+ } else {
37
+ console.log(`successfully worked on the jobs, let's immediately loop`)
38
+ setTimeout(() => { main() }, 50)
39
  }
 
40
 
 
 
 
41
  }
src/production/generateAudio.mts CHANGED
@@ -1,62 +1,76 @@
1
- import path from "node:path"
2
-
3
  import { v4 as uuidv4 } from "uuid"
4
- import tmpDir from "temp-dir"
5
  import puppeteer from "puppeteer"
6
 
7
  import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts"
8
  import { moveFileFromTmpToPending } from "../utils/moveFileFromTmpToPending.mts"
9
 
 
 
 
 
10
  const instances: string[] = [
11
  process.env.VC_AUDIO_GENERATION_SPACE_API_URL
12
  ]
13
 
14
  // TODO we should use an inference endpoint instead
15
  export async function generateAudio(prompt: string, audioFileName: string) {
16
- const instance = instances.shift()
17
- instances.push(instance)
18
 
19
- const browser = await puppeteer.launch({
20
- headless: true,
21
- protocolTimeout: 120000,
22
- })
 
23
 
24
  try {
25
- const page = await browser.newPage()
 
26
 
27
- await page.goto(instance, {
28
- waitUntil: "networkidle2",
 
29
  })
30
 
31
- await new Promise(r => setTimeout(r, 3000))
 
32
 
33
- const firstTextboxInput = await page.$('input[data-testid="textbox"]')
 
 
34
 
35
- await firstTextboxInput.type(prompt)
36
 
37
- // console.log("looking for the button to submit")
38
- const submitButton = await page.$("button.lg")
39
 
40
- // console.log("clicking on the button")
41
- await submitButton.click()
42
 
43
- await page.waitForSelector("a[download]", {
44
- timeout: 120000, // no need to wait for too long, generation is quick
45
- })
 
 
46
 
47
- const audioRemoteUrl = await page.$$eval("a[download]", el => el.map(x => x.getAttribute("href"))[0])
 
 
48
 
 
49
 
50
- // it is always a good idea to download to a tmp dir before saving to the pending dir
51
- // because there is always a risk that the download will fail
52
-
53
- const tmpFileName = `${uuidv4()}.mp4`
54
 
55
- await downloadFileToTmp(audioRemoteUrl, tmpFileName)
56
- await moveFileFromTmpToPending(tmpFileName, audioFileName)
 
 
 
 
 
 
 
 
 
 
57
  } catch (err) {
58
  throw err
59
  } finally {
60
- await browser.close()
61
  }
62
- }
 
 
 
1
  import { v4 as uuidv4 } from "uuid"
 
2
  import puppeteer from "puppeteer"
3
 
4
  import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts"
5
  import { moveFileFromTmpToPending } from "../utils/moveFileFromTmpToPending.mts"
6
 
7
+ export const state = {
8
+ load: 0,
9
+ }
10
+
11
  const instances: string[] = [
12
  process.env.VC_AUDIO_GENERATION_SPACE_API_URL
13
  ]
14
 
15
  // TODO we should use an inference endpoint instead
16
  export async function generateAudio(prompt: string, audioFileName: string) {
 
 
17
 
18
+ if (state.load === instances.length) {
19
+ throw new Error(`all audio generation servers are busy, try again later..`)
20
+ }
21
+
22
+ state.load += 1
23
 
24
  try {
25
+ const instance = instances.shift()
26
+ instances.push(instance)
27
 
28
+ const browser = await puppeteer.launch({
29
+ headless: true,
30
+ protocolTimeout: 120000,
31
  })
32
 
33
+ try {
34
+ const page = await browser.newPage()
35
 
36
+ await page.goto(instance, {
37
+ waitUntil: "networkidle2",
38
+ })
39
 
40
+ await new Promise(r => setTimeout(r, 3000))
41
 
42
+ const firstTextboxInput = await page.$('input[data-testid="textbox"]')
 
43
 
44
+ await firstTextboxInput.type(prompt)
 
45
 
46
+ // console.log("looking for the button to submit")
47
+ const submitButton = await page.$("button.lg")
48
+
49
+ // console.log("clicking on the button")
50
+ await submitButton.click()
51
 
52
+ await page.waitForSelector("a[download]", {
53
+ timeout: 120000, // no need to wait for too long, generation is quick
54
+ })
55
 
56
+ const audioRemoteUrl = await page.$$eval("a[download]", el => el.map(x => x.getAttribute("href"))[0])
57
 
 
 
 
 
58
 
59
+ // it is always a good idea to download to a tmp dir before saving to the pending dir
60
+ // because there is always a risk that the download will fail
61
+
62
+ const tmpFileName = `${uuidv4()}.mp4`
63
+
64
+ await downloadFileToTmp(audioRemoteUrl, tmpFileName)
65
+ await moveFileFromTmpToPending(tmpFileName, audioFileName)
66
+ } catch (err) {
67
+ throw err
68
+ } finally {
69
+ await browser.close()
70
+ }
71
  } catch (err) {
72
  throw err
73
  } finally {
74
+ state.load -= 1
75
  }
76
+ }
src/production/generateAudioLegacy.mts CHANGED
@@ -2,6 +2,10 @@ import { client } from '@gradio/client'
2
 
3
  import { generateSeed } from "../utils/generateSeed.mts"
4
 
 
 
 
 
5
  const instances: string[] = [
6
  process.env.VC_AUDIO_GENERATION_SPACE_API_URL
7
  ]
@@ -11,25 +15,38 @@ export const generateAudio = async (prompt: string, options?: {
11
  nbFrames: number;
12
  nbSteps: number;
13
  }) => {
14
- const seed = options?.seed || generateSeed()
15
- const nbFrames = options?.nbFrames || 24 // we can go up to 48 frames, but then upscaling quill require too much memory!
16
- const nbSteps = options?.nbSteps || 35
17
-
18
- const instance = instances.shift()
19
- instances.push(instance)
20
-
21
- const api = await client(instance, {
22
- hf_token: `${process.env.VC_HF_API_TOKEN}` as any
23
- })
24
-
25
- const rawResponse = await api.predict('/run', [
26
- prompt, // string in 'Prompt' Textbox component
27
- seed, // number (numeric value between 0 and 2147483647) in 'Seed' Slider component
28
- nbFrames, // 24 // it is the nb of frames per seconds I think?
29
- nbSteps, // 10, (numeric value between 10 and 50) in 'Number of inference steps' Slider component
30
- ]) as any
31
-
32
- const { name } = rawResponse?.data?.[0]?.[0] as { name: string, orig_name: string }
33
-
34
- return `${instance}/file=${name}`
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import { generateSeed } from "../utils/generateSeed.mts"
4
 
5
+ export const state = {
6
+ load: 0
7
+ }
8
+
9
  const instances: string[] = [
10
  process.env.VC_AUDIO_GENERATION_SPACE_API_URL
11
  ]
 
15
  nbFrames: number;
16
  nbSteps: number;
17
  }) => {
18
+
19
+ if (state.load === instances.length) {
20
+ throw new Error(`all audio generation servers are busy, try again later..`)
21
+ }
22
+
23
+ state.load += 1
24
+
25
+ try {
26
+ const seed = options?.seed || generateSeed()
27
+ const nbFrames = options?.nbFrames || 24 // we can go up to 48 frames, but then upscaling quill require too much memory!
28
+ const nbSteps = options?.nbSteps || 35
29
+
30
+ const instance = instances.shift()
31
+ instances.push(instance)
32
+
33
+ const api = await client(instance, {
34
+ hf_token: `${process.env.VC_HF_API_TOKEN}` as any
35
+ })
36
+
37
+ const rawResponse = await api.predict('/run', [
38
+ prompt, // string in 'Prompt' Textbox component
39
+ seed, // number (numeric value between 0 and 2147483647) in 'Seed' Slider component
40
+ nbFrames, // 24 // it is the nb of frames per seconds I think?
41
+ nbSteps, // 10, (numeric value between 10 and 50) in 'Number of inference steps' Slider component
42
+ ]) as any
43
+
44
+ const { name } = rawResponse?.data?.[0]?.[0] as { name: string, orig_name: string }
45
+
46
+ return `${instance}/file=${name}`
47
+ } catch (err) {
48
+ throw err
49
+ } finally {
50
+ state.load -= 1
51
+ }
52
+ }
src/production/generateVideo.mts CHANGED
@@ -2,6 +2,10 @@ import { client } from "@gradio/client"
2
 
3
  import { generateSeed } from "../utils/generateSeed.mts"
4
 
 
 
 
 
5
  // we don't use replicas yet, because it ain't easy to get their hostname
6
  const instances: string[] = [
7
  `${process.env.VC_ZEROSCOPE_SPACE_API_URL_1 || ""}`,
@@ -14,27 +18,40 @@ export const generateVideo = async (prompt: string, options?: {
14
  nbFrames: number;
15
  nbSteps: number;
16
  }) => {
17
- const seed = options?.seed || generateSeed()
18
- const nbFrames = options?.nbFrames || 24 // we can go up to 48 frames, but then upscaling quill require too much memory!
19
- const nbSteps = options?.nbSteps || 35
20
-
21
- const instance = instances.shift()
22
- instances.push(instance)
23
-
24
- const api = await client(instance, {
25
- hf_token: `${process.env.VC_HF_API_TOKEN}` as any
26
- })
27
-
28
- const rawResponse = await api.predict('/run', [
29
- prompt, // string in 'Prompt' Textbox component
30
- seed, // number (numeric value between 0 and 2147483647) in 'Seed' Slider component
31
- nbFrames, // 24 // it is the nb of frames per seconds I think?
32
- nbSteps, // 10, (numeric value between 10 and 50) in 'Number of inference steps' Slider component
33
- ]) as any
34
-
35
- // console.log("rawResponse:", rawResponse)
36
-
37
- const { name } = rawResponse?.data?.[0]?.[0] as { name: string, orig_name: string }
38
 
39
- return `${instance}/file=${name}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
 
2
 
3
  import { generateSeed } from "../utils/generateSeed.mts"
4
 
5
+ export const state = {
6
+ load: 0,
7
+ }
8
+
9
  // we don't use replicas yet, because it ain't easy to get their hostname
10
  const instances: string[] = [
11
  `${process.env.VC_ZEROSCOPE_SPACE_API_URL_1 || ""}`,
 
18
  nbFrames: number;
19
  nbSteps: number;
20
  }) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ if (state.load === instances.length) {
23
+ throw new Error(`all video generation servers are busy, try again later..`)
24
+ }
25
+
26
+ state.load += 1
27
+
28
+ try {
29
+ const seed = options?.seed || generateSeed()
30
+ const nbFrames = options?.nbFrames || 24 // we can go up to 48 frames, but then upscaling quill require too much memory!
31
+ const nbSteps = options?.nbSteps || 35
32
+
33
+ const instance = instances.shift()
34
+ instances.push(instance)
35
+
36
+ const api = await client(instance, {
37
+ hf_token: `${process.env.VC_HF_API_TOKEN}` as any
38
+ })
39
+
40
+ const rawResponse = await api.predict('/run', [
41
+ prompt, // string in 'Prompt' Textbox component
42
+ seed, // number (numeric value between 0 and 2147483647) in 'Seed' Slider component
43
+ nbFrames, // 24 // it is the nb of frames per seconds I think?
44
+ nbSteps, // 10, (numeric value between 10 and 50) in 'Number of inference steps' Slider component
45
+ ]) as any
46
+
47
+ // console.log("rawResponse:", rawResponse)
48
+
49
+ const { name } = rawResponse?.data?.[0]?.[0] as { name: string, orig_name: string }
50
+
51
+ return `${instance}/file=${name}`
52
+ } catch (err) {
53
+ throw err
54
+ } finally {
55
+ state.load -= 1
56
+ }
57
  }
src/production/generateVoice.mts CHANGED
@@ -2,61 +2,77 @@ import puppeteer from "puppeteer"
2
 
3
  import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts"
4
 
 
 
 
 
5
  const instances: string[] = [
6
  process.env.VC_VOICE_GENERATION_SPACE_API_URL
7
  ]
8
 
9
  // TODO we should use an inference endpoint instead
10
  export async function generateVoice(prompt: string, voiceFileName: string) {
11
- const instance = instances.shift()
12
- instances.push(instance)
 
13
 
14
- console.log("instance:", instance)
15
-
16
- const browser = await puppeteer.launch({
17
- headless: true,
18
- protocolTimeout: 800000,
19
- })
20
 
21
  try {
22
- const page = await browser.newPage()
23
-
24
- await page.goto(instance, {
25
- waitUntil: "networkidle2",
 
 
 
 
26
  })
27
 
28
- await new Promise(r => setTimeout(r, 3000))
 
29
 
30
- const firstTextarea = await page.$('textarea[data-testid="textbox"]')
 
 
31
 
32
- await firstTextarea.type(prompt)
33
 
34
- // console.log("looking for the button to submit")
35
- const submitButton = await page.$("button.lg")
36
 
37
- // console.log("clicking on the button")
38
- await submitButton.click()
39
 
40
- await page.waitForSelector("audio", {
41
- timeout: 800000, // need to be large enough in case someone else attemps to use our space
42
- })
43
 
44
- const voiceRemoteUrl = await page.$$eval("audio", el => el.map(x => x.getAttribute("src"))[0])
 
45
 
 
 
 
46
 
47
- console.log({
48
- voiceRemoteUrl,
49
- })
 
 
 
50
 
51
 
52
- console.log(`- downloading ${voiceFileName} from ${voiceRemoteUrl}`)
53
 
54
- await downloadFileToTmp(voiceRemoteUrl, voiceFileName)
55
 
56
- return voiceFileName
 
 
 
 
 
57
  } catch (err) {
58
  throw err
59
  } finally {
60
- await browser.close()
61
  }
62
  }
 
2
 
3
  import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts"
4
 
5
+ export const state = {
6
+ load: 0
7
+ }
8
+
9
  const instances: string[] = [
10
  process.env.VC_VOICE_GENERATION_SPACE_API_URL
11
  ]
12
 
13
  // TODO we should use an inference endpoint instead
14
  export async function generateVoice(prompt: string, voiceFileName: string) {
15
+ if (state.load === instances.length) {
16
+ throw new Error(`all voice generation servers are busy, try again later..`)
17
+ }
18
 
19
+ state.load += 1
 
 
 
 
 
20
 
21
  try {
22
+ const instance = instances.shift()
23
+ instances.push(instance)
24
+
25
+ console.log("instance:", instance)
26
+
27
+ const browser = await puppeteer.launch({
28
+ headless: true,
29
+ protocolTimeout: 800000,
30
  })
31
 
32
+ try {
33
+ const page = await browser.newPage()
34
 
35
+ await page.goto(instance, {
36
+ waitUntil: "networkidle2",
37
+ })
38
 
39
+ await new Promise(r => setTimeout(r, 3000))
40
 
41
+ const firstTextarea = await page.$('textarea[data-testid="textbox"]')
 
42
 
43
+ await firstTextarea.type(prompt)
 
44
 
45
+ // console.log("looking for the button to submit")
46
+ const submitButton = await page.$("button.lg")
 
47
 
48
+ // console.log("clicking on the button")
49
+ await submitButton.click()
50
 
51
+ await page.waitForSelector("audio", {
52
+ timeout: 800000, // need to be large enough in case someone else attemps to use our space
53
+ })
54
 
55
+ const voiceRemoteUrl = await page.$$eval("audio", el => el.map(x => x.getAttribute("src"))[0])
56
+
57
+
58
+ console.log({
59
+ voiceRemoteUrl,
60
+ })
61
 
62
 
63
+ console.log(`- downloading ${voiceFileName} from ${voiceRemoteUrl}`)
64
 
65
+ await downloadFileToTmp(voiceRemoteUrl, voiceFileName)
66
 
67
+ return voiceFileName
68
+ } catch (err) {
69
+ throw err
70
+ } finally {
71
+ await browser.close()
72
+ }
73
  } catch (err) {
74
  throw err
75
  } finally {
76
+ state.load -= 1
77
  }
78
  }
src/production/interpolateVideo.mts CHANGED
@@ -1,66 +1,81 @@
1
  import path from "node:path"
2
 
3
  import { v4 as uuidv4 } from "uuid"
4
- import tmpDir from "temp-dir"
5
  import puppeteer from "puppeteer"
6
 
7
  import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts"
8
  import { pendingFilesDirFilePath } from "../config.mts"
9
  import { moveFileFromTmpToPending } from "../utils/moveFileFromTmpToPending.mts"
10
 
 
 
 
 
11
  const instances: string[] = [
12
  process.env.VC_VIDEO_INTERPOLATION_SPACE_API_URL
13
  ]
14
 
15
  // TODO we should use an inference endpoint instead
16
  export async function interpolateVideo(fileName: string, steps: number, fps: number) {
17
- const inputFilePath = path.join(pendingFilesDirFilePath, fileName)
 
 
18
 
19
- console.log(`interpolating ${fileName}`)
20
- console.log(`warning: interpolateVideo parameter "${steps}" is ignored!`)
21
- console.log(`warning: interpolateVideo parameter "${fps}" is ignored!`)
22
 
23
- const instance = instances.shift()
24
- instances.push(instance)
25
 
26
- const browser = await puppeteer.launch({
27
- headless: true,
28
- protocolTimeout: 400000,
29
- })
30
 
31
- try {
32
- const page = await browser.newPage()
33
- await page.goto(instance, { waitUntil: 'networkidle2' })
34
-
35
- await new Promise(r => setTimeout(r, 3000))
36
 
37
- const fileField = await page.$('input[type=file]')
 
 
 
38
 
39
- // console.log(`uploading file..`)
40
- await fileField.uploadFile(inputFilePath)
 
 
 
41
 
42
- // console.log('looking for the button to submit')
43
- const submitButton = await page.$('button.lg')
44
 
45
- // console.log('clicking on the button')
46
- await submitButton.click()
47
 
48
- await page.waitForSelector('a[download="interpolated_result.mp4"]', {
49
- timeout: 400000, // need to be large enough in case someone else attemps to use our space
50
- })
 
 
 
 
 
 
51
 
52
- const interpolatedFileUrl = await page.$$eval('a[download="interpolated_result.mp4"]', el => el.map(x => x.getAttribute("href"))[0])
53
 
54
- // it is always a good idea to download to a tmp dir before saving to the pending dir
55
- // because there is always a risk that the download will fail
56
 
57
- const tmpFileName = `${uuidv4()}.mp4`
58
 
59
- await downloadFileToTmp(interpolatedFileUrl, tmpFileName)
60
- await moveFileFromTmpToPending(tmpFileName, fileName)
 
 
 
 
 
61
  } catch (err) {
62
  throw err
63
  } finally {
64
- await browser.close()
65
  }
66
  }
 
1
  import path from "node:path"
2
 
3
  import { v4 as uuidv4 } from "uuid"
 
4
  import puppeteer from "puppeteer"
5
 
6
  import { downloadFileToTmp } from "../utils/downloadFileToTmp.mts"
7
  import { pendingFilesDirFilePath } from "../config.mts"
8
  import { moveFileFromTmpToPending } from "../utils/moveFileFromTmpToPending.mts"
9
 
10
+ export const state = {
11
+ load: 0
12
+ }
13
+
14
  const instances: string[] = [
15
  process.env.VC_VIDEO_INTERPOLATION_SPACE_API_URL
16
  ]
17
 
18
  // TODO we should use an inference endpoint instead
19
  export async function interpolateVideo(fileName: string, steps: number, fps: number) {
20
+ if (state.load === instances.length) {
21
+ throw new Error(`all video interpolation servers are busy, try again later..`)
22
+ }
23
 
24
+ state.load += 1
 
 
25
 
26
+ try {
27
+ const inputFilePath = path.join(pendingFilesDirFilePath, fileName)
28
 
29
+ console.log(`interpolating ${fileName}`)
30
+ console.log(`warning: interpolateVideo parameter "${steps}" is ignored!`)
31
+ console.log(`warning: interpolateVideo parameter "${fps}" is ignored!`)
 
32
 
33
+ const instance = instances.shift()
34
+ instances.push(instance)
 
 
 
35
 
36
+ const browser = await puppeteer.launch({
37
+ headless: true,
38
+ protocolTimeout: 400000,
39
+ })
40
 
41
+ try {
42
+ const page = await browser.newPage()
43
+ await page.goto(instance, { waitUntil: 'networkidle2' })
44
+
45
+ await new Promise(r => setTimeout(r, 3000))
46
 
47
+ const fileField = await page.$('input[type=file]')
 
48
 
49
+ // console.log(`uploading file..`)
50
+ await fileField.uploadFile(inputFilePath)
51
 
52
+ // console.log('looking for the button to submit')
53
+ const submitButton = await page.$('button.lg')
54
+
55
+ // console.log('clicking on the button')
56
+ await submitButton.click()
57
+
58
+ await page.waitForSelector('a[download="interpolated_result.mp4"]', {
59
+ timeout: 400000, // need to be large enough in case someone else attemps to use our space
60
+ })
61
 
62
+ const interpolatedFileUrl = await page.$$eval('a[download="interpolated_result.mp4"]', el => el.map(x => x.getAttribute("href"))[0])
63
 
64
+ // it is always a good idea to download to a tmp dir before saving to the pending dir
65
+ // because there is always a risk that the download will fail
66
 
67
+ const tmpFileName = `${uuidv4()}.mp4`
68
 
69
+ await downloadFileToTmp(interpolatedFileUrl, tmpFileName)
70
+ await moveFileFromTmpToPending(tmpFileName, fileName)
71
+ } catch (err) {
72
+ throw err
73
+ } finally {
74
+ await browser.close()
75
+ }
76
  } catch (err) {
77
  throw err
78
  } finally {
79
+ state.load -= 1
80
  }
81
  }
src/production/interpolateVideoLegacy.mts CHANGED
@@ -7,35 +7,50 @@ import tmpDir from "temp-dir"
7
 
8
  import { downloadFileToTmp } from '../utils/downloadFileToTmp.mts'
9
 
 
 
 
 
10
  const instances: string[] = [
11
  process.env.VC_VIDEO_INTERPOLATION_SPACE_API_URL
12
  ]
13
 
14
  export const interpolateVideo = async (fileName: string, steps: number, fps: number) => {
15
-
16
- const inputFilePath = path.join(tmpDir, fileName)
17
-
18
- const instance = instances.shift()
19
- instances.push(instance)
20
-
21
- const api = await client(instance, {
22
- hf_token: `${process.env.VC_HF_API_TOKEN}` as any
23
- })
24
-
25
- const video = await fs.readFile(inputFilePath)
26
-
27
- const blob = new Blob([video], { type: 'video/mp4' })
28
- // const blob = blobFrom(filePath)
29
- const result = await api.predict(1, [
30
- blob, // blob in 'parameter_5' Video component
31
- steps, // number (numeric value between 1 and 4) in 'Interpolation Steps' Slider component
32
- fps, // string (FALSE! it's a number) in 'FPS output' Radio component
33
- ])
34
-
35
- const data = (result as any).data[0]
36
- console.log('raw data:', data)
37
- const { orig_name, data: remoteFilePath } = data
38
- const remoteUrl = `${instance}/file=${remoteFilePath}`
39
- console.log("remoteUrl:", remoteUrl)
40
- await downloadFileToTmp(remoteUrl, fileName)
 
 
 
 
 
 
 
 
 
 
 
41
  }
 
7
 
8
  import { downloadFileToTmp } from '../utils/downloadFileToTmp.mts'
9
 
10
+ export const state = {
11
+ load: 0
12
+ }
13
+
14
  const instances: string[] = [
15
  process.env.VC_VIDEO_INTERPOLATION_SPACE_API_URL
16
  ]
17
 
18
  export const interpolateVideo = async (fileName: string, steps: number, fps: number) => {
19
+ if (state.load === instances.length) {
20
+ throw new Error(`all video interpolation servers are busy, try again later..`)
21
+ }
22
+
23
+ state.load += 1
24
+
25
+ try {
26
+ const inputFilePath = path.join(tmpDir, fileName)
27
+
28
+ const instance = instances.shift()
29
+ instances.push(instance)
30
+
31
+ const api = await client(instance, {
32
+ hf_token: `${process.env.VC_HF_API_TOKEN}` as any
33
+ })
34
+
35
+ const video = await fs.readFile(inputFilePath)
36
+
37
+ const blob = new Blob([video], { type: 'video/mp4' })
38
+ // const blob = blobFrom(filePath)
39
+ const result = await api.predict(1, [
40
+ blob, // blob in 'parameter_5' Video component
41
+ steps, // number (numeric value between 1 and 4) in 'Interpolation Steps' Slider component
42
+ fps, // string (FALSE! it's a number) in 'FPS output' Radio component
43
+ ])
44
+
45
+ const data = (result as any).data[0]
46
+ console.log('raw data:', data)
47
+ const { orig_name, data: remoteFilePath } = data
48
+ const remoteUrl = `${instance}/file=${remoteFilePath}`
49
+ console.log("remoteUrl:", remoteUrl)
50
+ await downloadFileToTmp(remoteUrl, fileName)
51
+ } catch (err) {
52
+ throw err
53
+ } finally {
54
+ state.load -= 1
55
+ }
56
  }
src/production/renderStaticScene.mts CHANGED
@@ -3,11 +3,14 @@ import path from "node:path"
3
  import { v4 as uuidv4 } from "uuid"
4
  import tmpDir from "temp-dir"
5
 
6
- import { ImageSegment, RenderedScene, RenderRequest } from "../types.mts"
7
  import { segmentImage } from "../utils/segmentImage.mts"
8
  import { generateImageSDXLAsBase64 } from "../utils/generateImageSDXL.mts"
9
  import { writeBase64ToFile } from "../utils/writeBase64ToFile.mts"
10
 
 
 
 
11
  export async function renderStaticScene(scene: RenderRequest): Promise<RenderedScene> {
12
 
13
  let imageBase64 = ""
 
3
  import { v4 as uuidv4 } from "uuid"
4
  import tmpDir from "temp-dir"
5
 
6
+ import { ImageSegment, RenderedScene, RenderingJob, RenderRequest } from "../types.mts"
7
  import { segmentImage } from "../utils/segmentImage.mts"
8
  import { generateImageSDXLAsBase64 } from "../utils/generateImageSDXL.mts"
9
  import { writeBase64ToFile } from "../utils/writeBase64ToFile.mts"
10
 
11
+
12
+ const pendingJobs: RenderingJob[] = []
13
+
14
  export async function renderStaticScene(scene: RenderRequest): Promise<RenderedScene> {
15
 
16
  let imageBase64 = ""
src/types.mts CHANGED
@@ -312,4 +312,17 @@ export interface RenderedScene {
312
  error: string
313
  maskBase64: string
314
  segments: ImageSegment[]
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  }
 
312
  error: string
313
  maskBase64: string
314
  segments: ImageSegment[]
315
+ }
316
+
317
+ // note: for video generation we are always going to have slow jobs,
318
+ // because we need multiple seconds, minutes, hours.. of video + audio
319
+ // but for rendering we aim at shorter delays, less than 45 seconds
320
+ // so the goal of rendering "jobs" is mostly to give the illusion that
321
+ // things go faster, by already providing some things like the background image,
322
+ // before we send
323
+ export interface RenderingJob {
324
+ scene: RenderRequest
325
+ result: RenderedScene
326
+
327
+ status: 'pending' | 'completed' | 'error'
328
  }