mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
better UI and flow for uploading video and triggering generate MoCap
This commit is contained in:
1 parent
2c595414a4
commit
fa32ac5edc
10 files changed
+296
-68
No files matched your search
@@ -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*/}
|
||||
|
||||
|
||||
+47
@@ -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(
|
||||
|
||||
+90
-34
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user