diff --git a/packages/fakeyou.com/public/videos/face-animator-instruction-en.mp4 b/packages/fakeyou.com/public/videos/face-animator-instruction-en.mp4 new file mode 100644 index 0000000000..9b839fe748 Binary files /dev/null and b/packages/fakeyou.com/public/videos/face-animator-instruction-en.mp4 differ diff --git a/packages/fakeyou.com/src/components/common/BasicVideo/BasicVideo.scss b/packages/fakeyou.com/src/components/common/BasicVideo/BasicVideo.scss new file mode 100644 index 0000000000..52baadeff5 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/BasicVideo/BasicVideo.scss @@ -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; + } + } +}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/components/common/BasicVideo/BasicVideo.tsx b/packages/fakeyou.com/src/components/common/BasicVideo/BasicVideo.tsx new file mode 100644 index 0000000000..31bebbc534 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/BasicVideo/BasicVideo.tsx @@ -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
+ + +
Face Animator Sample
+ +
+
; +}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/components/common/BasicVideo/index.ts b/packages/fakeyou.com/src/components/common/BasicVideo/index.ts new file mode 100644 index 0000000000..006a41e25a --- /dev/null +++ b/packages/fakeyou.com/src/components/common/BasicVideo/index.ts @@ -0,0 +1 @@ +export { default } from './BasicVideo'; \ No newline at end of file diff --git a/packages/fakeyou.com/src/components/common/ImageInput/ImageInput.scss b/packages/fakeyou.com/src/components/common/ImageInput/ImageInput.scss index e9804a0ae8..c6a2169d5d 100644 --- a/packages/fakeyou.com/src/components/common/ImageInput/ImageInput.scss +++ b/packages/fakeyou.com/src/components/common/ImageInput/ImageInput.scss @@ -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 { diff --git a/packages/fakeyou.com/src/components/common/index.tsx b/packages/fakeyou.com/src/components/common/index.tsx index 63363e3f37..0401d60177 100644 --- a/packages/fakeyou.com/src/components/common/index.tsx +++ b/packages/fakeyou.com/src/components/common/index.tsx @@ -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 }; \ No newline at end of file diff --git a/packages/fakeyou.com/src/hooks/index.tsx b/packages/fakeyou.com/src/hooks/index.tsx index 142e5b804d..180f81aab4 100644 --- a/packages/fakeyou.com/src/hooks/index.tsx +++ b/packages/fakeyou.com/src/hooks/index.tsx @@ -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 }; diff --git a/packages/fakeyou.com/src/hooks/useVideo/index.ts b/packages/fakeyou.com/src/hooks/useVideo/index.ts new file mode 100644 index 0000000000..9eb5a944ae --- /dev/null +++ b/packages/fakeyou.com/src/hooks/useVideo/index.ts @@ -0,0 +1 @@ +export { default } from './useVideo'; \ No newline at end of file diff --git a/packages/fakeyou.com/src/hooks/useVideo/useVideo.tsx b/packages/fakeyou.com/src/hooks/useVideo/useVideo.tsx new file mode 100644 index 0000000000..a89c7a7746 --- /dev/null +++ b/packages/fakeyou.com/src/hooks/useVideo/useVideo.tsx @@ -0,0 +1,23 @@ +import { useRef } from 'react'; + +interface Props { + onEnded?: any; +} + +export default function useVideo({ onEnded }: Props) { + const ref = useRef(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 }]; +}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorInput.tsx b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorInput.tsx new file mode 100644 index 0000000000..1e58d19a1f --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorInput.tsx @@ -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 +
+
{t("headings.image")}
+ toggle.image(imageProps.file ? true : false), + }} + /> +
+
+
{t("headings.audio")}
+ { + toggle.audio(!!audioProps.file); + }, + hideActions: true, + }} + /> + +
+
+
+ Video Dimensions + +
+
+ Watermark + +
+
+ Animation + + +
+
+
; +}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorSuccess.tsx b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorSuccess.tsx index d76fce1c44..5d57ca41f5 100644 --- a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorSuccess.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorSuccess.tsx @@ -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({ success: true, pending_job_count: 0, diff --git a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorTypes.ts b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorTypes.ts new file mode 100644 index 0000000000..86db526447 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorTypes.ts @@ -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; + inferenceJobsByCategory: Map>; +} \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorWorking.tsx b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorWorking.tsx new file mode 100644 index 0000000000..fdfd242941 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/FaceAnimatorWorking.tsx @@ -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 +
+

+ {" "} + {transitions(({ opacity, position }, i) => { + return ( + + {workStatus[index]} ... + + ); + })} +

+ +
+
; +}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.scss b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.scss index 0592b46ec6..f4b01ae774 100644 --- a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.scss +++ b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.scss @@ -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 ;} + } diff --git a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.tsx b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.tsx index 8bc50a6e9e..daf0ea63d9 100644 --- a/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/lipsync/LipsyncEditor/LipsyncEditor.tsx @@ -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; - inferenceJobsByCategory: Map>; -} - -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 -
-
{t("headings.image")}
- toggle.image(imageProps.file ? true : false), - }} - /> - -
-
-
{t("headings.audio")}
- { - toggle.audio(!!audioProps.file); - }, - hideActions: true, - }} - /> -
-
-
- Video Dimensions - -
-
- Watermark - -
-
- Animation - - -
-
- -
; -}; - -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 ( - -
-

- {" "} - {transitions(({ opacity, position }, i) => { - return ( - - {workStatus[index]} ... - - ); - })} -

- -
-
- ); -}; +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({
{transitions((style, i) => { - const Page = [InputPage, Working, FaceAnimatorSuccess][page]; + const Page = [FaceAnimatorInput, FaceAnimatorWorking, FaceAnimatorSuccess][page]; return Page ? ( +
+ +
); }