Commit
·
51365b6
1
Parent(s):
c8c2f6c
up
Browse files- src/app/main.tsx +33 -7
- src/app/store.ts +30 -0
src/app/main.tsx
CHANGED
@@ -3,6 +3,7 @@
|
|
3 |
import React, { useEffect, useRef, useTransition } from 'react'
|
4 |
import { IoMdPhonePortrait } from 'react-icons/io'
|
5 |
import { GiRollingDices } from 'react-icons/gi'
|
|
|
6 |
import { useLocalStorage } from "usehooks-ts"
|
7 |
import { ClapProject, ClapMediaOrientation, ClapSegmentCategory, updateClap } from '@aitube/clap'
|
8 |
import Image from 'next/image'
|
@@ -79,6 +80,7 @@ export function Main() {
|
|
79 |
const setCurrentVideo = useStore(s => s.setCurrentVideo)
|
80 |
const progress = useStore(s => s.progress)
|
81 |
const setProgress = useStore(s => s.setProgress)
|
|
|
82 |
const saveClap = useStore(s => s.saveClap)
|
83 |
const loadClap = useStore(s => s.loadClap)
|
84 |
|
@@ -254,7 +256,7 @@ export function Main() {
|
|
254 |
|
255 |
clap = await editClapVideos({
|
256 |
clap,
|
257 |
-
turbo:
|
258 |
}).then(r => r.promise)
|
259 |
|
260 |
if (!clap) { throw new Error(`failed to edit the videos`) }
|
@@ -313,9 +315,12 @@ export function Main() {
|
|
313 |
turbo: true
|
314 |
})
|
315 |
|
|
|
|
|
|
|
|
|
316 |
console.log(`handleSubmit(): received a video: ${assetUrl.slice(0, 60)}...`)
|
317 |
setFinalGenerationStatus("finished")
|
318 |
-
setCurrentVideo(assetUrl)
|
319 |
return assetUrl
|
320 |
} catch (err) {
|
321 |
setFinalGenerationStatus("error")
|
@@ -873,10 +878,10 @@ export function Main() {
|
|
873 |
)
|
874 |
: status === "error"
|
875 |
? <span>{error || ""}</span>
|
876 |
-
: <span> </span> // to prevent layout changes
|
877 |
}</p>
|
878 |
</div>
|
879 |
-
: currentVideo ? <video
|
880 |
src={currentVideo}
|
881 |
controls
|
882 |
playsInline
|
@@ -917,9 +922,30 @@ export function Main() {
|
|
917 |
</div>
|
918 |
</DeviceFrameset>
|
919 |
|
920 |
-
{
|
921 |
-
|
922 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
923 |
</div>
|
924 |
</div>
|
925 |
</div>
|
|
|
3 |
import React, { useEffect, useRef, useTransition } from 'react'
|
4 |
import { IoMdPhonePortrait } from 'react-icons/io'
|
5 |
import { GiRollingDices } from 'react-icons/gi'
|
6 |
+
import { FaCloudDownloadAlt } from "react-icons/fa"
|
7 |
import { useLocalStorage } from "usehooks-ts"
|
8 |
import { ClapProject, ClapMediaOrientation, ClapSegmentCategory, updateClap } from '@aitube/clap'
|
9 |
import Image from 'next/image'
|
|
|
80 |
const setCurrentVideo = useStore(s => s.setCurrentVideo)
|
81 |
const progress = useStore(s => s.progress)
|
82 |
const setProgress = useStore(s => s.setProgress)
|
83 |
+
const saveVideo = useStore(s => s.saveVideo)
|
84 |
const saveClap = useStore(s => s.saveClap)
|
85 |
const loadClap = useStore(s => s.loadClap)
|
86 |
|
|
|
256 |
|
257 |
clap = await editClapVideos({
|
258 |
clap,
|
259 |
+
turbo: false
|
260 |
}).then(r => r.promise)
|
261 |
|
262 |
if (!clap) { throw new Error(`failed to edit the videos`) }
|
|
|
315 |
turbo: true
|
316 |
})
|
317 |
|
318 |
+
setCurrentVideo(assetUrl)
|
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, 60)}...`)
|
323 |
setFinalGenerationStatus("finished")
|
|
|
324 |
return assetUrl
|
325 |
} catch (err) {
|
326 |
setFinalGenerationStatus("error")
|
|
|
878 |
)
|
879 |
: status === "error"
|
880 |
? <span>{error || ""}</span>
|
881 |
+
: <span>{error ? error : <span> </span>}</span> // to prevent layout changes
|
882 |
}</p>
|
883 |
</div>
|
884 |
+
: (currentVideo && currentVideo?.length > 128) ? <video
|
885 |
src={currentVideo}
|
886 |
controls
|
887 |
playsInline
|
|
|
922 |
</div>
|
923 |
</DeviceFrameset>
|
924 |
|
925 |
+
{(currentVideo && currentVideo.length > 128) ? <div
|
926 |
+
className={cn(`
|
927 |
+
w-full
|
928 |
+
flex flex-row
|
929 |
+
items-center justify-center
|
930 |
+
transition-all duration-150 ease-in-out
|
931 |
+
|
932 |
+
text-stone-800
|
933 |
+
|
934 |
+
group
|
935 |
+
pt-2 md:pt-4
|
936 |
+
`,
|
937 |
+
isBusy ? 'opacity-50' : 'cursor-pointer opacity-100 hover:scale-110 active:scale-150 hover:text-stone-950 active:text-black'
|
938 |
+
)}
|
939 |
+
style={{ textShadow: "rgb(255 255 255 / 19%) 0px 0px 2px" }}
|
940 |
+
onClick={isBusy ? undefined : saveVideo}
|
941 |
+
>
|
942 |
+
<div className="
|
943 |
+
text-base md:text-lg lg:text-xl
|
944 |
+
transition-all duration-150 ease-out
|
945 |
+
group-hover:animate-swing
|
946 |
+
"><FaCloudDownloadAlt /></div>
|
947 |
+
<div className="text-xs md:text-sm lg:text-base"> Download</div>
|
948 |
+
</div> : null}
|
949 |
</div>
|
950 |
</div>
|
951 |
</div>
|
src/app/store.ts
CHANGED
@@ -61,6 +61,7 @@ export const useStore = create<{
|
|
61 |
|
62 |
setProgress: (progress: number) => void
|
63 |
setError: (error: string) => void
|
|
|
64 |
saveClap: () => Promise<void>
|
65 |
loadClap: (blob: Blob, fileName?: string) => Promise<ClapProject>
|
66 |
}>((set, get) => ({
|
@@ -188,6 +189,35 @@ export const useStore = create<{
|
|
188 |
},
|
189 |
setProgress: (progress: number) => { set({ progress }) },
|
190 |
setError: (error: string) => { set({ error }) },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
saveClap: async (): Promise<void> => {
|
192 |
const { currentClap , storyPrompt } = get()
|
193 |
|
|
|
61 |
|
62 |
setProgress: (progress: number) => void
|
63 |
setError: (error: string) => void
|
64 |
+
saveVideo: () => Promise<void>
|
65 |
saveClap: () => Promise<void>
|
66 |
loadClap: (blob: Blob, fileName?: string) => Promise<ClapProject>
|
67 |
}>((set, get) => ({
|
|
|
189 |
},
|
190 |
setProgress: (progress: number) => { set({ progress }) },
|
191 |
setError: (error: string) => { set({ error }) },
|
192 |
+
saveVideo: async (): Promise<void> => {
|
193 |
+
const { currentVideo, storyPrompt } = get()
|
194 |
+
|
195 |
+
if (!currentVideo) { throw new Error(`cannot save a video.. if there is no video`) }
|
196 |
+
|
197 |
+
const currentClapBlob: Blob = await fetch(currentVideo).then(r => r.blob())
|
198 |
+
|
199 |
+
// Create an object URL for the compressed clap blob
|
200 |
+
const objectUrl = URL.createObjectURL(currentClapBlob)
|
201 |
+
|
202 |
+
// Create an anchor element and force browser download
|
203 |
+
const anchor = document.createElement("a")
|
204 |
+
anchor.href = objectUrl
|
205 |
+
|
206 |
+
const firstPartOfStoryPrompt = storyPrompt // .split(",").shift() || ""
|
207 |
+
|
208 |
+
const cleanStoryPrompt = firstPartOfStoryPrompt.replace(/([^a-z0-9, ]+)/gi, "_")
|
209 |
+
|
210 |
+
const cleanName = `${cleanStoryPrompt.slice(0, 50)}`
|
211 |
+
|
212 |
+
anchor.download = `${cleanName}.mp4`
|
213 |
+
|
214 |
+
document.body.appendChild(anchor) // Append to the body (could be removed once clicked)
|
215 |
+
anchor.click() // Trigger the download
|
216 |
+
|
217 |
+
// Cleanup: revoke the object URL and remove the anchor element
|
218 |
+
URL.revokeObjectURL(objectUrl)
|
219 |
+
document.body.removeChild(anchor)
|
220 |
+
},
|
221 |
saveClap: async (): Promise<void> => {
|
222 |
const { currentClap , storyPrompt } = get()
|
223 |
|