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,