jbilcke-hf HF staff commited on
Commit
3e3029e
1 Parent(s): 9d89208
src/app/main.tsx CHANGED
@@ -39,6 +39,7 @@ export function Main() {
39
  const mainCharacterVoice = useStore(s => s.mainCharacterVoice)
40
  const orientation = useStore(s => s.orientation)
41
  const status = useStore(s => s.status)
 
42
  const storyGenerationStatus = useStore(s => s.storyGenerationStatus)
43
  const assetGenerationStatus = useStore(s => s.assetGenerationStatus)
44
  const voiceGenerationStatus = useStore(s => s.voiceGenerationStatus)
@@ -55,6 +56,7 @@ export function Main() {
55
  const toggleOrientation = useStore(s => s.toggleOrientation)
56
  const error = useStore(s => s.error)
57
  const setError = useStore(s => s.setError)
 
58
  const setStoryGenerationStatus = useStore(s => s.setStoryGenerationStatus)
59
  const setAssetGenerationStatus = useStore(s => s.setAssetGenerationStatus)
60
  const setVoiceGenerationStatus = useStore(s => s.setVoiceGenerationStatus)
@@ -78,24 +80,93 @@ export function Main() {
78
 
79
  const isBusy = status === "generating" || hasPendingTasks
80
 
81
-
82
- const { openFilePicker, filesContent, loading } = useFilePicker({
83
  accept: '.clap',
84
  readAs: "ArrayBuffer"
85
  })
86
-
87
  const fileData = filesContent[0]
88
 
89
  useEffect(() => {
90
  const fn = async () => {
91
- if (fileData?.name) {
92
- try {
93
- const blob = new Blob([fileData.content])
94
- await loadClap(blob, fileData.name)
95
- } catch (err) {
96
- console.error("failed to load the Clap file:", err)
97
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
  fn()
101
  }, [fileData?.name])
@@ -456,6 +527,29 @@ export function Main() {
456
  </Button>
457
  */}
458
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  {canSeeBetaFeatures ?
460
  <Button
461
  onClick={() => saveClap()}
@@ -469,9 +563,11 @@ export function Main() {
469
  storyPromptDraft ? "opacity-100" : "opacity-80"
470
  )}
471
  >
472
- <span className="mr-1">Save project</span>
 
473
  </Button> : <div></div>
474
  }
 
475
 
476
  <div className="
477
  flex flex-row
@@ -486,9 +582,12 @@ export function Main() {
486
  cursor-pointer
487
  "
488
  onClick={() => toggleOrientation()}>
489
- <div>Orientation:</div>
 
 
 
490
  <div className="
491
- w-10 h-10
492
  flex flex-row items-center justify-center
493
  "
494
  >
@@ -506,7 +605,7 @@ export function Main() {
506
  disabled={!storyPromptDraft || isBusy}
507
  // variant="ghost"
508
  className={cn(
509
- `text-lg md:text-xl lg:text-2xl`,
510
  `bg-stone-800/90 text-amber-400/100 dark:bg-stone-800/90 dark:text-amber-400/100`,
511
  `font-bold`,
512
  `hover:bg-stone-800/100 hover:text-amber-300/100 dark:hover:bg-stone-800/100 dark:hover:text-amber-300/100`,
@@ -566,10 +665,11 @@ export function Main() {
566
  <p className="text-2xl font-bold">{progress}%</p>
567
  <p className="text-base text-white/70">{isBusy
568
  ? (
569
- storyGenerationStatus === "generating" ? "Enhancing the story.."
570
- : assetGenerationStatus === "generating" ? "Creating characters.."
571
- : imageGenerationStatus === "generating" ? "Generating storyboards.."
572
- : voiceGenerationStatus === "generating" ? "Generating voices.."
 
573
  : videoGenerationStatus === "generating" ? "Assembling final video.."
574
  : "Please wait.."
575
  )
@@ -603,7 +703,7 @@ export function Main() {
603
  font-sans
604
  -mb-0
605
  `,
606
- isLandscape ? 'h-4' : 'h-16'
607
  )}
608
  style={{ width: isPortrait ? 288 : 512 }}>
609
  <span className="text-stone-100/50 text-4xs"
 
39
  const mainCharacterVoice = useStore(s => s.mainCharacterVoice)
40
  const orientation = useStore(s => s.orientation)
41
  const status = useStore(s => s.status)
42
+ const parseGenerationStatus = useStore(s => s.parseGenerationStatus)
43
  const storyGenerationStatus = useStore(s => s.storyGenerationStatus)
44
  const assetGenerationStatus = useStore(s => s.assetGenerationStatus)
45
  const voiceGenerationStatus = useStore(s => s.voiceGenerationStatus)
 
56
  const toggleOrientation = useStore(s => s.toggleOrientation)
57
  const error = useStore(s => s.error)
58
  const setError = useStore(s => s.setError)
59
+ const setParseGenerationStatus = useStore(s => s.setParseGenerationStatus)
60
  const setStoryGenerationStatus = useStore(s => s.setStoryGenerationStatus)
61
  const setAssetGenerationStatus = useStore(s => s.setAssetGenerationStatus)
62
  const setVoiceGenerationStatus = useStore(s => s.setVoiceGenerationStatus)
 
80
 
81
  const isBusy = status === "generating" || hasPendingTasks
82
 
83
+ const { openFilePicker, filesContent } = useFilePicker({
 
84
  accept: '.clap',
85
  readAs: "ArrayBuffer"
86
  })
 
87
  const fileData = filesContent[0]
88
 
89
  useEffect(() => {
90
  const fn = async () => {
91
+ if (!fileData?.name) { return }
92
+
93
+ const {
94
+ setStatus,
95
+ setProgress,
96
+ setParseGenerationStatus,
97
+ setVideoGenerationStatus,
98
+ setGeneratedVideo
99
+ } = useStore.getState()
100
+
101
+ let clap: ClapProject | undefined = undefined
102
+
103
+ setStatus("generating")
104
+ setProgress(25)
105
+ setParseGenerationStatus("generating")
106
+
107
+ try {
108
+ const blob = new Blob([fileData.content])
109
+ clap = await loadClap(blob, fileData.name)
110
+ } catch (err) {
111
+ console.error("failed to load the Clap file:", err)
112
+ setError(`${err}`)
113
+ }
114
+
115
+ if (!clap) {
116
+ setParseGenerationStatus("error")
117
+ setStatus("error")
118
+ setProgress(0)
119
+ return
120
+ }
121
+
122
+ setParseGenerationStatus("finished")
123
+
124
+ try {
125
+ setProgress(60)
126
+ setVoiceGenerationStatus("generating")
127
+ clap = await editClapDialogues({ clap })
128
+
129
+ if (!clap) { throw new Error(`failed to edit the dialogues`) }
130
+
131
+ console.log(`handleSubmit(): received a clap with dialogues = `, clap)
132
+ setCurrentClap(clap)
133
+ setVoiceGenerationStatus("finished")
134
+ } catch (err) {
135
+ setVoiceGenerationStatus("error")
136
+ setStatus("error")
137
+ setError(`${err}`)
138
+ return
139
  }
140
+ if (!clap) {
141
+ return
142
+ }
143
+
144
+ setVideoGenerationStatus("generating")
145
+
146
+ let assetUrl = ""
147
+ try {
148
+ assetUrl = await exportClapToVideo({ clap })
149
+ } catch (err) {
150
+ console.error("failed to render the Clap file:", err)
151
+ setError(`${err}`)
152
+ }
153
+
154
+ if (!assetUrl) {
155
+ setVideoGenerationStatus("error")
156
+ setStatus("error")
157
+ setProgress(0)
158
+ return
159
+ }
160
+
161
+ setVideoGenerationStatus("finished")
162
+
163
+ setProgress(80)
164
+
165
+ console.log(`loadClap(): generated a video: ${assetUrl.slice(0, 60)}...`)
166
+
167
+ setGeneratedVideo(assetUrl)
168
+ setStatus("finished")
169
+ setError("")
170
  }
171
  fn()
172
  }, [fileData?.name])
 
527
  </Button>
528
  */}
529
 
530
+ <div className="
531
+
532
+ flex flex-row
533
+ justify-between items-center
534
+ space-x-3">
535
+ {canSeeBetaFeatures ?
536
+ <Button
537
+ onClick={openFilePicker}
538
+ disabled={isBusy}
539
+ // variant="ghost"
540
+ className={cn(
541
+ `text-sm md:text-base lg:text-lg`,
542
+ `bg-stone-800/90 text-amber-400/100 dark:bg-stone-800/90 dark:text-amber-400/100`,
543
+ `font-bold`,
544
+ `hover:bg-stone-800/100 hover:text-amber-300/100 dark:hover:bg-stone-800/100 dark:hover:text-amber-300/100`,
545
+ storyPromptDraft ? "opacity-100" : "opacity-80"
546
+ )}
547
+ >
548
+ <span className="hidden xl:inline mr-1">Load project</span>
549
+ <span className="inline xl:hidden mr-1">Load</span>
550
+ </Button> : <div></div>
551
+ }
552
+
553
  {canSeeBetaFeatures ?
554
  <Button
555
  onClick={() => saveClap()}
 
563
  storyPromptDraft ? "opacity-100" : "opacity-80"
564
  )}
565
  >
566
+ <span className="hidden xl:inline mr-1">Save project</span>
567
+ <span className="inline xl:hidden mr-1">Save</span>
568
  </Button> : <div></div>
569
  }
570
+ </div>
571
 
572
  <div className="
573
  flex flex-row
 
582
  cursor-pointer
583
  "
584
  onClick={() => toggleOrientation()}>
585
+ <div>
586
+ <span className="hidden xl:inline mr-1">Orientation:</span>
587
+ <span className="inline xl:hidden mr-1"></span>
588
+ </div>
589
  <div className="
590
+ w-8 h-8
591
  flex flex-row items-center justify-center
592
  "
593
  >
 
605
  disabled={!storyPromptDraft || isBusy}
606
  // variant="ghost"
607
  className={cn(
608
+ `text-base md:text-lg lg:text-xl xl:text-2xl`,
609
  `bg-stone-800/90 text-amber-400/100 dark:bg-stone-800/90 dark:text-amber-400/100`,
610
  `font-bold`,
611
  `hover:bg-stone-800/100 hover:text-amber-300/100 dark:hover:bg-stone-800/100 dark:hover:text-amber-300/100`,
 
665
  <p className="text-2xl font-bold">{progress}%</p>
666
  <p className="text-base text-white/70">{isBusy
667
  ? (
668
+ storyGenerationStatus === "generating" ? "Writing the story.."
669
+ : parseGenerationStatus === "generating" ? "Loading the project.."
670
+ : assetGenerationStatus === "generating" ? "Casting characters.."
671
+ : imageGenerationStatus === "generating" ? "Creating storyboards.."
672
+ : voiceGenerationStatus === "generating" ? "Recording voices.."
673
  : videoGenerationStatus === "generating" ? "Assembling final video.."
674
  : "Please wait.."
675
  )
 
703
  font-sans
704
  -mb-0
705
  `,
706
+ isLandscape ? 'h-4' : 'h-14'
707
  )}
708
  style={{ width: isPortrait ? 288 : 512 }}>
709
  <span className="text-stone-100/50 text-4xs"
src/app/server/aitube/config.ts CHANGED
@@ -1,2 +1,7 @@
1
 
2
  export const serverHuggingfaceApiKey = `${process.env.HF_API_TOKEN || ""}`
 
 
 
 
 
 
1
 
2
  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
+ export const RESOLUTION_LONG = 1024
7
+ export const RESOLUTION_SHORT = 576
src/app/server/aitube/createClap.ts CHANGED
@@ -4,12 +4,9 @@ import { ClapProject } from "@aitube/clap"
4
  import { createClap as apiCreateClap } from "@aitube/client"
5
 
6
  import { VideoOrientation } from "../../types"
7
- import { getToken } from "./getToken"
8
 
9
- // initially I used 1024x512 (a 2:1 ratio)
10
- // but that is a bit too extreme, most phones only take 16:9
11
- const RESOLUTION_LONG = 1024
12
- const RESOLUTION_SHORT = 576
13
 
14
  export async function createClap({
15
  prompt = "",
 
4
  import { createClap as apiCreateClap } from "@aitube/client"
5
 
6
  import { VideoOrientation } from "../../types"
 
7
 
8
+ import { getToken } from "./getToken"
9
+ import { RESOLUTION_LONG, RESOLUTION_SHORT } from "./config"
 
 
10
 
11
  export async function createClap({
12
  prompt = "",
src/app/store.ts CHANGED
@@ -4,9 +4,11 @@ import { ClapProject, parseClap, serializeClap } from "@aitube/clap"
4
  import { create } from "zustand"
5
 
6
  import { GlobalStatus, TaskStatus } from "@/types"
 
 
7
 
8
  import { VideoOrientation } from "./types"
9
- import { getVideoOrientation } from "@/lib/utils/getVideoOrientation"
10
 
11
  export const useStore = create<{
12
  mainCharacterImage: string
@@ -19,6 +21,7 @@ export const useStore = create<{
19
  orientation: VideoOrientation
20
 
21
  status: GlobalStatus
 
22
  storyGenerationStatus: TaskStatus
23
  assetGenerationStatus: TaskStatus
24
  voiceGenerationStatus: TaskStatus
@@ -39,6 +42,7 @@ export const useStore = create<{
39
  setStoryPromptDraft: (storyPromptDraft: string) => void
40
  setStoryPrompt: (storyPrompt: string) => void
41
  setStatus: (status: GlobalStatus) => void
 
42
  setStoryGenerationStatus: (storyGenerationStatus: TaskStatus) => void
43
  setAssetGenerationStatus: (assetGenerationStatus: TaskStatus) => void
44
  setVoiceGenerationStatus: (voiceGenerationStatus: TaskStatus) => void
@@ -51,8 +55,8 @@ export const useStore = create<{
51
 
52
  setProgress: (progress: number) => void
53
  setError: (error: string) => void
54
- saveClap: (fileName?: string) => Promise<void>
55
- loadClap: (blob: Blob, fileName?: string) => Promise<void>
56
  }>((set, get) => ({
57
  mainCharacterImage: "",
58
  mainCharacterVoice: "",
@@ -60,6 +64,7 @@ export const useStore = create<{
60
  storyPrompt: "",
61
  orientation: VideoOrientation.PORTRAIT,
62
  status: "idle",
 
63
  storyGenerationStatus: "idle",
64
  assetGenerationStatus: "idle",
65
  voiceGenerationStatus: "idle",
@@ -93,6 +98,7 @@ export const useStore = create<{
93
  setStoryPromptDraft: (storyPromptDraft: string) => { set({ storyPromptDraft }) },
94
  setStoryPrompt: (storyPrompt: string) => { set({ storyPrompt }) },
95
  setStatus: (status: GlobalStatus) => { set({ status }) },
 
96
  setStoryGenerationStatus: (storyGenerationStatus: TaskStatus) => { set({ storyGenerationStatus }) },
97
  setAssetGenerationStatus: (assetGenerationStatus: TaskStatus) => { set({ assetGenerationStatus }) },
98
  setVoiceGenerationStatus: (voiceGenerationStatus: TaskStatus) => { set({ voiceGenerationStatus }) },
@@ -109,8 +115,8 @@ export const useStore = create<{
109
  },
110
  setProgress: (progress: number) => { set({ progress }) },
111
  setError: (error: string) => { set({ error }) },
112
- saveClap: async (fileName: string = "untitled_story.clap"): Promise<void> => {
113
- const { currentClap } = get()
114
 
115
  if (!currentClap) { throw new Error(`cannot save a clap.. if there is no clap`) }
116
 
@@ -123,7 +129,13 @@ export const useStore = create<{
123
  const anchor = document.createElement("a")
124
  anchor.href = objectUrl
125
 
126
- anchor.download = fileName
 
 
 
 
 
 
127
 
128
  document.body.appendChild(anchor) // Append to the body (could be removed once clicked)
129
  anchor.click() // Trigger the download
@@ -132,15 +144,34 @@ export const useStore = create<{
132
  URL.revokeObjectURL(objectUrl)
133
  document.body.removeChild(anchor)
134
  },
135
- loadClap: async (blob: Blob, fileName: string = "untitled_story.clap"): Promise<void> => {
136
  if (!blob) {
137
  throw new Error(`missing blob`)
138
  }
139
 
140
- const currentClap: ClapProject = await parseClap(blob)
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  set({
143
  currentClap,
 
 
 
 
144
  })
 
 
145
  },
146
  }))
 
4
  import { create } from "zustand"
5
 
6
  import { GlobalStatus, TaskStatus } from "@/types"
7
+ import { getVideoOrientation } from "@/lib/utils/getVideoOrientation"
8
+ import { parseVideoOrientation } from "@/lib/utils/parseVideoOrientation"
9
 
10
  import { VideoOrientation } from "./types"
11
+ import { RESOLUTION_LONG, RESOLUTION_SHORT } from "./server/aitube/config"
12
 
13
  export const useStore = create<{
14
  mainCharacterImage: string
 
21
  orientation: VideoOrientation
22
 
23
  status: GlobalStatus
24
+ parseGenerationStatus: TaskStatus
25
  storyGenerationStatus: TaskStatus
26
  assetGenerationStatus: TaskStatus
27
  voiceGenerationStatus: TaskStatus
 
42
  setStoryPromptDraft: (storyPromptDraft: string) => void
43
  setStoryPrompt: (storyPrompt: string) => void
44
  setStatus: (status: GlobalStatus) => void
45
+ setParseGenerationStatus: (parseGenerationStatus: TaskStatus) => void
46
  setStoryGenerationStatus: (storyGenerationStatus: TaskStatus) => void
47
  setAssetGenerationStatus: (assetGenerationStatus: TaskStatus) => void
48
  setVoiceGenerationStatus: (voiceGenerationStatus: TaskStatus) => void
 
55
 
56
  setProgress: (progress: number) => void
57
  setError: (error: string) => void
58
+ saveClap: () => Promise<void>
59
+ loadClap: (blob: Blob, fileName?: string) => Promise<ClapProject>
60
  }>((set, get) => ({
61
  mainCharacterImage: "",
62
  mainCharacterVoice: "",
 
64
  storyPrompt: "",
65
  orientation: VideoOrientation.PORTRAIT,
66
  status: "idle",
67
+ parseGenerationStatus: "idle",
68
  storyGenerationStatus: "idle",
69
  assetGenerationStatus: "idle",
70
  voiceGenerationStatus: "idle",
 
98
  setStoryPromptDraft: (storyPromptDraft: string) => { set({ storyPromptDraft }) },
99
  setStoryPrompt: (storyPrompt: string) => { set({ storyPrompt }) },
100
  setStatus: (status: GlobalStatus) => { set({ status }) },
101
+ setParseGenerationStatus: (parseGenerationStatus: TaskStatus) => { set({ parseGenerationStatus }) },
102
  setStoryGenerationStatus: (storyGenerationStatus: TaskStatus) => { set({ storyGenerationStatus }) },
103
  setAssetGenerationStatus: (assetGenerationStatus: TaskStatus) => { set({ assetGenerationStatus }) },
104
  setVoiceGenerationStatus: (voiceGenerationStatus: TaskStatus) => { set({ voiceGenerationStatus }) },
 
115
  },
116
  setProgress: (progress: number) => { set({ progress }) },
117
  setError: (error: string) => { set({ error }) },
118
+ saveClap: async (): Promise<void> => {
119
+ const { currentClap , storyPrompt } = get()
120
 
121
  if (!currentClap) { throw new Error(`cannot save a clap.. if there is no clap`) }
122
 
 
129
  const anchor = document.createElement("a")
130
  anchor.href = objectUrl
131
 
132
+ const firstPartOfStoryPrompt = storyPrompt // .split(",").shift() || ""
133
+
134
+ const cleanStoryPrompt = firstPartOfStoryPrompt.replace(/([^a-z0-9, ]+)/gi, "_")
135
+
136
+ const cleanName = `${cleanStoryPrompt.slice(0, 50)}`
137
+
138
+ anchor.download = `${cleanName}.clap`
139
 
140
  document.body.appendChild(anchor) // Append to the body (could be removed once clicked)
141
  anchor.click() // Trigger the download
 
144
  URL.revokeObjectURL(objectUrl)
145
  document.body.removeChild(anchor)
146
  },
147
+ loadClap: async (blob: Blob, fileName: string = "untitled_story.clap"): Promise<ClapProject> => {
148
  if (!blob) {
149
  throw new Error(`missing blob`)
150
  }
151
 
152
+ const currentClap: ClapProject | undefined = await parseClap(blob)
153
+
154
+ if (!currentClap) { throw new Error(`failed to import the clap`) }
155
+
156
+
157
+ const storyPrompt = currentClap.meta.description.split("||").pop() || ""
158
+
159
+ // TODO: parseVideoOrientation should be put inside @aitube/clap (in the utils)
160
+ // const orientation = parseVideoOrientation(currentClap.meta.orientation)
161
+ // let's use the UI settings for now
162
+ const { orientation } = get()
163
+
164
+ currentClap.meta.height = orientation === VideoOrientation.LANDSCAPE ? RESOLUTION_SHORT : RESOLUTION_LONG
165
+ currentClap.meta.width = orientation === VideoOrientation.PORTRAIT ? RESOLUTION_SHORT : RESOLUTION_LONG
166
 
167
  set({
168
  currentClap,
169
+ storyPrompt,
170
+ storyPromptDraft: storyPrompt,
171
+ orientation,
172
+ currentVideoOrientation: orientation,
173
  })
174
+
175
+ return currentClap
176
  },
177
  }))
src/lib/utils/parseVideoOrientation.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { VideoOrientation } from "@/app/types"
2
+
3
+ export function parseVideoOrientation(input?: any): VideoOrientation {
4
+ const orientation = `${input || ""}`.trim().toLowerCase() || "square"
5
+
6
+ return (
7
+ (orientation === "vertical" || orientation === "portrait") ? VideoOrientation.PORTRAIT
8
+ : (orientation === "horizontal" || orientation === "landscape") ? VideoOrientation.LANDSCAPE
9
+ : VideoOrientation.SQUARE
10
+ )
11
+ }