Merge pull request #507 from storytold/feature/page/video_transfer

new UI
This commit is contained in:
wil authored and GitHub committed 2024-02-24 22:09:34 -05:00
commit f749b5f80f
11 files changed
+333 -182

No files matched your search

@@ -1,11 +1,11 @@
import React, { useState, forwardRef } from "react";
import { useMedia } from "hooks";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
import { MediaFileType } from "@storyteller/components/src/api";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import makeClass from "resources/makeClass";
import { Label } from "components/common"
import { Label } from "components/common";
import './styles.scss';
interface Props {
@@ -26,7 +26,7 @@ const VideoFromFakeyou = forwardRef<Ref, Props>(({
onResponse,
...rest
}: Props, ref) => {
const [mediaFile, setMediaFile] = useState<MediaFile>();
const [mediaFile, setMediaFile] = useState<MediaFileType>();
useMedia({
mediaToken: mediaToken,
onSuccess: (res: any) => {
@@ -39,9 +39,15 @@ const VideoFromFakeyou = forwardRef<Ref, Props>(({
if (mediaLink){
return (
<div {...{ ...makeClass("fy-basic-video",className) }}>
<div {...{ ...makeClass("fy-video vh50",className) }}>
{label && <Label label={label}/>}
<video controls key={mediaToken} {...rest} ref={ref}>
<video
controls
ref={ref}
key={mediaToken}
className="object-fit-contain"
{...rest}
>
<source src={mediaLink} type="video/mp4" />
</video>
</div>
@@ -0,0 +1,57 @@
@import "scss/colours.scss";
.fy-video {
overflow: hidden;
width: 100%;
margin: 0;
position: relative;
&.fit-content {
height: fit-content;
}
&.vh50{
height: 50vh;
}
video, .video-overlay {
border-radius: .5rem;
width: 100%;
height: 100%;
}
video{
object-fit: cover;
&.object-fit-cover {object-fit: cover;}
&.object-fit-contain {object-fit: contain;}
}
.video-overlay {
background: linear-gradient(rgba($primary,0.5) 0%, rgba($secondary,0.75) 75%);
cursor: pointer;
padding: 2rem;
position: absolute;
top: 0px;
h6, p {
color: #fff;
filter: drop-shadow(0 3px 3px rgba($primary-dark,.25));
font-size: 1.5rem;
text-align: center;
}
svg {
filter: drop-shadow(0 3px 7px rgba($primary-dark,.45));
position: absolute;
width: 3rem;
height: 3rem;
inset: 50%;
transform: translate(-50%, -50%);
}
.video-play-button {
background-color: green;
width: 4rem;
height: 4rem;
stroke: white;
}
}
};
@@ -29,7 +29,9 @@ import Radio from "./Radio";
import Searcher from "./Searcher";
import SegmentButtons from "./SegmentButtons";
import Select from "./Select";
import SelectModal from "./SelectModal";
import SelectModal, { SelectModalData } from "./SelectModal";
import SelectModalVideoTabs from "./SelectModalVideoTabs";
import SelectModalWeightsTabs from "./SelectModalWeightsTabs";
import Skeleton from "./Skeleton";
import Slider from "./Slider";
import Spinner from "./Spinner";
@@ -47,6 +49,10 @@ import VideoFakeyou from "./VideoFakeyou";
import VideoInput from "./VideoInput";
import SelectionBubbles from "./SelectionBubbles";
export type{
SelectModalData
};
export {
Accordion,
AudioBlobPreview,
@@ -85,6 +91,8 @@ export {
Searcher,
Select,
SelectModal,
SelectModalVideoTabs,
SelectModalWeightsTabs,
Skeleton,
Slider,
Spinner,
@@ -20,7 +20,7 @@ export default function VideoStyleTransfer(props:{
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
sessionWrapper: SessionWrapper;
}){
const debug = false;
const debug = true;
const {t} = useLocalize("VideoStyleTransfer");
const { NO_FILE } = states;
const [pageState, dispatchPageState] = useReducer(reducer, {
@@ -27,9 +27,12 @@ export function TableOfKeyValues(
border: "1px solid white",
borderTopLeftRadius: "1rem",
borderBottomLeftRadius: "1rem",
}}><tbody>{
Object.entries(keyValues).map(([key, val], index: number)=>{
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
})
}</tbody></table>);
}}><tbody>
<tr><th>Debug of a certain Key-Values set</th></tr>
{
Object.entries(keyValues).map(([key, val], index: number)=>{
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
})
}
</tbody></table>);
}
@@ -0,0 +1,49 @@
export {initialValues, hiddenValues};
const initialValues = {
//config
outputPath: "",
workflowConfig: "weight_f5qh4s1dtp266yv313p09tkpa",
//video Settings
width: 544,
height: 544,
maxFrames: 17,
framesCap: 16,
skipFrames: 0,
everyNthFrame: 2,
inputFps: 24,
interpolationMultiplier: 2,
//basics
seed: "",
sdModelToken: "weight_83xpresyjswy97ppk0fyct146",
loraModelToken: "",
posPrompt: "",
negPrompt: "",
//advance
firstPass: 0,
upscalePass: 0,
motionScale:0,
upscaleMultiplier: 0,
useEmptyLatent: false,
useFaceDetailer: false,
denoiseFaceDetailer: 0,
useLCM: false,
lcmCFG: 0,
lcmSteps: 0,
//Control Net
cnCanny: 0.7,
cnDepth: 0.7,
cnLinearAnime: 0.7,
cnLinearRealistic: 0.7,
cnOpenPose: 0.7,
cnPipeFace: 0.7,
cnSparse: 0.7,
cnTile: 0.7,
};
const hiddenValues = {
posPrompt: "(masterpiece,detailed,highres:1.4), ",
negPrompt: "flare, lens flare, glare, naked, nsfw, text, logo, Shutterstock, watermark:1.3, embedding:easynegative, embedding:badhandv4,(worst quality, low quality:1.4), lowres, blurry, monochrome, ",
}
@@ -1,4 +1,5 @@
import { v4 as uuidv4 } from "uuid";
import { hiddenValues } from "./defaultValues";
export type WorkflowValuesType = {
// File Settings
@@ -60,8 +61,10 @@ export function mapRequest(workflowValues: WorkflowValuesType){
"maybe_input_file": workflowValues.fileToken,
"maybe_output_path": workflowValues.outputPath,
"maybe_json_modifications": {
"$.510.inputs.Text": workflowValues.posPrompt,
"$.8.inputs.text": workflowValues.negPrompt,
"$.510.inputs.Text":
hiddenValues.posPrompt + workflowValues.posPrompt,
"$.8.inputs.text":
hiddenValues.negPrompt+ workflowValues.negPrompt,
"$.173.inputs.seed": workflowValues.seed,
"$.401.inputs.Value": workflowValues.firstPass,
"$.918.inputs.Value": workflowValues.motionScale,
@@ -1,45 +0,0 @@
const initialValues = {
//config
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
//video Settings
width: 544,
height: 544,
maxFrames: 17,
framesCap: 16,
skipFrames: 0,
everyNthFrame: 2,
inputFps: 24,
interpolationMultiplier: 2,
//basics
seed: "",
sdModelToken: "",
loraModelToken: "",
posPrompt: "",
negPrompt: "",
//advance
firstPass: 1,
upscalePass: 0.42,
motionScale:1,
upscaleMultiplier: 1.5,
useEmptyLatent: false,
useFaceDetailer: false,
denoiseFaceDetailer: 0.45,
useLCM: false,
lcmCFG: 2,
lcmSteps: 8,
//Control Net
cnCanny: 0,
cnDepth: 0,
cnLinearAnime: 0,
cnLinearRealistic: 0,
cnOpenPose: 0,
cnPipeFace: 0,
cnSparse: 0.7,
cnTile: 0,
};
export default initialValues;
@@ -2,26 +2,27 @@ import React, { useRef, useState } from "react";
import { NavLink } from 'react-router-dom';
import { useParams, useHistory } from "react-router-dom";
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
import { Action, State } from "../../reducer";
import { TableOfKeyValues } from "../../commons";
import ivs from "./initialValues";
import {
isInputValid,
mapRequest,
WorkflowValuesType,
} from "./helpers";
import {
Button,
Label,
NumberSliderV2,
Panel,
SelectModal,
NumberSliderV2,
TextArea,
VideoFakeyou
} from "components/common";
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
import { Action, State } from "../../reducer";
import { TableOfKeyValues } from "../../commons";
import { initialValues } from "./defaultValues";
import {
mapRequest,
WorkflowValuesType,
} from "./helpers";
import SectionAdvanceOptions from "./sectionAdvanceOptions";
export default function PageVSTApp({
debug, t, pageState, dispatchPageState, parentPath
}: {
@@ -36,10 +37,9 @@ export default function PageVSTApp({
const [workflowValues, setWorkflowValues] = useState<WorkflowValuesType>({
fileToken: pageState.mediaFileToken || mediaToken,
...ivs
...initialValues
});
const [styleStrength, setStyleStrength]= useState<number>(8);
if(videoRef?.current){
if(debug) console.log("set-up video element listeners");
@@ -69,44 +69,31 @@ export default function PageVSTApp({
}
const handleOnChange = (key: string, newValue:any,) => {
setWorkflowValues((curr)=>({...curr, [key]: newValue}));
const handleOnChange = (val:{[key: string]: number|string|boolean|undefined}) => {
setWorkflowValues((curr)=>({...curr, ...val}));
}
const handleFramesCap = (newValue: number)=>{
if(newValue - workflowValues.skipFrames >= 16)
setWorkflowValues((curr)=>({...curr, framesCap: newValue}));
}
const handleSkipFrames = (newValue: number)=>{
if(workflowValues.framesCap - newValue >= 16)
setWorkflowValues((curr)=>({...curr, skipFrames: newValue}));
}
const handleStyleStrength = (value:number)=>{
setStyleStrength(value);
};
const history = useHistory();
const handleGenerate = ()=>{
if(debug) console.log(workflowValues)
if (isInputValid(workflowValues)){
const request = mapRequest(workflowValues);
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`);
}else{
alert("you must pick an sd weight and input a prompt");
}
const request = mapRequest(workflowValues);
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(
@@ -131,87 +118,51 @@ export default function PageVSTApp({
<div className="col-12 col-md-6">
<Label label={t("image.label.preview")}/>
{debug && <TableOfKeyValues keyValues={workflowValues} height={400}/>}
{/*TODO: MAYBE INTEGRATE INTO PLAYER */}
<br/>
<NumberSliderV2 {...{
min: 16, max: workflowValues.maxFrames, step: 1,
value: workflowValues.framesCap,
label: "Frames Cap",
thumbTip: "24 frames = 1 sec",
onChange: handleFramesCap
}}/>
<br/>
<NumberSliderV2 {...{
min: 0, max: workflowValues.maxFrames-16, step: 1,
value: workflowValues.skipFrames,
label: "Skip Frames",
thumbTip: "24 frames = 1 sec",
onChange: handleSkipFrames
}}/>
{/*TODO: END */}
</div>
</div>
<div className="row g-3 mb-4">
<div className="col-12 col-md-6">
<SelectModal
modalTitle={t("modal.title.selectStyle")}
label={t("input.label.selectStyle")}
onSelect={({token})=>{
handleOnChange("sdModelToken", token);
}}
tabs={[
{
label: "All Weights",
tabKey: "allWeights",
typeFilter: "sd_1.5",
searcher: true,
type: "weights",
},
{
label: "Bookmarked",
tabKey: "bookmarkedWeights",
typeFilter: "sd_1.5",
searcher: false,
type: "weights",
},
]}
<TextArea
{...{
label: t("input.label.prompt"),
placeholder: t("input.placeholder.prompt"),
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange({posPrompt: e.target.value}),
value: workflowValues.posPrompt,
required: false,
}}
/>
<br/>
<NumberSliderV2 {...{
min: 1, max: 10, step: 1,
initialValue: styleStrength,
label: t("input.label.styleStrength"),
thumbTip: t("input.thumbtip.styleStrength"),
withRevert:true,
onChange: handleStyleStrength
}}/>
</div>
<div className="col-12 col-md-6">
<TextArea
{...{
label: t("input.label.prompt"),
placeholder: t("input.placeholder.prompt"),
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("posPrompt", e.target.value),
value: workflowValues.posPrompt,
required: false,
}}
/>
<br/>
<br/>
<TextArea
{...{
label: t("input.label.negPrompt"),
placeholder: t("input.placeholder.negPrompt"),
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("negPrompt", e.target.value),
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange({negPrompt: e.target.value}),
value: workflowValues.negPrompt,
required: false,
}}
/>
</div>
</div>
<div className="row g-3">
<div className="row g-3 mb-4">
<div className="col-12 col-md-6">
<NumberSliderV2 {...{
min: 1, max: 60, step: 1,
initialValue: workflowValues.inputFps,
label: "Input FPS",
thumbTip: "Input FPS",
onChange: (val)=>{handleOnChange({inputFps: val})}
}}/>
</div>
</div>
<div className="row g-3 mt-4">
<SectionAdvanceOptions
onChange={handleOnChange}
workflowValues={workflowValues}
/>
</div>
<div className="row g-3 mt-4">
<div className="col-12 d-flex justify-content-between">
<NavLink to={`${parentPath}`}>
<Button
@@ -0,0 +1,127 @@
import React from 'react';
import {
faFaceViewfinder,
faPerson,
faUser,
faBuildingColumns,
faText,
faRotateLeft,
}
from "@fortawesome/pro-solid-svg-icons";
import {
Accordion,
Button,
Label,
NumberSliderV2
} from 'components/common';
import { WorkflowValuesType } from './helpers';
export default function sectionAdvanceOptions({
workflowValues: wfVal,
onChange: handleOnChange
}:{
workflowValues : WorkflowValuesType,
onChange: (val:{[key: string]: number|string|boolean|undefined})=>void
}){
return (
<>
<Label label="Video's Camera Angle"/>
<div className="d-flex justify-content-between w-100">
<Button icon={faFaceViewfinder} label="Face Closeups"/>
<Button icon={faUser} label="Half Body Shots"/>
<Button icon={faPerson} label="Full Body Shots"/>
<Button icon={faBuildingColumns} label="Architecture or Landscape"/>
<Button icon={faText} label="Flat Logos, or Typographies"/>
</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: wfVal.cnCanny,
label: "Canny",
thumbTip: "Canny",
onChange: (val)=>{handleOnChange({cnCanny: val})}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnLinearAnime,
label: "Line Art Anime",
thumbTip: "Line Art Anime",
onChange: (val)=>{handleOnChange({cnLinearAnime: val})}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnDepth,
label: "Depth",
thumbTip: "Depth",
onChange: (val)=>{handleOnChange({cnDepth: val})}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnOpenPose,
label: "OpenPose",
thumbTip: "OpenPose",
onChange: (val)=>{handleOnChange({cnOpenPose: val})}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnPipeFace,
label: "Media Pipe Face",
thumbTip: "Media Pipe Face",
onChange: (val)=>{handleOnChange({cnPipeFace: val})}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnSparse,
label: "Sparse Scribble",
thumbTip: "Sparse Scribble",
onChange: (val)=>{handleOnChange({cnSparse: val})}
}}/>
</div>
</div>
<div className="row g-3 p-3">
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnLinearRealistic,
label: "Video CN (Linear Realistic)",
thumbTip: "Video CN",
onChange: (val)=>{handleOnChange({cnLinearRealistic: val})}
}}/>
</div>
<div className="col-md-6">
<NumberSliderV2 {...{
min: 0, max: 1, step: 0.1,
initialValue: wfVal.cnTile,
label: "Tile CN",
thumbTip: "Tile CN",
onChange: (val)=>{handleOnChange({cnTile: val})}
}}/>
</div>
</div>
<div className="d-flex p-3 justify-content-end">
<Button icon={faRotateLeft} label="Reset"/>
</div>
</Accordion.Item>
</Accordion>
</>
);
}
@@ -2,7 +2,7 @@ import React from "react";
import { NavLink } from "react-router-dom";
import { states, Action, State } from "../reducer";
import { Button } from "components/common";
import SelectModal, { SelectModalData } from "components/common/SelectModal/SelectModal";
import { SelectModalVideoTabs, SelectModalData } from "components/common/";
import VideoFakeyou from "components/common/VideoFakeyou";
export default function TabContentLibrary({
@@ -29,19 +29,11 @@ export default function TabContentLibrary({
return (
<div className="row g-3">
<div className="col-12">
<SelectModal
<SelectModalVideoTabs
value={pageState.mediaFileToken}
modalTitle="Select a Video"
label="Select a Video"
inputLabel="Select a Video"
onSelect={handleOnSelect}
tabs={[
{
label: "All Videos",
tabKey: "allVideos",
typeFilter: "video",
searcher: false,
type: "media",
},
]}
/>
{pageState.mediaFileToken &&
<VideoFakeyou mediaToken={pageState.mediaFileToken} />