File size: 2,828 Bytes
9a42933
 
7448744
6c5a5b1
7448744
 
827f345
9a42933
8f35fb6
 
7448744
a9e45ba
6c5a5b1
9a42933
7448744
 
 
 
 
8bc9511
7448744
 
 
 
 
9a42933
 
 
827f345
 
7448744
 
8bc9511
7448744
6c5a5b1
 
 
 
7448744
 
6c5a5b1
 
 
 
 
 
b56210f
 
9a42933
 
6c5a5b1
7448744
 
 
 
 
 
 
 
 
6c5a5b1
 
 
5c5e659
 
a9e45ba
5c5e659
 
 
 
 
 
 
7448744
 
5c5e659
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9e45ba
7448744
 
 
 
 
 
 
 
 
5c5e659
 
 
 
9a42933
 
 
 
 
1
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"use server"

import { VideoOptions } from "@/types"

import { generateVideoWithGradioAPI } from "./generateWithGradioApi"
import { generateVideoWithReplicateAPI } from "./generateWithReplicateAPI"
import { filterOutBadWords } from "./censorship"

const videoEngine = `${process.env.VIDEO_ENGINE || ""}`

// const officialApi = `${process.env.VIDEO_HOTSHOT_XL_API_OFFICIAL || ""}`
const nodeApi = `${process.env.VIDEO_HOTSHOT_XL_API_NODE || ""}`

export async function generateAnimation({
  positivePrompt = "",
  negativePrompt = "",
  size = "512x512",
  huggingFaceLora,
  replicateLora,
  triggerWord,
  nbFrames = 8,
  duration = 1000,
  steps = 30,
}: VideoOptions): Promise<string> {
  if (!positivePrompt?.length) {
    throw new Error(`prompt is too short!`)
  }

  positivePrompt = filterOutBadWords(positivePrompt)

  // pimp the prompt
  positivePrompt = [
    triggerWord,
    positivePrompt,
    "beautiful",
    "hd"
  ].join(", ")

  negativePrompt = [
    negativePrompt,
    "cropped",
    "dark",
    "underexposed",
    "overexposed",
    "watermark",
    "watermarked",
  ].join(", ")

  try {

    if (videoEngine === "VIDEO_HOTSHOT_XL_API_REPLICATE") {
      return generateVideoWithReplicateAPI({
        positivePrompt,
        negativePrompt,
        size,
        huggingFaceLora,
        replicateLora,
        nbFrames,
        duration,
        steps,
      })
      
    } else if (videoEngine === "VIDEO_HOTSHOT_XL_API_NODE") {
      // TODO: support other API to avoid duplicate work?
      // (are the other API supporting custom LoRAs?)
      const res = await fetch(nodeApi, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          // access isn't secured for now, the free lunch is open
          // Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
          prompt: positivePrompt,
          lora: huggingFaceLora,
          size,
        }),
        cache: "no-store",
        // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
        // next: { revalidate: 1 }
      })

      const content = await res.text()

      // Recommendation: handle errors
      if (res.status !== 200) {
        console.error(content)
        // This will activate the closest `error.js` Error Boundary
        throw new Error('Failed to fetch data')
      }

      return content
    } else if (videoEngine === "VIDEO_HOTSHOT_XL_API_GRADIO") {
      return generateVideoWithGradioAPI({
        positivePrompt,
        negativePrompt,
        size,
        huggingFaceLora,
        replicateLora,
        nbFrames,
        duration,
        steps,
      })
    } else {
      throw new Error(`not implemented yet!`)
    }

  } catch (err) {
    throw new Error(`failed to generate the image ${err}`)
  }
}