mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 17:58:55 +00:00
Merge pull request #293 from storytold/olivicmic-fy-faceani-edu-video
Face Animator Sample Video
This commit is contained in:
15 files changed
+324
-200
No files matched your search
Binary file not shown.
@@ -0,0 +1,48 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.fy-basic-video {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
height: fit-content;
|
||||
position: relative;
|
||||
|
||||
video, .video-overlay {
|
||||
border-radius: .5rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
video {object-fit: cover; }
|
||||
|
||||
.video-overlay {
|
||||
background: linear-gradient(rgba($primary,0.5) 0%, rgba($secondary,0.75) 75%);
|
||||
cursor: pointer;
|
||||
padding: 2rem;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
|
||||
h6 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { useState } from "react";
|
||||
import { a, useSpring } from "@react-spring/web";
|
||||
import { useVideo } from "hooks";
|
||||
import makeClass from "resources/makeClass";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlay } from "@fortawesome/free-solid-svg-icons";
|
||||
import "./BasicVideo.scss"
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
src?: string;
|
||||
}
|
||||
|
||||
export default function BasicVideo({ className, src }: Props) {
|
||||
const [tint,tintSet] = useState(true);
|
||||
const [{ playCtrl },vidProps] = useVideo({ onEnded: () => tintSet(true) });
|
||||
const style = useSpring({
|
||||
onRest: () => { if (!tint) { playCtrl!(tintSet) }},
|
||||
opacity: tint ? 1 : 0
|
||||
});
|
||||
const onClick = () => {
|
||||
tintSet(!tint)
|
||||
if (!tint) playCtrl!();
|
||||
};
|
||||
|
||||
return <div {...{ ...makeClass("fy-basic-video",className) }}>
|
||||
<video {...{ playsInline: true, ...vidProps }}>
|
||||
<source {...{ src, type: "video/mp4" }} />
|
||||
</video>
|
||||
<a.div {...{ className: "video-overlay", onClick, style }}>
|
||||
<h6>Face Animator Sample</h6>
|
||||
<FontAwesomeIcon {...{ icon: faPlay }} />
|
||||
</a.div>
|
||||
</div>;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './BasicVideo';
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
.image-upload-frame {
|
||||
// aspect-ratio: 4/3;
|
||||
// @include breakpoint(min-width 992px) {
|
||||
// aspect-ratio: 1/1;
|
||||
// }
|
||||
@include breakpoint(min-width 992px) {
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
}
|
||||
|
||||
.fy-image-uploader {
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import AudioInput from './AudioInput';
|
||||
import Check from './Check';
|
||||
import Checkbox from './Checkbox';
|
||||
import FileActions from './FileActions';
|
||||
import FileDetails from './FileDetails';
|
||||
import FileWrapper from './FileWrapper';
|
||||
import FileLabel from './FileLabel';
|
||||
import ImageInput from './ImageInput';
|
||||
import NumberSlider from './NumberSlider';
|
||||
import Radio from './Radio';
|
||||
import SegmentButtons from './SegmentButtons';
|
||||
import Slider from './Slider';
|
||||
import Spinner from './Spinner';
|
||||
import AudioInput from "./AudioInput";
|
||||
import BasicVideo from "./BasicVideo";
|
||||
import Check from "./Check";
|
||||
import Checkbox from "./Checkbox";
|
||||
import FileActions from "./FileActions";
|
||||
import FileDetails from "./FileDetails";
|
||||
import FileWrapper from "./FileWrapper";
|
||||
import FileLabel from "./FileLabel";
|
||||
import ImageInput from "./ImageInput";
|
||||
import NumberSlider from "./NumberSlider";
|
||||
import Radio from "./Radio";
|
||||
import SegmentButtons from "./SegmentButtons";
|
||||
import Slider from "./Slider";
|
||||
import Spinner from "./Spinner";
|
||||
|
||||
export {
|
||||
AudioInput,
|
||||
Check,
|
||||
Checkbox,
|
||||
FileActions,
|
||||
FileDetails,
|
||||
FileWrapper,
|
||||
FileLabel,
|
||||
ImageInput,
|
||||
NumberSlider,
|
||||
Radio,
|
||||
SegmentButtons,
|
||||
Slider,
|
||||
Spinner
|
||||
AudioInput,
|
||||
BasicVideo,
|
||||
Check,
|
||||
Checkbox,
|
||||
FileActions,
|
||||
FileDetails,
|
||||
FileWrapper,
|
||||
FileLabel,
|
||||
ImageInput,
|
||||
NumberSlider,
|
||||
Radio,
|
||||
SegmentButtons,
|
||||
Slider,
|
||||
Spinner
|
||||
};
|
||||
@@ -3,5 +3,6 @@ import useHover from './useHover';
|
||||
import useId from './useId';
|
||||
import useIdempotency from './useIdempotency';
|
||||
import useLocalize from './useLocalize';
|
||||
import useVideo from './useVideo';
|
||||
|
||||
export { useFile, useHover, useId, useIdempotency, useLocalize };
|
||||
export { useFile, useHover, useId, useIdempotency, useLocalize, useVideo };
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './useVideo';
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
interface Props {
|
||||
onEnded?: any;
|
||||
}
|
||||
|
||||
export default function useVideo({ onEnded }: Props) {
|
||||
const ref = useRef<HTMLVideoElement>(null);
|
||||
const playCtrl = (toDo = (b:any) => {}) => {
|
||||
if (ref.current) {
|
||||
let isPlaying = !!(ref.current.currentTime > 0 && !ref.current.paused && !ref.current.ended && ref.current.readyState > 2);
|
||||
if (!isPlaying) {
|
||||
toDo(false);
|
||||
ref.current.play();
|
||||
} else {
|
||||
toDo(true);
|
||||
ref.current.pause();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [{ playCtrl },{ onEnded, ref }];
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
import { animated } from "@react-spring/web";
|
||||
import { AudioInput, BasicVideo, Checkbox, ImageInput, SegmentButtons } from "components/common";
|
||||
import { FaceAnimatorSlide } from "./FaceAnimatorTypes";
|
||||
|
||||
export default function FaceAnimatorInput({
|
||||
audioProps,
|
||||
imageProps,
|
||||
frameDimensions,
|
||||
frameDimensionsChange,
|
||||
disableFaceEnhancement,
|
||||
disableFaceEnhancementChange,
|
||||
still,
|
||||
stillChange,
|
||||
toggle,
|
||||
style,
|
||||
t,
|
||||
removeWatermark,
|
||||
removeWatermarkChange, }: FaceAnimatorSlide) {
|
||||
return <animated.div {...{ className: "lipsync-editor row", style }}>
|
||||
<div {...{ className: "media-input-column col-lg-6" }}>
|
||||
<h5>{t("headings.image")}</h5>
|
||||
<ImageInput
|
||||
{...{
|
||||
...imageProps,
|
||||
onRest: () => toggle.image(imageProps.file ? true : false),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div {...{ className: "media-input-column audio-input-column col-lg-6" }}>
|
||||
<h5>{t("headings.audio")}</h5>
|
||||
<AudioInput
|
||||
{...{
|
||||
...audioProps,
|
||||
onRest: (p: any, c: any, item: any, l: any) => {
|
||||
toggle.audio(!!audioProps.file);
|
||||
},
|
||||
hideActions: true,
|
||||
}}
|
||||
/>
|
||||
<BasicVideo {...{ className: "face-animator-wide-sample", src: "/videos/face-animator-instruction-en.mp4" }}/>
|
||||
</div>
|
||||
<div {...{ className: "animation-configure-panel panel" }}>
|
||||
<fieldset {...{ className: "input-block" }}>
|
||||
<legend>Video Dimensions</legend>
|
||||
<SegmentButtons {...{
|
||||
onChange: frameDimensionsChange,
|
||||
options: [{ label: "Landscape (Wide)", value: "twitter_landscape" },{ label: "Portrait (Tall)", value: "twitter_portrait" },{ label: "Square", value: "twitter_square" }],
|
||||
value: frameDimensions
|
||||
}}/>
|
||||
</fieldset>
|
||||
<fieldset {...{ className: "input-block" }}>
|
||||
<legend>Watermark</legend>
|
||||
<Checkbox {...{ checked: removeWatermark, label: "Remove Watermark (premium only)", onChange: removeWatermarkChange }}/>
|
||||
</fieldset>
|
||||
<fieldset {...{ className: "input-block" }}>
|
||||
<legend>Animation</legend>
|
||||
<Checkbox {...{ checked: still, label: "Reduce Movement (not recommended)", onChange: stillChange}}/>
|
||||
<Checkbox {...{ checked: disableFaceEnhancement, label: "Disable Face Enhancer (not recommended)", onChange: disableFaceEnhancementChange}}/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</animated.div>;
|
||||
};
|
||||
+2
-11
@@ -11,20 +11,11 @@ import { JobState } from "@storyteller/components/src/jobs/JobStates";
|
||||
import { Analytics } from "common/Analytics";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface Props {
|
||||
audioProps: any;
|
||||
imageProps: any;
|
||||
style: any;
|
||||
enqueueInferenceJob: any;
|
||||
sessionSubscriptionsWrapper: any;
|
||||
inferenceJobsByCategory: any;
|
||||
t: any;
|
||||
}
|
||||
import { FaceAnimatorSlide } from "./FaceAnimatorTypes";
|
||||
|
||||
const DEFAULT_QUEUE_REFRESH_INTERVAL_MILLIS = 15000;
|
||||
|
||||
export default function FaceAnimatorSuccess({ audioProps, imageProps, style, enqueueInferenceJob, sessionSubscriptionsWrapper, inferenceJobsByCategory, t }: Props) {
|
||||
export default function FaceAnimatorSuccess({ audioProps, imageProps, style, enqueueInferenceJob, sessionSubscriptionsWrapper, inferenceJobsByCategory, t }: FaceAnimatorSlide) {
|
||||
const [pending, pendingSet] = useState<GetPendingTtsJobCountSuccessResponse>({
|
||||
success: true,
|
||||
pending_job_count: 0,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { FrontendInferenceJobType, InferenceJob } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
|
||||
export interface FaceAnimatorSlide {
|
||||
audioProps: any;
|
||||
children?: any;
|
||||
imageProps: any;
|
||||
frameDimensions: any;
|
||||
frameDimensionsChange: any;
|
||||
disableFaceEnhancement: any;
|
||||
disableFaceEnhancementChange: any;
|
||||
index: number;
|
||||
still: any;
|
||||
stillChange: any;
|
||||
style: any;
|
||||
toggle: any;
|
||||
enqueueInferenceJob: any;
|
||||
sessionSubscriptionsWrapper: any;
|
||||
t: any;
|
||||
inferenceJobsByCategory: any;
|
||||
removeWatermark: any;
|
||||
removeWatermarkChange: any;
|
||||
}
|
||||
|
||||
export interface FaceAnimatorCore {
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
enqueueInferenceJob: (
|
||||
jobToken: string,
|
||||
frontendInferenceJobType: FrontendInferenceJobType
|
||||
) => void;
|
||||
inferenceJobs: Array<InferenceJob>;
|
||||
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { a, useTransition } from "@react-spring/web";
|
||||
import { Spinner } from "components/common";
|
||||
import { springs } from "resources";
|
||||
import { FaceAnimatorSlide } from "./FaceAnimatorTypes";
|
||||
|
||||
export default function FaceAnimatorWorking({ audioProps, imageProps, index, style, t }: FaceAnimatorSlide) {
|
||||
const workStatus = [
|
||||
"",
|
||||
t("status.uploadingAudio"),
|
||||
t("status.uploadingImage"),
|
||||
t("status.requestingAnimation"),
|
||||
"",
|
||||
];
|
||||
|
||||
const transitions = useTransition(index, {
|
||||
...springs.soft,
|
||||
from: { opacity: 0, position: "absolute" },
|
||||
enter: { opacity: 1, position: "relative" },
|
||||
leave: { opacity: 0, position: "absolute" },
|
||||
});
|
||||
return <a.div {...{ className: "lipsync-working", style }}>
|
||||
<div {...{ className: "lipsync-working-notice" }}>
|
||||
<h2 {...{ className: "working-title" }}>
|
||||
{" "}
|
||||
{transitions(({ opacity, position }, i) => {
|
||||
return (
|
||||
<a.span
|
||||
{...{
|
||||
className: "working-title-text",
|
||||
style: { opacity, position: position as any },
|
||||
}}
|
||||
>
|
||||
{workStatus[index]} ...
|
||||
</a.span>
|
||||
);
|
||||
})}
|
||||
</h2>
|
||||
<Spinner />
|
||||
</div>
|
||||
</a.div>;
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
@import "scss/_breakpoint.scss";
|
||||
@import "scss/colours.scss";
|
||||
|
||||
$under992: max-width 992px;
|
||||
$over992: min-width 992px;
|
||||
|
||||
.lipsync-editor {
|
||||
padding-top: 2rem;
|
||||
@@ -13,13 +16,18 @@ $under992: max-width 992px;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
.audio-input-column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.fy-audio-uploder {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.animation-configure-panel {
|
||||
margin: 1rem 0 -.5rem;
|
||||
margin: 1.5rem 0 -.5rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -123,3 +131,22 @@ $under992: max-width 992px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.face-animator-mobile-sample {
|
||||
padding: 1rem;
|
||||
|
||||
@include breakpoint($over992) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.face-animator-wide-sample {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
|
||||
@include breakpoint($under992) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
video { position: absolute ;}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { animated, useTransition } from "@react-spring/web";
|
||||
import { useTransition } from "@react-spring/web";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useFile, useLocalize } from "hooks";
|
||||
import { AudioInput, Checkbox, ImageInput, SegmentButtons, Spinner } from "components/common";
|
||||
import { springs } from "resources";
|
||||
import {
|
||||
UploadAudio,
|
||||
@@ -19,162 +18,21 @@ import {
|
||||
// EnqueueFaceAnimationIsSuccess,
|
||||
// EnqueueFaceAnimationRequest,
|
||||
} from "@storyteller/components/src/api/face_animation/EnqueueFaceAnimation";
|
||||
// import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import FaceAnimatorTitle from "./FaceAnimatorTitle";
|
||||
import FaceAnimatorInput from './FaceAnimatorInput';
|
||||
import FaceAnimatorWorking from './FaceAnimatorWorking';
|
||||
import FaceAnimatorSuccess from "./FaceAnimatorSuccess";
|
||||
import { FaceAnimatorCore } from "./FaceAnimatorTypes";
|
||||
import { BasicVideo } from "components/common";
|
||||
import "./LipsyncEditor.scss";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
InferenceJob,
|
||||
} from "@storyteller/components/src/jobs/InferenceJob";
|
||||
|
||||
interface LipSyncProps {
|
||||
// animationChange: any;
|
||||
// animationStyle: any;
|
||||
audioProps: any;
|
||||
children?: any;
|
||||
// cropChange: any;
|
||||
// cropping: any;
|
||||
imageProps: any;
|
||||
frameDimensions: any;
|
||||
frameDimensionsChange: any;
|
||||
disableFaceEnhancement: any;
|
||||
disableFaceEnhancementChange: any;
|
||||
index: number;
|
||||
still: any;
|
||||
stillChange: any;
|
||||
style: any;
|
||||
toggle: any;
|
||||
enqueueInferenceJob: any;
|
||||
sessionSubscriptionsWrapper: any;
|
||||
t: any;
|
||||
inferenceJobsByCategory: any;
|
||||
removeWatermark: any;
|
||||
removeWatermarkChange: any;
|
||||
}
|
||||
interface EditorProps {
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
enqueueInferenceJob: (
|
||||
jobToken: string,
|
||||
frontendInferenceJobType: FrontendInferenceJobType
|
||||
) => void;
|
||||
inferenceJobs: Array<InferenceJob>;
|
||||
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
|
||||
}
|
||||
|
||||
const softSpring = { config: { mass: 1, tension: 80, friction: 10 } };
|
||||
|
||||
const InputPage = ({
|
||||
// animationChange,
|
||||
// animationStyle,
|
||||
audioProps,
|
||||
// cropping,
|
||||
// cropChange,
|
||||
imageProps,
|
||||
frameDimensions,
|
||||
frameDimensionsChange,
|
||||
disableFaceEnhancement,
|
||||
disableFaceEnhancementChange,
|
||||
still,
|
||||
stillChange,
|
||||
toggle,
|
||||
style,
|
||||
t,
|
||||
removeWatermark,
|
||||
removeWatermarkChange,
|
||||
}: LipSyncProps) => {
|
||||
|
||||
return <animated.div {...{ className: "lipsync-editor row", style }}>
|
||||
<div {...{ className: "media-input-column col-lg-6" }}>
|
||||
<h5>{t("headings.image")}</h5>
|
||||
<ImageInput
|
||||
{...{
|
||||
...imageProps,
|
||||
onRest: () => toggle.image(imageProps.file ? true : false),
|
||||
}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div {...{ className: "media-input-column col-lg-6" }}>
|
||||
<h5>{t("headings.audio")}</h5>
|
||||
<AudioInput
|
||||
{...{
|
||||
...audioProps,
|
||||
onRest: (p: any, c: any, item: any, l: any) => {
|
||||
toggle.audio(!!audioProps.file);
|
||||
},
|
||||
hideActions: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div {...{ className: "animation-configure-panel panel" }}>
|
||||
<fieldset {...{ className: "input-block" }}>
|
||||
<legend>Video Dimensions</legend>
|
||||
<SegmentButtons {...{
|
||||
onChange: frameDimensionsChange,
|
||||
options: [{ label: "Landscape (Wide)", value: "twitter_landscape" },{ label: "Portrait (Tall)", value: "twitter_portrait" },{ label: "Square", value: "twitter_square" }],
|
||||
value: frameDimensions
|
||||
}}/>
|
||||
</fieldset>
|
||||
<fieldset {...{ className: "input-block" }}>
|
||||
<legend>Watermark</legend>
|
||||
<Checkbox {...{ checked: removeWatermark, label: "Remove Watermark (premium only)", onChange: removeWatermarkChange }}/>
|
||||
</fieldset>
|
||||
<fieldset {...{ className: "input-block" }}>
|
||||
<legend>Animation</legend>
|
||||
<Checkbox {...{ checked: still, label: "Reduce Movement (not recommended)", onChange: stillChange}}/>
|
||||
<Checkbox {...{ checked: disableFaceEnhancement, label: "Disable Face Enhancer (not recommended)", onChange: disableFaceEnhancementChange}}/>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</animated.div>;
|
||||
};
|
||||
|
||||
const Working = ({ audioProps, imageProps, index, style, t }: LipSyncProps) => {
|
||||
const workStatus = [
|
||||
"",
|
||||
t("status.uploadingAudio"),
|
||||
t("status.uploadingImage"),
|
||||
t("status.requestingAnimation"),
|
||||
"",
|
||||
];
|
||||
const transitions = useTransition(index, {
|
||||
...springs.soft,
|
||||
from: { opacity: 0, position: "absolute" },
|
||||
enter: { opacity: 1, position: "relative" },
|
||||
leave: { opacity: 0, position: "absolute" },
|
||||
});
|
||||
return (
|
||||
<animated.div {...{ className: "lipsync-working", style }}>
|
||||
<div {...{ className: "lipsync-working-notice" }}>
|
||||
<h2 {...{ className: "working-title" }}>
|
||||
{" "}
|
||||
{transitions(({ opacity, position }, i) => {
|
||||
return (
|
||||
<animated.span
|
||||
{...{
|
||||
className: "working-title-text",
|
||||
style: { opacity, position: position as any },
|
||||
}}
|
||||
>
|
||||
{workStatus[index]} ...
|
||||
</animated.span>
|
||||
);
|
||||
})}
|
||||
</h2>
|
||||
<Spinner />
|
||||
</div>
|
||||
</animated.div>
|
||||
);
|
||||
};
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
|
||||
export default function LipsyncEditor({
|
||||
enqueueInferenceJob,
|
||||
sessionSubscriptionsWrapper,
|
||||
inferenceJobsByCategory,
|
||||
...rest
|
||||
}: EditorProps) {
|
||||
}: FaceAnimatorCore) {
|
||||
const { t } = useLocalize("FaceAnimator");
|
||||
|
||||
// the ready states are set by functions which run after the upload input animation is completed, which then illuminates the respective checkmark in a staggered way to draw attention to the workflow, and reduces concurrent animations
|
||||
@@ -234,10 +92,6 @@ export default function LipsyncEditor({
|
||||
if ("upload_token" in responses.image) {
|
||||
indexSet(3); // set face animator API working page
|
||||
return EnqueueFaceAnimation({
|
||||
// backend_animation_key: animationStyle,
|
||||
// backend_cropping_key: cropping === "cropped",
|
||||
// backend_orientation_key: orientation === "landscape",
|
||||
// backend_watermark_key: watermark,
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
audio_source: {
|
||||
maybe_media_upload_token: responses.audio.upload_token,
|
||||
@@ -279,7 +133,7 @@ export default function LipsyncEditor({
|
||||
};
|
||||
|
||||
const transitions = useTransition(index, {
|
||||
...softSpring,
|
||||
...springs.soft,
|
||||
from: { opacity: 0, position: "absolute" },
|
||||
enter: { opacity: 1, position: "relative" },
|
||||
leave: { opacity: 0, position: "absolute" },
|
||||
@@ -290,7 +144,7 @@ export default function LipsyncEditor({
|
||||
<div {...{ className: "panel lipsync-panel p-3 py-4 p-md-4" }}>
|
||||
<FaceAnimatorTitle {...headerProps} />
|
||||
{transitions((style, i) => {
|
||||
const Page = [InputPage, Working, FaceAnimatorSuccess][page];
|
||||
const Page = [FaceAnimatorInput, FaceAnimatorWorking, FaceAnimatorSuccess][page];
|
||||
return Page ? (
|
||||
<Page
|
||||
{...{
|
||||
@@ -318,6 +172,9 @@ export default function LipsyncEditor({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div {...{ className: "face-animator-mobile-sample" }}>
|
||||
<BasicVideo {...{ src: "/videos/face-animator-instruction-en.mp4" }}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user