diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElement.tsx index 1520f19497..284c1245da 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElement.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElement.tsx @@ -1,5 +1,5 @@ import { useCallback, useContext, useEffect } from "react"; -import { MediaClip } from "~/pages/PageEnigma/models/track"; +import { ClipType, MediaClip } from "~/pages/PageEnigma/models/track"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; import { canDrop, @@ -11,7 +11,7 @@ import { useSignals } from "@preact/signals-react/runtime"; interface Props { clip: MediaClip; - type: "animations" | "audio"; + type: ClipType; } export const AnimationElement = ({ clip, type }: Props) => { diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElements.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElements.tsx index c9cbceaf51..9ef9b7eb71 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElements.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/AnimationElements.tsx @@ -3,6 +3,7 @@ import { AnimationElement } from "./AnimationElement"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; import { useSignals } from "@preact/signals-react/runtime"; import { timelineHeight } from "~/pages/PageEnigma/store"; +import { ClipType } from "~/pages/PageEnigma/models/track"; export const AnimationElements = () => { const { animationClips } = useContext(TrackContext); @@ -17,7 +18,11 @@ export const AnimationElements = () => {
{animationClips.map((clip) => { return ( - + ); })}
diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/AudioElement.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/AudioElement.tsx index 9da211d7ee..ca444a10e3 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/AudioElement.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/AudioElement.tsx @@ -1,5 +1,5 @@ import { useCallback, useContext, useEffect } from "react"; -import { MediaClip } from "~/pages/PageEnigma/models/track"; +import { ClipType, MediaClip } from "~/pages/PageEnigma/models/track"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; import { canDrop, @@ -11,7 +11,7 @@ import { useSignals } from "@preact/signals-react/runtime"; interface Props { clip: MediaClip; - type: "animations" | "audio"; + type: ClipType; } export const AudioElement = ({ clip, type }: Props) => { diff --git a/app/pages/PageEnigma/comps/Timeline/Character.tsx b/app/pages/PageEnigma/comps/Timeline/Character.tsx index cf10ea6d9c..ffe1d229e0 100644 --- a/app/pages/PageEnigma/comps/Timeline/Character.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Character.tsx @@ -7,10 +7,11 @@ import { updateCharacters, } from "~/pages/PageEnigma/store"; import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames"; +import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track"; function buildUpdaters( updateCharacters: (options: { - type: "animations" | "positions" | "lipSync"; + type: ClipType; id: string; length: number; offset: number; @@ -21,17 +22,17 @@ function buildUpdaters( length: number; offset: number; }) { - updateCharacters({ ...options, type: "animations" }); + updateCharacters({ ...options, type: ClipType.ANIMATION }); } function updateClipPosition(options: { id: string; offset: number }) { - updateCharacters({ ...options, length: 0, type: "positions" }); + updateCharacters({ ...options, length: 0, type: ClipType.TRANSFORM }); } function updateClipLipSync(options: { id: string; length: number; offset: number; }) { - updateCharacters({ ...options, type: "lipSync" }); + updateCharacters({ ...options, type: ClipType.AUDIO }); } return { updateClipLipSync, updateClipPosition, updateClipAnimations }; } @@ -68,15 +69,15 @@ export const Character = ({ characterId }: Props) => { clips={animationClips} title="Animation" updateClip={updateClipAnimations} - style="character" - type="animations" + group={ClipGroup.CHARACTER} + type={ClipType.ANIMATION} /> { updateClip={updateClipLipSync} muted={character.muted} toggleMute={toggleCharacterLipSyncMute} - style="character" - type="lipSync" + group={ClipGroup.CHARACTER} + type={ClipType.AUDIO} /> diff --git a/app/pages/PageEnigma/comps/Timeline/ObjectTrack.tsx b/app/pages/PageEnigma/comps/Timeline/ObjectTrack.tsx index a63c6f4e2d..32e90f9959 100644 --- a/app/pages/PageEnigma/comps/Timeline/ObjectTrack.tsx +++ b/app/pages/PageEnigma/comps/Timeline/ObjectTrack.tsx @@ -1,5 +1,6 @@ import { fullWidth, objectGroup, updateObject } from "~/pages/PageEnigma/store"; import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames"; +import { ClipGroup } from "~/pages/PageEnigma/models/track"; export const ObjectTrack = () => { return ( @@ -17,7 +18,7 @@ export const ObjectTrack = () => { keyframes={object.keyframes} title={`${object.name} Position/Rotation`} updateKeyframe={updateObject} - style="objects" + group={ClipGroup.OBJECT} /> ))} diff --git a/app/pages/PageEnigma/comps/Scrubber/Scrubber.tsx b/app/pages/PageEnigma/comps/Timeline/Scrubber.tsx similarity index 100% rename from app/pages/PageEnigma/comps/Scrubber/Scrubber.tsx rename to app/pages/PageEnigma/comps/Timeline/Scrubber.tsx diff --git a/app/pages/PageEnigma/comps/Timeline/Timeline.tsx b/app/pages/PageEnigma/comps/Timeline/Timeline.tsx index 3de3cb7155..5ceb0ae6db 100644 --- a/app/pages/PageEnigma/comps/Timeline/Timeline.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Timeline.tsx @@ -13,7 +13,7 @@ import { import { useQueueHandler } from "~/pages/PageEnigma/comps/Timeline/utils/useQueueHandler"; import { useSignals } from "@preact/signals-react/runtime"; import { TimerGrid } from "~/pages/PageEnigma/comps/TimerGrid/TimerGrid"; -import { Scrubber } from "~/pages/PageEnigma/comps/Scrubber/Scrubber"; +import { Scrubber } from "~/pages/PageEnigma/comps/Timeline/Scrubber"; import { Characters } from "~/pages/PageEnigma/comps/Timeline/Characters"; import { ObjectGroups } from "~/pages/PageEnigma/comps/Timeline/ObjectGroups"; import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe"; diff --git a/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx b/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx index d12685ac28..64532b0e2e 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx @@ -1,17 +1,17 @@ import { useMouseEventsClip } from "./utils/useMouseEventsClip"; import { useState } from "react"; import { scale, selectedItem } from "~/pages/PageEnigma/store"; -import { Clip } from "~/pages/PageEnigma/models/track"; +import { Clip, ClipGroup } from "~/pages/PageEnigma/models/track"; interface Props { min: number; max: number; - style: "character" | "camera" | "audio" | "objects"; + group: ClipGroup; clip: Clip; updateClip: (options: { id: string; offset: number; length: number }) => void; } -export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { +export const TrackClip = ({ clip, min, max, group, updateClip }: Props) => { const [state, setState] = useState({ length: clip.length, offset: clip.offset, @@ -31,8 +31,8 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { const classes = [ "absolute", clip.clip_uuid === selectedClipId - ? `bg-${style}-selected` - : `bg-${style}-clip`, + ? `bg-${group}-selected` + : `bg-${group}-clip`, ]; return ( diff --git a/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx b/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx index 565944df23..6d45a2f9c6 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackClips.tsx @@ -1,7 +1,7 @@ import { TrackClip } from "~/pages/PageEnigma/comps/Timeline/TrackClip"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons"; -import { Clip } from "~/pages/PageEnigma/models/track"; +import { Clip, ClipGroup, ClipType } from "~/pages/PageEnigma/models/track"; import { PointerEvent } from "react"; import { canDrop, @@ -17,13 +17,38 @@ interface Props { id: string; clips: Clip[]; title: string; - style: "character" | "audio" | "camera" | "objects"; - type?: "animations" | "positions" | "lipSync"; + group: ClipGroup; + type?: ClipType; toggleMute?: () => void; muted?: boolean; updateClip: (options: { id: string; length: number; offset: number }) => void; } +function getCanBuild({ + dragType, + type, + group, +}: { + dragType: ClipType | null; + type?: ClipType; + group: ClipGroup; +}) { + if (dragType === ClipType.ANIMATION) { + if (dragType === type) { + return true; + } + } + if (dragType === ClipType.AUDIO) { + if (group === ClipGroup.CHARACTER && type === ClipType.AUDIO) { + return true; + } + if (group === ClipGroup.GLOBAL_AUDIO) { + return true; + } + } + return false; +} + export const TrackClips = ({ id, clips, @@ -31,36 +56,22 @@ export const TrackClips = ({ updateClip, muted, title, - style, + group, type, }: Props) => { - const trackType = type ?? style; + const trackType = (type ?? group) as ClipType; function onPointerOver() { - if (dragType.value === "animations") { - if (dragType.value !== trackType) { - return; - } + if (getCanBuild({ dragType: dragType.value, type, group })) { + dropId.value = id; } - if (dragType.value === "audio") { - if (["lipSync", "audio"].indexOf(trackType) === -1) { - return; - } - } - dropId.value = id; } function onPointerMove(event: PointerEvent) { - if (dragType.value === "animations") { - if (dragType.value !== trackType) { - return; - } - } - if (dragType.value === "audio") { - if (["lipSync", "audio"].indexOf(trackType) === -1) { - return; - } + if (!getCanBuild({ dragType: dragType.value, type, group })) { + return; } + const track = document.getElementById(`track-${trackType}-${id}`); if (!track) { return; @@ -101,7 +112,7 @@ export const TrackClips = ({
diff --git a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx index c828baebc0..2dbf099e8e 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx @@ -1,28 +1,21 @@ import { Keyframe } from "~/pages/PageEnigma/models/track"; import { useState } from "react"; -import { scale, selectedItem } from "~/pages/PageEnigma/store"; +import { filmLength, scale, selectedItem } from "~/pages/PageEnigma/store"; import { useMouseEventsKeyframe } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faSortUpDown } from "@fortawesome/pro-solid-svg-icons"; +import { useSignals } from "@preact/signals-react/runtime"; interface Props { - min: number; - max: number; keyframe: Keyframe; updateKeyframe: (options: { id: string; offset: number }) => void; } -export const TrackKeyFrame = ({ - keyframe, - min, - max, - updateKeyframe, -}: Props) => { +export const TrackKeyFrame = ({ keyframe, updateKeyframe }: Props) => { + useSignals(); const [offset, setOffset] = useState(keyframe.offset); const { onPointerDown } = useMouseEventsKeyframe({ keyframe, - max, - min, + max: filmLength.value * 60, + min: 0, updateKeyframe, setOffset, }); diff --git a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx index aea6f7334c..ca1316a033 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx @@ -1,15 +1,18 @@ -import { Keyframe } from "~/pages/PageEnigma/models/track"; -import { filmLength } from "~/pages/PageEnigma/store"; +import { ClipGroup, Keyframe } from "~/pages/PageEnigma/models/track"; import { TrackKeyFrame } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrame"; interface Props { id: string; keyframes: Keyframe[]; title: string; - style: "character" | "audio" | "camera" | "objects"; + group: ClipGroup; toggleMute?: () => void; muted?: boolean; - updateKeyframe: (options: { id: string; offset: number }) => void; + updateKeyframe: (options: { + id: string; + offset: number; + force?: boolean; + }) => void; } export const TrackKeyFrames = ({ @@ -17,23 +20,17 @@ export const TrackKeyFrames = ({ keyframes, updateKeyframe, title, - style, + group, }: Props) => { return (
- {keyframes.map((keyframe, index) => ( + {keyframes.map((keyframe) => ( 0 ? keyframes[index - 1].offset + 1 : 0} - max={ - index < keyframes.length - 1 - ? keyframes[index + 1].offset - : filmLength.value * 60 - } updateKeyframe={updateKeyframe} keyframe={keyframe} /> diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsClip.ts b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsClip.ts index 1dbe7ed124..4746501ee8 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsClip.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsClip.ts @@ -37,13 +37,11 @@ export const useMouseEventsClip = ( if (isActive.current === "drag") { if (deltaOffset < min) { currOffset.current = min; - return; + } else if (deltaOffset + currLength.current > max) { + currOffset.current = max; + } else { + currOffset.current = deltaOffset; } - if (deltaOffset + currLength.current > max) { - currOffset.current = max - currLength.current; - return; - } - currOffset.current = deltaOffset; } if (isActive.current === "left") { if (initLength.current - delta < 30 || deltaOffset < min) { diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts index 20f46e2e5f..8fde4ec267 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts @@ -1,6 +1,10 @@ import React, { Dispatch, useCallback, useEffect, useRef } from "react"; import { Keyframe } from "~/pages/PageEnigma/models/track"; -import { canDrop, scale } from "~/pages/PageEnigma/store"; +import { + canDrop, + scale, + showDuplicateOffsetWarning, +} from "~/pages/PageEnigma/store"; import { useSignals } from "@preact/signals-react/runtime"; export const useMouseEventsKeyframe = ({ @@ -40,15 +44,13 @@ export const useMouseEventsKeyframe = ({ if (isActive.current === "drag") { if (deltaOffset < min) { currOffset.current = min; - return; - } - if (deltaOffset > max) { + } else if (deltaOffset > max) { currOffset.current = max; - return; + } else { + currOffset.current = deltaOffset; } - currOffset.current = deltaOffset; + setOffset(currOffset.current); } - setOffset(currOffset.current); }, [max, min, setOffset], ); diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx index adc2738f8e..b3038d6be3 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx @@ -3,6 +3,7 @@ import { MediaClip, Keyframe, QueueKeyframe, + ClipType, } from "~/pages/PageEnigma/models/track"; export const TrackContext = createContext<{ @@ -15,7 +16,7 @@ export const TrackContext = createContext<{ audioClips: MediaClip[]; // drag and drop - startDrag: (type: "animations" | "audio", id: string, length: number) => void; + startDrag: (type: ClipType, id: string, length: number) => void; endDrag: () => void; }>({ addKeyframe: () => {}, diff --git a/app/pages/PageEnigma/datastructures/clips/clip_ui.ts b/app/pages/PageEnigma/datastructures/clips/clip_ui.ts index eb06cc2eba..04dce4428c 100644 --- a/app/pages/PageEnigma/datastructures/clips/clip_ui.ts +++ b/app/pages/PageEnigma/datastructures/clips/clip_ui.ts @@ -1,47 +1,49 @@ // Clip offsets represent the state of the clip on the timeline as well as what type of clip it is. // it is created from a media id. +import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track"; + export class ClipUI { - version: number - type: "transform" | "audio" | "animation" | "lipsync" - group: "character" | "object" | "camera" | "global_audio" - name: string - media_id: string - object_uuid: string - offset: number // in frames - length: number // in frames + version: number; + type: ClipType; + group: ClipGroup; + name: string; + media_id: string; + object_uuid: string; + offset: number; // in frames + length: number; // in frames constructor( version: number, - type: "transform" | "audio" | "animation" | "lipsync", - group: "character" | "object" | "camera" | "global_audio", + type: ClipType, + group: ClipGroup, name: string, media_id: string, object_uuid: string, offset: number, length: number, ) { - this.version = version - this.group = group // Only needed for UI + this.version = version; + this.group = group; // Only needed for UI - this.name = name // UI - this.type = type // UI and Animation / Audio / Lipsync / : Engine + this.name = name; // UI + this.type = type; // UI and Animation / Audio / Lipsync / : Engine - this.object_uuid = object_uuid // Animation / Audio / Lipsync / : Engine - this.media_id = media_id // Animation / Audio / Lipsync / : Engine - this.offset = offset - this.length = length + this.object_uuid = object_uuid; // Animation / Audio / Lipsync / : Engine + this.media_id = media_id; // Animation / Audio / Lipsync / : Engine + this.offset = offset; + this.length = length; } toJSON(): string { return JSON.stringify({ version: this.version, - group:this.group, + group: this.group, name: this.name, type: this.type, object_uuid: this.object_uuid, media_id: this.media_id, start_offset: this.offset, - ending_offset: this.length - }) + ending_offset: this.length, + }); } } diff --git a/app/pages/PageEnigma/js/api_manager.ts b/app/pages/PageEnigma/js/api_manager.ts index f9e61c42f0..fde45ddd37 100644 --- a/app/pages/PageEnigma/js/api_manager.ts +++ b/app/pages/PageEnigma/js/api_manager.ts @@ -54,9 +54,9 @@ export enum Visibility { */ type Data = { [key: string]: any }; class APIManagerResponseSuccess { - public user_message: String; + public user_message: string; public data: Data | null; - constructor(user_message: String = "", data: Data | null = null) { + constructor(user_message: string = "", data: Data | null = null) { this.data = data; this.user_message = user_message; } @@ -74,7 +74,7 @@ class APIManagerResponseError extends Error { } export class APIManager { - baseUrl: String; + baseUrl: string; constructor() { this.baseUrl = "https://api.fakeyou.com"; @@ -139,9 +139,9 @@ export class APIManager { throw new APIManagerResponseError("Failed to download file"); } // Convert the response from a blob to json text - let blob = await file_response.blob(); + const blob = await file_response.blob(); const json_result: string = await new Promise((resolve, reject) => { - let reader = new FileReader(); + const reader = new FileReader(); reader.onloadend = () => resolve(JSON.parse(reader.result as string)); reader.onerror = reject; reader.readAsText(blob); @@ -156,7 +156,7 @@ export class APIManager { scene_glb_media_file_token, ); console.log(`GLB ${media_bucket_path}`); - let glbLoader = new GLTFLoader(); + const glbLoader = new GLTFLoader(); // promisify this const loadGlb = ( bucket_path: string, @@ -188,13 +188,13 @@ export class APIManager { * @returns */ private async getMediaFile(media_file_token: string): Promise { - let api_base_url = "https://api.fakeyou.com"; - let url = `${api_base_url}/v1/media_files/file/${media_file_token}`; - let response = await fetch(url); - let json = await JSON.parse(await response.text()); - let bucketPath = json["media_file"]["public_bucket_path"]; - let media_base_url = "https://storage.googleapis.com/vocodes-public"; - let media_url = `${media_base_url}${bucketPath}`; // gets you a bucket path + const api_base_url = "https://api.fakeyou.com"; + const url = `${api_base_url}/v1/media_files/file/${media_file_token}`; + const response = await fetch(url); + const json = await JSON.parse(await response.text()); + const bucketPath = json["media_file"]["public_bucket_path"]; + const media_base_url = "https://storage.googleapis.com/vocodes-public"; + const media_url = `${media_base_url}${bucketPath}`; // gets you a bucket path return media_url; } @@ -218,7 +218,7 @@ export class APIManager { scene_glb_media_file_token: string | null, ): Promise { const url = `${this.baseUrl}/v1/media_files/write/engine_asset`; - let uuid = uuidv4(); + const uuid = uuidv4(); const form_data = new FormData(); form_data.append("uuid_idempotency_token", uuid); @@ -257,7 +257,7 @@ export class APIManager { timeline_state: TimelineDataState | null, // only for now. ): Promise { const url = `${this.baseUrl}/v1/media_files/write/scene_file`; - let uuid = uuidv4(); + const uuid = uuidv4(); console.log( `Saving Scene scene_media_file_token:${scene_media_file_token} | scene_glb_media_file_token:${scene_glb_media_file_token}`, @@ -311,7 +311,7 @@ export class APIManager { fileName: string, ): Promise { const url = `${this.baseUrl}/v1/media_uploads/upload`; - let uuid = uuidv4(); + const uuid = uuidv4(); const formData = new FormData(); formData.append("uuid_idempotency_token", uuid); @@ -346,11 +346,11 @@ export class APIManager { negative_prompt: string, ): Promise { const url = `https://funnel.tailce84f.ts.net/preview/`; - + const payload = { style: style, positive_prompt: positive_prompt, - negative_prompt: negative_prompt + negative_prompt: negative_prompt, }; const formData = new FormData(); @@ -393,14 +393,15 @@ export class APIManager { return result; } - - public async stylizeVideo(media_token: string, - style: ArtStyle, - positive_prompt:string, - negative_prompt:string, - visibility:Visibility) { - let uuid = uuidv4(); - + public async stylizeVideo( + media_token: string, + style: ArtStyle, + positive_prompt: string, + negative_prompt: string, + visibility: Visibility, + ) { + const uuid = uuidv4(); + const data = { uuid_idempotency_token: uuid, style: style, @@ -410,10 +411,10 @@ export class APIManager { trim_start_millis: 0, trim_end_millis: 3000, enable_lipsync: true, - creator_set_visibility: visibility - } + creator_set_visibility: visibility, + }; - const json_data = JSON.stringify(data) + const json_data = JSON.stringify(data); const response = await fetch(`${this.baseUrl}/v1/video/enqueue_vst`, { method: "POST", @@ -435,4 +436,3 @@ export class APIManager { return await response.json(); } } - diff --git a/app/pages/PageEnigma/js/timeline.ts b/app/pages/PageEnigma/js/timeline.ts index 31c73a78b5..3bf8545c4b 100644 --- a/app/pages/PageEnigma/js/timeline.ts +++ b/app/pages/PageEnigma/js/timeline.ts @@ -11,6 +11,7 @@ import Queue from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "../Queue/QueueNames"; import { toEngineActions } from "../Queue/toEngineActions"; import { fromEngineActions } from "../Queue/fromEngineActions"; +import { ClipType } from "~/pages/PageEnigma/models/track"; // Every object uuid / entity has a track. export class TimelineCurrentState { @@ -169,10 +170,10 @@ export class TimeLine { ); switch (type) { - case "animation": + case ClipType.ANIMATION: this.animation_engine.load_object(object_uuid, media_id, name); break; - case "transform": + case ClipType.TRANSFORM: this.transform_engine.loadObject(object_uuid, data["data"]["length"]); break; } diff --git a/app/pages/PageEnigma/store/cameraGroup.ts b/app/pages/PageEnigma/store/cameraGroup.ts index 1330f9b872..093e751eb2 100644 --- a/app/pages/PageEnigma/store/cameraGroup.ts +++ b/app/pages/PageEnigma/store/cameraGroup.ts @@ -13,6 +13,11 @@ export const cameraGroup = signal({ id: "CG1", keyframes: [] }); export function updateCamera({ id, offset }: { id: string; offset: number }) { const oldCameraGroup = cameraGroup.value; + + if (oldCameraGroup.keyframes.some((row) => row.offset === offset)) { + return; + } + const newKeyframes = [...oldCameraGroup.keyframes]; const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id); if (!keyframe) { @@ -33,7 +38,10 @@ export function updateCamera({ id, offset }: { id: string; offset: number }) { } export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) { - console.log("cam", keyframe); + const oldCameraGroup = cameraGroup.value; + if (oldCameraGroup.keyframes.some((row) => row.offset === offset)) { + return; + } const newKeyframe = { version: keyframe.version, keyframe_uuid: uuid.v4(), @@ -46,7 +54,6 @@ export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) { selected: false, } as Keyframe; - const oldCameraGroup = cameraGroup.value; cameraGroup.value = { ...oldCameraGroup, keyframes: [...oldCameraGroup.keyframes, newKeyframe].sort( diff --git a/app/pages/PageEnigma/store/characterGroups.ts b/app/pages/PageEnigma/store/characterGroups.ts index 3d972fe95f..8bed1c6436 100644 --- a/app/pages/PageEnigma/store/characterGroups.ts +++ b/app/pages/PageEnigma/store/characterGroups.ts @@ -28,14 +28,16 @@ export function updateCharacters({ id, offset, length, + force, }: { - type: "animations" | "positions" | "lipSync"; + type: ClipType; id: string; - length: number; + length?: number; offset: number; + force?: boolean; }) { const oldCharacterGroups = characterGroups.value; - if (type === "animations") { + if (type === ClipType.ANIMATION) { characterGroups.value = oldCharacterGroups.map((character) => { const newAnimationClips = [...character.animationClips]; const clipIndex = newAnimationClips.findIndex( @@ -46,7 +48,7 @@ export function updateCharacters({ } const clip = newAnimationClips[clipIndex]; clip.offset = offset; - clip.length = length; + clip.length = length!; Queue.publish({ queueName: QueueNames.TO_ENGINE, @@ -61,7 +63,7 @@ export function updateCharacters({ }); } - if (type === "positions") { + if (type === ClipType.TRANSFORM) { characterGroups.value = oldCharacterGroups.map((character) => { const newPositionKeyframes = [...character.positionKeyframes]; const keyframeIndex = newPositionKeyframes.findIndex( @@ -85,7 +87,7 @@ export function updateCharacters({ }; }); } - if (type === "lipSync") { + if (type === ClipType.AUDIO) { characterGroups.value = oldCharacterGroups.map((character) => { const newLipSyncClips = [...character.lipSyncClips]; const clipIndex = newLipSyncClips.findIndex( @@ -96,7 +98,7 @@ export function updateCharacters({ } const clip = newLipSyncClips[clipIndex]; clip.offset = offset; - clip.length = length; + clip.length = length!; Queue.publish({ queueName: QueueNames.TO_ENGINE, @@ -204,6 +206,18 @@ export function addCharacterAudio({ } export function addCharacterKeyframe(keyframe: QueueKeyframe, offset: number) { + const oldCharacterGroups = characterGroups.value; + + if ( + oldCharacterGroups.some((characterGroup) => { + return characterGroup.positionKeyframes.some( + (row) => row.offset === offset, + ); + }) + ) { + return; + } + const newKeyframe = { version: keyframe.version, keyframe_uuid: uuid.v4(), @@ -216,7 +230,6 @@ export function addCharacterKeyframe(keyframe: QueueKeyframe, offset: number) { selected: false, } as Keyframe; - const oldCharacterGroups = characterGroups.value; characterGroups.value = oldCharacterGroups.map((character) => { if (character.id !== keyframe.object_uuid) { return { ...character }; diff --git a/app/pages/PageEnigma/store/dragAndDrop.ts b/app/pages/PageEnigma/store/dragAndDrop.ts index 0f286f36d6..ba3d1bec9a 100644 --- a/app/pages/PageEnigma/store/dragAndDrop.ts +++ b/app/pages/PageEnigma/store/dragAndDrop.ts @@ -1,6 +1,7 @@ import { signal } from "@preact/signals-core"; +import { ClipType } from "~/pages/PageEnigma/models/track"; -export const dragType = signal<"animations" | "audio" | null>(null); +export const dragType = signal(null); export const dragId = signal(null); export const canDrop = signal(false); export const overTimeline = signal(false); diff --git a/app/pages/PageEnigma/store/objectGroup.ts b/app/pages/PageEnigma/store/objectGroup.ts index f2778dad68..72304b0a86 100644 --- a/app/pages/PageEnigma/store/objectGroup.ts +++ b/app/pages/PageEnigma/store/objectGroup.ts @@ -15,16 +15,24 @@ export const objectGroup = signal({ }); export function updateObject({ id, offset }: { id: string; offset: number }) { - const oldObject = objectGroup.value; + const oldObjectGroup = objectGroup.value; + const obj = oldObjectGroup.objects.find((objectTrack) => + objectTrack.keyframes.some((row) => row.keyframe_uuid === id), + ); + + if (obj && obj.keyframes.some((row) => row.offset === offset)) { + return; + } + objectGroup.value = { - id: oldObject.id, - objects: oldObject.objects.map((object) => ({ + id: oldObjectGroup.id, + objects: oldObjectGroup.objects.map((object) => ({ object_uuid: object.object_uuid, name: object.name, - keyframes: object.keyframes.map((keyFrame) => { - if (keyFrame.keyframe_uuid !== id) { + keyframes: object.keyframes.map((keyframe) => { + if (keyframe.keyframe_uuid !== id) { return { - ...keyFrame, + ...keyframe, }; } @@ -32,13 +40,13 @@ export function updateObject({ id, offset }: { id: string; offset: number }) { queueName: QueueNames.TO_ENGINE, action: toEngineActions.UPDATE_KEYFRAME, data: { - ...keyFrame, + ...keyframe, offset, }, }); return { - ...keyFrame, + ...keyframe, offset, }; }), @@ -52,6 +60,10 @@ export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) { (row) => row.object_uuid === keyframe.object_uuid, ); + if (obj && obj.keyframes.some((row) => row.offset === offset)) { + return; + } + const newObject = obj ?? { object_uuid: keyframe.object_uuid, name: keyframe.object_name ?? "unknown", @@ -69,19 +81,16 @@ export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) { selected: false, } as Keyframe; newObject.keyframes.push(newKeyframe); - console.log(newObject); + newObject.keyframes.sort( (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, ); objectGroup.value = { ...oldObjectGroup, - objects: [ - ...oldObjectGroup.objects.filter( - (object) => object.object_uuid !== keyframe.object_uuid, - ), - newObject, - ].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)), + objects: [...oldObjectGroup.objects, newObject].sort((objA, objB) => + objA.object_uuid < objB.object_uuid ? -1 : 1, + ), }; Queue.publish({ queueName: QueueNames.TO_ENGINE,