Merge pull request #620 from storytold/unused-file-cleanup

Unused file cleanup, header text wrap fix
This commit is contained in:
Victor O authored and GitHub committed 2024-07-16 19:34:03 -07:00
commit 2a0286da6d
35 files changed
+19 -2980

No files matched your search

@@ -448,7 +448,7 @@ export default function SideNav({
<li>
<NavLink
to="/video-styletransfer"
to="/style-video"
activeClassName="active-link"
onClick={handleNavLinkClick}
>
@@ -449,7 +449,7 @@ export default function TopNav({
</Link>
{domain.titlePart === "FakeYou" && (
<div className="d-none d-lg-block">
<div className="d-none d-lg-block no-wrap">
<NavItem
icon={faScrewdriverWrench}
label="Creator Tools"
@@ -462,13 +462,13 @@ export default function TopNav({
icon={faCompass}
label="Explore"
link="/explore"
className="d-none d-lg-block"
className="d-none d-lg-block no-wrap"
/>
<NavItem
icon={faStar}
label="Pricing"
link="/pricing"
className="me-3 d-none d-lg-block"
className="me-3 d-none d-lg-block no-wrap"
/>
</div>
</div>
@@ -9,7 +9,6 @@ import { ModerationIpBanListFc } from "./pages/moderation/moderation_ip_ban_list
import { ModerationViewIpBanFc } from "./pages/moderation/moderation_view_ip_ban/ModerationViewIpBanFc";
import FaceAnimator from "./pages/face_animator";
import VideoMocap from "./pages/video_mocap";
import VideoStyleTransfer from "./pages/video_styletransfer";
import { ProfileEditFc } from "./pages/profile/profile_edit/ProfileEditFc";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
@@ -816,14 +815,6 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/video-styletransfer">
<VideoStyleTransfer
{...{
sessionWrapper: this.props.sessionWrapper,
}}
/>
</Route>
<Route path="/text-to-image">
<TextToImagePage
sessionSubscriptionsWrapper={
@@ -10,22 +10,22 @@ const columns = [
{
title: "Original Video",
videoSrc: "/videos/landing/vst/1-original.mp4",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
title: "2D Flat Anime",
videoSrc: "/videos/landing/vst/1-2d_flat_anime.mp4",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
title: "Ink B&W",
videoSrc: "/videos/landing/vst/1-ink_bw.mp4",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
title: "Origami",
videoSrc: "/videos/landing/vst/1-origami.mp4",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
];
@@ -70,7 +70,7 @@ export default function VstSectionV1(props: VstSectionV1Props) {
small={true}
icon={faArrowRight}
iconFlip={true}
to="/video-styletransfer"
to="/style-video"
/>
</div>
</div>
@@ -88,7 +88,7 @@ export default function VstSectionV1(props: VstSectionV1Props) {
<Badge label={column.title} color="gray" />
</div>
{/* <div className="vst-sbs-video-overlay-cta">
<Link to={column.ctaLink || "/video-styletransfer"}>
<Link to={column.ctaLink || "/style-video"}>
<Badge label="Try this style" />
</Link>
</div> */}
@@ -61,22 +61,22 @@ export default function VstSectionV2(props: VstSectionV2Props) {
{
src: "/videos/landing/vst/1-2d_flat_anime.mp4",
label: "2D Flat Anime",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
src: "/videos/landing/vst/1-3d_cartoon.mp4",
label: "3D Cartoon",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
src: "/videos/landing/vst/1-ink_bw.mp4",
label: "Ink B&W",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
src: "/videos/landing/vst/1-origami.mp4",
label: "Origami",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
],
},
@@ -87,22 +87,22 @@ export default function VstSectionV2(props: VstSectionV2Props) {
{
src: "/videos/landing/vst/2-2d_flat_anime.mp4",
label: "2D Flat Anime",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
src: "/videos/landing/vst/2-pop_art.mp4",
label: "Pop Art",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
src: "/videos/landing/vst/2-ink_splash.mp4",
label: "Ink Splash",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
{
src: "/videos/landing/vst/2-origami.mp4",
label: "Origami",
ctaLink: "/video-styletransfer",
ctaLink: "/style-video",
},
],
},
@@ -240,7 +240,7 @@ export default function VstSectionV2(props: VstSectionV2Props) {
<Badge label={styledVideo.label} color="gray" />
</div>
{/* <div className="vst-styled-videos-panel-overlay-cta">
<Link to={styledVideo.ctaLink || "/video-styletransfer"}>
<Link to={styledVideo.ctaLink || "/style-video"}>
<Badge label="Try this style" />
</Link>
</div> */}
@@ -1,37 +0,0 @@
.studio-intro-page {
display: flex;
flex-direction: column;
height: calc(100vh - 66px);
.studio-intro-exporter {
background: rgba(#000, .5);
backdrop-filter: blur(10px);
border-radius: .5rem;
right: 1rem;
bottom: 1rem;
padding: .5rem;
position: absolute;
.exporter-title {
width: 100%;
span {
opacity: .66;
font-size: .875rem;
height: 1em;
line-height: 1em;
display: block;
margin-bottom: .25rem;
}
}
.exporter-controls {
display: flex;
gap: .5rem;
}
}
}
.result-video-wrapper {
max-width: 1100px;
}
@@ -1,142 +0,0 @@
import React, { useCallback, useState } 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 } 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";
import Scene3D from "components/common/Scene3D/Scene3D";
import { EngineMode } from "components/common/Scene3D/EngineMode";
import { SplitFirstPeriod } from "utils/SplitFirstPeriod";
interface Props {
sessionWrapper: SessionWrapper;
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
}
const PAGE_TITLE = "Storyteller Studio";
function StudioIntroPage(props: Props) {
// NB: The URL parameter might be a raw media token (for .scn.ron files), or it might
// have an appended suffix to assist the engine in loading the correct scene format.
// For example, this is a valid "mediaTokenSpec": `m_zk0qkm1tgsdbh6e3c9kedy34vaympd.glb`
const { mediaToken: mediaTokenSpec } = useParams<{ mediaToken: string }>();
const { base: mediaToken, maybeRemainder: maybeExtension } =
SplitFirstPeriod(mediaTokenSpec);
const history = useHistory();
const inferenceJobs = useInferenceJobs();
// If the user saves the scene in the engine, we'll need to use the new token
// for subsequent steps of this flow.
const [savedMediaToken, setSavedMediaToken] = useState(mediaToken);
// We don't show the "next step" buttons until the engine loads.
// Unfortunately the engine sometimes fires this twice, with one instance
// being called before the scene loads. Until this is fixed, we'll count the
// number of event fires and assume we must have two calls.
const [sceneIsLoadedCount, sceneIsLoadedCountSet] = useState(0);
// If the scene is saved, we know the user must have interacted.
// This can serve as a second optional gate for enabling the next steps.
const [sceneIsSaved, sceneIsSavedSet] = useState(false);
usePrefixedDocumentTitle(PAGE_TITLE);
const onSaveCallback = useCallback(
(sceneMediaToken: string) => {
console.log(`Saved scene, new media token: ${sceneMediaToken}`);
setSavedMediaToken(sceneMediaToken);
sceneIsSavedSet(true); // Just in case we missed the "scene loaded" event.
// Replace the history state without causing a React re-render
window.history.replaceState(
null,
PAGE_TITLE,
`/studio-intro/${sceneMediaToken}`
);
},
[setSavedMediaToken, sceneIsSavedSet]
);
const onSceneReadyCallback = useCallback(() => {
sceneIsLoadedCountSet(sceneIsLoadedCount + 1);
}, [sceneIsLoadedCount, sceneIsLoadedCountSet]);
if (!props.sessionWrapper.canAccessStudio()) {
return <StudioNotAvailable />;
}
let assetDescriptor;
// We should prefer to start the onboarding flow with an existing scene, but if
// one is unavailable, we should show the sample room.
if (maybeExtension !== undefined) {
assetDescriptor = {
sceneImportToken: mediaToken,
extension: maybeExtension,
};
} else if (mediaToken) {
assetDescriptor = {
storytellerSceneMediaFileToken: mediaToken,
};
} else {
assetDescriptor = {
objectId: "sample-room.gltf",
};
}
const onClick = () => {
// This opens the job modal. Needs to be disabled.
EnqueueEngineCompositing("", {
uuid_idempotency_token: uuidv4(),
media_file_token: savedMediaToken,
skybox: "meadow_4k",
}).then((res: any) => {
if (res && res.success) {
inferenceJobs.enqueue(
res.inference_job_token,
FrontendInferenceJobType.EngineComposition
);
history.push(`/studio-intro/style/${res.inference_job_token}`);
}
});
};
const sceneIsLoaded = sceneIsLoadedCount > 1 || sceneIsSaved;
let progressButtons = <></>;
if (sceneIsLoaded) {
progressButtons = (
<div {...{ className: "p-3 d-flex justify-content-center" }}>
<Button label="Create Movie from 3D Scene" onClick={onClick} />
</div>
);
}
return (
<div className="studio-intro-page">
<Scene3D
fullScreen={true}
mode={EngineMode.Studio}
asset={assetDescriptor}
className="flex-grow-1"
onSceneReadyCallback={onSceneReadyCallback}
onSceneSavedCallback={onSaveCallback}
/>
{progressButtons}
</div>
);
}
export { StudioIntroPage };
@@ -1,108 +0,0 @@
import React, { useEffect, 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, useInferenceJobs } from "hooks";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { MediaFileType } from "@storyteller/components/src/api/media_files";
import { JobState } from "@storyteller/components/src/jobs/JobStates";
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 [rething,rethingSet] = useState(false);
const { jobToken } = useParams<{ jobToken: string }>();
const history = useHistory();
const { enqueue, inferenceJobs } = useInferenceJobs();
const job = inferenceJobs.find((item: any) => item.jobToken === jobToken);
useEffect(() => {
if (!job && !rething) {
rethingSet(true);
enqueue(jobToken);
}
},[enqueue,job,jobToken,rething]);
// const [mediaToken, setMediaToken] = useState(
// "m_f5kp3hm74qeq16eq7536jb73jkbvkh"
// ); // Set the media token after polling success
// const [jobExists, setJobExists] = useState<boolean | null>(null);
// const job = useJobStatus({ jobToken });
const [mediaFile, setMediaFile] = useState<MediaFileType>();
const mediaToken = job?.maybeResultToken|| "";
useMedia({
mediaToken,
onSuccess: (res: any) => {
setMediaFile(res);
},
});
const mediaLink =
mediaFile && new BucketConfig().getGcsUrl(mediaFile.public_bucket_path);
usePrefixedDocumentTitle("Storyteller Studio");
if (!jobToken) {
history.push("/");
}
const contentSwitch = () => {
switch (job?.jobState) {
case JobState.UNKNOWN:
case JobState.PENDING:
case JobState.STARTED: return <LoadingSpinner label="Generating your movie..." />;
case JobState.ATTEMPT_FAILED: return <div {...{ className: "d-flex justify-content-center align-items-center" }}>
<h3>{`Attempt failed, retrying (attempt ${ job.attemptCount } )`}</h3>
</div>;
case JobState.COMPLETE_SUCCESS: return <video src={mediaLink} controls />;
case JobState.DEAD: return <h3>Job dead</h3>;
case JobState.CANCELED_BY_USER: return <h3>Job canceled by user</h3>;
default: return;
};
}
// Should also check if job actually exists or not
//
// if (!jobExists) {
// history.push("/");
// }
return (
<Container
type="full"
className="mt-4 d-flex flex-column align-items-center"
>
<Panel clear={true}>
<h2 className="fw-bold mb-0 mb-4 text-center">Generation Result</h2>
</Panel>
<Panel className="overflow-hidden rounded result-video-wrapper">
<div className="ratio ratio-16x9">
{
contentSwitch()
}
</div>
</Panel>
{mediaToken ? (
<div className="d-flex justify-content-center p-4">
<Button label="Sign up to use your own scene" />
</div>
) : null}
</Container>
);
}
export { StudioIntroResultPage };
@@ -1,190 +0,0 @@
import React, { useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { NavLink, useParams, useHistory } from "react-router-dom";
import { Button, Container, Panel, Select, TextArea } from "components/common";
// import { VideoPlayer } from "components/common/VideoPlayer";
import { useJobStatus, useInferenceJobs, useMedia } from "hooks";
import {
EnqueueVST,
EnqueueVSTResponse,
} from "@storyteller/components/src/api/workflows/EnqueueVST";
import { initialValues } from "./defaultValues";
import { VSTType } from "./helpers";
import LoadingSpinner from "components/common/LoadingSpinner";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { STYLE_OPTIONS, StyleOption } from "common/StyleOptions";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
export default function PageVSTApp() {
const { jobToken } = useParams<{ jobToken: string }>();
const videoRef = useRef<HTMLVideoElement>(null);
const history = useHistory();
const job = useJobStatus({ jobToken });
const { enqueue } = useInferenceJobs();
const [vstValues, setVstValues] = useState<VSTType>({
...initialValues,
// fileToken: job?.maybe_result?.entity_token || "",
});
// console.log(job);
// console.log(job?.maybe_result);
// console.log(job?.maybe_result?.entity_token);
const handleOnChange = (val: {
[key: string]: number | string | boolean | undefined;
}) => {
setVstValues(curr => ({ ...curr, ...val }));
};
const handleGenerate = () => {
EnqueueVST("", {
creator_set_visibility: vstValues.visibility,
enable_lipsync: false,
input_file: job?.maybe_result?.entity_token || "",
negative_prompt: vstValues.negPrompt,
prompt: vstValues.posPrompt,
style: vstValues.sdModelToken,
trim_end_millis: 3000,
trim_start_millis: 0,
uuid_idempotency_token: uuidv4(),
}).then((res: EnqueueVSTResponse) => {
if (res.success && res.inference_job_token) {
enqueue(
res.inference_job_token,
FrontendInferenceJobType.VideoStyleTransfer
);
// 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 handleStyleSelection = (selectedOption: StyleOption) => {
console.log("option", selectedOption);
const selectedSdModelToken = selectedOption ? selectedOption.value : null;
if (selectedSdModelToken) {
setVstValues(curr => ({ ...curr, sdModelToken: selectedSdModelToken }));
}
};
if (!jobToken) {
history.push("/");
}
// const contentSwitch = () => {
// switch (job.jobState) {
// case JobState.UNKNOWN:
// case JobState.PENDING:
// case JobState.STARTED: return <LoadingSpinner label="Loading Video" />;
// case JobState.ATTEMPT_FAILED: return <div {...{ className: "d-flex justify-content-center align-items-center" }}>
// { `Video compositor attempt failed, retrying (attempt ${ job.attemptCount } )` }
// </div>;
// case JobState.COMPLETE_SUCCESS: return <VideoFakeyou mediaToken={job.maybe_result.entity_token}/>;
// case JobState.DEAD: return <div>Job dead</div>;
// case JobState.CANCELED_BY_USER: return <div>Job canceled by user</div>;
// };
// }
// Should also check if job actually exists or not
//
// if (!jobExists) {
// history.push("/");
// }
const { media } = useMedia({
mediaToken: job?.maybe_result?.entity_token,
});
const mediaLink =
media?.public_bucket_path &&
new BucketConfig().getGcsUrl(media?.public_bucket_path || "");
return (
<Container type="panel" className="mt-5">
<Panel clear={true}>
<h2 className="fw-bold mb-0 mb-5 text-center">Style Your Scene</h2>
</Panel>
<Panel padding={true}>
<div className="row g-5">
<div className="col-12 col-md-6">
{job.isSuccessful && job.maybe_result ? (
<div className="ratio ratio-4x3 panel-inner rounded">
<video src={mediaLink} controls />
</div>
) : (
<div className="ratio ratio-4x3 panel-inner rounded">
<LoadingSpinner label="Loading Video" />
</div>
)}
</div>
<div className="col-12 col-md-6 d-flex flex-column gap-3 justify-content-center">
<div>
<label className="sub-title">Select a Style</label>
<Select
label="Label"
options={STYLE_OPTIONS}
onChange={handleStyleSelection}
/>
</div>
<TextArea
label="Describe Your Scene"
placeholder="Enter your description..."
onChange={e => handleOnChange({ posPrompt: e.target.value })}
value={vstValues.posPrompt}
required={false}
rows={5}
resize={false}
/>
{/* <TextArea
label="Negative Prompt"
placeholder="Enter your negative prompt"
onChange={e => handleOnChange({ negPrompt: e.target.value })}
value={vstValues.negPrompt}
required={false}
/> */}
{/* <SectionAdvanceOptions
onChange={handleOnChange}
vstValues={vstValues}
/> */}
<div className="d-flex gap-2 justify-content-end mt-3">
<NavLink to="/">
<Button label="Cancel" variant="secondary" />
</NavLink>
<Button
label="Generate Your Movie"
onClick={handleGenerate}
variant="primary"
disabled={!job.isSuccessful}
/>
</div>
</div>
</div>
</Panel>
</Container>
);
}
@@ -1,98 +0,0 @@
type CNPreset =
'custom'|
'closeup'|
'default' |
'halfbody'|
'fullbody'|
'landscape'|
'typog';
const defaultPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 1,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
}
const closeupPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0.7,
cnSoftEdge: 0.7,
cnRegularSteps: 20,
}
const halfbodyPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0.7,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
}
const fullbodyPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 1,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
}
const landscapePreset = {
cnCanny: 0,
cnDepth: 0.4,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 0,
cnRegularSteps: 20,
}
const typogPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 0.7,
cnRegularSteps: 20,
}
export type {
CNPreset
}
export {
defaultPreset,
closeupPreset,
halfbodyPreset,
fullbodyPreset,
landscapePreset,
typogPreset,
};
@@ -1,36 +0,0 @@
import { defaultPreset as defaultCN } from "./cnPresets";
export { initialValues, hiddenValues };
const initialValues = {
//files Settings
fileToken: "",
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
//video Settings
width: 1024,
height: 1024,
maxDuration: 0,
trimStart: 0,
trimEnd: 3,
//Presents
workflowConfig: "weight_0a04e66y14t4e2bwxjfyg1mb2", //weight_99tz2nzbp5x9v55gqn5ekj1sd
sdModelToken: "weight_yqexh77ntqyawzgh9fzash798",
loraModelToken: "",
loraModelStrength: 0,
//basics
inputFps: 24,
posPrompt: "",
negPrompt: "",
visibility: "private",
//Control Net
...defaultCN,
};
const hiddenValues = {
posPrompt: "",
negPrompt:
"flare, lens flare, glare,naked, nsfw, text, logo, watermark:1.3, letterboxed,embedding:easynegative, embedding:badhandv4,(worst quality, low quality:1.4), lowres, blurry, monochrome, ",
};
@@ -1,91 +0,0 @@
import { v4 as uuidv4 } from "uuid";
import { hiddenValues } from "./defaultValues";
export type VSTType = {
// File Settings
fileToken: string;
outputPath: string;
// Video Settings
width: number;
height: number;
maxDuration: number;
trimStart: number;
trimEnd: number;
// Presets
workflowConfig: string;
sdModelToken: string;
// loraModelToken: string;
// loraModelStrength: number;
//basics
inputFps: number;
posPrompt: string;
negPrompt: string;
visibility: string;
//advance
cnCanny: number;
cnDepth: number;
cnLineArtAnime: number;
cnLineArtRealistic: number;
cnLipsStrength: number;
cnOpenPose: number;
cnPipeFace: number;
cnSparseScribble: number;
cnSoftEdge: number;
cnRegularSteps: number;
};
export function isInputValid(keyValues: {
[key: string]: number | string | boolean | undefined;
}) {
return (
keyValues.sdModelToken &&
keyValues.sdModelToken !== "" &&
keyValues.posPrompt &&
keyValues.posPrompt !== ""
);
}
export function mapRequest(vstValues: VSTType) {
//TODO: improve the typing of this
return {
uuid_idempotency_token: uuidv4(),
maybe_sd_model: vstValues.sdModelToken,
maybe_workflow_config: vstValues.workflowConfig,
maybe_input_file: vstValues.fileToken,
maybe_trim_start_seconds: 0, //vstValues.trimStart
maybe_trim_end_seconds: 3, //vstValues.trimEnd
maybe_output_path: vstValues.outputPath,
creator_set_visibility: vstValues.visibility,
maybe_json_modifications: {
"$.154.inputs.Value": vstValues.inputFps,
"$.510.inputs.Text": hiddenValues.posPrompt + vstValues.posPrompt,
"$.8.inputs.text": hiddenValues.negPrompt + vstValues.negPrompt,
"$.401.inputs.Value": 0.7, //Denoise First Pass
"$.140.inputs.Value": 1, // Every nth frame
"$.536.inputs.boolean_number": 1, // use LCM
"$.1075.inputs.Value": 1280,
"$.1076.inputs.Value": 720,
"$.1079.inputs.Value": 1280,
"$.1080.inputs.Value": 720,
"$.403.inputs.Value": vstValues.cnSparseScribble,
"$.771.inputs.Value": vstValues.cnOpenPose,
"$.772.inputs.Value": vstValues.cnDepth,
"$.796.inputs.Value": vstValues.cnLineArtRealistic,
"$.797.inputs.Value": vstValues.cnLineArtAnime,
"$.1398.inputs.Value": vstValues.cnSoftEdge,
"$.1531.inputs.Value": vstValues.cnRegularSteps,
//"$.800.inputs.Value": 0, // vstValues.cnCanny,
//"$.1636.inputs.Value": 0, //vstValues.cnLipsStrength,
// "$.208.inputs.lora_01": vstValues.loraModelToken,
// "$.208.inputs.strength_01": vstValues.loraModelStrength
// "$.1449.inputs.filename_prefix": "vid2vid/SparseUpscaleInterp",
},
};
}
@@ -1 +0,0 @@
export { default } from "./StudioVSTPage";
@@ -1,273 +0,0 @@
import React, {useState} from 'react';
import {
faBuildingColumns,
faFaceViewfinder,
faPerson,
// faRotateLeft,
faStar,
// faSparkles,
faText,
faUser,
}
from "@fortawesome/pro-solid-svg-icons";
import {
// Accordion,
Button,
Label,
// NumberSliderV2
} from 'components/common';
import { VSTType } from './helpers';
import {
CNPreset,
closeupPreset,
halfbodyPreset,
fullbodyPreset,
landscapePreset,
typogPreset,
defaultPreset,
} from './cnPresets';
export default function SectionAdvanceOptions({
vstValues: vstVal,
onChange: handleOnChange
}:{
vstValues : VSTType,
onChange: (val:{[key: string]: number|string|boolean|undefined})=>void
}){
const [preset, pickPreset] = useState<CNPreset>('default')
function handlePreset(newPreset: CNPreset){
switch (newPreset){
case 'closeup':
handleOnChange(closeupPreset);
break;
case 'halfbody':
handleOnChange(halfbodyPreset);
break;
case 'fullbody':
handleOnChange(fullbodyPreset);
break;
case 'landscape':
handleOnChange(landscapePreset);
break;
case 'typog':
handleOnChange(typogPreset);
break;
case 'custom':
default:
handleOnChange(defaultPreset);
break;
}
pickPreset(newPreset);
}
return (
<>
<Label label="Video's Camera Angle"/>
{/* <div className="d-flex flex-wrap w-100">
<div className="my-1 me-1">
<Button
icon={faStar}
label="Default"
isActive={preset==='default'}
onClick={()=>handlePreset('default')}
/>
</div>
<div className="my-1 me-1">
<Button
icon={faSparkles}
label="Custom"
isActive={preset==='custom'}
disabled
/>
</div>
</div> */}
<div className="d-flex flex-wrap w-100">
<div className="m-1">
<Button
icon={faStar}
label="Default"
isActive={preset==='default'}
onClick={()=>handlePreset('default')}
/>
</div>
<div className="m-1">
<Button
icon={faFaceViewfinder}
label="Face Closeups"
isActive={preset==='closeup'}
onClick={()=>handlePreset('closeup')}
/>
</div>
<div className="m-1">
<Button
icon={faUser}
label="Half Body Shots"
isActive={preset==='halfbody'}
onClick={()=>handlePreset('halfbody')}
/>
</div>
<div className="m-1">
<Button
icon={faPerson}
label="Full Body Shots"
isActive={preset==='fullbody'}
onClick={()=>handlePreset('fullbody')}
/>
</div>
<div className="m-1">
<Button
icon={faBuildingColumns}
label="Architecture or Landscape"
isActive={preset==='landscape'}
onClick={()=>handlePreset('landscape')}
/>
</div>
<div className="m-1">
<Button
icon={faText}
label="Flat Logos, or Typographies"
isActive={preset==='typog'}
onClick={()=>handlePreset('typog')}
/>
</div>
</div>
{/* <Accordion className="mt-4">
<Accordion.Item title={"Advanced Options"}>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnCanny,
label: "Canny",
thumbTip: "Canny",
onChange: (val)=>{
handleOnChange({cnCanny: val});
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnDepth,
label: "Depth",
thumbTip: "Depth",
onChange: (val)=>{
handleOnChange({cnDepth: val});
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnLineArtAnime,
label: "Line Art - Anime",
thumbTip: "Line Art - Anime",
onChange: (val)=>{
handleOnChange({cnLineArtAnime: val});
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnLineArtRealistic,
label: "Line Art - Realistic",
thumbTip: "Line Art - Realistic)",
onChange: (val)=>{
handleOnChange({cnLineArtRealistic: val});
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnOpenPose,
label: "OpenPose",
thumbTip: "OpenPose",
onChange: (val)=>{
handleOnChange({cnOpenPose: val});
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnPipeFace,
label: "Media Pipe Face",
thumbTip: "Media Pipe Face",
onChange: (val)=>{
handleOnChange({cnPipeFace: val})
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnSparseScribble,
label: "Sparse Scribble",
thumbTip: "Sparse Scribble",
onChange: (val)=>{
handleOnChange({cnSparseScribble: val})
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnSoftEdge,
label: "Soft Edge",
thumbTip: "SoftEdge",
onChange: (val)=>{
handleOnChange({cnSoftEdge: val});
pickPreset('custom');
}
}}/>
</div>
</div>
{/* <div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 1, max: 64, step: 1,
initialValue: vstVal.cnRegularSteps,
label: "Regular Steps",
thumbTip: "Regular Steps",
onChange: (val)=>{
handleOnChange({cnRegularSteps: val});
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="d-flex p-3 justify-content-end">
<Button
icon={faRotateLeft}
label="Reset to Default"
onClick={()=>{
handlePreset('default');
pickPreset('default');
}}
/>
</div>
</Accordion.Item>
</Accordion> */}
</>
);
}
@@ -1,29 +0,0 @@
@import "scss/colours.scss";
.studio-tutorial-page {
display: flex;
background: $panel;
.studio-tutorial-style {
min-width: fit-content;
max-width: 50%;
}
.tutorial-style-tab {
padding: 1rem;
}
.tutorial-tab-loading {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
}
@@ -1,326 +0,0 @@
import React, { useCallback, useEffect, useState } 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 { BasicTabs, Spinner } from "components/common";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
import { useHistory, useParams, useLocation } from "react-router-dom";
import { useInferenceJobs, useMedia } from "hooks";
import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine_compositor/EnqueueEngineCompositing";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { jobStateCanChange } from "@storyteller/components/src/jobs/JobStates";
import { v4 as uuidv4 } from "uuid";
import Scene3D from "components/common/Scene3D/Scene3D";
import { EngineMode } from "components/common/Scene3D/EngineMode";
import { SplitFirstPeriod } from "utils/SplitFirstPeriod";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import StyleEditor from "./StyleEditor";
import { initialValues } from "../storyteller_studio_intro/StudioVST/defaultValues";
import { VSTType } from "../storyteller_studio_intro/StudioVST/helpers";
import "./StudioTutorial.scss";
import {
EnqueueVST,
EnqueueVSTResponse,
} from "@storyteller/components/src/api/workflows/EnqueueVST";
interface Props {
sessionWrapper: SessionWrapper;
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
}
export default function StudioTutorial(props: Props) {
// NB: The URL parameter might be a raw media token (for .scn.ron files), or it might
// have an appended suffix to assist the engine in loading the correct scene format.
// For example, this is a valid "mediaTokenSpec": `m_zk0qkm1tgsdbh6e3c9kedy34vaympd.glb`
const { mediaToken: mediaTokenSpec } = useParams<{ mediaToken: string }>();
const [vstValues, setVstValues] = useState<VSTType>({
...initialValues,
// fileToken: job?.maybe_result?.entity_token || "",
});
const { base: mediaToken, maybeRemainder: maybeExtension } =
SplitFirstPeriod(mediaTokenSpec);
// for updating url state
const history = useHistory();
const { pathname, search: locSearch } = useLocation();
const urlQueries = new URLSearchParams(locSearch);
const queryCompositeJob = urlQueries.get("compositeJobToken");
const queryStyleJob = urlQueries.get("styleJobToken");
// side bar tab state
const [selectedTab, selectedTabSet] = useState("");
// for both jobs
const { enqueue, inferenceJobs = [] } = useInferenceJobs();
// engine compositor job state
const [compositeJobToken, compositeJobTokenSet] = useState(
queryCompositeJob || ""
);
const compositeJobStatus = inferenceJobs.find(
(item: any) => item.jobToken === compositeJobToken
);
const compositing =
compositeJobStatus && jobStateCanChange(compositeJobStatus.jobState);
const compositeMediaToken = compositeJobStatus?.maybeResultToken || "";
const [refreshedCompositor, refreshedCompositorSet] = useState(false);
// video style style transfer job state
const [styleJobToken, styleJobTokenSet] = useState(queryStyleJob || "");
const [styleEnqueued, styleEnqueuedSet] = useState(false);
const styleJobStatus = inferenceJobs.find(
(item: any) => item.jobToken === styleJobToken
);
const styling = styleJobStatus && jobStateCanChange(styleJobStatus.jobState);
const styleMediaToken = styleJobStatus?.maybeResultToken || "";
const [refreshedStyle, refreshedStyleSet] = useState(false);
const { media: styleMedia } = useMedia({
mediaToken: styleMediaToken,
});
const styleMediaLink =
styleMediaToken &&
styleMedia &&
new BucketConfig().getGcsUrl(styleMedia?.public_bucket_path || "");
// If the user saves the scene in the engine, we'll need to use the new token
// for subsequent steps of this flow.
const [savedMediaToken, setSavedMediaToken] = useState(mediaToken);
// We don't show the "next step" buttons until the engine loads.
// Unfortunately the engine sometimes fires this twice, with one instance
// being called before the scene loads. Until this is fixed, we'll count the
// number of event fires and assume we must have two calls.
const [sceneIsLoadedCount, sceneIsLoadedCountSet] = useState(0);
// If the scene is saved, we know the user must have interacted.
// This can serve as a second optional gate for enabling the next steps.
const [sceneIsSaved, sceneIsSavedSet] = useState(false);
const [
camera,
// cameraSet
] = useState("zoom");
usePrefixedDocumentTitle("Storyteller Studio");
const onSaveCallback = useCallback(
(sceneMediaToken: string) => {
setSavedMediaToken(sceneMediaToken);
sceneIsSavedSet(true); // Just in case we missed the "scene loaded" event.
},
[setSavedMediaToken, sceneIsSavedSet]
);
const onSceneReadyCallback = useCallback(() => {
sceneIsLoadedCountSet(sceneIsLoadedCount + 1);
}, [sceneIsLoadedCount, sceneIsLoadedCountSet]);
let assetDescriptor;
// We should prefer to start the onboarding flow with an existing scene, but if
// one is unavailable, we should show the sample room.
if (maybeExtension !== undefined) {
assetDescriptor = {
sceneImportToken: mediaToken,
extension: maybeExtension,
};
} else if (mediaToken) {
assetDescriptor = {
storytellerSceneMediaFileToken: mediaToken,
};
} else {
assetDescriptor = {
objectId: "sample-room.gltf",
};
}
const compositorStart = () => {
// This opens the job modal. Needs to be disabled.
EnqueueEngineCompositing("", {
uuid_idempotency_token: uuidv4(),
media_file_token: savedMediaToken,
camera: camera,
camera_speed: 0.2,
skybox: "meadow_4k",
}).then((res: any) => {
if (res && res.success) {
let newURLQueries = new URLSearchParams({
compositeJobToken: res.inference_job_token,
}).toString();
history.replace({ pathname, search: newURLQueries });
// enqueue(res.inference_job_token,false,FrontendInferenceJobType.EngineComposition);
compositeJobTokenSet(res.inference_job_token);
}
});
};
const tabs = [
{ value: "animation", label: "Animation" },
{ value: "camera", label: "Camera" },
{ value: "audio", label: "Audio" },
{ value: "style", label: "Style" },
];
const sceneIsLoaded = sceneIsLoadedCount > 0 || sceneIsSaved;
const workingText = () => {
if (compositing && !styling) return "Compositing";
else if (styling && !compositing) return "Styling";
return "";
};
const tabContent = () => {
if (!sceneIsLoaded || compositing || styling)
return (
<div {...{ className: "tutorial-tab-loading" }}>
<div>
<h3>{workingText()}</h3>
<Spinner />
</div>
</div>
);
else if (styleMediaToken)
return (
<div>
<video controls {...{ src: styleMediaLink }} />
</div>
);
else if (sceneIsLoaded)
return <StyleEditor {...{ compositorStart, setVstValues, vstValues }} />;
else return null;
};
useEffect(() => {
const enqueueStyle = () => {
console.log("💜 style enqueue started", { vstValues, styleEnqueued });
EnqueueVST("", {
creator_set_visibility: vstValues.visibility,
enable_lipsync: true,
input_file: compositeMediaToken || "",
negative_prompt: vstValues.negPrompt,
prompt: "A dog in a sunny field with lots of flowers",
style: vstValues.sdModelToken,
trim_end_millis: 3000,
trim_start_millis: 0,
uuid_idempotency_token: uuidv4(),
}).then((res: EnqueueVSTResponse) => {
console.log("✅", res);
if (res.success && res.inference_job_token) {
let newURLQueries = new URLSearchParams({
compositeJobToken,
engineJobToken: res.inference_job_token,
}).toString();
// enqueue(res.inference_job_token,false,FrontendInferenceJobType.VideoStyleTransfer);
styleJobTokenSet(res.inference_job_token);
history.replace({ pathname, search: newURLQueries });
// 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 (compositeMediaToken && !styleEnqueued) {
styleEnqueuedSet(true);
enqueueStyle();
}
if (sceneIsLoaded && !selectedTab) {
selectedTabSet("style");
}
if (compositeJobToken && !compositeJobStatus && !refreshedCompositor) {
console.log("🧱", "compositor refreshed");
refreshedCompositorSet(true);
enqueue(
compositeJobToken,
FrontendInferenceJobType.EngineComposition,
false
);
}
if (styleJobToken && !styleJobStatus && !refreshedStyle) {
console.log("🎨", "styler refreshed");
refreshedStyleSet(true);
enqueue(
styleJobToken,
FrontendInferenceJobType.VideoStyleTransfer,
false
);
}
}, [
camera,
enqueue,
compositeJobStatus,
compositeJobToken,
compositeMediaToken,
history,
pathname,
refreshedCompositor,
refreshedStyle,
savedMediaToken,
selectedTab,
sceneIsLoaded,
styleEnqueued,
styleJobStatus,
styleJobToken,
vstValues,
]);
console.log("🚒 tutorial overall state", {
compositeJobStatus,
compositing,
compositeMediaToken,
queryCompositeJob,
queryStyleJob,
sceneIsLoadedCount,
styleJobStatus,
styleEnqueued,
styling,
styleMediaToken,
urlQueries,
styleMediaLink,
});
return !props.sessionWrapper.canAccessStudio() ? (
<StudioNotAvailable />
) : (
<div className="studio-tutorial-page">
<Scene3D
{...{
asset: assetDescriptor,
mode: EngineMode.Studio,
onSaveCallback,
onSceneReadyCallback,
// overrideURL: "http://127.0.0.1:4200", // COMMENT OUT BEFORE PUSHING
}}
/>
<div {...{ className: "studio-tutorial-style" }}>
<BasicTabs
{...{
onChange: ({ target }: { target: any }) =>
selectedTabSet(target.value),
tabs,
value: selectedTab,
}}
/>
{tabContent()}
</div>
</div>
);
}
@@ -1,87 +0,0 @@
import React from 'react';
import { Button, TextArea } from "components/common";
import SelectionBubblesV2 from "components/common/SelectionBubblesV2";
import { VSTType } from "../storyteller_studio_intro/StudioVST/helpers";
interface Props {
compositorStart: () => any,
setVstValues: (input: any) => any,
vstValues: VSTType,
}
export default function StyleEditor({ compositorStart, setVstValues, vstValues }: Props) {
const styleOptions = [
{
label: "2D Anime",
imageUrl: "/images/landing/onboarding/styles/style-2d-anime.webp",
token: "anime_2d_flat",
},
{
label: "3D Cartoon",
imageUrl: "/images/landing/onboarding/styles/style-3d-cartoon.webp",
token: "cartoon_3d",
},
{
label: "Ink B&W",
imageUrl: "/images/landing/onboarding/styles/style-ink-bw.webp",
token: "ink_bw_style",
},
{
label: "Origami",
imageUrl: "/images/landing/onboarding/styles/style-origami.webp",
token: "paper_origami",
},
];
const selectStyle = (selectedLabel: any) => {
const selectedOption = styleOptions.find(
option => option.label === selectedLabel
);
const selectedSdModelToken = selectedOption ? selectedOption.token : null;
if (selectedSdModelToken) {
setVstValues((curr: VSTType) => ({ ...curr, sdModelToken: selectedSdModelToken }));
}
};
const handleOnChange = (val: {
[key: string]: number | string | boolean | undefined;
}) => {
setVstValues((curr: VSTType) => ({ ...curr, ...val }));
};
return <div {...{ className: "tutorial-style-tab" }}>
<SelectionBubblesV2 {...{
options: Object.values(styleOptions),
onSelect: selectStyle,
mobileSideScroll: true,
selectedStyle: "outline",
variant: "card"
}}/>
<TextArea
label="Enter a prompt"
placeholder="Enter your description..."
onChange={e => handleOnChange({ posPrompt: e.target.value })}
value={vstValues.posPrompt}
required={false}
rows={5}
resize={false}
/>
<TextArea
label="Negative prompt"
placeholder="Enter your description..."
onChange={e => handleOnChange({ negPrompt: e.target.value })}
value={vstValues.negPrompt}
required={false}
rows={5}
resize={false}
/>
<Button {...{
label: "Generate Your Movie",
onClick: compositorStart,
}} />
</div>;
};
@@ -1,63 +0,0 @@
import React, { useReducer, useEffect } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useInferenceJobs, useLocalize } from "hooks";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { Container } from "components/common";
import { states, reducer } from "./reducer";
import SubRoutes from "./routes";
import PageHeaderWithImage from "components/layout/PageHeaderWithImage";
import { faFilms } from "@fortawesome/pro-solid-svg-icons";
import { VideoStyleTransferNotAvailable } from "v2/view/_common/VideoStyleTransferNotAvailable";
export default function VideoStyleTransfer({
sessionWrapper,
}: {
sessionWrapper: SessionWrapper;
}) {
const debug = false;
const { t } = useLocalize("VideoStyleTransfer");
const { NO_FILE } = states;
const [pageState, dispatchPageState] = useReducer(reducer, {
status: NO_FILE,
});
const { enqueueInferenceJob } = useInferenceJobs();
useEffect(() => {
if (
pageState.status === states.JOB_ENQUEUED &&
pageState.inferenceJobToken
) {
enqueueInferenceJob(
pageState.inferenceJobToken,
FrontendInferenceJobType.VideoStyleTransfer
);
dispatchPageState({
type: "enqueueJobSuccess",
payload: { inferenceJobToken: undefined },
});
}
}, [pageState, enqueueInferenceJob]);
if (!sessionWrapper.canAccessVideoStyleTransfer()) {
return <VideoStyleTransferNotAvailable />;
}
return (
<Container type="panel" className="mb-5">
{debug && (
<p>{`Status:${pageState.status} MediaToken:${pageState.mediaFileToken}`}</p>
)}
<PageHeaderWithImage
headerImage="/mascot/vst.webp"
titleIcon={faFilms}
title={t("headings.title")}
subText={t("headings.subtitle")}
yOffset="80%"
/>
<SubRoutes {...{ debug, t, pageState, dispatchPageState }} />
</Container>
);
}
@@ -1,43 +0,0 @@
import React from 'react';
export const inferenceFailures = (fail = "") => {
switch (fail) {
case "sample case":
return "Sample Case, this should not have been shown";
default:
return "Unknown failure";
}
};
export function TableOfKeyValues(
{
title,
keyValues,
height
}:{
title: string;
keyValues:{
[key:string]:number|string|boolean|undefined
|{ [key:string]:number|string}
};
height?: number | string
}){
return(<table style={{
display:'block',
height: height || "100%",
overflowY: "scroll",
overflowX: "clip",
// border: "1px solid white",
// borderTopLeftRadius: "1rem",
// borderBottomLeftRadius: "1rem",
}}><tbody>
<tr><th>{title}</th></tr>
{
Object.entries(keyValues).map(([key, val], index: number)=>{
if(key!=="defaultCN" && key!=="defaultPrompts")
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
else return null
})
}
</tbody></table>);
}
@@ -1,46 +0,0 @@
import React, { memo } from "react";
import { NavLink } from "react-router-dom";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { Analytics } from "common/Analytics";
import { Button, Spinner } from "components/common";
import { states, Action, State } from "../reducer";
import { inferenceFailures } from "../commons";
export default memo(function PageWorkflowJoblist({
t,
pageState,
dispatchPageState,
parentPath,
}: {
debug?: boolean;
t: Function;
pageState: State;
parentPath: string;
dispatchPageState: (action: Action) => void;
}) {
return (
<>
<div className="row mb-3">
<NavLink to={`${parentPath}`}>
<Button label={t("button.generateNew")} variant="primary" />
</NavLink>
</div>
{pageState.status === states.JOB_ENQUEUEING && (
<div>
<h2>t("message.requestingJob")</h2>
<Spinner />
</div>
)}
<InferenceJobsList
{...{
showNoJobs: true,
failures: inferenceFailures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.VideoStyleTransfer,
}}
/>
</>
);
});
@@ -1,110 +0,0 @@
import React, {
useState
} from "react";
import { useHistory } from "react-router-dom";
import {
Accordion,
Button,
TempTextArea as TextArea,
} from "components/common";
import { mapRequest, VSTType } from './helpers';
import { TableOfKeyValues } from "../../commons";
import { Action } from "../../reducer";
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
export const CompAdminPanel = ({
parentPath,
vstValues,
dispatchPageState
}:{
parentPath: string;
vstValues : VSTType;
dispatchPageState: (action: Action) => void;
})=>{
const history = useHistory();
const exampleJSON = JSON.stringify(mapRequest(vstValues), null, 4);
const [jsonRequest, setJsonRequest] = useState<string>(exampleJSON);
const handleSendRequest = ()=>{
EnqueueVideoStyleTransfer(JSON.parse(jsonRequest)).then(res => {
if (res.success && res.inference_job_token) {
dispatchPageState({
type: "enqueueJobSuccess",
payload: {
inferenceJobToken: res.inference_job_token,
},
});
} else {
alert("Error, Check Console Log");
console.log(res);
}
});
dispatchPageState({ type: "enqueueJob" });
history.push(`${parentPath}/jobs`);
}
return (
<Accordion className="mt-4">
<Accordion.Item title={"Admin Panel"}>
<div className="row p-3 g-3">
<div className="col-8">
<TextArea
{...{
label:"JSON Input",
rows: 10,
placeholder: "Paste JSON here and send request directly",
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
setJsonRequest(e.target.value),
value: jsonRequest,
required: false,
}}
/>
</div>
<div className="col-4 pt-5">
<Button
label="Console Log JSON Reqest"
variant="secondary"
onClick={()=>{
console.log(JSON.parse(jsonRequest));
}}
/>
<br/>
<Button
label="Send JSON"
onClick={handleSendRequest}
/>
</div>
</div>
<div className="row p-3 g-3">
<div className="col-8">
<TableOfKeyValues
title="All values in the VST app state"
keyValues={vstValues}
/>
</div>
<div className="col-4">
<p className="me-3"><b>Log Debug Values: </b></p>
<br/>
<Button
label="Console Log State"
variant="secondary"
onClick={()=>{
console.log(vstValues);
}}
/>
<br/>
<Button
label="Console Log Request"
variant="secondary"
onClick={()=>{
const request = mapRequest(vstValues);
console.log(request);
}}
/>
</div>
</div>
</Accordion.Item>
</Accordion>
);
}
@@ -1,277 +0,0 @@
import React, { useState } from "react";
import {
faBuildingColumns,
faFaceViewfinder,
faPerson,
// faRotateLeft,
faStar,
// faSparkles,
faText,
faUser,
} from "@fortawesome/pro-solid-svg-icons";
import {
// Accordion,
Button,
Label,
// NumberSliderV2
} from "components/common";
import { VSTType } from "./helpers";
import {
CNPreset,
closeupPreset,
halfbodyPreset,
fullbodyPreset,
landscapePreset,
typogPreset,
// defaultPreset,
} from "./dataCnPresets";
export default function CompAdvanceOptions({
debug: propsDebug = false,
t,
vstValues: vstVal,
onChange: handleOnChange,
currDefaultCN: defaultPreset
}: {
debug?: boolean;
t: Function;
vstValues: VSTType;
currDefaultCN: {[key: string]: number};
onChange: (val: {
[key: string]: number | string | boolean | undefined;
}) => void;
}) {
const [preset, pickPreset] = useState<CNPreset>("default");
function handlePreset(newPreset: CNPreset) {
switch (newPreset) {
case "closeup":
handleOnChange(closeupPreset);
break;
case "halfbody":
handleOnChange(halfbodyPreset);
break;
case "fullbody":
handleOnChange(fullbodyPreset);
break;
case "landscape":
handleOnChange(landscapePreset);
break;
case "typog":
handleOnChange(typogPreset);
break;
case "custom":
default:
handleOnChange(defaultPreset);
break;
}
pickPreset(newPreset);
}
return (
<div>
<Label label="Video's Camera Angle" />
{/* <div className="d-flex flex-wrap w-100">
<div className="my-1 me-1">
<Button
icon={faStar}
label="Default"
isActive={preset==='default'}
onClick={()=>handlePreset('default')}
/>
</div>
<div className="my-1 me-1">
<Button
icon={faSparkles}
label="Custom"
isActive={preset==='custom'}
disabled
/>
</div>
</div> */}
<div className="d-flex flex-wrap w-100">
<div className="m-1">
<Button
icon={faStar}
label="Default"
isActive={preset === "default"}
onClick={() => handlePreset("default")}
/>
</div>
<div className="m-1">
<Button
icon={faFaceViewfinder}
label="Face Closeups"
isActive={preset === "closeup"}
onClick={() => handlePreset("closeup")}
/>
</div>
<div className="m-1">
<Button
icon={faUser}
label="Half Body Shots"
isActive={preset === "halfbody"}
onClick={() => handlePreset("halfbody")}
/>
</div>
<div className="m-1">
<Button
icon={faPerson}
label="Full Body Shots"
isActive={preset === "fullbody"}
onClick={() => handlePreset("fullbody")}
/>
</div>
<div className="m-1">
<Button
icon={faBuildingColumns}
label="Architecture or Landscape"
isActive={preset === "landscape"}
onClick={() => handlePreset("landscape")}
/>
</div>
<div className="m-1">
<Button
icon={faText}
label="Flat Logos, or Typographies"
isActive={preset === "typog"}
onClick={() => handlePreset("typog")}
/>
</div>
</div>
{/* <Accordion className="mt-4">
<Accordion.Item title={"Advanced Options"}>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnCanny,
label: "Canny",
thumbTip: "Canny",
onChange: (val)=>{
handleOnChange({cnCanny: val});
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnDepth,
label: "Depth",
thumbTip: "Depth",
onChange: (val)=>{
handleOnChange({cnDepth: val});
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnLineArtAnime,
label: "Line Art - Anime",
thumbTip: "Line Art - Anime",
onChange: (val)=>{
handleOnChange({cnLineArtAnime: val});
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnLineArtRealistic,
label: "Line Art - Realistic",
thumbTip: "Line Art - Realistic)",
onChange: (val)=>{
handleOnChange({cnLineArtRealistic: val});
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnOpenPose,
label: "OpenPose",
thumbTip: "OpenPose",
onChange: (val)=>{
handleOnChange({cnOpenPose: val});
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnPipeFace,
label: "Media Pipe Face",
thumbTip: "Media Pipe Face",
onChange: (val)=>{
handleOnChange({cnPipeFace: val})
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnSparseScribble,
label: "Sparse Scribble",
thumbTip: "Sparse Scribble",
onChange: (val)=>{
handleOnChange({cnSparseScribble: val})
pickPreset('custom');
}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: vstVal.cnSoftEdge,
label: "Soft Edge",
thumbTip: "SoftEdge",
onChange: (val)=>{
handleOnChange({cnSoftEdge: val});
pickPreset('custom');
}
}}/>
</div>
</div>
{/* <div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 1, max: 64, step: 1,
initialValue: vstVal.cnRegularSteps,
label: "Regular Steps",
thumbTip: "Regular Steps",
onChange: (val)=>{
handleOnChange({cnRegularSteps: val});
pickPreset('custom');
}
}}/>
</div>
</div>
<div className="d-flex p-3 justify-content-end">
<Button
icon={faRotateLeft}
label="Reset to Default"
onClick={()=>{
handlePreset('default');
pickPreset('default');
}}
/>
</div>
</Accordion.Item>
</Accordion> */}
</div>
);
}
@@ -1,97 +0,0 @@
import React, { useRef, useState } from "react";
import { MasonryGrid, WeightsCards } from "components/common";
import { SelectModalV2 } from "components/common/SelectModal";
import { WeightCategory } from "@storyteller/components/src/api/_common/enums/WeightCategory";
import styleModels from "./dataStyleModels";
import { defaultPreset as noStyleCNPreset } from "./dataCnPresets";
export default function CompStyleModal({
debug,
t,
value,
onChange,
}: {
debug?: boolean;
t: Function;
value: string;
onChange: (val: {
[key: string]:
| number
| string
| boolean
| undefined
| { [key: string]: number | string };
}) => void;
}) {
const gridContainerRef = useRef<HTMLDivElement | null>(null);
const [onSelectTimeStamp, setOnSelectTimeStamp] = useState<Date>(new Date());
return (
<SelectModalV2
modalTitle={t("input.title.selectStyle")}
label={t("input.label.selectStyle")}
value={value}
placeholder={t("input.placeholder.selectStyle")}
forcedClose={onSelectTimeStamp}
onClear={() =>
onChange({
sdModelToken: "",
sdModelTitle: "",
defaultCN: noStyleCNPreset,
defaultPrompts: {
positivePrompt: "",
negativePrompt: "",
positivePromptHidden: "",
negativePromptHidden: "",
},
...noStyleCNPreset,
positivePrompt: "",
negativePrompt: "",
positivePromptHidden: "",
negativePromptHidden: "",
})
}
>
<MasonryGrid
gridRef={gridContainerRef}
onLayoutComplete={() => {
if (debug) console.log("Layout complete!");
}}
>
{styleModels.map((data: any, key: number) => {
const weightProps: any = {
data,
type: "weights",
inSelectModal: true,
onResultSelect: (data: any) => {
const selectedStyle = styleModels.find(
styleItem => (styleItem.weight_token = data.token)
);
if (selectedStyle !== undefined) {
onChange({
sdModelToken: data.token,
sdModelTitle: data.title,
defaultCN: selectedStyle.defaultCN,
defaultPrompts: selectedStyle.defaultPrompts,
...selectedStyle.defaultCN,
...selectedStyle.defaultPrompts,
});
setOnSelectTimeStamp(new Date());
}
},
};
//console.log(data);
return (
<div
key={key}
className="col-12 col-sm-6 col-lg-6 col-xl-4 col-xxl-3 grid-item"
>
<WeightsCards type={WeightCategory.SD} props={weightProps} />
</div>
);
})}
</MasonryGrid>
</SelectModalV2>
);
}
@@ -1,98 +0,0 @@
type CNPreset =
'custom'|
'closeup'|
'default' |
'halfbody'|
'fullbody'|
'landscape'|
'typog';
const defaultPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 0,
cnRegularSteps: 20,
}
const closeupPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0.7,
cnSoftEdge: 0.7,
cnRegularSteps: 20,
}
const halfbodyPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0.7,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
}
const fullbodyPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 1,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
}
const landscapePreset = {
cnCanny: 0,
cnDepth: 0.4,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 0,
cnRegularSteps: 20,
}
const typogPreset = {
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 0.7,
cnRegularSteps: 20,
}
export type {
CNPreset
}
export {
defaultPreset,
closeupPreset,
halfbodyPreset,
fullbodyPreset,
landscapePreset,
typogPreset,
};
@@ -1,54 +0,0 @@
// import { defaultPreset as defaultCN } from "./dataCnPresets";
import styleModels from "./dataStyleModels";
import { VSTType } from "./helpers";
export {initialValues, hiddenValues};
const initialValues: VSTType = {
//files Settings
fileToken: "",
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
//video Settings
width: 1024,
height: 1024,
maxDuration: 0,
trimStart: 0,
trimEnd: 0,
//Presents
workflowConfig: "weight_0a04e66y14t4e2bwxjfyg1mb2",
sdModelToken: styleModels[0].weight_token,
sdModelTitle: styleModels[0].title,
// loraModelToken: "",
// loraModelStrength: 0,
//basics
inputFps: 24,
...styleModels[0].defaultPrompts,
// positivePrompt:"",
// negativePrompt:"",
// positivePromptHidden:"",
// negativePromptHidden:"",
visibility: "private",
//Control Net
...styleModels[0].defaultCN,
// cnCanny: 0,
// cnDepth: 0,
// cnLineArtAnime: 0,
// cnLineArtRealistic: 0,
// cnLipsStrength: 0,
// cnOpenPose: 0,
// cnPipeFace: 0,
// cnSparseScribble: 0,
// cnSoftEdge: 0,
// cnRegularSteps: 0,
defaultCN:styleModels[0].defaultCN,
defaultPrompts:styleModels[0].defaultPrompts,
};
const hiddenValues = {
posPrompt: "",
negPrompt: "flare, lens flare, glare,naked, nsfw, text, logo, watermark:1.3, letterboxed,embedding:easynegative, embedding:badhandv4,(worst quality, low quality:1.4), lowres, blurry, monochrome, ",
}
@@ -1,95 +0,0 @@
const styleModels = [
{
"weight_token": "weight_yqexh77ntqyawzgh9fzash798",
"weight_type": "sd_1.5",
"weight_category": "image_generation",
"title": "Flat2D Animerge v3",
"maybe_cover_image_public_bucket_path": "/media/2/t/5/a/q/2t5aq2nvtdt4zcfvmx8xm6xqzg0tka7n/upload_2t5aq2nvtdt4zcfvmx8xm6xqzg0tka7n.png",
"cover_image": {
"maybe_cover_image_public_bucket_path": "/media/2/t/5/a/q/2t5aq2nvtdt4zcfvmx8xm6xqzg0tka7n/upload_2t5aq2nvtdt4zcfvmx8xm6xqzg0tka7n.png",
"default_cover": {
"image_index": 16,
"color_index": 2
}
},
"creator": {
"user_token": "U:38X9ACM68GFK7",
"username": "crossproduct1",
"display_name": "crossproduct1",
"gravatar_hash": "e7cc43eed149c304c917383c665fe9e2",
"default_avatar": {
"image_index": 4,
"color_index": 8
}
},
"creator_set_visibility": "public",
"file_size_bytes": 0,
"file_checksum_sha2": "5dd56bfa12e9ea2f1f3e86a04d0d2cde06a58f2cdf3bfc46cbff6aaa8f00921d",
"creator_username": "crossproduct1",
"creator_display_name": "crossproduct1",
"creator_email_gravatar_hash": "e7cc43eed149c304c917383c665fe9e2",
"stats": {
"positive_rating_count": 2,
"bookmark_count": 1
},
"created_at": "2024-02-28T21:00:43Z",
"updated_at": "2024-02-28T21:00:43Z",
defaultCN:{
cnCanny: 0,
cnDepth: 0,
cnLineArtAnime: 0.7,
cnLineArtRealistic: 0,
cnLipsStrength: 0,
cnOpenPose: 1,
cnPipeFace: 0,
cnSparseScribble: 0,
cnSoftEdge: 1,
cnRegularSteps: 20,
},
defaultPrompts:{
positivePrompt:"perfect anime illustration,(best-quality:0.8),",
negativePrompt:"",
positivePromptHidden:"",
negativePromptHidden:"flare, lens flare, glare,naked, nsfw, text, logo, watermark:1.3, letterboxed,embedding:easynegative, embedding:badhandv4,(worst quality, low quality:1.4), lowres, blurry, monochrome, ",
}
},
// {
// "weight_token": "weight_vbz0wq7wkv80casj3vfptrrpg",
// "title": "Aniverse V15 Pruned",
// "weight_type": "sd_1.5",
// "weight_category": "image_generation",
// "description_markdown": "Used In Ghibli Style with Ghubli Lora",
// "description_rendered_html": "",
// "creator": {
// "user_token": "U:38X9ACM68GFK7",
// "username": "crossproduct1",
// "display_name": "crossproduct1",
// "gravatar_hash": "e7cc43eed149c304c917383c665fe9e2",
// "default_avatar": {
// "image_index": 4,
// "color_index": 8
// }
// },
// "creator_set_visibility": "public",
// "file_size_bytes": 0,
// "file_checksum_sha2": "3335ce7830ff1de58ba1b120c91091935c8e84581244f63c94d01eeb53d42797",
// "cover_image": {
// "maybe_cover_image_public_bucket_path": "/media/3/x/5/z/5/3x5z550m80v7cz0x23jmb7aerbwasq71/upload_3x5z550m80v7cz0x23jmb7aerbwasq71.png",
// "default_cover": {
// "image_index": 1,
// "color_index": 5
// }
// },
// "maybe_cover_image_public_bucket_path": "/media/3/x/5/z/5/3x5z550m80v7cz0x23jmb7aerbwasq71/upload_3x5z550m80v7cz0x23jmb7aerbwasq71.png",
// "stats": {
// "positive_rating_count": 1,
// "bookmark_count": 1
// },
// "version": 0,
// "created_at": "2024-02-29T19:49:52Z",
// "updated_at": "2024-02-29T19:49:52Z"
// }
]
export default styleModels;
@@ -1,113 +0,0 @@
import { v4 as uuidv4 } from "uuid";
export type VSTType = {
// File Settings
fileToken: string;
outputPath: string;
// Video Settings
width: number;
height: number;
maxDuration: number;
trimStart: number;
trimEnd: number;
// Presets
workflowConfig: string;
sdModelToken: string;
sdModelTitle: string;
// loraModelToken: string;
// loraModelStrength: number;
//basics
inputFps: number;
positivePrompt: string;
negativePrompt: string;
positivePromptHidden:string,
negativePromptHidden:string,
visibility: string;
//advance
cnCanny: number;
cnDepth: number;
cnLineArtAnime: number;
cnLineArtRealistic: number;
cnLipsStrength: number;
cnOpenPose: number;
cnPipeFace: number;
cnSparseScribble: number;
cnSoftEdge: number;
cnRegularSteps: number
defaultCN: {
cnCanny: number;
cnDepth: number;
cnLineArtAnime: number;
cnLineArtRealistic: number;
cnLipsStrength: number;
cnOpenPose: number;
cnPipeFace: number;
cnSparseScribble: number;
cnSoftEdge: number;
cnRegularSteps: number
},
defaultPrompts:{
positivePrompt:string,
negativePrompt:string,
positivePromptHidden:string,
negativePromptHidden:string,
}
}
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
return (
keyValues.sdModelToken
&& keyValues.sdModelToken !== ""
&& keyValues.posPrompt
&& keyValues.posPrompt !== ""
);
}
export function mapRequest(vstValues: VSTType){
//TODO: improve the typing of this
return{
"uuid_idempotency_token": uuidv4(),
"maybe_sd_model": vstValues.sdModelToken,
"maybe_workflow_config":vstValues.workflowConfig,
"maybe_input_file": vstValues.fileToken,
"maybe_trim_start_seconds": vstValues.trimStart,
"maybe_trim_end_seconds": vstValues.trimEnd,
"maybe_output_path": vstValues.outputPath,
"creator_set_visibility": vstValues.visibility,
"maybe_json_modifications": {
"$.154.inputs.Value": vstValues.inputFps,
"$.510.inputs.Text":
vstValues.positivePromptHidden + vstValues.positivePrompt,
"$.8.inputs.text":
vstValues.negativePromptHidden+ vstValues.negativePrompt,
"$.401.inputs.Value": 0.7, //Denoise First Pass
"$.140.inputs.Value": 1, // Every nth frame
"$.536.inputs.boolean_number": 1, // use LCM
"$.1075.inputs.Value": 1280,
"$.1076.inputs.Value": 720,
"$.1079.inputs.Value": 1280,
"$.1080.inputs.Value": 720,
"$.403.inputs.Value": vstValues.cnSparseScribble,
"$.771.inputs.Value": vstValues.cnOpenPose,
"$.772.inputs.Value": vstValues.cnDepth,
"$.796.inputs.Value": vstValues.cnLineArtRealistic,
"$.797.inputs.Value": vstValues.cnLineArtAnime,
"$.1398.inputs.Value": vstValues.cnSoftEdge,
"$.1531.inputs.Value": vstValues.cnRegularSteps,
//"$.800.inputs.Value": 0, // vstValues.cnCanny,
//"$.1636.inputs.Value": 0, //vstValues.cnLipsStrength,
// "$.208.inputs.lora_01": vstValues.loraModelToken,
// "$.208.inputs.strength_01": vstValues.loraModelStrength
// "$.1449.inputs.filename_prefix": "vid2vid/SparseUpscaleInterp",
},
}
}
@@ -1 +0,0 @@
export { default } from "./pageVSTApp";
@@ -1,160 +0,0 @@
import React, { useState } from "react";
import { NavLink } from "react-router-dom";
import { useParams, useHistory } from "react-router-dom";
import { Button, Panel, TextArea } from "components/common";
import {VideoPlayerQuickTrim} from "components/common/VideoPlayerQuickTrim";
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
import { Action, State } from "../../reducer";
import { initialValues } from "./dataDefaultValues";
import { mapRequest, VSTType } from "./helpers";
import CompStyleModal from "./compStyleModal";
import CompAdvanceOptions from "./compAdvanceOptions";
import { CompAdminPanel } from "./compAdminPanel";
export default function PageVSTApp({
debug: debugProps,
t,
pageState,
dispatchPageState,
parentPath,
}: {
debug?: boolean;
t: Function;
pageState: State;
parentPath: string;
dispatchPageState: (action: Action) => void;
}) {
const debug = debugProps || true;
const { mediaToken } = useParams<any>();
const [vstValues, setVstValues] = useState<VSTType>({
...initialValues,
fileToken: pageState.mediaFileToken || mediaToken,
});
const handleOnChange = (val: {
[key: string]:
number |
string |
boolean |
undefined |
{[key: string]: number | string}
;
}) => {
setVstValues(curr => ({ ...curr, ...val }));
};
const history = useHistory();
const handleGenerate = () => {
if (debug) console.log(vstValues);
const request = mapRequest(vstValues);
if (debug) console.log(request);
EnqueueVideoStyleTransfer(request).then(res => {
if (res.success && res.inference_job_token) {
dispatchPageState({
type: "enqueueJobSuccess",
payload: {
inferenceJobToken: res.inference_job_token,
},
});
} else {
console.log(res);
}
});
dispatchPageState({ type: "enqueueJob" });
history.push(`${parentPath}/jobs`);
};
return (
<Panel padding={true}>
<div className="row g-5 mb-4">
<div className="col-12 col-md-6">
<VideoPlayerQuickTrim
trimStartMs={vstValues.trimStart}
trimEndMs={vstValues.trimEnd}
mediaToken={vstValues.fileToken}
onSelectTrim={(val: {
trimStartMs: number;
trimEndMs: number;
}) =>{
handleOnChange({
trimStart: val.trimStartMs,
trimEnd: val.trimEndMs,
})
}}
onResponse={res => {
dispatchPageState({
type: "loadFileSuccess",
payload: {
mediaFile: res,
mediaFileToken: pageState.mediaFileToken || mediaToken,
},
});
}}
/>
</div>
<div className="col-12 col-md-6 d-flex flex-column gap-3">
<TextArea
{...{
label: t("input.label.prompt"),
placeholder: t("input.placeholder.prompt"),
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
handleOnChange({ posPrompt: e.target.value }),
value: vstValues.positivePrompt,
required: false,
}}
/>
<TextArea
{...{
label: t("input.label.negPrompt"),
placeholder: t("input.placeholder.negPrompt"),
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
handleOnChange({ negPrompt: e.target.value }),
value: vstValues.negativePrompt,
required: false,
}}
/>
<CompStyleModal
debug={debug}
t={t}
value={vstValues.sdModelTitle}
onChange={handleOnChange}
/>
<CompAdvanceOptions
debug={debug}
t={t}
currDefaultCN={vstValues.defaultCN}
onChange={handleOnChange}
vstValues={vstValues}
/>
</div>
</div>
<div className="row g-3 mt-4">
<div className="col-12 d-flex justify-content-between">
<NavLink to={`${parentPath}`}>
<Button label={t("button.cancel")} variant="secondary" />
</NavLink>
<Button
label={t("button.enqueue")}
onClick={handleGenerate}
variant="primary"
disabled={vstValues.trimEnd === 0}
/>
</div>
{debug &&
<CompAdminPanel
parentPath={parentPath}
dispatchPageState={dispatchPageState}
vstValues={vstValues}
/>
}
</div>
</Panel>
);
}
@@ -1,82 +0,0 @@
import React from "react";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { Analytics } from "common/Analytics";
import { inferenceFailures } from "../commons";
import { Panel, Tabs } from "components/common";
import { State, Action } from "../reducer";
import TabContentUpload from "./tabContentUpload";
import TabContentLibrary from "./tabContentLibrary";
export default function PageVideoProvision({
debug = false,
parentPath,
t,
pageState,
dispatchPageState,
}: {
debug?: boolean;
parentPath: string;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const tabs = [
{
label: t("tabTitle.upload"),
content: (
<TabContentUpload
{...{
debug,
t,
pageState,
dispatchPageState,
}}
/>
),
to: `${parentPath}/upload`,
padding: true,
},
{
label: t("tabTitle.library"),
content: (
<TabContentLibrary
{...{
debug,
t,
pageState,
dispatchPageState,
}}
/>
),
to: `${parentPath}/select-media`,
padding: true,
},
];
return (
<>
<Panel className="mb-4">
<div className="row g-0">
<div className="col-12 col-md-6">
<Tabs tabs={tabs} />
</div>
<div className="col-12 col-md-6 p-5 mt-3">
<p>{t("video.instruction")}</p>
</div>
</div>
</Panel>
<InferenceJobsList
{...{
showNoJobs: false,
failures: inferenceFailures,
onSelect: () => Analytics.voiceConversionClickDownload(),
jobType: FrontendInferenceJobType.VideoStyleTransfer,
}}
/>
</>
);
}
@@ -1,59 +0,0 @@
import React from "react";
import { NavLink } from "react-router-dom";
import { states, Action, State } from "../reducer";
import { Button } from "components/common";
import { SelectModalVideoTabs, SelectModalData } from "components/common/";
import VideoFakeyou from "components/common/VideoFakeyou";
export default function TabContentLibrary({
t, pageState, dispatchPageState
}: {
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}){
console.log(`Tab Rerender, selected: ${pageState.mediaFileToken}`);
const handleProceed = ()=>{
dispatchPageState({type: "loadFile"})
}
const handleOnSelect = (data:SelectModalData)=>{
if (data.token !== ""){
dispatchPageState({
type: "selectedFile",
payload: {mediaFileToken: data.token}
});
}else{
dispatchPageState({type: "clearedFile"});
};
};
return (
<div className="row g-3">
<div className="col-12">
<SelectModalVideoTabs
value={pageState.mediaFileToken}
modalTitle="Select a Video"
inputLabel="Select a Video"
onSelect={handleOnSelect}
/>
<br/>
{pageState.mediaFileToken &&
<VideoFakeyou
height={500}
mediaToken={pageState.mediaFileToken}
/>
}
</div>
{pageState.status === states.FILE_SELECTED &&
<div className="col-12 d-flex justify-content-center mt-4">
<NavLink to={`load/${pageState.mediaFileToken}`}>
<Button
label={t("button.proceed")}
onClick={handleProceed}
/>
</NavLink>
</div>
}
</div>
);
}
@@ -1,98 +0,0 @@
import React from "react";
import { Redirect } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
import { useFile } from "hooks";
import { ErrorMessage, VideoInput, Spinner } from "components/common";
import { states, Action, State } from "../reducer";
export default function TabContentUpload({
debug=false, t, pageState, dispatchPageState
}: {
debug?: boolean;
t: Function;
pageState: State;
dispatchPageState: (action: Action) => void;
}) {
const videoProps = useFile({});
const {NO_FILE, FILE_STAGED, FILE_SELECTED, FILE_UPLOADING, FILE_UPLOADED} = states;
const makeVideoUploadRequest = () => ({
uuid_idempotency_token: uuidv4(),
file: videoProps.file,
source: "file",
type: "video",
});
const handleUploadVideo = () => {
dispatchPageState({type: 'uploadFile'});
UploadMedia(makeVideoUploadRequest()).then(res => {
if (res.success && res.media_file_token) {
dispatchPageState({
type: 'uploadFileSuccess',
payload:{
mediaFileToken :res.media_file_token
}
})
}
});
};
// contains upload inout state and controls, see docs
if (pageState.status === NO_FILE
|| pageState.status === FILE_STAGED
|| pageState.status === FILE_SELECTED
) {
return (
<>
<div className="row">
<div className="col-12">
<VideoInput
{...{
t,
...videoProps,
onStateChange: () =>{
if (pageState.status === NO_FILE && videoProps.file)
dispatchPageState({type: "stagedFile"})
else if (pageState.status === FILE_STAGED && !videoProps.file)
dispatchPageState({type: "clearedFile"})
}
}}
/>
</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={pageState.status !== FILE_STAGED}
onClick={handleUploadVideo}
>
{t("button.upload")}
</button>
</div>
</div>
</div>
</>
);
}else if (pageState.status === FILE_UPLOADING){
return(
<div className="row">
<div className="col-12">
<h1>{t("message.fileUploading")}</h1>
</div>
<div className="col-12">
<Spinner />
</div>
</div>
);
}else if (pageState.status === FILE_UPLOADED){
return(
<Redirect to={`load/${pageState.mediaFileToken}`}/>
)
}
return <ErrorMessage />;
}
@@ -1,2 +0,0 @@
export {default} from "./VideoStyleTransfer";
@@ -1,86 +0,0 @@
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
export enum states{
NO_FILE,
FILE_STAGED,
FILE_SELECTED,
FILE_UPLOADING,
FILE_UPLOADED,
FILE_LOADING,
FILE_LOADED,
JOB_ENQUEUEING,
JOB_ENQUEUED
}
export type State = {
status: number;
mediaFileToken?: string;
mediaFile?: MediaFile;
inferenceJobToken?: string;
}
export type Action =
| {type: 'reset'}
| {type: 'stagedFile'}
| {type: 'clearedFile'}
| {type: 'selectedFile', payload: { mediaFileToken: string}}
| {type: 'uploadFile'}
| {type: 'uploadFileSuccess', payload:{ mediaFileToken: string}}
| {type: 'loadFile'}
| {type: 'loadFileSuccess', payload:{
mediaFileToken: string, mediaFile: MediaFile
}}
| {type: 'enqueueJob'}
| {type: 'enqueueJobSuccess', payload: {inferenceJobToken: string|undefined}}
export function reducer (state: State, action: Action): State {
switch(action.type){
case 'stagedFile':
return {...state, status: states.FILE_STAGED}
case 'clearedFile':
return {...state,
status: states.NO_FILE,
mediaFileToken: ""
}
case 'selectedFile':
return {
...state,
status: states.FILE_SELECTED,
mediaFileToken: action.payload.mediaFileToken
}
case 'uploadFile':
return {...state,status: states.FILE_UPLOADING};
case 'uploadFileSuccess':
return {
...state,
status: states.FILE_UPLOADED,
mediaFileToken: action.payload.mediaFileToken
}
case 'loadFile':
return {...state, status: states.FILE_LOADING}
case 'loadFileSuccess':
console.log("LOAD FILE SUCCESS");
return {
...state,
status: states.FILE_LOADED,
mediaFile: action.payload.mediaFile,
mediaFileToken : action.payload.mediaFileToken,
}
case 'enqueueJob':
return {
...state,
status: states.JOB_ENQUEUEING
}
case 'enqueueJobSuccess':
return {
...state,
status: states.JOB_ENQUEUED,
inferenceJobToken: action.payload.inferenceJobToken
}
case 'reset':
default:
return {status: states.NO_FILE};
}
}
@@ -1,50 +0,0 @@
import React from "react";
import {Switch, Route, useRouteMatch, Redirect, useHistory} from "react-router-dom";
import PageVSTApp from "./components/pageVSTApp";
import PageVideoProvision from "./components/pageVideoProvision";
import PageJoblist from "./components/pageJoblist";
import {states,State,Action} from "./reducer";
export default function SubRoutes ({
debug, t, pageState, dispatchPageState
}:{
debug?:boolean,
t:Function,
pageState:State,
dispatchPageState: (action: Action) => void
}){
const { path } = useRouteMatch();
const history = useHistory();
history.listen(({pathname}, action) => {
if ((pathname === path || pathname.match(/upload|select-media/g)) && pageState.status >= states.FILE_LOADING){
//reset page if landed on page "Freshly"
dispatchPageState({type: 'reset'})
}
})
const commonPageProps = {
debug, parentPath: path, t, pageState, dispatchPageState
};
return(
<Switch>
<Route exact path={`${path}/load/:mediaToken`} >
<PageVSTApp {...commonPageProps}/>
</Route>
<Route exact path={`${path}/upload`}>
<PageVideoProvision {...commonPageProps}/>
</Route>
<Route exact path={`${path}/select-media`}>
<PageVideoProvision {...commonPageProps}/>
</Route>
<Route exact path={`${path}/jobs`}>
<PageJoblist {...commonPageProps}/>
</Route>
<Route path={`${path}`}>
<Redirect to={`${path}/upload`} />
</Route>
</Switch>
)
}