better UI and flow for uploading video and triggering generate MoCap

This commit is contained in:
dev committed 2024-01-18 18:22:10 -05:00
1 parent 2c595414a4
commit fa32ac5edc
10 files changed
+296 -68

No files matched your search

+5
View File
@@ -471,6 +471,11 @@ class ApiConfig {
return `${this.getApiOrigin()}/v1/animation/face_animation/create`;
}
// =============== Motion Capture / mocap ===============
enqueueVideoMotionCapture(): string{
return `${this.getApiOrigin()}/v1/mocap/mocapnet/create`
}
// =============== Generic Model Downloads ===============
enqueueRemoteDownloadJob(): string {
@@ -0,0 +1,42 @@
import { ApiConfig } from "@storyteller/components";
export interface EnqueueVideoMotionCaptureRequest {
uuid_idempotency_token: string,
video_source: string,
}
export interface EnqueueVideoMotionCaptureResponse {
success: boolean,
inference_job_token?: string,
}
export async function EnqueueVideoMotionCapture(request: EnqueueVideoMotionCaptureRequest) : Promise<EnqueueVideoMotionCaptureResponse>
{
const endpoint = new ApiConfig().enqueueVideoMotionCapture();
return await fetch(endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (!res) {
return { success : false };
}
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -0,0 +1,7 @@
export type {
EnqueueVideoMotionCaptureRequest, EnqueueVideoMotionCaptureResponse,
} from './EnqueueVideoMotionCapture';
export {
EnqueueVideoMotionCapture
} from './EnqueueVideoMotionCapture';
@@ -8,6 +8,7 @@ export enum FrontendInferenceJobType {
VoiceConversion,
VoiceDesignerCreateVoice,
VoiceDesignerTts,
VideoMotionCapture
}
// NB: Many of these fields are optional despite the response payload containing them
@@ -659,7 +659,13 @@ class PageContainer extends React.Component<
<Route path="/video-mocap/:mediaToken?">
<VideoMocap
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
inferenceJobs: this.props.inferenceJobs,
inferenceJobsByCategory: this.props.inferenceJobsByCategory,
}}
/>
</Route>
@@ -1,13 +1,46 @@
import React from "react";
import { useLocalize } from "hooks";
import React, { useState, useEffect } from "react";
import TabContentUpload from "./components/tabContentUpload";
import TabContentLibrary from "./components/tabContentLibrary";
import { useLocalize } from "hooks";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
import { FrontendInferenceJobType, InferenceJob } from "@storyteller/components/src/jobs/InferenceJob";
import { PageVideoProvision } from "./components/pageVideoProvision";
import { PageInferenceStatuses } from "./components/pageInferenceStatuses";
import { BasicVideo } from "components/common";
export default function VideoMotionCapture(){
export default function VideoMotionCapture(props: {
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper,
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void,
inferenceJobs: Array<InferenceJob>,
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>,
}){
const { enqueueInferenceJob } = props;
const { t } = useLocalize("VideoMotionCapture");
enum pageStates { VIDEO_PROVISION, SHOW_JOB_STATUS }
const { VIDEO_PROVISION, SHOW_JOB_STATUS} = pageStates;
const [pageState, setPageState] = useState<{
index:number,
inputMediaToken: string,
jobToken: string,
resultMediaToken: string
}>({ index: VIDEO_PROVISION, inputMediaToken: "", jobToken: "", resultMediaToken: ""});
const handlePageState = (
{tokenType, token }:{tokenType:string, token:string | undefined}
) => {
if(token && tokenType == "jobToken"){
setPageState({
...pageState,
index: SHOW_JOB_STATUS,
jobToken: token
})
}
}
return (
<div className="container-panel py-4">
<div className="panel p-4">
@@ -21,34 +54,25 @@ export default function VideoMotionCapture(){
<div className="row g-5 mt-1">
{/*Video Chooser Tabs*/}
{/*Video Provision Tabs & Job Statuses*/}
<div className="col-12 col-md-6">
<ul className="nav nav-tabs" id="vmcTab">
<li className="nav-item">
<button
className="nav-link active"
id="vmcUploadTab"
data-bs-toggle="tab"
data-bs-target="#vmcUpload"
>
{t("tabTitle.upload")}
</button>
</li>
<li className="nav-item">
<button
className="nav-link"
id="vmcLibraryTab"
data-bs-toggle="tab"
data-bs-target="#vmcLibrary"
>
{t("tabTitle.library")}
</button>
</li>
</ul>
<div className="tab-content" id="vmcTabContent">
<TabContentUpload t={t}/>
<TabContentLibrary t={t}/>
</div>
{ pageState.index === VIDEO_PROVISION &&
<PageVideoProvision
t={t}
pageStateCallback={handlePageState}
/>
}
{ pageState.index === SHOW_JOB_STATUS &&
<PageInferenceStatuses
{...{
t,
enqueueInferenceJob,
pageStates,
pageState,
pageStateCallback: handlePageState
}}
/>
}
</div>
{/*ENDS Video Chooser Tabs*/}
@@ -0,0 +1,47 @@
import React, { useEffect } from 'react'
import { Analytics } from "common/Analytics";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
export function PageInferenceStatuses (props:{
t: Function,
pageStates: Object,
pageState: {
index:number,
inputMediaToken: string,
jobToken: string,
resultMediaToken: string
},
enqueueInferenceJob: (
jobToken: string,
frontendInferenceJobType: FrontendInferenceJobType
) => void,
pageStateCallback: (data:{tokenType:string, token:string | undefined}) => void
}){
const { enqueueInferenceJob } = props;
useEffect(()=>{
console.log("ENQUEUE INFERENCE JOB>>")
enqueueInferenceJob(
props.pageState.jobToken,
FrontendInferenceJobType.VideoMotionCapture
);
}, [])
const failures = (fail = "") => {
switch (fail) {
case "sample case":
return "Sample Case, this should not have been shown";
default:
return "Uknown failure";
}
};
return(
<InferenceJobsList {...{
failures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.VideoMotionCapture,
}}/>
)
}
@@ -0,0 +1,39 @@
import React from "react";
import TabContentUpload from "./tabContentUpload";
import TabContentLibrary from "./tabContentLibrary";
export function PageVideoProvision (props:{
t: Function
pageStateCallback: (data:{tokenType:string, token:string | undefined}) => void
}){
const {t} = props;
return(<>
<ul className="nav nav-tabs" id="vmcTab">
<li className="nav-item">
<button
className="nav-link active"
id="vmcUploadTab"
data-bs-toggle="tab"
data-bs-target="#vmcUpload"
>
{t("tabTitle.upload")}
</button>
</li>
<li className="nav-item">
<button
disabled
className="nav-link"
id="vmcLibraryTab"
data-bs-toggle="tab"
data-bs-target="#vmcLibrary"
>
{t("tabTitle.library")}
</button>
</li>
</ul>
<div className="tab-content" id="vmcTabContent">
<TabContentUpload {...props}/>
<TabContentLibrary {...props}/>
</div>
</>);
}
@@ -1,7 +1,8 @@
import React from "react";
export default function TabContentLibrary(props: {
t: Function
t: Function,
pageStateCallback: Function
}){
const { t } = props
return(
@@ -2,20 +2,25 @@ import React, {useState, useEffect} from "react";
import { v4 as uuidv4 } from "uuid";
import { UploadVideo } from '@storyteller/components/src/api/upload/UploadVideo'
import {
EnqueueVideoMotionCapture
} from "@storyteller/components/src/api/video_mocap";
import { useFile } from "hooks";
import VideoInput from "v2/view/pages/video_mocap/components/VideoInput";
export default function TabContentUpload(props:{
t: Function
pageStateCallback: (data:{tokenType:string, token:string | undefined}) => void
}){
const { t } = props
const { t, pageStateCallback } = props
const videoProps = useFile({})
const NO_FILE = 0
const FILE_STAGED = 1
const FILE_UPLOADING = 2
const FILE_UPLOADED = 3
const [videoState, setVideoState] = useState<number>(NO_FILE);
enum tabStates {
NO_FILE, FILE_STAGED, FILE_UPLOADING, FILE_UPLOADED
}
const { NO_FILE, FILE_STAGED, FILE_UPLOADING, FILE_UPLOADED } = tabStates;
const [tabState, setTabState] = useState<number>(NO_FILE);
const [token, setToken] = useState<string>("");
const makeRequest = () => ({
uuid_idempotency_token: uuidv4(),
@@ -25,39 +30,90 @@ export default function TabContentUpload(props:{
});
const handleUploadVideo = ()=>{
setVideoState(FILE_UPLOADING)
setTabState(FILE_UPLOADING)
UploadVideo(makeRequest()).then((res=>{
console.log("RESPONSE >>>")
console.log(res)
setVideoState(FILE_UPLOADED)
if(res.success && "upload_token" in res){
setToken(res.upload_token)
}
}));
}
// contains upload inout state and controls, see docs
return(
<div
className="tab-pane fade show active py-4"
id="vmcUpload"
>
<div className="row">
<div className="col-12">
<VideoInput {...{t, ...videoProps,
onStateChange: () => setVideoState(videoProps.file ? FILE_STAGED : NO_FILE),
}}/>
</div>
</div>
useEffect(()=>{
if (token!=="") setTabState(FILE_UPLOADED)
}, [token])
<div className="row py-3">
<div className="col-12">
<div className="d-flex justify-content-end gap-3">
<button
className="btn btn-primary"
disabled={videoState!==FILE_STAGED}
onClick={handleUploadVideo}
>{t("button.upload")}</button>
<button className="btn btn-primary" disabled={videoState!==FILE_UPLOADED}>{t("button.generate")}</button>
const handleEnqueueMocapNet = ()=>{
const request = {
video_source: token,
uuid_idempotency_token: uuidv4()
}
EnqueueVideoMotionCapture(request)
.then(res=>{
pageStateCallback({
tokenType: "jobToken",
token: res.inference_job_token
})
});
}
// contains upload inout state and controls, see docs
if (tabState === NO_FILE || tabState === FILE_STAGED){
return(
<div className="tab-pane fade show active py-4" id="vmcUpload">
<div className="row">
<div className="col-12">
<VideoInput {...{t, ...videoProps,
onStateChange: () => setTabState(videoProps.file ? FILE_STAGED : NO_FILE),
}}/>
</div>
</div>
<div className="row py-3">
<div className="col-12">
<div className="d-flex justify-content-end gap-3">
<button
className="btn btn-primary"
disabled={tabState!==FILE_STAGED}
onClick={handleUploadVideo}
>{t("button.upload")}</button>
</div>
</div>
</div>
</div>
)
} else if (tabState === FILE_UPLOADING){
return(
<div className="tab-pane fade show active py-4" id="vmcUpload">
<div className="row">
<div className="col-12">
<h2>{t("tab.message.fileUploading")}</h2>
</div>
</div>
</div>
)
}else if (tabState === FILE_UPLOADED){
return(
<div className="tab-pane fade show active py-4" id="vmcUpload">
<div className="row">
<div className="col-12">
<h2>{t("tab.message.fileUploaded")}</h2>
</div>
<div className="col-12">
<button
className="btn btn-primary"
onClick={handleEnqueueMocapNet}
>{t("button.generate")}</button>
</div>
</div>
</div>
)
}else {
return(
<div className="tab-pane fade show active py-4" id="vmcUpload">
<div className="row">
<div className="col-12">
<h1>{t("tab.message.error")}</h1>
</div>
</div>
</div>
)
</div>
)
}
}