diff --git a/packages/fakeyou.com/src/components/common/LoadingSpinner/LoadingSpinner.tsx b/packages/fakeyou.com/src/components/common/LoadingSpinner/LoadingSpinner.tsx index e5d1697afa..f83b40ff55 100644 --- a/packages/fakeyou.com/src/components/common/LoadingSpinner/LoadingSpinner.tsx +++ b/packages/fakeyou.com/src/components/common/LoadingSpinner/LoadingSpinner.tsx @@ -2,18 +2,23 @@ import React from "react"; interface LoadingSpinnerProps { className?: string; + label?: string; } -export default function LoadingSpinner({ className }: LoadingSpinnerProps) { +export default function LoadingSpinner({ + className, + label, +}: LoadingSpinnerProps) { return (
Loading...
+ {label &&
{label}
}
); } diff --git a/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.scss b/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.scss index 6fe22684fd..7544744324 100644 --- a/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.scss +++ b/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.scss @@ -11,3 +11,9 @@ width: 100%; border-radius: 0.5rem; } + +.fy-scene-3d-fullscreen-intro { + height: calc(100vh - 66px - 66px); + width: 100%; + border-radius: 0rem; +} diff --git a/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.tsx b/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.tsx index 32b6153ea5..92708195c1 100644 --- a/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.tsx +++ b/packages/fakeyou.com/src/components/common/Scene3D/Scene3D.tsx @@ -12,6 +12,7 @@ interface Scene3DProps { urlParams?: string; objectId?: string; sceneMediaFileToken?: string; + className?: string; } export default function Scene3D({ @@ -20,8 +21,8 @@ export default function Scene3D({ objectId, sceneMediaFileToken, urlParams, + className, }: Scene3DProps) { - let engineUrl = `https://engine.fakeyou.com?mode=${mode}`; if (sceneMediaFileToken) { @@ -35,14 +36,9 @@ export default function Scene3D({
- +
); } diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx index b9b10a12bf..2ebbd27fab 100644 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx @@ -12,13 +12,9 @@ import { faPause, faVolume, faVolumeSlash, -} from "@fortawesome/pro-solid-svg-icons" +} from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - Button, - SelectionBubbles, - VideoFakeyou -} from "components/common"; +import { Button, SelectionBubbles, VideoFakeyou } from "components/common"; import { VideoFakeyouProps } from "../VideoFakeyou/VideoFakeyou"; import { QuickTrimData, TrimStates, PlaybarStates } from "./types"; @@ -26,35 +22,35 @@ import { formatSecondsToHHMMSSCS } from "./helpers"; import TrimScrbber from "./TrimScrubber"; import './styles.scss' -interface VideoQuickTrimProps extends VideoFakeyouProps{ - onSelect: (values: QuickTrimData)=>void; +interface VideoQuickTrimProps extends VideoFakeyouProps { + onSelect: (values: QuickTrimData) => void; } const trimOptions: { [key: string]: number } = { "3s": 3, "5s": 5, "10s": 10, - "15s" : 15, + "15s": 15, }; -const initialTrimState:TrimStates = { +const initialTrimState: TrimStates = { canNotTrim: true, isScrubbingTrim: false, trimDuration: 0, trimStart: 0, trimEnd: 0, maxDuration: 0, -} +}; -const initialPlaybarState:PlaybarStates = { +const initialPlaybarState: PlaybarStates = { playbarWidth: 0, timeCursorOffset: 0, -} +}; export default memo(function VideoQuickTrim({ onSelect, ...rest -}: VideoQuickTrimProps){ +}: VideoQuickTrimProps) { // console.log("VideoQuickTrim Rerender!!"); const videoRef = useRef(null); @@ -120,29 +116,30 @@ export default memo(function VideoQuickTrim({ }, [playbarWidth, onSelect, isRepeatOn, trimEnd]); //END videoRefCallback function handleWindowResize() { - if(playbarRef.current !== null){ + if (playbarRef.current !== null) { const newWidth = playbarRef.current.getBoundingClientRect().width; - setPlaybarState((curr)=>({ + setPlaybarState(curr => ({ playbarWidth: newWidth, - timeCursorOffset: (curr.timeCursorOffset / (curr.playbarWidth-8)) * (newWidth-8) + timeCursorOffset: + (curr.timeCursorOffset / (curr.playbarWidth - 8)) * (newWidth - 8), })); } } const playbarRefCallback = useCallback(node => { - if(node !== null) { + if (node !== null) { playbarRef.current = node; - setPlaybarState((curr)=>({ + setPlaybarState(curr => ({ ...curr, - playbarWidth: node.getBoundingClientRect().width + playbarWidth: node.getBoundingClientRect().width, })); } }, []); - useLayoutEffect(()=>{ - window.addEventListener('resize', handleWindowResize); + useLayoutEffect(() => { + window.addEventListener("resize", handleWindowResize); return () => { - window.removeEventListener('resize', handleWindowResize); + window.removeEventListener("resize", handleWindowResize); }; - },[]); + }, []); const handleChangeTrimDuration = (selected: string) =>{ if(!canNotTrim){ @@ -171,7 +168,7 @@ export default memo(function VideoQuickTrim({ }else{ videoRef.current?.pause(); } - } + }; const trimScrubberWidth = videoRef.current ? trimDuration > 0 && trimDuration < videoRef.current.duration @@ -190,24 +187,29 @@ export default memo(function VideoQuickTrim({ />
- {playpause === 'paused' && - - } - {playpause === 'playing' && - - } + )}
- {videoRef.current && canNotTrim && + {videoRef.current && canNotTrim && (

Warning: Sorry Your Video is TOO Short

- } - {/* END of Video Wrapper */} + )} + + {/* END of Video Wrapper */}
@@ -218,7 +220,7 @@ export default memo(function VideoQuickTrim({
diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss b/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss index bb3c5b87fc..dac5941c2e 100644 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss @@ -3,7 +3,7 @@ $playbar-height: 2rem; .fy-video-quicktrim { - .video-wrapper{ + .video-wrapper { position: relative; background-color: $bg; border-radius: 0.5rem 0.5rem 0 0; @@ -11,71 +11,76 @@ $playbar-height: 2rem; border-left: $panel-inner-border 1px solid; border-right: $panel-inner-border 1px solid; } - .fy-video{ - height: 500px; + .fy-video { + max-height: 500px; + margin-bottom: -7px; + + video { + border-radius: 0.5rem 0.5rem 0 0; + } } - .playpause-overlay{ + .playpause-overlay { position: absolute; cursor: pointer; height: 100%; width: 100%; - top:0; - left:0; + top: 0; + left: 0; display: flex; justify-content: center; align-items: center; - svg{ + svg { opacity: 0; - transition: opacity 0.5s; + transition: opacity 0.5s; } - &:hover svg{ + &:hover svg { opacity: 1; } } - .warning-too-short{ + .warning-too-short { position: absolute; height: 100%; width: 100%; - top:0; - left:0; + top: 0; + left: 0; display: flex; justify-content: center; align-items: center; - div{ + div { position: absolute; height: 100%; width: 100%; - top:0; - left:0; + top: 0; + left: 0; background-color: #e66462; opacity: 70%; } - p{ + p { position: absolute; margin-top: 230px; width: 100%; - top:0; + top: 0; } } - .playbar{ - position:relative; + .playbar { + position: relative; height: $playbar-height; width: 100%; } - .playbar-bg{ + .playbar-bg { position: absolute; height: 100%; width: 100%; - top:0; + top: 0; left: 0; background-color: $panel-inner-border; border-radius: 0 0 0.5rem 0.5rem; } .trim-scrubber{ position: absolute; - top:-1rem; + top: -1rem; height: 3rem; background-color: #e66462; opacity: 0.8; @@ -92,16 +97,16 @@ $playbar-height: 2rem; color: $panel-inner-border; } } - .playcursor{ + .playcursor { position: absolute; - top:0; + top: 0; width: 0.5rem; height: $playbar-height; background-color: $white; } - .playpause-external{ - .button-playpause{ + .playpause-external { + .button-playpause { width: 55px; height: 40px; margin-right: 0.5rem; @@ -116,7 +121,7 @@ $playbar-height: 2rem; span{ width: 50px; } - div{ + div { width: 10px; margin-left: 3px; margin-right: 5px; @@ -124,4 +129,4 @@ $playbar-height: 2rem; } } } -} \ No newline at end of file +} diff --git a/packages/fakeyou.com/src/components/layout/InferenceJobsList/InferenceJobsList.tsx b/packages/fakeyou.com/src/components/layout/InferenceJobsList/InferenceJobsList.tsx index 5c502019f6..3e8da898f9 100644 --- a/packages/fakeyou.com/src/components/layout/InferenceJobsList/InferenceJobsList.tsx +++ b/packages/fakeyou.com/src/components/layout/InferenceJobsList/InferenceJobsList.tsx @@ -24,6 +24,7 @@ const resultPaths = { EngineComposition: "/media", FaceAnimation: "/media", TextToSpeech: "/media", + VideoStyleTransfer: "/media", VoiceConversion: "/media", VoiceDesignerCreateVoice: "/voice-designer/voice", VoiceDesignerTts: "/media", diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index afcbc616b2..73c2c0820e 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -123,6 +123,9 @@ import TextToImagePage from "./pages/text_to_image/TextToImagePage"; import DomainConfigProvider from "context/DomainConfigContext"; import DevUpload from "./pages/dev_upload/DevUpload"; import DevMediaInput from "./pages/dev_upload/DevMediaInput"; +import { StudioIntroPage } from "./pages/storyteller_studio_intro/StudioIntroPage"; +import StudioVSTPage from "./pages/storyteller_studio_intro/StudioVST/StudioVSTPage"; +import { StudioIntroResultPage } from "./pages/storyteller_studio_intro/StudioIntroResultPage"; interface Props { sessionWrapper: SessionWrapper; @@ -755,6 +758,28 @@ class PageContainer extends React.Component< /> + + + + + + + + + + + + @@ -783,6 +808,11 @@ class PageContainer extends React.Component< + { + // + // + // + } {/* Route for initial voice creation */} - - - - diff --git a/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntro.scss b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntro.scss new file mode 100644 index 0000000000..d5314fae00 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntro.scss @@ -0,0 +1,9 @@ +.studio-intro-page { + display: flex; + flex-direction: column; + height: calc(100vh - 66px); +} + +.result-video-wrapper { + max-width: 1100px; +} diff --git a/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntroPage.tsx b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntroPage.tsx new file mode 100644 index 0000000000..98b020d084 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntroPage.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; +import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable"; +import { Button, Scene3D } from "components/common"; +import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle"; +import { useParams, useHistory } from "react-router-dom"; +import { useInferenceJobs } from "hooks"; +import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine_compositor/EnqueueEngineCompositing"; +import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob"; +import { v4 as uuidv4 } from "uuid"; +import "./StudioIntro.scss"; + +interface Props { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +function StudioIntroPage(props: Props) { + const { mediaToken } = useParams<{ mediaToken: string }>(); + const history = useHistory(); + const inferenceJobs = useInferenceJobs( + FrontendInferenceJobType.EngineComposition + ); + + usePrefixedDocumentTitle("Storyteller Studio"); + + if (!props.sessionWrapper.canAccessStudio()) { + return ; + } + + let engineParams = {}; + + if (mediaToken) { + engineParams = { + sceneMediaFileToken: mediaToken, + }; + } else { + engineParams = { + objectId: "sample-room.gltf", + }; + } + + const onClick = () => { + // This opens the job modal. Needs to be disabled. + EnqueueEngineCompositing("", { + uuid_idempotency_token: uuidv4(), + media_file_token: mediaToken || "", + }).then((res: any) => { + if (res && res.success) { + inferenceJobs.enqueue(res.inference_job_token); + history.push(`/studio-intro/style/${res.inference_job_token}`); + } + }); + }; + + return ( +
+ +
+
+
+ ); +} + +export { StudioIntroPage }; diff --git a/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntroResultPage.tsx b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntroResultPage.tsx new file mode 100644 index 0000000000..e1cd967d77 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioIntroResultPage.tsx @@ -0,0 +1,79 @@ +import React, { useState } from "react"; +import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper"; +import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper"; +import { Button, Container, Panel } from "components/common"; +import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle"; +import { useHistory, useParams } from "react-router-dom"; +import "./StudioIntro.scss"; +import LoadingSpinner from "components/common/LoadingSpinner"; +import { useMedia } from "hooks"; +import { BucketConfig } from "@storyteller/components/src/api/BucketConfig"; +import { MediaFileType } from "@storyteller/components/src/api/media_files"; + +interface Props { + sessionWrapper: SessionWrapper; + sessionSubscriptionsWrapper: SessionSubscriptionsWrapper; +} + +// This page just needs to poll the job and show the video when it's done. + +function StudioIntroResultPage(props: Props) { + const { jobToken } = useParams<{ jobToken: string }>(); + const history = useHistory(); + const [mediaToken, setMediaToken] = useState( + "m_f5kp3hm74qeq16eq7536jb73jkbvkh" + ); // Set the media token after polling success + + // const [jobExists, setJobExists] = useState(null); + + const [mediaFile, setMediaFile] = useState(); + + useMedia({ + mediaToken: mediaToken, + onSuccess: (res: any) => { + setMediaFile(res); + }, + }); + + const mediaLink = + mediaFile && new BucketConfig().getGcsUrl(mediaFile.public_bucket_path); + + usePrefixedDocumentTitle("Storyteller Studio"); + + if (!jobToken) { + history.push("/"); + } + + // Should also check if job actually exists or not + // + // if (!jobExists) { + // history.push("/"); + // } + + return ( + + +

Generation Result

+
+ +
+ {mediaToken ? ( +
+
+ {mediaToken ? ( +
+
+ ) : null} +
+ ); +} + +export { StudioIntroResultPage }; diff --git a/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx new file mode 100644 index 0000000000..4a8f5a0bef --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx @@ -0,0 +1,161 @@ +import React, { useRef, useState } from "react"; +import { NavLink, useParams, useHistory } from "react-router-dom"; +import { + Button, + Container, + Panel, + SelectionBubbles, + TextArea, +} from "components/common"; +import VideoQuickTrim, { + QuickTrimData, +} from "components/common/VideoQuickTrim"; +import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer"; +import { initialValues } from "./defaultValues"; +import { mapRequest, VSTType } from "./helpers"; +import LoadingSpinner from "components/common/LoadingSpinner"; +// import SectionAdvanceOptions from "./sectionAdvanceOptions"; + +export default function PageVSTApp() { + const { jobToken } = useParams<{ jobToken: string }>(); + const videoRef = useRef(null); + const history = useHistory(); + const [mediaToken, setMediaToken] = useState( + "m_wnnz9jey96dwhss9qk1891v9gmvnj8" // Currently hardcoded video media token value. Let a job polling function set the value when job returns media on success. + ); + // const [jobExists, setJobExists] = useState(null); + const [vstValues, setVstValues] = useState({ + ...initialValues, + fileToken: mediaToken, + }); + + const handleOnChange = (val: { + [key: string]: number | string | boolean | undefined; + }) => { + setVstValues(curr => ({ ...curr, ...val })); + }; + + const handleGenerate = () => { + const request = mapRequest(vstValues); + EnqueueVideoStyleTransfer(request).then(res => { + if (res.success && res.inference_job_token) { + console.log("Job enqueued successfully", res.inference_job_token); + history.push(`/studio-intro/result/${res.inference_job_token}`); + } else { + console.log("Failed to enqueue job", res); + } + }); + }; + + if (videoRef?.current) { + const ve = videoRef.current; + ve.onloadedmetadata = () => { + const newValues: { + width?: number; + height?: number; + maxDuration?: number; + } = {}; + if (ve.duration) { + newValues.maxDuration = ve.duration; + } + setVstValues(curr => ({ + ...curr, + ...newValues, + })); + }; + } + + const styleMap: { [key: string]: string } = { + Anime: "weight_yqexh77ntqyawzgh9fzash798", // set SD Model Weight Tokens, currently hardcoded all to Anime style + Pixel: "weight_yqexh77ntqyawzgh9fzash798", + Painting: "weight_yqexh77ntqyawzgh9fzash798", + }; + + const handleStyleSelection = (selected: string) => { + const selectedSdModelToken = styleMap[selected]; + setVstValues(curr => ({ ...curr, sdModelToken: selectedSdModelToken })); + }; + + const styleOptions = Object.keys(styleMap); + + if (!jobToken) { + history.push("/"); + } + + // Should also check if job actually exists or not + // + // if (!jobExists) { + // history.push("/"); + // } + + return ( + + +

Style Your Scene

+
+ +
+
+ {vstValues.fileToken ? ( + + handleOnChange({ + trimStart: val.trimStartSeconds, + trimEnd: val.trimEndSeconds, + }) + } + /> + ) : ( +
+ +
+ )} +
+
+
+ + +
+ +