Spaces:
Paused
Paused
Julian Bilcke
commited on
Commit
·
1373ff5
1
Parent(s):
0d06ec6
use PuLIB for the turbo mode
Browse files- src/production/renderImage.mts +3 -3
- src/providers/image-generation/generateImagePulib.mts +125 -0
- src/providers/video-generation/generateVideoWithAnimateDiffLightning.mts +10 -3
- src/providers/voice-generation/generateVoiceWithOpenVoice.mts +2 -0
- src/types.mts +3 -0
- src/utils/image/addBase64HeaderToPng.mts +14 -0
- src/utils/misc/makeSureSpaceIsRunning.mts +1 -1
- src/utils/requests/hashRequest.mts +1 -0
src/production/renderImage.mts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
| 1 |
-
import { generateImageLCMAsBase64 } from "../providers/image-generation/generateImageLCMGradio.mts"
|
| 2 |
-
import { generateImageSDXLTurboAsBase64 } from "../providers/image-generation/generateImageSDXLTurbo.mts"
|
| 3 |
import { generateImageSDXLAsBase64 } from "../providers/image-generation/generateImageSDXLGradio.mts"
|
| 4 |
import { generateImageSDXL360AsBase64 } from "../providers/image-generation/generateImageSDXL360.mts"
|
| 5 |
import { RenderedScene, RenderRequest } from "../types.mts"
|
|
|
|
| 6 |
|
| 7 |
export async function renderImage(
|
| 8 |
request: RenderRequest,
|
|
@@ -22,7 +21,7 @@ export async function renderImage(
|
|
| 22 |
// but much, much faster to run
|
| 23 |
// for the moment we use SDXL + LCM, as it offers better scene coherence,
|
| 24 |
// but we might switch to SDXL Turbo in the future if its quality improves
|
| 25 |
-
?
|
| 26 |
|
| 27 |
: generateImageSDXLAsBase64
|
| 28 |
|
|
@@ -31,6 +30,7 @@ export async function renderImage(
|
|
| 31 |
const params = {
|
| 32 |
positivePrompt: request.prompt,
|
| 33 |
negativePrompt: request.negativePrompt,
|
|
|
|
| 34 |
seed: request.seed,
|
| 35 |
nbSteps: request.nbSteps,
|
| 36 |
width: request.width,
|
|
|
|
|
|
|
|
|
|
| 1 |
import { generateImageSDXLAsBase64 } from "../providers/image-generation/generateImageSDXLGradio.mts"
|
| 2 |
import { generateImageSDXL360AsBase64 } from "../providers/image-generation/generateImageSDXL360.mts"
|
| 3 |
import { RenderedScene, RenderRequest } from "../types.mts"
|
| 4 |
+
import { generateImagePulibAsBase64 } from "../providers/image-generation/generateImagePulib.mts"
|
| 5 |
|
| 6 |
export async function renderImage(
|
| 7 |
request: RenderRequest,
|
|
|
|
| 21 |
// but much, much faster to run
|
| 22 |
// for the moment we use SDXL + LCM, as it offers better scene coherence,
|
| 23 |
// but we might switch to SDXL Turbo in the future if its quality improves
|
| 24 |
+
? generateImagePulibAsBase64 // generateImageSDXLTurboAsBase64
|
| 25 |
|
| 26 |
: generateImageSDXLAsBase64
|
| 27 |
|
|
|
|
| 30 |
const params = {
|
| 31 |
positivePrompt: request.prompt,
|
| 32 |
negativePrompt: request.negativePrompt,
|
| 33 |
+
identityImage: request.identityImage,
|
| 34 |
seed: request.seed,
|
| 35 |
nbSteps: request.nbSteps,
|
| 36 |
width: request.width,
|
src/providers/image-generation/generateImagePulib.mts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import { client } from "@gradio/client"
|
| 3 |
+
|
| 4 |
+
import { generateSeed } from "../../utils/misc/generateSeed.mts"
|
| 5 |
+
import { getValidNumber } from "../../utils/validators/getValidNumber.mts"
|
| 6 |
+
import { convertToWebp } from "../../utils/image/convertToWebp.mts"
|
| 7 |
+
import { addBase64HeaderToPng } from "../../utils/image/addBase64HeaderToPng.mts"
|
| 8 |
+
|
| 9 |
+
// TODO add a system to mark failed instances as "unavailable" for a couple of minutes
|
| 10 |
+
// console.log("process.env:", process.env)
|
| 11 |
+
|
| 12 |
+
// note: to reduce costs I use the small A10s (not the large)
|
| 13 |
+
// anyway, we will soon not need to use this cloud anymore
|
| 14 |
+
// since we will be able to leverage the Inference API
|
| 15 |
+
const gradioSpaceApiUrl = `https://jbilcke-hf-ai-tube-model-pulid.hf.space`
|
| 16 |
+
const gradioSpace = `jbilcke-hf/ai-tube-model-pulid`
|
| 17 |
+
const secretToken = `${process.env.VC_MICROSERVICE_SECRET_TOKEN || ""}`
|
| 18 |
+
|
| 19 |
+
// console.log("DEBUG:", JSON.stringify({ instances, secretToken }, null, 2))
|
| 20 |
+
|
| 21 |
+
export async function generateImagePulibAsBase64(options: {
|
| 22 |
+
positivePrompt: string;
|
| 23 |
+
negativePrompt?: string;
|
| 24 |
+
identityImage?: string;
|
| 25 |
+
seed?: number;
|
| 26 |
+
width?: number;
|
| 27 |
+
height?: number;
|
| 28 |
+
nbSteps?: number;
|
| 29 |
+
}): Promise<string> {
|
| 30 |
+
|
| 31 |
+
const positivePrompt = options?.positivePrompt || ""
|
| 32 |
+
if (!positivePrompt) {
|
| 33 |
+
throw new Error("missing prompt")
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// the negative prompt CAN be missing, since we use a trick
|
| 37 |
+
// where we make the interface mandatory in the TS doc,
|
| 38 |
+
// but browsers might send something partial
|
| 39 |
+
const negativePrompt = options?.negativePrompt || ""
|
| 40 |
+
|
| 41 |
+
// we treat 0 as meaning "random seed"
|
| 42 |
+
const seed = (options?.seed ? options.seed : 0) || generateSeed()
|
| 43 |
+
|
| 44 |
+
const width = getValidNumber(options?.width, 256, 1024, 512)
|
| 45 |
+
const height = getValidNumber(options?.height, 256, 1024, 512)
|
| 46 |
+
const nbSteps = getValidNumber(options?.nbSteps, 1, 8, 4)
|
| 47 |
+
// console.log("SEED:", seed)
|
| 48 |
+
|
| 49 |
+
const identityImage = `${options.identityImage || ""}`
|
| 50 |
+
|
| 51 |
+
const positive = [
|
| 52 |
+
positivePrompt,
|
| 53 |
+
].filter(word => word)
|
| 54 |
+
.join(", ")
|
| 55 |
+
|
| 56 |
+
const negative = [
|
| 57 |
+
negativePrompt,
|
| 58 |
+
"watermark",
|
| 59 |
+
"copyright",
|
| 60 |
+
"blurry",
|
| 61 |
+
// "artificial",
|
| 62 |
+
// "cropped",
|
| 63 |
+
"low quality",
|
| 64 |
+
"ugly",
|
| 65 |
+
'flaws in the eyes',
|
| 66 |
+
'flaws in the face',
|
| 67 |
+
'flaws',
|
| 68 |
+
'lowres',
|
| 69 |
+
'non-HDRi',
|
| 70 |
+
'low quality',
|
| 71 |
+
'worst quality',
|
| 72 |
+
'artifacts noise',
|
| 73 |
+
'text',
|
| 74 |
+
'glitch',
|
| 75 |
+
'deformed',
|
| 76 |
+
'mutated',
|
| 77 |
+
'disfigured hands',
|
| 78 |
+
'low resolution',
|
| 79 |
+
'partially rendered objects',
|
| 80 |
+
'deformed or partially rendered eyes',
|
| 81 |
+
'ddeformed eyeballs',
|
| 82 |
+
'cross-eyed',
|
| 83 |
+
].filter(word => word)
|
| 84 |
+
.join(", ")
|
| 85 |
+
|
| 86 |
+
const api = await client(gradioSpaceApiUrl, {
|
| 87 |
+
hf_token: `${process.env.VC_HF_API_TOKEN}` as any
|
| 88 |
+
})
|
| 89 |
+
|
| 90 |
+
// we hardcode the number of steps to 4
|
| 91 |
+
const steps = 4
|
| 92 |
+
|
| 93 |
+
// console.log("querying " + gradioSpaceApiUrl + " with tons of params")
|
| 94 |
+
|
| 95 |
+
const rawResponse = (await api.predict("/run", [
|
| 96 |
+
secretToken, // # str in 'parameter_4' Textbox component
|
| 97 |
+
identityImage || "", // 'ID image (main)' Image component
|
| 98 |
+
"", // 'Additional ID image (auxiliary)' Image component
|
| 99 |
+
"", // 'Additional ID image (auxiliary)' Image component
|
| 100 |
+
"", // 'Additional ID image (auxiliary)' Image component
|
| 101 |
+
positive, // # str in 'Prompt' Textbox component
|
| 102 |
+
negative, // # str in 'Negative Prompt' Textbox component
|
| 103 |
+
1.2, // # int | float (numeric value between 1 and 1.5) in 'CFG, recommend value range [1, 1.5], 1 will be faster ' Slider component
|
| 104 |
+
generateSeed(), //, # int | float (numeric value between 0 and 4294967295) in 'Seed' Slider component
|
| 105 |
+
steps, // # int | float (numeric value between 1 and 100) in 'Steps' Slider component
|
| 106 |
+
height, // # int | float (numeric value between 512 and 1280) in 'Height' Slider component
|
| 107 |
+
width, // # int | float (numeric value between 512 and 1280) in 'Width' Slider component
|
| 108 |
+
0.8, // # int | float (numeric value between 0 and 5) in 'ID scale' Slider component
|
| 109 |
+
"fidelity", // # str (Option from: ['fidelity', 'extremely style']) in 'mode' Dropdown component
|
| 110 |
+
false, // 'ID Mix (if you want to mix two ID image, please turn this on, otherwise, turn this off)' Checkbox component
|
| 111 |
+
])) as any
|
| 112 |
+
|
| 113 |
+
const result = rawResponse?.data?.[0] as string
|
| 114 |
+
if (!result?.length) {
|
| 115 |
+
throw new Error(`the returned image was empty`)
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
try {
|
| 119 |
+
const finalImage = await convertToWebp(addBase64HeaderToPng(result))
|
| 120 |
+
return finalImage
|
| 121 |
+
} catch (err) {
|
| 122 |
+
// console.log("err:", err)
|
| 123 |
+
throw new Error(err)
|
| 124 |
+
}
|
| 125 |
+
}
|
src/providers/video-generation/generateVideoWithAnimateDiffLightning.mts
CHANGED
|
@@ -3,8 +3,11 @@ import { generateSeed } from "../../utils/misc/generateSeed.mts"
|
|
| 3 |
import { tryApiCalls } from "../../utils/misc/tryApiCall.mts"
|
| 4 |
import { getValidNumber } from "../../utils/validators/getValidNumber.mts"
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
| 8 |
const accessToken = `${process.env.VC_MICROSERVICE_SECRET_TOKEN || ""}`
|
| 9 |
|
| 10 |
export const generateVideoWithAnimateDiffLightning = async (
|
|
@@ -14,6 +17,8 @@ export const generateVideoWithAnimateDiffLightning = async (
|
|
| 14 |
|
| 15 |
const debug = false
|
| 16 |
|
|
|
|
|
|
|
| 17 |
const actualFunction = async (): Promise<RenderedScene> => {
|
| 18 |
|
| 19 |
const prompt = request.prompt || ""
|
|
@@ -61,7 +66,9 @@ export const generateVideoWithAnimateDiffLightning = async (
|
|
| 61 |
})
|
| 62 |
}
|
| 63 |
|
| 64 |
-
|
|
|
|
|
|
|
| 65 |
method: "POST",
|
| 66 |
headers: {
|
| 67 |
"Content-Type": "application/json",
|
|
|
|
| 3 |
import { tryApiCalls } from "../../utils/misc/tryApiCall.mts"
|
| 4 |
import { getValidNumber } from "../../utils/validators/getValidNumber.mts"
|
| 5 |
|
| 6 |
+
const replicas = [
|
| 7 |
+
"https://jbilcke-hf-ai-tube-model-adl-1.hf.space",
|
| 8 |
+
"https://jbilcke-hf-ai-tube-model-adl-2.hf.space",
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
const accessToken = `${process.env.VC_MICROSERVICE_SECRET_TOKEN || ""}`
|
| 12 |
|
| 13 |
export const generateVideoWithAnimateDiffLightning = async (
|
|
|
|
| 17 |
|
| 18 |
const debug = false
|
| 19 |
|
| 20 |
+
let replica = replicas[0] || ""
|
| 21 |
+
|
| 22 |
const actualFunction = async (): Promise<RenderedScene> => {
|
| 23 |
|
| 24 |
const prompt = request.prompt || ""
|
|
|
|
| 66 |
})
|
| 67 |
}
|
| 68 |
|
| 69 |
+
replicas.push(replica = replicas.shift())
|
| 70 |
+
|
| 71 |
+
const res = await fetch(replica + (replica.endsWith("/") ? "" : "/") + "api/predict", {
|
| 72 |
method: "POST",
|
| 73 |
headers: {
|
| 74 |
"Content-Type": "application/json",
|
src/providers/voice-generation/generateVoiceWithOpenVoice.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// TODO
|
| 2 |
+
export const todo = "todo"
|
src/types.mts
CHANGED
|
@@ -279,6 +279,9 @@ export type RenderRequest = {
|
|
| 279 |
// unused for now
|
| 280 |
negativePrompt: string
|
| 281 |
|
|
|
|
|
|
|
|
|
|
| 282 |
// whether to use video segmentation
|
| 283 |
// disabled (default)
|
| 284 |
// firstframe: we only analyze the first frame
|
|
|
|
| 279 |
// unused for now
|
| 280 |
negativePrompt: string
|
| 281 |
|
| 282 |
+
// image used for the consistent identity of the main entity (optional)
|
| 283 |
+
identityImage: string
|
| 284 |
+
|
| 285 |
// whether to use video segmentation
|
| 286 |
// disabled (default)
|
| 287 |
// firstframe: we only analyze the first frame
|
src/utils/image/addBase64HeaderToPng.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export function addBase64HeaderToPng(base64Data: string) {
|
| 2 |
+
if (typeof base64Data !== "string" || !base64Data) {
|
| 3 |
+
return ""
|
| 4 |
+
}
|
| 5 |
+
if (base64Data.startsWith('data:')) {
|
| 6 |
+
if (base64Data.startsWith('data:image/png;base64,')) {
|
| 7 |
+
return base64Data
|
| 8 |
+
} else {
|
| 9 |
+
throw new Error("fatal: the input string is NOT a PNG!")
|
| 10 |
+
}
|
| 11 |
+
} else {
|
| 12 |
+
return `data:image/png;base64,${base64Data}`
|
| 13 |
+
}
|
| 14 |
+
}
|
src/utils/misc/makeSureSpaceIsRunning.mts
CHANGED
|
@@ -9,7 +9,7 @@ export async function makeSureSpaceIsRunning({
|
|
| 9 |
// userName,
|
| 10 |
// spaceName,
|
| 11 |
}: {
|
| 12 |
-
space
|
| 13 |
|
| 14 |
maxWaitTimeInSec?: number
|
| 15 |
|
|
|
|
| 9 |
// userName,
|
| 10 |
// spaceName,
|
| 11 |
}: {
|
| 12 |
+
space?: string // a joined "user_name/space_name"
|
| 13 |
|
| 14 |
maxWaitTimeInSec?: number
|
| 15 |
|
src/utils/requests/hashRequest.mts
CHANGED
|
@@ -8,6 +8,7 @@ export function hashRequest(request: RenderRequest) {
|
|
| 8 |
version: 1,
|
| 9 |
prompt: request.prompt,
|
| 10 |
negativePrompt: request.negativePrompt,
|
|
|
|
| 11 |
segmentation: request.segmentation,
|
| 12 |
actionnables: request.actionnables,
|
| 13 |
nbFrames: request.nbFrames,
|
|
|
|
| 8 |
version: 1,
|
| 9 |
prompt: request.prompt,
|
| 10 |
negativePrompt: request.negativePrompt,
|
| 11 |
+
identityImage: request.identityImage,
|
| 12 |
segmentation: request.segmentation,
|
| 13 |
actionnables: request.actionnables,
|
| 14 |
nbFrames: request.nbFrames,
|