Commit
·
3a86e21
1
Parent(s):
451df29
let's up the game
Browse files- src/app/main.tsx +44 -18
- src/app/server/aitube/config.ts +6 -2
- src/app/server/aitube/editClapDialogues.ts +13 -8
- src/app/server/aitube/editClapEntities.ts +14 -9
- src/app/store.ts +4 -3
- src/lib/utils/logImage.ts +33 -0
src/app/main.tsx
CHANGED
@@ -34,11 +34,13 @@ import { getParam } from '@/lib/utils/getParam'
|
|
34 |
import { GenerationStage } from '@/types'
|
35 |
import { FileContent } from 'use-file-picker/dist/interfaces'
|
36 |
import { generateRandomStory } from '@/lib/utils/generateRandomStory'
|
|
|
37 |
|
38 |
export function Main() {
|
39 |
const [storyPromptDraft, setStoryPromptDraft] = useLocalStorage<string>(
|
40 |
"AI_STORIES_FACTORY_STORY_PROMPT_DRAFT",
|
41 |
-
"Yesterday I was walking in SF when I saw a zebra"
|
|
|
42 |
)
|
43 |
const promptDraftRef = useRef("")
|
44 |
promptDraftRef.current = storyPromptDraft
|
@@ -143,7 +145,7 @@ export function Main() {
|
|
143 |
setCurrentClap(clap)
|
144 |
setStoryGenerationStatus("finished")
|
145 |
|
146 |
-
console.log("
|
147 |
console.table(clap.segments, [
|
148 |
// 'startTimeInMs',
|
149 |
'endTimeInMs',
|
@@ -168,14 +170,14 @@ export function Main() {
|
|
168 |
// generating entities requires a "smart" LLM
|
169 |
turbo: false,
|
170 |
// turbo: true,
|
171 |
-
})
|
172 |
|
173 |
if (!clap) { throw new Error(`failed to edit the entities`) }
|
174 |
|
175 |
console.log(`handleSubmit(): received a clap with entities = `, clap)
|
176 |
setCurrentClap(clap)
|
177 |
setAssetGenerationStatus("finished")
|
178 |
-
console.log("
|
179 |
console.table(clap.entities, [
|
180 |
'category',
|
181 |
'label',
|
@@ -205,7 +207,7 @@ export function Main() {
|
|
205 |
console.log(`handleSubmit(): received a clap with music = `, clap)
|
206 |
setCurrentClap(clap)
|
207 |
setMusicGenerationStatus("finished")
|
208 |
-
console.log("
|
209 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.MUSIC), [
|
210 |
'endTimeInMs',
|
211 |
'prompt',
|
@@ -227,7 +229,7 @@ export function Main() {
|
|
227 |
// the turbo is mandatory here,
|
228 |
// since this uses a model with character consistency,
|
229 |
// which is not the case for the non-turbo one
|
230 |
-
turbo:
|
231 |
}).then(r => r.promise)
|
232 |
|
233 |
if (!clap) { throw new Error(`failed to edit the storyboards`) }
|
@@ -236,7 +238,18 @@ export function Main() {
|
|
236 |
console.log(`handleSubmit(): received a clap with images = `, clap)
|
237 |
setCurrentClap(clap)
|
238 |
setImageGenerationStatus("finished")
|
239 |
-
console.log("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.STORYBOARD), [
|
241 |
'endTimeInMs',
|
242 |
'prompt',
|
@@ -264,7 +277,7 @@ export function Main() {
|
|
264 |
console.log(`handleSubmit(): received a clap with videos = `, clap)
|
265 |
setCurrentClap(clap)
|
266 |
setVideoGenerationStatus("finished")
|
267 |
-
console.log("
|
268 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.VIDEO), [
|
269 |
'endTimeInMs',
|
270 |
'prompt',
|
@@ -277,6 +290,13 @@ export function Main() {
|
|
277 |
}
|
278 |
}
|
279 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
280 |
const generateDialogues = async (clap: ClapProject): Promise<ClapProject> => {
|
281 |
try {
|
282 |
// setProgress(70)
|
@@ -284,14 +304,14 @@ export function Main() {
|
|
284 |
clap = await editClapDialogues({
|
285 |
clap,
|
286 |
turbo: true
|
287 |
-
})
|
288 |
|
289 |
if (!clap) { throw new Error(`failed to edit the dialogues`) }
|
290 |
|
291 |
console.log(`handleSubmit(): received a clap with dialogues = `, clap)
|
292 |
setCurrentClap(clap)
|
293 |
setVoiceGenerationStatus("finished")
|
294 |
-
console.log("
|
295 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.DIALOGUE), [
|
296 |
'endTimeInMs',
|
297 |
'prompt',
|
@@ -319,7 +339,7 @@ export function Main() {
|
|
319 |
|
320 |
if (assetUrl.length < 128) { throw new Error(`handleSubmit(): the generated video is too small, so we failed`) }
|
321 |
|
322 |
-
console.log(`handleSubmit(): received a video: ${assetUrl.slice(0,
|
323 |
setFinalGenerationStatus("finished")
|
324 |
return assetUrl
|
325 |
} catch (err) {
|
@@ -336,12 +356,14 @@ export function Main() {
|
|
336 |
try {
|
337 |
let clap = await generateStory()
|
338 |
|
339 |
-
const
|
340 |
generateMusic(clap),
|
341 |
-
|
342 |
-
]
|
343 |
|
344 |
-
|
|
|
|
|
345 |
|
346 |
for (const newerClap of claps) {
|
347 |
clap = await updateClap(clap, newerClap, {
|
@@ -379,6 +401,9 @@ export function Main() {
|
|
379 |
// clap = await generateVideos(clap)
|
380 |
// clap = await generateDialogues(clap)
|
381 |
|
|
|
|
|
|
|
382 |
await generateFinalVideo(clap)
|
383 |
|
384 |
setStatus("finished")
|
@@ -644,7 +669,8 @@ export function Main() {
|
|
644 |
setStoryPromptDraft(e.target.value)
|
645 |
promptDraftRef.current = e.target.value
|
646 |
}}
|
647 |
-
placeholder="Yesterday I was at my favorite pizza place and.."
|
|
|
648 |
inputClassName="
|
649 |
transition-all duration-200 ease-in-out
|
650 |
h-32 md:h-56 lg:h-64
|
@@ -873,7 +899,7 @@ export function Main() {
|
|
873 |
: imageGenerationStatus === "generating" ? "Creating storyboards.."
|
874 |
: videoGenerationStatus === "generating" ? "Filming shots.."
|
875 |
: voiceGenerationStatus === "generating" ? "Recording dialogues.."
|
876 |
-
: finalGenerationStatus === "generating" ? "
|
877 |
: "Please wait.."
|
878 |
)
|
879 |
: status === "error"
|
@@ -914,7 +940,7 @@ export function Main() {
|
|
914 |
Powered by
|
915 |
</span>
|
916 |
<span className="ml-1 mr-0.5">
|
917 |
-
<Image src={HFLogo} alt="Hugging Face" width=
|
918 |
</span>
|
919 |
<span className="text-stone-100/80 text-3xs font-semibold"
|
920 |
style={{ textShadow: "rgb(0 0 0 / 80%) 0px 0px 2px" }}>Hugging Face</span>
|
|
|
34 |
import { GenerationStage } from '@/types'
|
35 |
import { FileContent } from 'use-file-picker/dist/interfaces'
|
36 |
import { generateRandomStory } from '@/lib/utils/generateRandomStory'
|
37 |
+
import { logImage } from '@/lib/utils/logImage'
|
38 |
|
39 |
export function Main() {
|
40 |
const [storyPromptDraft, setStoryPromptDraft] = useLocalStorage<string>(
|
41 |
"AI_STORIES_FACTORY_STORY_PROMPT_DRAFT",
|
42 |
+
// "Yesterday I was walking in SF when I saw a zebra"
|
43 |
+
"underwater footage, coral, fishes"
|
44 |
)
|
45 |
const promptDraftRef = useRef("")
|
46 |
promptDraftRef.current = storyPromptDraft
|
|
|
145 |
setCurrentClap(clap)
|
146 |
setStoryGenerationStatus("finished")
|
147 |
|
148 |
+
console.log("---------------- GENERATED STORY ----------------")
|
149 |
console.table(clap.segments, [
|
150 |
// 'startTimeInMs',
|
151 |
'endTimeInMs',
|
|
|
170 |
// generating entities requires a "smart" LLM
|
171 |
turbo: false,
|
172 |
// turbo: true,
|
173 |
+
}).then(r => r.promise)
|
174 |
|
175 |
if (!clap) { throw new Error(`failed to edit the entities`) }
|
176 |
|
177 |
console.log(`handleSubmit(): received a clap with entities = `, clap)
|
178 |
setCurrentClap(clap)
|
179 |
setAssetGenerationStatus("finished")
|
180 |
+
console.log("---------------- GENERATED ENTITIES ----------------")
|
181 |
console.table(clap.entities, [
|
182 |
'category',
|
183 |
'label',
|
|
|
207 |
console.log(`handleSubmit(): received a clap with music = `, clap)
|
208 |
setCurrentClap(clap)
|
209 |
setMusicGenerationStatus("finished")
|
210 |
+
console.log("---------------- GENERATED MUSIC ----------------")
|
211 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.MUSIC), [
|
212 |
'endTimeInMs',
|
213 |
'prompt',
|
|
|
229 |
// the turbo is mandatory here,
|
230 |
// since this uses a model with character consistency,
|
231 |
// which is not the case for the non-turbo one
|
232 |
+
turbo: false
|
233 |
}).then(r => r.promise)
|
234 |
|
235 |
if (!clap) { throw new Error(`failed to edit the storyboards`) }
|
|
|
238 |
console.log(`handleSubmit(): received a clap with images = `, clap)
|
239 |
setCurrentClap(clap)
|
240 |
setImageGenerationStatus("finished")
|
241 |
+
console.log("---------------- GENERATED STORYBOARDS ----------------")
|
242 |
+
clap.segments
|
243 |
+
.filter(s => s.category === ClapSegmentCategory.STORYBOARD)
|
244 |
+
.forEach((s, i) => {
|
245 |
+
if (s.status === "completed" && s.assetUrl) {
|
246 |
+
// console.log(` [${i}] storyboard: ${s.prompt}`)
|
247 |
+
logImage(s.assetUrl, 0.35)
|
248 |
+
} else {
|
249 |
+
console.log(` [${i}] failed to generate storyboard`)
|
250 |
+
}
|
251 |
+
// console.log(`------------------`)
|
252 |
+
})
|
253 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.STORYBOARD), [
|
254 |
'endTimeInMs',
|
255 |
'prompt',
|
|
|
277 |
console.log(`handleSubmit(): received a clap with videos = `, clap)
|
278 |
setCurrentClap(clap)
|
279 |
setVideoGenerationStatus("finished")
|
280 |
+
console.log("---------------- GENERATED VIDEOS ----------------")
|
281 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.VIDEO), [
|
282 |
'endTimeInMs',
|
283 |
'prompt',
|
|
|
290 |
}
|
291 |
}
|
292 |
|
293 |
+
const generateStoryboardsThenVideos = async (clap: ClapProject): Promise<ClapProject> => {
|
294 |
+
clap = await generateStoryboards(clap)
|
295 |
+
clap = await generateVideos(clap)
|
296 |
+
return clap
|
297 |
+
}
|
298 |
+
|
299 |
+
|
300 |
const generateDialogues = async (clap: ClapProject): Promise<ClapProject> => {
|
301 |
try {
|
302 |
// setProgress(70)
|
|
|
304 |
clap = await editClapDialogues({
|
305 |
clap,
|
306 |
turbo: true
|
307 |
+
}).then(r => r.promise)
|
308 |
|
309 |
if (!clap) { throw new Error(`failed to edit the dialogues`) }
|
310 |
|
311 |
console.log(`handleSubmit(): received a clap with dialogues = `, clap)
|
312 |
setCurrentClap(clap)
|
313 |
setVoiceGenerationStatus("finished")
|
314 |
+
console.log("---------------- GENERATED DIALOGUES ----------------")
|
315 |
console.table(clap.segments.filter(s => s.category === ClapSegmentCategory.DIALOGUE), [
|
316 |
'endTimeInMs',
|
317 |
'prompt',
|
|
|
339 |
|
340 |
if (assetUrl.length < 128) { throw new Error(`handleSubmit(): the generated video is too small, so we failed`) }
|
341 |
|
342 |
+
console.log(`handleSubmit(): received a video: ${assetUrl.slice(0, 120)}...`)
|
343 |
setFinalGenerationStatus("finished")
|
344 |
return assetUrl
|
345 |
} catch (err) {
|
|
|
356 |
try {
|
357 |
let clap = await generateStory()
|
358 |
|
359 |
+
const tasks = [
|
360 |
generateMusic(clap),
|
361 |
+
generateStoryboardsThenVideos(clap)
|
362 |
+
]
|
363 |
|
364 |
+
const claps = await Promise.all(tasks)
|
365 |
+
|
366 |
+
console.log(`finished processing ${tasks.length} tasks in parallel`)
|
367 |
|
368 |
for (const newerClap of claps) {
|
369 |
clap = await updateClap(clap, newerClap, {
|
|
|
401 |
// clap = await generateVideos(clap)
|
402 |
// clap = await generateDialogues(clap)
|
403 |
|
404 |
+
|
405 |
+
|
406 |
+
console.log("final clap: ", clap)
|
407 |
await generateFinalVideo(clap)
|
408 |
|
409 |
setStatus("finished")
|
|
|
669 |
setStoryPromptDraft(e.target.value)
|
670 |
promptDraftRef.current = e.target.value
|
671 |
}}
|
672 |
+
// placeholder="Yesterday I was at my favorite pizza place and.."
|
673 |
+
placeholder="underwater footage, coral, fishes"
|
674 |
inputClassName="
|
675 |
transition-all duration-200 ease-in-out
|
676 |
h-32 md:h-56 lg:h-64
|
|
|
899 |
: imageGenerationStatus === "generating" ? "Creating storyboards.."
|
900 |
: videoGenerationStatus === "generating" ? "Filming shots.."
|
901 |
: voiceGenerationStatus === "generating" ? "Recording dialogues.."
|
902 |
+
: finalGenerationStatus === "generating" ? "Editing final cut.."
|
903 |
: "Please wait.."
|
904 |
)
|
905 |
: status === "error"
|
|
|
940 |
Powered by
|
941 |
</span>
|
942 |
<span className="ml-1 mr-0.5">
|
943 |
+
<Image src={HFLogo} alt="Hugging Face" width={14} height={13} />
|
944 |
</span>
|
945 |
<span className="text-stone-100/80 text-3xs font-semibold"
|
946 |
style={{ textShadow: "rgb(0 0 0 / 80%) 0px 0px 2px" }}>Hugging Face</span>
|
src/app/server/aitube/config.ts
CHANGED
@@ -3,5 +3,9 @@ export const serverHuggingfaceApiKey = `${process.env.HF_API_TOKEN || ""}`
|
|
3 |
|
4 |
// initially I used 1024x512 (a 2:1 ratio)
|
5 |
// but that is a bit too extreme, most phones only take 16:9
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
// initially I used 1024x512 (a 2:1 ratio)
|
5 |
// but that is a bit too extreme, most phones only take 16:9
|
6 |
+
// we can also try this
|
7 |
+
// > 896x512
|
8 |
+
export const RESOLUTION_LONG = 896 // 832 // 768
|
9 |
+
export const RESOLUTION_SHORT = 512 // 448 // 384
|
10 |
+
|
11 |
+
// ValueError: `height` and `width` have to be divisible by 8 but are 512 and 1.
|
src/app/server/aitube/editClapDialogues.ts
CHANGED
@@ -4,6 +4,7 @@ import { ClapProject } from "@aitube/clap"
|
|
4 |
import { editClapDialogues as apiEditClapDialogues, ClapCompletionMode } from "@aitube/client"
|
5 |
|
6 |
import { getToken } from "./getToken"
|
|
|
7 |
|
8 |
export async function editClapDialogues({
|
9 |
clap,
|
@@ -11,13 +12,17 @@ export async function editClapDialogues({
|
|
11 |
}: {
|
12 |
clap: ClapProject
|
13 |
turbo?: boolean
|
14 |
-
}):
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
21 |
|
22 |
-
return
|
|
|
|
|
23 |
}
|
|
|
4 |
import { editClapDialogues as apiEditClapDialogues, ClapCompletionMode } from "@aitube/client"
|
5 |
|
6 |
import { getToken } from "./getToken"
|
7 |
+
import { Workaround } from "./types"
|
8 |
|
9 |
export async function editClapDialogues({
|
10 |
clap,
|
|
|
12 |
}: {
|
13 |
clap: ClapProject
|
14 |
turbo?: boolean
|
15 |
+
}): Workaround<ClapProject> {
|
16 |
+
async function promise() {
|
17 |
+
return await apiEditClapDialogues({
|
18 |
+
clap,
|
19 |
+
completionMode: ClapCompletionMode.MERGE,
|
20 |
+
turbo,
|
21 |
+
token: await getToken()
|
22 |
+
})
|
23 |
+
}
|
24 |
|
25 |
+
return {
|
26 |
+
promise: promise()
|
27 |
+
}
|
28 |
}
|
src/app/server/aitube/editClapEntities.ts
CHANGED
@@ -4,6 +4,7 @@ import { ClapProject } from "@aitube/clap"
|
|
4 |
import { editClapEntities as apiEditClapEntities, ClapCompletionMode, ClapEntityPrompt } from "@aitube/client"
|
5 |
|
6 |
import { getToken } from "./getToken"
|
|
|
7 |
|
8 |
export async function editClapEntities({
|
9 |
clap,
|
@@ -13,14 +14,18 @@ export async function editClapEntities({
|
|
13 |
clap: ClapProject
|
14 |
entityPrompts?: ClapEntityPrompt[]
|
15 |
turbo?: boolean
|
16 |
-
}):
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
|
|
|
|
24 |
|
25 |
-
return
|
|
|
|
|
26 |
}
|
|
|
4 |
import { editClapEntities as apiEditClapEntities, ClapCompletionMode, ClapEntityPrompt } from "@aitube/client"
|
5 |
|
6 |
import { getToken } from "./getToken"
|
7 |
+
import { Workaround } from "./types"
|
8 |
|
9 |
export async function editClapEntities({
|
10 |
clap,
|
|
|
14 |
clap: ClapProject
|
15 |
entityPrompts?: ClapEntityPrompt[]
|
16 |
turbo?: boolean
|
17 |
+
}): Workaround<ClapProject> {
|
18 |
+
async function promise() {
|
19 |
+
return await apiEditClapEntities({
|
20 |
+
clap,
|
21 |
+
entityPrompts,
|
22 |
+
completionMode: ClapCompletionMode.MERGE,
|
23 |
+
turbo,
|
24 |
+
token: await getToken()
|
25 |
+
})
|
26 |
+
}
|
27 |
|
28 |
+
return {
|
29 |
+
promise: promise()
|
30 |
+
}
|
31 |
}
|
src/app/store.ts
CHANGED
@@ -67,7 +67,8 @@ export const useStore = create<{
|
|
67 |
}>((set, get) => ({
|
68 |
mainCharacterImage: "",
|
69 |
mainCharacterVoice: "",
|
70 |
-
storyPromptDraft: "Yesterday I was at my favorite pizza place and..",
|
|
|
71 |
storyPrompt: "",
|
72 |
orientation: ClapMediaOrientation.PORTRAIT,
|
73 |
status: "idle",
|
@@ -207,7 +208,7 @@ export const useStore = create<{
|
|
207 |
|
208 |
const cleanStoryPrompt = firstPartOfStoryPrompt.replace(/([^a-z0-9, ]+)/gi, "_")
|
209 |
|
210 |
-
const cleanName = `${cleanStoryPrompt.slice(0,
|
211 |
|
212 |
anchor.download = `${cleanName}.mp4`
|
213 |
|
@@ -238,7 +239,7 @@ export const useStore = create<{
|
|
238 |
|
239 |
const cleanStoryPrompt = firstPartOfStoryPrompt.replace(/([^a-z0-9, ]+)/gi, "_")
|
240 |
|
241 |
-
const cleanName = `${cleanStoryPrompt.slice(0,
|
242 |
|
243 |
anchor.download = `${cleanName}.clap`
|
244 |
|
|
|
67 |
}>((set, get) => ({
|
68 |
mainCharacterImage: "",
|
69 |
mainCharacterVoice: "",
|
70 |
+
// storyPromptDraft: "Yesterday I was at my favorite pizza place and..",
|
71 |
+
storyPromptDraft: "underwater footage, coral, fishes",
|
72 |
storyPrompt: "",
|
73 |
orientation: ClapMediaOrientation.PORTRAIT,
|
74 |
status: "idle",
|
|
|
208 |
|
209 |
const cleanStoryPrompt = firstPartOfStoryPrompt.replace(/([^a-z0-9, ]+)/gi, "_")
|
210 |
|
211 |
+
const cleanName = `${cleanStoryPrompt.slice(0, 120)}`
|
212 |
|
213 |
anchor.download = `${cleanName}.mp4`
|
214 |
|
|
|
239 |
|
240 |
const cleanStoryPrompt = firstPartOfStoryPrompt.replace(/([^a-z0-9, ]+)/gi, "_")
|
241 |
|
242 |
+
const cleanName = `${cleanStoryPrompt.slice(0, 120)}`
|
243 |
|
244 |
anchor.download = `${cleanName}.clap`
|
245 |
|
src/lib/utils/logImage.ts
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export async function logImage(uri: string, scale = 1.0): Promise<void> {
|
2 |
+
// Create an image element
|
3 |
+
const img = new Image();
|
4 |
+
|
5 |
+
// Load the image asynchronously
|
6 |
+
img.src = uri;
|
7 |
+
await new Promise<void>((resolve, reject) => {
|
8 |
+
img.onload = () => resolve();
|
9 |
+
img.onerror = (error) => reject(error);
|
10 |
+
});
|
11 |
+
|
12 |
+
// Get the image dimensions
|
13 |
+
let { width, height } = img;
|
14 |
+
width *= scale
|
15 |
+
height *= scale
|
16 |
+
|
17 |
+
// Log the image in the console
|
18 |
+
console.log(
|
19 |
+
"%c+",
|
20 |
+
`font-size: 1px; padding: ${Math.floor(height / 4)}px ${Math.floor(width / 2)}px; line-height: ${Math.round(height * 0.52)}px; background: url('${uri}'); background-size: ${width}px ${height}px; background-repeat: no-repeat; color: transparent;`
|
21 |
+
);
|
22 |
+
}
|
23 |
+
|
24 |
+
(async function() {
|
25 |
+
|
26 |
+
if (typeof window !== "undefined") {
|
27 |
+
// Add the logImage function to the console object
|
28 |
+
(console as any).image = logImage;
|
29 |
+
|
30 |
+
// Example usage
|
31 |
+
// console.image('https://example.com/path/to/your/image.jpg');
|
32 |
+
}
|
33 |
+
})()
|