mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
better workflow control UI, framescap is more-o-less accurate
This commit is contained in:
1 parent
a09c53acdb
commit
64d8d7700d
5 files changed
+150
-67
No files matched your search
+74
@@ -1,3 +1,46 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export type WorkflowValuesType = {
|
||||
// File Settings
|
||||
fileToken: string;
|
||||
outputPath: string;
|
||||
// Video Settings
|
||||
width: number;
|
||||
height: number;
|
||||
maxFrames: number;
|
||||
framesCap: number;
|
||||
skipFrames: number;
|
||||
everyNthFrame: number;
|
||||
inputFps: number;
|
||||
interpolationMultiplier: number;
|
||||
// Basic Inputs
|
||||
workflowConfig: string;
|
||||
seed: string;
|
||||
sdModelToken: string;
|
||||
loraModelToken: string;
|
||||
posPrompt: string;
|
||||
negPrompt: string;
|
||||
// Advance Options Values
|
||||
firstPass: number;
|
||||
upscalePass: number;
|
||||
motionScale: number;
|
||||
upscaleMultiplier: number;
|
||||
useEmptyLatent: boolean;
|
||||
useFaceDetailer: boolean;
|
||||
denoiseFaceDetailer: number;
|
||||
useLCM: boolean;
|
||||
lcmCFG: number;
|
||||
lcmSteps: number;
|
||||
// Control Net Values
|
||||
cnCanny: number;
|
||||
cnDepth: number;
|
||||
cnLinearAnime: number;
|
||||
cnLinearRealistic: number;
|
||||
cnOpenPose: number;
|
||||
cnPipeFace: number;
|
||||
cnSparse: number;
|
||||
cnTile: number;
|
||||
}
|
||||
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
|
||||
return (
|
||||
keyValues.sdModelToken
|
||||
@@ -5,4 +48,35 @@ export function isInputValid(keyValues: {[key:string]:number|string|boolean|unde
|
||||
&& keyValues.posPrompt
|
||||
&& keyValues.posPrompt !== ""
|
||||
);
|
||||
}
|
||||
|
||||
export function mapRequest(workflowValues: WorkflowValuesType){
|
||||
//TODO: improve the typing of this
|
||||
return {
|
||||
"uuid_idempotency_token": uuidv4(),
|
||||
"maybe_sd_model": workflowValues.sdModelToken,
|
||||
"maybe_workflow_config": workflowValues.workflowConfig,
|
||||
"maybe_input_file": workflowValues.fileToken,
|
||||
"maybe_output_path": workflowValues.outputPath,
|
||||
"maybe_json_modifications": {
|
||||
"$.510.inputs.Text": workflowValues.posPrompt,
|
||||
"$.8.inputs.text": workflowValues.negPrompt,
|
||||
"$.173.inputs.seed": workflowValues.seed,
|
||||
"$.401.inputs.Value": workflowValues.firstPass,
|
||||
"$.918.inputs.Value": workflowValues.motionScale,
|
||||
"$.137.inputs.Value": workflowValues.framesCap,
|
||||
"$.186.inputs.Value": workflowValues.skipFrames,
|
||||
"$.140.inputs.Value": workflowValues.everyNthFrame,
|
||||
"$.154.inputs.Value": workflowValues.inputFps,
|
||||
"$.445.inputs.number": workflowValues.interpolationMultiplier,
|
||||
"$.947.inputs.Value": workflowValues.cnTile,
|
||||
"$.800.inputs.Value": workflowValues.cnCanny,
|
||||
"$.797.inputs.Value": workflowValues.cnLinearAnime,
|
||||
"$.796.inputs.Value": workflowValues.cnLinearRealistic,
|
||||
"$.772.inputs.Value": workflowValues.cnDepth,
|
||||
"$.771.inputs.Value": workflowValues.cnOpenPose,
|
||||
"$.527.inputs.Value": workflowValues.cnPipeFace,
|
||||
"$.403.inputs.Value": workflowValues.cnSparse
|
||||
},
|
||||
}
|
||||
}
|
||||
+40
-53
@@ -1,8 +1,5 @@
|
||||
import React, {useState, useRef} from "react";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useMedia } from "hooks";
|
||||
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
@@ -11,7 +8,6 @@ import { states, Action, State } from "../../videoWorkflowReducer";
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
BasicVideo,
|
||||
ErrorMessage,
|
||||
InputSeed,
|
||||
Panel,
|
||||
@@ -21,9 +17,13 @@ import {
|
||||
} from "components/common";
|
||||
|
||||
import SectionControlNets, {ControlNetsInitialValues as CnIvs} from "./sectionControlNets";
|
||||
import SectionVideoSettings from "./sectionVideoSettings";
|
||||
import SectionVideoSettings, {VideoSettingsInitialValues as VideoIvs} from "./sectionVideoSettings";
|
||||
import SectionAdvanceOptions, {AdvanceOptionsInitialValues as AdvIvs} from "./sectionAdvanceOptions";
|
||||
import { isInputValid } from "./helpers";
|
||||
import {
|
||||
isInputValid,
|
||||
mapRequest,
|
||||
WorkflowValuesType
|
||||
} from "./helpers";
|
||||
|
||||
export default function PageFilterControls({
|
||||
debug, t, pageState, dispatchPageState, parentPath
|
||||
@@ -49,7 +49,8 @@ export default function PageFilterControls({
|
||||
},
|
||||
});
|
||||
|
||||
const [workflowValues, setworkflowValues] = useState({
|
||||
const [workflowValues, setworkflowValues] = useState<WorkflowValuesType>({
|
||||
fileToken: pageState.mediaFileToken || mediaToken,
|
||||
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
|
||||
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
|
||||
seed: "",
|
||||
@@ -57,11 +58,7 @@ export default function PageFilterControls({
|
||||
loraModelToken: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
framesCap:16,
|
||||
skipFrames:0,
|
||||
nthFrames:2,
|
||||
inputFps: 24,
|
||||
interpolationMutiplier: 2,
|
||||
...VideoIvs,
|
||||
...AdvIvs,
|
||||
...CnIvs
|
||||
});
|
||||
@@ -75,33 +72,8 @@ export default function PageFilterControls({
|
||||
if(debug) console.log(workflowValues)
|
||||
|
||||
if (isInputValid(workflowValues)){
|
||||
const request = {
|
||||
"uuid_idempotency_token": uuidv4(),
|
||||
"maybe_sd_model": workflowValues.sdModelToken,
|
||||
"maybe_workflow_config": workflowValues.workflowConfig,
|
||||
"maybe_input_file": pageState.mediaFileToken || mediaToken,
|
||||
"maybe_output_path": workflowValues.outputPath,
|
||||
"maybe_json_modifications": {
|
||||
"$.510.inputs.Text": workflowValues.posPrompt,
|
||||
"$.8.inputs.text": workflowValues.negPrompt,
|
||||
"$.173.inputs.seed": workflowValues.seed,
|
||||
"$.401.inputs.Value": workflowValues.firstPass,
|
||||
"$.918.inputs.Value": workflowValues.motionScale,
|
||||
"$.137.inputs.Value": workflowValues.framesCap,
|
||||
"$.186.inputs.Value": workflowValues.skipFrames,
|
||||
"$.140.inputs.Value": workflowValues.nthFrames,
|
||||
"$.154.inputs.Value": workflowValues.inputFps,
|
||||
"$.445.inputs.number": workflowValues.interpolationMutiplier,
|
||||
"$.947.inputs.Value": workflowValues.cnTile,
|
||||
"$.800.inputs.Value": workflowValues.cnCanny,
|
||||
"$.797.inputs.Value": workflowValues.cnLinearAnime,
|
||||
"$.796.inputs.Value": workflowValues.cnLinearRealistic,
|
||||
"$.772.inputs.Value": workflowValues.cnDepth,
|
||||
"$.771.inputs.Value": workflowValues.cnOpenPose,
|
||||
"$.527.inputs.Value": workflowValues.cnPipeFace,
|
||||
"$.403.inputs.Value": workflowValues.cnSparse
|
||||
},
|
||||
}
|
||||
const request = mapRequest(workflowValues);
|
||||
console.log(request);
|
||||
EnqueueVideoWorkflow(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
dispatchPageState({
|
||||
@@ -129,28 +101,43 @@ export default function PageFilterControls({
|
||||
<>
|
||||
<Panel>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-5">
|
||||
<div className="col-6">
|
||||
<div className="fy-basic-video">
|
||||
<video controls ref={videoRef}>
|
||||
<video controls ref={videoRef}
|
||||
style={{
|
||||
maxHeight: "500px",
|
||||
objectFit: "contain"
|
||||
}}>
|
||||
<source src={mediaLink} type="video/mp4"/>
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-2 d-flex align-items-center justify-content-center">
|
||||
<FontAwesomeIcon icon={faChevronRight} className="fa-7x"/>
|
||||
</div>
|
||||
<div className="col-5">
|
||||
<BasicVideo src={mediaLink} />
|
||||
<div className="col-6">
|
||||
<SectionVideoSettings
|
||||
workflowValues={workflowValues}
|
||||
onChange={(key,val)=>handleOnChange(key,val)}
|
||||
videoElement={videoRef?.current}
|
||||
/>
|
||||
<div className="d-flex my-3 justify-content-end">
|
||||
<Button
|
||||
className="me-3"
|
||||
label={t("previewWorkflow")}
|
||||
tooltip="feature not available yet"
|
||||
variant="primary"
|
||||
disabled
|
||||
/>
|
||||
<Button
|
||||
className="me-3"
|
||||
label={t("enqueueWorkflow")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
<Accordion className="mt-4">
|
||||
<Accordion.Item title="Video Settings">
|
||||
<SectionVideoSettings
|
||||
onChange={(key,val)=>handleOnChange(key,val)}
|
||||
videoElement={videoRef?.current}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title={"Basics"} defaultOpen>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
@@ -250,7 +237,7 @@ export default function PageFilterControls({
|
||||
<div className="row g-3 py-3">
|
||||
<div className="col-12 d-flex justify-content-end">
|
||||
<Button
|
||||
label={t("Process Filter")}
|
||||
label={t("enqueueWorkflow")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import React, { memo } from 'react'
|
||||
import { NumberSliderV2, Checkbox } from 'components/common'
|
||||
import { WorkflowValuesType } from './helpers';
|
||||
|
||||
export const AdvanceOptionsInitialValues = {
|
||||
firstPass: 1,
|
||||
@@ -18,7 +19,7 @@ export default memo( function SectionAdvanceOptions({
|
||||
workflowValues: wfVal,
|
||||
onChange : handleOnChange
|
||||
} : {
|
||||
workflowValues : any
|
||||
workflowValues : WorkflowValuesType
|
||||
onChange: (key:string, val:number|boolean)=>void}
|
||||
){
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import React, { memo } from 'react'
|
||||
import { NumberSliderV2 } from 'components/common'
|
||||
import { WorkflowValuesType } from './helpers';
|
||||
|
||||
export const ControlNetsInitialValues = {
|
||||
cnCanny: 0,
|
||||
@@ -16,7 +17,7 @@ export default memo( function SectionControlNets({
|
||||
workflowValues: wfVal,
|
||||
onChange : handleOnChange
|
||||
} : {
|
||||
workflowValues : any
|
||||
workflowValues : WorkflowValuesType
|
||||
onChange: (key:string, val:number)=>void}
|
||||
){
|
||||
|
||||
|
||||
+32
-12
@@ -1,10 +1,11 @@
|
||||
import React, { memo, useState } from 'react'
|
||||
|
||||
import { TempInput as Input } from 'components/common'
|
||||
import React, { memo } from 'react'
|
||||
import { TempInput as Input, NumberSliderV2 } from 'components/common'
|
||||
import { WorkflowValuesType } from './helpers';
|
||||
|
||||
export const VideoSettingsInitialValues = {
|
||||
width: 544,
|
||||
height: 544,
|
||||
maxFrames: 17,
|
||||
framesCap: 16,
|
||||
skipFrames: 0,
|
||||
everyNthFrame: 2,
|
||||
@@ -14,46 +15,65 @@ export const VideoSettingsInitialValues = {
|
||||
|
||||
export default memo( function SectionVideoSettings({
|
||||
onChange : handleOnChange,
|
||||
workflowValues: wfVal,
|
||||
videoElement: ve
|
||||
} : {
|
||||
onChange: (key:string, val:number)=>void,
|
||||
workflowValues: WorkflowValuesType,
|
||||
videoElement: HTMLVideoElement | null,
|
||||
}){
|
||||
const iv = VideoSettingsInitialValues;
|
||||
const [{width, height}, setDimensions] = useState({
|
||||
width:iv.width, height:iv.height
|
||||
});
|
||||
|
||||
if(ve && ve!==null){
|
||||
ve.onloadedmetadata = () =>{
|
||||
if (ve.videoWidth && ve.videoHeight) {
|
||||
const aspectRatio = ve.videoWidth/ve.videoHeight
|
||||
if (aspectRatio > 1){
|
||||
setDimensions({width: 960, height: iv.height});
|
||||
handleOnChange("width",960);
|
||||
}
|
||||
else if (aspectRatio < 1) {
|
||||
setDimensions({width: iv.width, height: 960});
|
||||
handleOnChange("height",960);
|
||||
}else{
|
||||
console.log(`aspectRaio: ${aspectRatio}`);
|
||||
}
|
||||
}
|
||||
if(ve.duration){
|
||||
handleOnChange("maxFrames",
|
||||
Math.floor(ve.duration)*wfVal.inputFps
|
||||
);
|
||||
}
|
||||
//TODO: Optimizer to make ONE handleOnChange only
|
||||
//TODO: deal with maxFrames with more reliable math
|
||||
}
|
||||
}
|
||||
return(
|
||||
<>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Width" value={width} readOnly/>
|
||||
<Input label="Width" value={wfVal.width} readOnly/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Height" value={height} readOnly/>
|
||||
<Input label="Height" value={wfVal.height} readOnly/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Frames Cap" value={iv.framesCap} readOnly/>
|
||||
<NumberSliderV2 {...{
|
||||
min: 16, max: wfVal.maxFrames, step: 1,
|
||||
initialValue: wfVal.framesCap,
|
||||
label: "Frames Cap",
|
||||
thumbTip: "24 frames = 1 sec",
|
||||
onChange: (val)=>{handleOnChange("framesCap",val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Skip Frames" value={iv.skipFrames} readOnly/>
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: wfVal.maxFrames-16, step: 1,
|
||||
initialValue: wfVal.skipFrames,
|
||||
label: "Skip Frames",
|
||||
thumbTip: "24 frames = 1 sec",
|
||||
onChange: (val)=>{handleOnChange("skipFrames",val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
|
||||
Reference in new issue
Block a user