Editor Refactoring (#402)

Co-authored-by: Christopher Oliphant <RALifeCoach@users.noreply.github.com>
Co-authored-by: Quicksticks-oss <chongobongotoday@gmail.com>
This commit is contained in:
authored and GitHub committed 2024-05-20 16:36:13 -04:00
1 parent 971286773b
commit 50889ed358
46 files changed
+1516 -1130

No files matched your search

+2
View File
@@ -23,6 +23,8 @@ module.exports = {
// Base config
extends: ["eslint:recommended", "plugin:storybook/recommended"],
ignorePatterns: ["storybook-static/*"],
overrides: [
// React
{
+5 -4
View File
@@ -1,4 +1,4 @@
export const apiHost = "https://api.fakeyou.com";
export const apiHost = "https://api.storyteller.ai";
// Authentications
// const createAccount = `${apiHost}}/create_account`;
@@ -19,7 +19,6 @@ export const uploadAudioV2V = `${apiHost}/v1/media_uploads/upload_audio`;
export const listMediaByUser = (username: string) =>
`${apiHost}/v1/media_files/list/user/${username}`;
// Get Any Media
export const getMediaFileByToken = (fileToken: string) =>
`${apiHost}/v1/media_files/file/${fileToken}`;
@@ -30,5 +29,7 @@ export const uploadMedia = `${apiHost}/v1/media_files/upload`;
// Scenes
export const uploadNewScene = `${apiHost}/v1/media_files/upload/new_scene`;
export const uploadThumbnail = `${apiHost}/v1/media_files/cover_image/`;
export const updateExistingScene = (sceneToken:string)=>`${apiHost}/v1/media_files/upload/saved_scene/${sceneToken}`
export const renameScene = (sceneToken:string)=>`${apiHost}/v1/media_files/rename/${sceneToken}`
export const updateExistingScene = (sceneToken: string) =>
`${apiHost}/v1/media_files/upload/saved_scene/${sceneToken}`;
export const renameScene = (sceneToken: string) =>
`${apiHost}/v1/media_files/rename/${sceneToken}`;
@@ -0,0 +1,48 @@
.fy-file-wrapper {
// flex-grow: 1;
// height: calc(100% - 4px);
.file-picker-button {
cursor: pointer;
}
.fy-file-wrapper-input {
visibility: hidden;
height: 0;
width: 0;
// overflow: hidden;
position: absolute;
+ label {
cursor: pointer;
}
}
.fy-uploader-layout {
display: flex;
align-items: center;
width: 100%;
.fy-uploader-layout-icon {
font-size: 2.625rem;
margin-right: 1rem;
}
}
}
.fy-file-dragging {
background: rgba(#676781, 0.25);
border-color: rgba(#fff, 0.33);
}
.fy-uploader {
[type="file"] {
+ label {
border: dashed 2px rgba(255, 255, 255, 0.12) !important;
border-radius: 0.5rem;
transition: 0.1s all;
&:hover {
// background-color: lighten($panel, 6%);
}
}
}
}
@@ -0,0 +1,12 @@
import React from "react";
export default function LoadingIcon() {
return <>
<span
className="spinner-border spinner-border-sm ms-3"
role="status"
aria-hidden="true"
></span>
<span className="visually-hidden">Loading...</span>
</>;
};
@@ -0,0 +1,12 @@
/**
* This is designed to surface user customer facing messages as errors.
* Errors shouldn't be 404 or something confusing should be
*/
class APIManagerResponseError extends Error {
constructor(message?: string) {
super(message);
this.name = "APIManagerResponseError";
}
}
export { APIManagerResponseError };
@@ -0,0 +1,14 @@
/**
* This is designed to surface user customer facing messages as errors.
*/
type Data = { [key: string]: any };
class APIManagerResponseSuccess {
public user_message: string;
public data: Data | null;
constructor(user_message: string = "", data: Data | null = null) {
this.data = data;
this.user_message = user_message;
}
}
export { APIManagerResponseSuccess };
@@ -22,5 +22,3 @@ export class AnimationEngine {
//this.clips[object_uuid + media_id]._load_animation();
}
}
export default AnimationEngine;
File diff suppressed because it is too large. Load diff
File renamed without changes.
+218
View File
@@ -0,0 +1,218 @@
import { EventDispatcher, Quaternion, Vector3, Vector2 } from "three";
import * as THREE from "three";
class FreeCam extends EventDispatcher {
object: THREE.PerspectiveCamera;
domElement: HTMLElement | Document;
enabled = true;
movementSpeed = 1.0;
rollSpeed = 0.005;
dragToLook = false;
autoForward = false;
lastMousePosition = new Vector2();
mouseVelocity = new Vector2();
tmpQuaternion = new Quaternion();
status = 0;
moveState = {
up: 0,
down: 0,
left: 0,
right: 0,
forward: 0,
back: 0,
pitchUp: 0,
pitchDown: 0,
yawLeft: 0,
yawRight: 0,
rollLeft: 0,
rollRight: 0,
};
moveVector = new Vector3(0, 0, 0);
rotationVector = new Vector3(0, 0, 0);
constructor(
object: THREE.PerspectiveCamera,
domElement: HTMLElement | Document,
) {
super();
this.object = object;
this.domElement = domElement;
this.keydown = this.keydown.bind(this);
this.keyup = this.keyup.bind(this);
this.reset = this.reset.bind(this);
this.update = this.update.bind(this);
this.updateMovementVector = this.updateMovementVector.bind(this);
this.getContainerDimensions = this.getContainerDimensions.bind(this);
this.dispose = this.dispose.bind(this);
window.addEventListener("keydown", this.keydown);
window.addEventListener("keyup", this.keyup);
this.updateMovementVector();
}
keydown(event: KeyboardEvent) {
if (event.altKey || !this.enabled) {
return;
}
switch (event.code) {
case "KeyW":
this.moveState.forward = 1;
break;
case "KeyS":
this.moveState.back = 1;
break;
case "KeyA":
this.moveState.left = 1;
break;
case "KeyD":
this.moveState.right = 1;
break;
case "KeyQ":
this.moveState.down = 1;
break;
case "KeyE":
this.moveState.up = 1;
break;
case "ArrowUp":
this.moveState.pitchUp = 1;
break;
case "ArrowDown":
this.moveState.pitchDown = 1;
break;
case "ArrowLeft":
this.moveState.yawLeft = 1;
break;
case "ArrowRight":
this.moveState.yawRight = 1;
break;
}
this.updateMovementVector();
}
keyup(event: KeyboardEvent) {
if (!this.enabled) {
return;
}
switch (event.code) {
case "KeyW":
this.moveState.forward = 0;
break;
case "KeyS":
this.moveState.back = 0;
break;
case "KeyA":
this.moveState.left = 0;
break;
case "KeyD":
this.moveState.right = 0;
break;
case "ArrowUp":
this.moveState.pitchUp = 0;
break;
case "ArrowDown":
this.moveState.pitchDown = 0;
break;
case "ArrowLeft":
this.moveState.yawLeft = 0;
break;
case "ArrowRight":
this.moveState.yawRight = 0;
break;
case "KeyQ":
this.moveState.down = 0;
break;
case "KeyE":
this.moveState.up = 0;
break;
}
this.updateMovementVector();
}
reset() {
this.moveState.forward = 0;
this.moveState.back = 0;
this.moveState.left = 0;
this.moveState.right = 0;
this.moveState.pitchUp = 0;
this.moveState.pitchDown = 0;
this.moveState.yawLeft = 0;
this.moveState.yawRight = 0;
this.moveState.down = 0;
this.moveState.up = 0;
this.updateMovementVector();
}
update(delta: number) {
if (!this.enabled) {
return;
}
const moveMulti = delta * this.movementSpeed;
this.object.translateX(this.moveVector.x * moveMulti);
this.object.translateY(this.moveVector.y * moveMulti);
this.object.translateZ(this.moveVector.z * moveMulti);
}
updateMovementVector() {
const forward =
this.moveState.forward || (this.autoForward && !this.moveState.back)
? 1
: 0;
this.moveVector.x = -this.moveState.left + this.moveState.right;
this.moveVector.y = -this.moveState.down + this.moveState.up;
this.moveVector.z = -forward + this.moveState.back;
}
getContainerDimensions() {
if (this.domElement !== document) {
return {
size: [
(this.domElement as HTMLElement).offsetWidth,
(this.domElement as HTMLElement).offsetHeight,
],
offset: [
(this.domElement as HTMLElement).offsetLeft,
(this.domElement as HTMLElement).offsetTop,
],
};
} else {
return {
size: [window.innerWidth, window.innerHeight],
offset: [0, 0],
};
}
}
dispose() {
// this.domElement.removeEventListener("contextmenu", this.contextmenu);
// this.domElement.removeEventListener("pointerdown", this.pointerdown);
// this.domElement.removeEventListener("pointermove", this.pointermove);
// this.domElement.removeEventListener("pointerup", this.pointerup);
// this.domElement.removeEventListener("pointercancel", this.pointercancel);
//
window.removeEventListener("keydown", this.keydown);
window.removeEventListener("keyup", this.keyup);
}
}
export { FreeCam };
+218
View File
@@ -0,0 +1,218 @@
import Scene from "./scene";
import Editor from "./editor";
import * as THREE from "three";
import { XYZ } from "../datastructures/common";
import { editorState } from "../signals/engine";
import { EditorStates } from "../enums";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
import { MediaItem } from "~/pages/PageEnigma/models";
import { AssetType } from "~/enums";
import { hideObjectPanel } from "../signals";
export class Utils {
scene: Scene;
editor: Editor;
constructor(editor: Editor, scene: Scene) {
this.scene = scene;
this.editor = editor;
}
// If string is empty.
isEmpty(value: string): boolean {
return (
value == null || (typeof value === "string" && value.trim().length === 0)
);
}
// For react to see if an object has lipsync capability or not.
isObjectLipsync(object_uuid: string) {
const object = this.scene.get_object_by_uuid(object_uuid);
let hasLipsync = false;
if (object) {
object.traverse((c: THREE.Object3D) => {
if (c instanceof THREE.Mesh) {
if (c.morphTargetInfluences && c.morphTargetDictionary) {
const blendShapeIndexE = c.morphTargetDictionary["E"];
// console.log(c.morphTargetDictionary, blendShapeIndexE)
if (blendShapeIndexE !== null) {
hasLipsync = true;
}
}
}
});
}
return hasLipsync;
}
// Returns if the object is locked or unlocked.
isObjectLocked(object_uuid: string): boolean {
const object = this.scene.get_object_by_uuid(object_uuid);
if (object) {
if (object.userData["locked"] == undefined) {
object.userData["locked"] = false;
}
return object.userData["locked"];
}
console.log("No object found.");
return false;
}
// Locks or unlocks and object and returns its new state,
lockUnlockObject(object_uuid: string): boolean {
const object = this.scene.get_object_by_uuid(object_uuid);
if (object) {
if (object.userData["locked"] == undefined) {
object.userData["locked"] = false;
}
object.userData["locked"] = !object.userData["locked"];
if (object.userData["locked"]) {
this.removeTransformControls(false);
} else if (this.editor.control) {
this.scene.scene.add(this.editor.control);
this.editor.control.attach(this.editor.selected);
}
return object.userData["locked"];
}
console.log("No object found.");
return false;
}
// Removes transform controls and publishes selected.
removeTransformControls(remove_outline: boolean = true) {
if (this.editor.control == undefined) {
return;
}
if (this.editor.outlinePass == undefined) {
return;
}
if (remove_outline) {
this.editor.last_selected = this.editor.selected;
this.editor.selected = undefined;
this.editor.publishSelect();
}
this.editor.control.detach();
this.editor.activeScene.scene.remove(this.editor.control);
if (remove_outline) this.editor.outlinePass.selectedObjects = [];
}
// TO UPDATE selected objects in the scene might want to add to the scene ...
async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) {
if (this.editor.selected != undefined || this.editor.selected != null) {
this.editor.selected.position.x = position.x;
this.editor.selected.position.y = position.y;
this.editor.selected.position.z = position.z;
this.editor.selected.rotation.x = THREE.MathUtils.degToRad(rotation.x);
this.editor.selected.rotation.y = THREE.MathUtils.degToRad(rotation.y);
this.editor.selected.rotation.z = THREE.MathUtils.degToRad(rotation.z);
this.editor.selected.scale.x = scale.x;
this.editor.selected.scale.y = scale.y;
this.editor.selected.scale.z = scale.z;
}
}
switchCameraView() {
this.editor.camera_person_mode = !this.editor.camera_person_mode;
this.editor.cameraViewControls?.reset();
if (this.editor.cam_obj) {
if (this.editor.camera_person_mode) {
this.editor.last_cam_pos.copy(this.editor.camera.position);
this.editor.last_cam_rot.copy(this.editor.camera.rotation);
this.editor.camera.position.copy(this.editor.cam_obj.position);
this.editor.camera.rotation.copy(this.editor.cam_obj.rotation);
if (this.editor.orbitControls) {
this.editor.orbitControls.enabled = false;
}
if (this.editor.lockControls) {
this.editor.activeScene.scene.add(this.editor.lockControls.getObject());
}
if (this.editor.cameraViewControls) {
this.editor.cameraViewControls.enabled = true;
}
this.editor.cam_obj.scale.set(0, 0, 0);
this.editor.removeTransformControls();
this.editor.selected = this.editor.cam_obj;
this.editor.publishSelect();
this.editor.updateSelectedUI();
editorState.value = EditorStates.CAMERA_VIEW;
if (this.editor.activeScene.hot_items) {
this.editor.activeScene.hot_items.forEach((element) => {
element.visible = false;
});
}
} else {
this.editor.camera.position.copy(this.editor.last_cam_pos);
this.editor.camera.rotation.copy(this.editor.last_cam_rot);
if (this.editor.orbitControls) {
this.editor.orbitControls.enabled = true;
}
if (this.editor.lockControls) {
this.editor.activeScene.scene.remove(this.editor.lockControls.getObject());
}
if (this.editor.cameraViewControls) {
this.editor.cameraViewControls.enabled = false;
}
this.editor.cam_obj.scale.set(1, 1, 1);
if (this.editor.activeScene.hot_items) {
this.editor.activeScene.hot_items.forEach((element) => {
element.visible = true;
});
}
hideObjectPanel();
editorState.value = EditorStates.EDIT;
}
}
}
// Returns the "check sum" of the editors selected object.
getselectedSum(): number {
if (this.editor.selected === undefined) {
return 0;
}
const posCombo =
this.editor.selected.position.x +
this.editor.selected.position.y +
this.editor.selected.position.z;
const rotCombo =
this.editor.selected.rotation.x +
this.editor.selected.rotation.y +
this.editor.selected.rotation.z;
const sclCombo =
this.editor.selected.scale.x + this.editor.selected.scale.y + this.editor.selected.scale.z;
return posCombo + rotCombo + sclCombo;
}
deleteObject(uuid: string) {
const obj = this.scene.get_object_by_uuid(uuid);
this.removeTransformControls();
if (obj?.name === this.editor.camera_name) {
return;
}
if (obj) {
this.scene.scene.remove(obj);
}
this.editor.timeline.deleteObject(uuid);
Queue.publish({
queueName: QueueNames.FROM_ENGINE,
action: fromEngineActions.DELETE_OBJECT,
data: {
version: 1,
type: AssetType.OBJECT,
media_id: "",
object_uuid: uuid,
name: "",
} as MediaItem,
});
this.editor.selected = undefined;
this.editor.publishSelect();
hideObjectPanel();
this.editor.timeline.deleteObject(uuid);
}
}
@@ -0,0 +1,140 @@
import Editor from "./editor";
import * as THREE from "three";
import { hideObjectPanel, hotkeysStatus, showObjectPanel } from "../signals";
export class MouseControls {
editor: Editor;
constructor(editor: Editor) {
this.editor = editor;
}
onMouseDown(event: any) {
if (event.button === 1 && this.editor.camera_person_mode) {
this.editor.lockControls?.lock();
}
}
onMouseUp(event: any) {
if (event.button === 1) {
this.editor.lockControls?.unlock();
}
if (event.button !== 0) {
const camera_pos = new THREE.Vector3(
parseFloat(this.editor.camera.position.x.toFixed(2)),
parseFloat(this.editor.camera.position.y.toFixed(2)),
parseFloat(this.editor.camera.position.z.toFixed(2)),
);
this.editor.camera_last_pos.copy(camera_pos);
}
}
onkeydown(event: KeyboardEvent) {
if (hotkeysStatus.value.disabled) {
return;
}
if (event.key === "f" && this.editor.selected && this.editor.orbitControls) {
this.editor.orbitControls.target.copy(this.editor.selected.position);
this.editor.orbitControls.maxDistance = 4;
this.editor.orbitControls.update();
this.editor.orbitControls.maxDistance = 999;
return;
}
if (event.key === " ") {
if (!this.editor.rendering && !this.editor.switchPreviewToggle && this.editor.selectedCanvas) {
this.editor.togglePlayback();
}
return;
}
if (event.key === "Backspace" || event.key === "Delete") {
if (this.editor.selected) {
this.editor.deleteObject(this.editor.selected.uuid);
}
return;
}
}
// Sets new mouse location usually used in raycasts.
onMouseMove(event: any) {
const rect = this.editor.canvReference.getBoundingClientRect();
if (this.editor.mouse == undefined) {
return;
}
this.editor.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
this.editor.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
this.editor.timeline.mouse = this.editor.mouse;
}
// When the mouse clicks the screen.
onMouseClick() {
const camera_pos = new THREE.Vector3(
parseFloat(this.editor.camera.position.x.toFixed(2)),
parseFloat(this.editor.camera.position.y.toFixed(2)),
parseFloat(this.editor.camera.position.z.toFixed(2)),
);
if (this.editor.camera_last_pos.equals(new THREE.Vector3(0, 0, 0))) {
this.editor.camera_last_pos.copy(camera_pos);
}
if (
this.editor.raycaster == undefined ||
this.editor.mouse == undefined ||
this.editor.control == undefined ||
this.editor.outlinePass == undefined ||
this.editor.camera_person_mode ||
!this.editor.camera_last_pos.equals(camera_pos)
) {
this.editor.camera_last_pos.copy(camera_pos);
return;
}
this.editor.camera_last_pos.copy(camera_pos);
this.editor.raycaster.setFromCamera(this.editor.mouse, this.editor.camera);
const interactable: any[] = [];
this.editor.activeScene.scene.children.forEach((child: THREE.Object3D) => {
if (child.name != "") {
if (
child.type == "Mesh" ||
child.type == "Object3D" ||
child.type == "Group"
) {
interactable.push(child);
}
}
});
const intersects = this.editor.raycaster.intersectObjects(interactable, true);
if (intersects.length > 0) {
if (intersects[0].object.type != "GridHelper") {
let currentObject = intersects[0].object;
while (currentObject.parent && currentObject.parent.type !== "Scene") {
currentObject = currentObject.parent;
}
this.editor.selected = currentObject;
// Show panel here
if (this.editor.selected.type == "Scene") {
this.editor.selected = intersects[0].object;
}
this.editor.activeScene.selected = this.editor.selected;
this.editor.publishSelect();
// this.editor.update_properties()
if (this.editor.selected.userData["locked"] !== true) {
this.editor.activeScene.scene.add(this.editor.control);
this.editor.control.attach(this.editor.selected);
}
this.editor.outlinePass.selectedObjects = [this.editor.selected];
this.editor.transform_interaction = true;
// Contact react land
showObjectPanel();
this.editor.updateSelectedUI();
}
} else {
this.editor.removeTransformControls();
hideObjectPanel();
}
}
}
@@ -20,5 +20,3 @@ export class LipSyncEngine {
);
}
}
export default LipSyncEngine;
File renamed without changes.
+109
View File
@@ -0,0 +1,109 @@
import { StoryTellerProxyScene } from "../proxy/storyteller_proxy_scene";
import { StoryTellerProxyTimeline } from "../proxy/storyteller_proxy_timeline";
import { hideEditorLoader, showEditorLoader } from "../signals";
import Editor from "./editor";
export type EditorInitializeConfig = {
sceneToken: string;
};
export class SaveManager {
editor: Editor
constructor(editor: Editor) {
this.editor = editor;
}
public async saveScene({
sceneTitle,
sceneToken,
}: {
sceneTitle: string;
sceneToken?: string;
}): Promise<string> {
this.editor.generating_preview = true; // Set this to true to stop control panel from flipping out.
// remove controls when saving scene.
this.editor.removeTransformControls();
showEditorLoader();
const proxyScene = new StoryTellerProxyScene(
this.editor.version,
this.editor.activeScene,
);
const scene_json = await proxyScene.saveToScene();
const proxyTimeline = new StoryTellerProxyTimeline(
this.editor.version,
this.editor.timeline,
this.editor.transform_engine,
this.editor.animation_engine,
this.editor.audio_engine,
this.editor.lipsync_engine,
this.editor.emotion_engine,
);
const timeline_json = await proxyTimeline.saveToJson();
const save_data = {
version: this.editor.version,
scene: scene_json,
timeline: timeline_json,
};
// TODO turn scene information into and object ...
let sceneThumbnail = undefined;
if (this.editor.renderer) {
const imgData = this.editor.renderer.domElement.toDataURL();
const response = await fetch(imgData); // Fetch the data URL
sceneThumbnail = await response.blob(); // Convert to Blob
}
const result = await this.editor.api_manager.saveSceneState({
saveJson: JSON.stringify(save_data),
sceneTitle,
sceneToken,
sceneThumbnail,
});
hideEditorLoader();
this.editor.generating_preview = false; // FIX THIS LATER WITH VICCCCCCCCCCCCCCCTORRRRRRRR
return result;
}
public async loadScene(scene_media_token: string) {
showEditorLoader();
this.editor.current_scene_media_token = scene_media_token;
const scene_json = await this.editor.api_manager
.loadSceneState(this.editor.current_scene_media_token)
.catch((err) => {
hideEditorLoader();
throw err;
});
const proxyScene = new StoryTellerProxyScene(
this.editor.version,
this.editor.activeScene,
);
await proxyScene.loadFromSceneJson(scene_json["scene"]);
this.editor.cam_obj = this.editor.activeScene.get_object_by_name(this.editor.camera_name);
this.editor.cam_obj?.layers.set(1);
const proxyTimeline = new StoryTellerProxyTimeline(
this.editor.version,
this.editor.timeline,
this.editor.transform_engine,
this.editor.animation_engine,
this.editor.audio_engine,
this.editor.lipsync_engine,
this.editor.emotion_engine,
);
await proxyTimeline.loadFromJson(scene_json["timeline"]);
this.editor.timeline.checkEditorCanPlay();
hideEditorLoader();
this.editor.timeline.scrub({ data: { currentTime: 0 } })
}
}
File renamed without changes.
@@ -5,32 +5,23 @@ import { ClipUI } from "../datastructures/clips/clip_ui";
import Scene from "./scene.js";
import AudioEngine from "./audio_engine";
import TransformEngine from "./transform_engine";
import LipSyncEngine from "./lip_sync_engine";
import AnimationEngine from "./animation_engine";
import { LipSyncEngine } from "./lip_sync_engine";
import { AnimationEngine } from "./animation_engine";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import Queue, {
UnionedActionTypes,
UnionedDataTypes,
} from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "../Queue/QueueNames";
import { toEngineActions } from "../Queue/toEngineActions";
import { fromEngineActions } from "../Queue/fromEngineActions";
import { AssetType } from "~/enums";
import { ClipGroup, ClipType } from "~/pages/PageEnigma/enums";
import { MediaItem } from "~/pages/PageEnigma/models";
import Editor from "~/pages/PageEnigma/js/editor";
import { Keyframe, MediaItem, UpdateTime } from "~/pages/PageEnigma/models";
import Editor from "~/pages/PageEnigma/Editor/editor";
import EmotionEngine from "./emotion_engine";
import { GenerationOptions } from '../models/generationOptions';
// Every object uuid / entity has a track.
export class TimelineDataState {
timeline_items: ClipUI[];
scrubber_frame_position: number;
constructor(
timeline_items: ClipUI[] = [],
scrubber_frame_position: number = 0,
) {
this.timeline_items = timeline_items;
this.scrubber_frame_position = scrubber_frame_position;
}
}
import { GenerationOptions } from "~/pages/PageEnigma/models/generationOptions";
import { Vector3 } from "three";
export class TimeLine {
editorEngine: Editor;
@@ -56,7 +47,7 @@ export class TimeLine {
characters: { [key: string]: ClipGroup };
scene: Scene;
camera: THREE.Camera;
camera: THREE.Camera | null;
mouse: THREE.Vector2 | undefined;
current_time: number;
@@ -71,7 +62,7 @@ export class TimeLine {
animation_engine: AnimationEngine,
emotion_engine: EmotionEngine,
scene: Scene,
camera: THREE.Camera,
camera: THREE.Camera | null,
mouse: THREE.Vector2 | undefined,
camera_name: string,
) {
@@ -103,7 +94,6 @@ export class TimeLine {
this.current_time = 0;
this.camera_name = camera_name;
}
public async updateUI() {
@@ -114,7 +104,7 @@ export class TimeLine {
});
}
public async pushEvent(action: fromEngineActions, data: any) {
public async pushEvent(action: fromEngineActions, data: UnionedDataTypes) {
//this.current_time += 0.75;
Queue.publish({
queueName: QueueNames.FROM_ENGINE,
@@ -123,47 +113,50 @@ export class TimeLine {
});
}
public async handleTimelineActions(data: any) {
const action = data["action"];
public async handleTimelineActions(data: {
action: UnionedActionTypes;
data: UnionedDataTypes;
}) {
const action = data.action;
switch (action) {
case toEngineActions.ADD_KEYFRAME:
await this.addKeyFrame(data);
await this.addKeyFrame(data.data as Keyframe);
break;
case toEngineActions.UPDATE_KEYFRAME:
await this.updateKeyFrame(data);
await this.updateKeyFrame(data.data as Keyframe);
break;
case toEngineActions.DELETE_KEYFRAME:
await this.deleteKeyFrame(data);
await this.deleteKeyFrame(data.data as Keyframe);
break;
case toEngineActions.ADD_CLIP:
await this.addClip(data);
await this.addClip(data.data as ClipUI);
break;
case toEngineActions.DELETE_CLIP:
await this.deleteClip(data);
await this.deleteClip(data.data as ClipUI);
break;
case toEngineActions.UPDATE_CLIP:
await this.updateClip(data);
await this.updateClip(data.data as ClipUI);
break;
case toEngineActions.UPDATE_TIME:
await this.scrub(data);
await this.scrub(data.data as UpdateTime);
break;
case toEngineActions.MUTE:
await this.mute(data, false);
await this.mute(data.data as ClipUI, false);
break;
case toEngineActions.UNMUTE:
await this.mute(data, true);
await this.mute(data.data as ClipUI, true);
break;
case toEngineActions.ADD_CHARACTER:
this.addCharacter(data);
this.addCharacter(data.data as MediaItem);
break;
case toEngineActions.ADD_OBJECT: {
const newObject = await this.addObject(data);
this.queueNewObjectMessage(newObject, data);
const newObject = await this.addObject(data.data as MediaItem);
this.queueNewObjectMessage(newObject, data.data as MediaItem);
break;
}
case toEngineActions.ADD_SHAPE: {
const newShape = await this.addShape(data);
this.queueNewObjectMessage(newShape, data);
const newShape = await this.addShape(data.data as MediaItem);
this.queueNewObjectMessage(newShape, data.data as MediaItem);
break;
}
case toEngineActions.ENTER_PREVIEW_STATE:
@@ -175,21 +168,22 @@ export class TimeLine {
case toEngineActions.TOGGLE_CAMERA_STATE:
this.editorEngine.switchCameraView();
break;
case toEngineActions.GENERATE_VIDEO:
const options = data["data"]; // super overloaded talk to the devs about this. TODO... refactor
this.editorEngine.generation_options = options;
case toEngineActions.GENERATE_VIDEO: {
const options = data.data; // super overloaded talk to the devs about this. TODO... refactor
this.editorEngine.generation_options = options as GenerationOptions;
this.editorEngine.generateVideo();
break;
}
default:
console.log("Action Not Wired", action);
}
}
public async addCharacter(data: { data: MediaItem }) {
const media_id = data.data.media_id;
const name = data.data.name;
public async addCharacter(data: MediaItem) {
const media_id = data.media_id;
const name = data.name;
const pos = this.getPos();
const new_data = { ...data.data };
const new_data = { ...data };
const obj = await this.scene.loadGlbWithPlaceholder(
media_id,
@@ -214,7 +208,7 @@ export class TimeLine {
this.addPlayableClip(
new ClipUI(
data.data["version"],
data.version,
ClipType.FAKE,
ClipGroup.CHARACTER,
"Default",
@@ -297,10 +291,10 @@ export class TimeLine {
return new THREE.Vector3(0, 0, 0);
}
public async addObject(data: { data: MediaItem }) {
public async addObject(data: MediaItem) {
const pos = this.getPos();
const media_id = data.data.media_id;
const name = data.data.name;
const media_id = data.media_id;
const name = data.name;
const obj = await this.scene.loadGlbWithPlaceholder(
media_id,
name,
@@ -313,12 +307,12 @@ export class TimeLine {
return obj;
}
public async addShape({ data }: { data: MediaItem }) {
public async addShape(data: MediaItem) {
const pos = this.getPos();
return this.editorEngine.create_parim(data.media_id, pos);
}
public async addKeyFrame(data: any) {
public async addKeyFrame(data: Keyframe) {
// KeyFrame Object
// version: number;
// clip_uuid: string;
@@ -329,9 +323,9 @@ export class TimeLine {
// rotation: XYZ;
// scale: XYZ;
// selected?: boolean;
const data_json = data["data"];
const uuid = data_json["object_uuid"];
const keyframe_uuid = data_json["keyframe_uuid"];
const data_json = data;
const uuid = data_json.object_uuid;
const keyframe_uuid = data_json.keyframe_uuid;
let object_name = this.scene.get_object_by_uuid(uuid)?.name;
if (object_name === undefined) {
@@ -340,12 +334,12 @@ export class TimeLine {
this.transform_engine.addFrame(
uuid,
data_json["offset"],
data_json["position"],
data_json["rotation"],
data_json["scale"],
data_json["offset"],
data_json["keyframe_uuid"],
data_json.offset,
data_json.position as Vector3,
data_json.rotation as Vector3,
data_json.scale as Vector3,
data_json.offset,
data_json.keyframe_uuid,
);
await this.addPlayableClip(
@@ -365,10 +359,10 @@ export class TimeLine {
);
const point = this.scene.createPoint(
data_json["position"],
data_json["rotation"],
data_json["scale"],
data_json["keyframe_uuid"],
data_json.position as Vector3,
data_json.rotation as Vector3,
data_json.scale as Vector3,
data_json.keyframe_uuid,
);
if (this.editorEngine.camera_person_mode) {
point.visible = false;
@@ -377,11 +371,7 @@ export class TimeLine {
}
public checkEditorCanPlay() {
if (this.getEndPoint() <= 1) {
this.editorEngine.can_playback = false;
} else {
this.editorEngine.can_playback = true;
}
this.editorEngine.can_playback = this.getEndPoint() > 1;
this.editorEngine.updateSelectedUI();
}
@@ -405,28 +395,25 @@ export class TimeLine {
// Update react land here.
}
public async addClip(data: any) {
const object_uuid = data["data"]["object_uuid"];
const media_id = data["data"]["media_id"];
const name = data["data"]["name"];
const group = data["data"]["group"];
const version = data["data"]["group"];
const type = data["data"]["type"];
const offset = data["data"]["offset"];
const end_offset = data["data"]["length"] + offset;
let object_name = this.scene.get_object_by_uuid(object_uuid)?.name;
const clip_uuid = data["data"]["clip_uuid"];
if (object_name === undefined) {
object_name = "Undefined.";
}
public async addClip(data: ClipUI) {
const object_uuid = data.object_uuid;
const media_id = data.media_id;
const name = data.name;
const group = data.group;
const version = 1;
const type = data.type;
const offset = data.offset;
const end_offset = data.length + offset;
const object_name =
this.scene.get_object_by_uuid(object_uuid)?.name ?? "undefined";
const clip_uuid = data.clip_uuid;
switch (type) {
case "animation":
this.animation_engine.load_object(object_uuid, media_id, name);
break;
case "transform":
this.transform_engine.loadObject(object_uuid, data["data"]["length"]);
this.transform_engine.loadObject(object_uuid, data.length);
break;
case "expression":
this.emotion_engine.loadClip(object_uuid, media_id);
@@ -475,9 +462,9 @@ export class TimeLine {
this.checkEditorCanPlay();
}
public async deleteKeyFrame(data: any) {
const keyframe_uuid = data["data"]["keyframe_uuid"];
const object_uuid = data["data"]["object_uuid"];
public async deleteKeyFrame(data: Keyframe) {
const keyframe_uuid = data.keyframe_uuid;
const object_uuid = data.object_uuid;
this.transform_engine.clips[object_uuid].removeKeyframe(keyframe_uuid);
this.scene.deletePoint(keyframe_uuid);
for (const element of this.timeline_items) {
@@ -498,14 +485,14 @@ export class TimeLine {
this.checkEditorCanPlay();
}
public async updateKeyFrame(data: any) {
const keyframe_uuid = data["data"]["keyframe_uuid"];
const keyframe_offset = data["data"]["offset"];
const object_uuid = data["data"]["object_uuid"];
public async updateKeyFrame(data: Keyframe) {
const keyframe_uuid = data.keyframe_uuid;
const keyframe_offset = data.offset;
const object_uuid = data.object_uuid;
const keyframe_pos = data["data"]["position"];
const keyframe_rot = data["data"]["rotation"];
const keyframe_scl = data["data"]["scale"];
const keyframe_pos = data.position;
const keyframe_rot = data.rotation;
const keyframe_scl = data.scale;
this.transform_engine.clips[object_uuid].setOffset(
keyframe_uuid,
@@ -513,26 +500,26 @@ export class TimeLine {
);
this.transform_engine.clips[object_uuid].setTransform(
keyframe_uuid,
keyframe_pos,
keyframe_rot,
keyframe_scl,
keyframe_pos as Vector3,
keyframe_rot as Vector3,
keyframe_scl as Vector3,
);
this.scene.updatePoint(
keyframe_uuid,
keyframe_pos,
keyframe_rot,
keyframe_scl,
keyframe_pos as Vector3,
keyframe_rot as Vector3,
keyframe_scl as Vector3,
);
this.checkEditorCanPlay();
}
public async updateClip(data: any) {
public async updateClip(data: ClipUI) {
// only length and offset changes here.
const object_uuid = data["data"]["object_uuid"];
const media_id = data["data"]["media_id"];
const offset = data["data"]["offset"];
const length = data["data"]["length"] + offset;
const clip_uuid = data["data"]["clip_uuid"];
const object_uuid = data.object_uuid;
const media_id = data.media_id;
const offset = data.offset;
const length = data.length + offset;
const clip_uuid = data.clip_uuid;
for (const element of this.timeline_items) {
if (
@@ -547,12 +534,10 @@ export class TimeLine {
this.checkEditorCanPlay();
}
public async deleteClip(data: any) {
//const json_data = data["data"];
const object_uuid = data["data"]["object_uuid"];
const media_id = data["data"]["media_id"];
//const type = data["type"];
const clip_uuid = data["data"]["clip_uuid"];
public async deleteClip(data: ClipUI) {
const object_uuid = data.object_uuid;
const media_id = data.media_id;
const clip_uuid = data.clip_uuid;
for (let i = 0; i < this.timeline_items.length; i++) {
const element = this.timeline_items[i];
@@ -569,13 +554,9 @@ export class TimeLine {
this.checkEditorCanPlay();
}
public async scrubberUpdate(data: any) {
console.log(data);
}
public async mute(data: any, isMute: boolean) {
public async mute(data: ClipUI, isMute: boolean) {
this.timeline_items.forEach((element) => {
if (element.group === data.data["group"]) {
if (element.group === data.group) {
element.should_play = isMute;
}
});
@@ -585,28 +566,20 @@ export class TimeLine {
this.timeline_items.push(clip);
}
public async scrub(data: any): Promise<void> {
public async scrub(data: UpdateTime): Promise<void> {
if (this.is_playing) {
return;
}
const value = Math.floor(data["data"]["currentTime"]);
this.setScrubberPosition(value);
const value = Math.floor(data.currentTime);
await this.setScrubberPosition(value);
this.current_time = value;
this.update();
await this.update();
if (this.editorEngine.switchPreviewToggle) {
this.editorEngine.generateFrame();
await this.editorEngine.generateFrame();
}
}
public async stepFrame(frames: number) {
this.update();
this.scrubber_frame_position += frames;
this.pushEvent(fromEngineActions.UPDATE_TIME, {
currentTime: this.scrubber_frame_position,
});
}
// public streaming events into the timeline from
public async setScrubberPosition(offset: number) {
this.scrubber_frame_position = offset; // in ms
@@ -696,7 +669,7 @@ export class TimeLine {
if (
element.offset <= this.scrubber_frame_position &&
this.scrubber_frame_position <= element.length &&
element.should_play === true
element.should_play
) {
// run async
// element.play()
@@ -750,8 +723,12 @@ export class TimeLine {
object.uuid + element.media_id
].play(object);
const fps = 60;
await this.animation_engine.clips[object.uuid + element.media_id].step(
(this.scrubber_frame_position-element.offset) / fps, this.is_playing, this.scrubber_frame_position// Double FPS for best result.
await this.animation_engine.clips[
object.uuid + element.media_id
].step(
(this.scrubber_frame_position - element.offset) / fps,
this.is_playing,
this.scrubber_frame_position, // Double FPS for best result.
);
//this.animation_engine.clips[object.uuid + element.media_id].update_bones();
}
@@ -0,0 +1,301 @@
import Editor from "./editor";
import { EditorStates } from "~/pages/PageEnigma/enums";
import { editorState, previewSrc } from "../signals/engine";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
import { ToastTypes } from "~/enums";
import { createFFmpeg, fetchFile, FFmpeg } from "@ffmpeg/ffmpeg";
import { ClipUI } from "../datastructures/clips/clip_ui.js";
import { ClipType } from "~/pages/PageEnigma/enums";
import { Visibility } from "./api_manager.js";
import * as THREE from "three";
import { getSceneSignals } from "~/signals";
export class VideoGeneration {
editor: Editor;
constructor(editor: Editor) {
this.editor = editor;
}
async generateFrame() {
if (!this.editor.generating_preview && this.editor.rawRenderer) {
this.editor.generating_preview = true;
this.editor.removeTransformControls();
this.editor.activeScene.renderMode(true);
if (this.editor.activeScene.hot_items) {
this.editor.activeScene.hot_items.forEach((element) => {
element.visible = false;
});
}
// if (this.editor.render_camera && this.editor.cam_obj) {
// this.editor.render_camera.position.copy(this.editor.cam_obj.position);
// this.editor.render_camera.rotation.copy(this.editor.cam_obj.rotation);
// }
previewSrc.value = "";
this.editor.rawRenderer.setSize(
this.editor.render_width,
this.editor.render_height,
);
this.editor.render_camera.aspect =
this.editor.render_width / this.editor.render_height;
this.editor.render_camera.updateProjectionMatrix();
this.editor.rawRenderer.render(
this.editor.activeScene.scene,
this.editor.render_camera,
);
const imgData = this.editor.rawRenderer.domElement.toDataURL();
const response = await fetch(imgData); // Fetch the data URL
const blob = await response.blob(); // Convert to Blob
if (!this.editor.camera_person_mode) {
this.editor.switchCameraView();
editorState.value = EditorStates.PREVIEW;
}
this.editor.generating_preview = false;
try {
const url = await this.editor.api_manager.uploadMediaFrameGeneration(
blob,
"render.png",
this.editor.art_style,
this.editor.positive_prompt,
this.editor.negative_prompt,
);
previewSrc.value = url;
return Promise.resolve(url);
} catch (err: any) {
Queue.publish({
queueName: QueueNames.FROM_ENGINE,
action: fromEngineActions.POP_A_TOAST,
data: {
type: ToastTypes.ERROR,
message: err.message,
},
});
}
}
}
async convertAudioClip(itteration: number, ffmpeg: FFmpeg, clip: ClipUI) {
// Base audio clip names.
const video_og: string = itteration + "tmp.mp4";
const wav_name: string = itteration + "tmp.wav";
const new_video: string = itteration + 1 + "tmp.mp4";
let startFrame: number = clip.offset;
let endFrame: number = clip.length;
if (endFrame > this.editor.timeline.timeline_limit) {
endFrame = this.editor.timeline.timeline_limit;
}
if (startFrame > this.editor.timeline.timeline_limit) {
startFrame = this.editor.timeline.timeline_limit - 1;
}
const startTime: number = startFrame / this.editor.cap_fps;
const endTime: number = endFrame / this.editor.cap_fps;
const end: number = endTime - startTime;
// Use ffmpeg to convert audio to video.
const audioSegment: string = "as_" + wav_name;
await ffmpeg.FS(
"writeFile",
wav_name,
await fetchFile(
await this.editor.api_manager.getMediaFile(clip.media_id),
),
);
await ffmpeg.run(
"-i",
wav_name,
"-ss",
"0",
"-to",
"" + end,
"-max_muxing_queue_size",
"999999",
audioSegment,
);
await ffmpeg.run(
"-i",
video_og,
"-max_muxing_queue_size",
"999999",
`${itteration}empty_tmp.wav`,
);
await ffmpeg.run(
"-i",
`${itteration}empty_tmp.wav`,
"-i",
audioSegment,
"-filter_complex",
"[1:a]adelay=" +
startTime * 1000 +
"|" +
startTime * 1000 +
"[a1];[0:a][a1]amix=inputs=2[a]",
"-map",
"[a]",
`${itteration}final_tmp.wav`,
);
// Combine final audio with video.
await ffmpeg.run(
"-i",
video_og,
"-i",
`${itteration}final_tmp.wav`,
"-c:v",
"copy",
"-c:a",
"aac",
"-map",
"0:v:0",
"-map",
"1:a:0",
"-strict",
"experimental",
new_video,
);
}
async stopPlaybackAndUploadVideo(compile_audio: boolean = true) {
this.editor.rendering = false;
//const videoBlob = new Blob(this.frame_buffer, { type: "video/webm" });
//const videoURL = URL.createObjectURL(videoBlob);
this.editor.generating_preview = true;
const ffmpeg = createFFmpeg({ log: false });
await ffmpeg.load();
this.editor.updateLoad(50, "Processing ...");
// Write the Uint8Array to the FFmpeg file system
//ffmpeg.FS("writeFile", "input.webm", await fetchFile(videoURL));
for (let index = 0; index < this.editor.frame_buffer.length; index++) {
const element = this.editor.frame_buffer[index];
await ffmpeg.FS(
"writeFile",
`image${index}.png`,
await fetchFile(element),
);
}
await ffmpeg.run(
"-framerate",
"" + this.editor.cap_fps,
"-i",
"image%d.png",
"input.mp4",
);
await ffmpeg.run(
"-i",
"input.mp4",
"-f",
"lavfi",
"-i",
"anullsrc", // This adds a silent audio track
"-max_muxing_queue_size",
"999999",
"-vf",
"select=gte(n\\,1),scale=1024:576", // scale=1024:576
"-c:v",
"libx264", // Specify video codec (optional, but recommended for MP4)
"-c:a",
"aac", // Specify audio codec (optional, but recommended for MP4)
"-shortest", // Ensure output duration matches the shortest stream (video or audio)
"-pix_fmt",
"yuv420p",
"-f",
"mp4",
"0tmp.mp4",
);
let itteration = 0;
if (compile_audio) {
for (const clip of this.editor.timeline.timeline_items) {
if (clip.type == ClipType.AUDIO) {
await this.convertAudioClip(itteration, ffmpeg, clip);
itteration += 1;
}
}
}
const output = ffmpeg.FS("readFile", itteration + "tmp.mp4");
ffmpeg.exit();
this.editor.generating_preview = false;
// Create a Blob from the output file for downloading
const blob = new Blob([output.buffer], { type: "video/mp4" });
const title = getSceneSignals().title || "Untitled";
const style_name = this.editor.art_style.toString();
const media_token = this.editor.current_scene_media_token || undefined;
const data: any = await this.editor.api_manager.uploadMedia({
blob,
fileName: `${title}.mp4`,
title,
styleName: style_name,
maybe_scene_source_media_file_token: media_token,
});
if (data == null) {
return;
}
const upload_token = data["media_file_token"];
const result = await this.editor.api_manager
.stylizeVideo(
upload_token,
this.editor.art_style,
this.editor.positive_prompt,
this.editor.negative_prompt,
Visibility.Public,
this.editor.generation_options.faceDetail,
this.editor.generation_options.upscale,
this.editor.generation_options.styleStrength,
)
.catch((error) => {
// TODO handle stylize error.
console.log(error);
});
// {"success":true,"inference_job_token":"jinf_j3nbqbd15wqxb0xcks13qh3f3bz"}
this.editor.updateLoad(100, "Done Check Your Media Tab On Profile.");
this.editor.endLoading();
this.editor.recorder = undefined;
if (this.editor.rawRenderer) {
this.editor.rawRenderer.setSize(
this.editor.startRenderWidth,
this.editor.startRenderHeight,
);
}
this.editor.canvasRenderCamReference =
document.getElementById("camera-view");
this.editor.rawRenderer = new THREE.WebGLRenderer({
antialias: false,
canvas: this.editor.canvasRenderCamReference,
preserveDrawingBuffer: true,
});
this.editor.activeScene.renderMode(false);
this.editor.switchEdit();
}
}
+2 -2
View File
@@ -18,12 +18,12 @@ export type ToastDataType = {
message: string;
};
type UnionedActionTypes =
export type UnionedActionTypes =
| fromEngineActions
| toEngineActions
| toTimelineActions;
type UnionedDataTypes =
export type UnionedDataTypes =
| QueueClip
| UpdateTime
| QueueKeyframe
@@ -65,8 +65,6 @@ export const ControlPanelSceneObject = () => {
const [color, setColor] = useState("#ffffff");
console.log("color", color);
const colorInputId = useId();
const toggleCollapse = () => {
@@ -111,8 +109,6 @@ export const ControlPanelSceneObject = () => {
return;
}
console.log("44");
const vectors = currentSceneObject.objectVectors;
// local state relies on strings
@@ -179,7 +175,12 @@ export const ControlPanelSceneObject = () => {
const handleOnAddKeyFrame = () => {
if (currentSceneObject) {
if (position == null || rotation == null || scale == null) {
if (
position == null ||
rotation == null ||
scale == null ||
currentSceneObject == null
) {
return;
}
@@ -1,5 +1,5 @@
import { twMerge } from "tailwind-merge";
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
import { H4 } from "~/components";
import { ImgHTMLAttributes } from "react";
@@ -37,7 +37,8 @@ export const ItemPicker = ({
maxWidth: (width as number) + 4,
maxHeight: (height as number) + 4,
}}
onClick={handleSelected}>
onClick={handleSelected}
>
<img
className="object-fill"
{...imgProps}
@@ -58,7 +59,8 @@ export const ItemPicker = ({
viewBox="0 0 512 512"
className={`absolute right-[5px] top-[5px] h-[18px] w-[18px] shadow-xl transition-opacity duration-200 ease-in-out ${
selected ? "opacity-100" : "opacity-0"
}`}>
}`}
>
<path
opacity="1"
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c-9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
@@ -9,7 +9,7 @@ import {
RandomTextsPositive,
RandomTextsNegative,
} from "~/pages/PageEnigma/constants/RandomTexts";
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
interface Props {
selection: ArtStyle;
@@ -3,7 +3,7 @@ import { useContext, useState } from "react";
import { ButtonIcon } from "~/components";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
import { faAngleLeft, faAngleRight } from "@fortawesome/pro-solid-svg-icons";
import { styleList } from "~/pages/PageEnigma/styleList";
import { sidePanelHeight, sidePanelWidth } from "~/pages/PageEnigma/signals";
@@ -5,7 +5,7 @@ import { StyleButtons } from "./StyleButtons";
import { sidePanelHeight } from "~/pages/PageEnigma/signals";
import { useSignals } from "@preact/signals-react/runtime";
import { useState } from "react";
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
import { styleList } from "~/pages/PageEnigma/styleList";
export function StylizeTab() {
@@ -1,6 +1,6 @@
import { twMerge } from "tailwind-merge";
import { H4 } from "~/components";
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
interface ItemPickerProps extends React.ImgHTMLAttributes<HTMLImageElement> {
label: string;
@@ -4,7 +4,7 @@ import { faAngleLeft, faAngleRight } from "@fortawesome/pro-solid-svg-icons";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { styleList } from "~/pages/PageEnigma/styleList";
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
import { ButtonIcon, Label, Textarea } from "~/components";
import { ItemPicker } from "./ItemPicker";
@@ -20,6 +20,7 @@ import {
QueueKeyframe,
UpdateTime,
} from "~/pages/PageEnigma/models";
import { ClipGroup } from "~/pages/PageEnigma/enums";
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
import { addToast } from "~/signals";
import { ToastTypes } from "~/enums";
@@ -1,4 +1,4 @@
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
export const RandomTextsPositive: Record<ArtStyle, string[]> = {
[ArtStyle.AnimeStandard]: [
@@ -1,4 +1,4 @@
import { createContext } from "react";
import Editor from "~/pages/PageEnigma/js/editor";
import Editor from "~/pages/PageEnigma/Editor/editor";
export const EngineContext = createContext<Editor|null>(null);
export const EngineContext = createContext<Editor | null>(null);
@@ -1,7 +1,7 @@
import { ReactNode, useEffect, useState } from "react";
import { EngineContext } from "./EngineContext";
import Editor from "~/pages/PageEnigma/js/editor";
import Editor from "~/pages/PageEnigma/Editor/editor";
interface Props {
sceneToken?: string;
@@ -2,7 +2,7 @@ import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { FBXLoader } from "three/examples/jsm/Addons.js";
import { MoveAIResult, Retarget } from "../../js/retargeting";
import { MoveAIResult, Retarget } from "../../Editor/retargeting";
import { environmentVariables } from "~/signals";
export class AnimationClip {
@@ -1,4 +1,4 @@
import { LipSync } from "../../js/lipsync";
import { LipSync } from "../../Editor/lipsync";
import * as THREE from "three";
import { environmentVariables } from "~/signals";
interface AudioData {
@@ -0,0 +1,5 @@
export enum AssetFilterOption {
ALL,
MINE,
BOOKMARKED,
}
+10
View File
@@ -0,0 +1,10 @@
export enum AssetType {
ANIMATION = "animation",
AUDIO = "audio",
CAMERA = "camera",
CHARACTER = "character",
EXPRESSION = "expression",
OBJECT = "object",
SHAPE = "shape",
STYLE = "style", // TODO Remove
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { UserDetailsLight } from "./voice";
import { UserDetailsLight } from "./types";
import {
MediaFileClass,
MediaFileSubtype,
@@ -4,7 +4,7 @@ import {
StoryTellerProxy3DObject,
ObjectJSON,
} from "./storyteller_proxy_3d_object";
import Scene from "../js/scene";
import Scene from "../Editor/scene";
interface LookUpDictionary {
[key: string]: StoryTellerProxy3DObject;
@@ -70,7 +70,9 @@ export class StoryTellerProxyScene {
let obj;
switch (token) {
case "Parim":
let prim_uuid = this.scene.instantiate(json_object.object_name).uuid;
let prim_uuid = this.scene.instantiate(
json_object.object_name,
).uuid;
obj = this.scene.get_object_by_uuid(prim_uuid);
break;
case "DirectionalLight":
@@ -78,7 +80,10 @@ export class StoryTellerProxyScene {
break;
default:
if (token.includes("m_")) {
obj = await this.scene.loadGlbWithPlaceholder(token, json_object.object_name);
obj = await this.scene.loadGlbWithPlaceholder(
token,
json_object.object_name,
);
} else if (token.includes("Point::")) {
let keyframe_uuid = token.replace("Point::", "");
obj = this.scene.createPoint(
@@ -4,151 +4,208 @@ import { ClipUI } from "../datastructures/clips/clip_ui";
import { EmotionClip } from "../datastructures/clips/emotion_clip";
import { LipSyncClip } from "../datastructures/clips/lipsync_clip";
import { TransformClip } from "../datastructures/clips/transform_clip";
import AnimationEngine from "../js/animation_engine";
import AudioEngine from "../js/audio_engine";
import EmotionEngine from "../js/emotion_engine";
import LipSyncEngine from "../js/lip_sync_engine";
import { TimeLine } from "../js/timeline";
import TransformEngine from "../js/transform_engine";
import AnimationEngine from "../Editor/animation_engine";
import AudioEngine from "../Editor/audio_engine";
import EmotionEngine from "../Editor/emotion_engine";
import LipSyncEngine from "../Editor/lip_sync_engine";
import { TimeLine } from "../Editor/timeline";
import TransformEngine from "../Editor/transform_engine";
export class StoryTellerProxyTimeline {
timeline: TimeLine
transform_engine: TransformEngine
animation_engine: AnimationEngine
audio_engine: AudioEngine
lipsync_engine: LipSyncEngine
emotion_engine: EmotionEngine
timeline: TimeLine;
transform_engine: TransformEngine;
animation_engine: AnimationEngine;
audio_engine: AudioEngine;
lipsync_engine: LipSyncEngine;
emotion_engine: EmotionEngine;
constructor(version: number, timeline: TimeLine, transform_engine: TransformEngine, animation_engine: AnimationEngine, audio_engine: AudioEngine, lipsync_engine: LipSyncEngine, emotion_engine: EmotionEngine) {
this.timeline = timeline;
this.transform_engine = transform_engine;
this.animation_engine = animation_engine;
this.audio_engine = audio_engine;
this.lipsync_engine = lipsync_engine;
this.emotion_engine = emotion_engine;
constructor(
version: number,
timeline: TimeLine,
transform_engine: TransformEngine,
animation_engine: AnimationEngine,
audio_engine: AudioEngine,
lipsync_engine: LipSyncEngine,
emotion_engine: EmotionEngine,
) {
this.timeline = timeline;
this.transform_engine = transform_engine;
this.animation_engine = animation_engine;
this.audio_engine = audio_engine;
this.lipsync_engine = lipsync_engine;
this.emotion_engine = emotion_engine;
}
private async getItems(items: any[]): Promise<any[]> {
let timeline_items_data = [];
for (let index = 0; index < items.length; index++) {
const element = items[index];
timeline_items_data.push(element.toJSON());
}
return timeline_items_data;
}
private async getItems(items: any[]): Promise<any[]> {
let timeline_items_data = [];
for (let index = 0; index < items.length; index++) {
const element = items[index];
timeline_items_data.push(element.toJSON());
};
return timeline_items_data;
private async getItemsDict(items: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = element.toJSON();
}
}
return timeline_items_data;
}
private async getItemsDict(items: { [key: string]: any }): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = element.toJSON();
}
}
return timeline_items_data;
public async saveToJson(): Promise<any> {
let timeline_json = {
timeline: await this.getItems(this.timeline.timeline_items),
transform: await this.getItemsDict(this.transform_engine.clips),
animation: await this.getItemsDict(this.animation_engine.clips),
audio: await this.getItemsDict(this.audio_engine.clips),
lipsync: await this.getItemsDict(this.lipsync_engine.clips),
emotion: await this.getItemsDict(this.emotion_engine.clips),
};
console.log(timeline_json);
return timeline_json;
}
private async loadTimelineClips(timeline_clips: any[]): Promise<any[]> {
let new_clips = [];
if (timeline_clips) {
for (let index = 0; index < timeline_clips.length; index++) {
const element = timeline_clips[index];
let clip = new ClipUI(
element.version,
element.type,
element.group,
element.name,
element.media_id,
element.clip_uuid,
element.object_uuid,
element.object_name,
element.start_offset,
element.ending_offset,
element.keyframe_offset,
);
new_clips.push(clip);
}
}
return new_clips;
}
public async saveToJson(): Promise<any> {
let timeline_json = {
timeline: await this.getItems(this.timeline.timeline_items),
transform: await this.getItemsDict(this.transform_engine.clips),
animation: await this.getItemsDict(this.animation_engine.clips),
audio: await this.getItemsDict(this.audio_engine.clips),
lipsync: await this.getItemsDict(this.lipsync_engine.clips),
emotion: await this.getItemsDict(this.emotion_engine.clips),
}
console.log(timeline_json)
return timeline_json;
private async loadTransformClips(items: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new TransformClip(
element.version,
element.object_uuid,
element.length,
element.media_id,
);
timeline_items_data[key].keyframes = element.keyframes;
}
}
return timeline_items_data;
}
private async loadTimelineClips(timeline_clips: any[]): Promise<any[]> {
let new_clips = [];
if (timeline_clips) {
for (let index = 0; index < timeline_clips.length; index++) {
const element = timeline_clips[index];
let clip = new ClipUI(
element.version, element.type,
element.group, element.name,
element.media_id, element.clip_uuid,
element.object_uuid,
element.object_name,
element.start_offset, element.ending_offset,
element.keyframe_offset);
new_clips.push(clip);
}
}
return new_clips;
private async loadAudioClips(items: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new AudioClip(
element.version,
element.media_id,
element.volume,
);
}
}
return timeline_items_data;
}
private async loadTransformClips(items: { [key: string]: any }): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new TransformClip(element.version, element.object_uuid, element.length, element.media_id);
timeline_items_data[key].keyframes = element.keyframes;
}
}
return timeline_items_data;
private async loadAnimationClips(items: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new AnimationClip(
element.version,
element.media_id,
"remote",
element.object_uuid,
element.speed,
element.length,
element.clip_name,
);
}
}
return timeline_items_data;
}
private async loadAudioClips(items: { [key: string]: any }): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new AudioClip(element.version, element.media_id, element.volume);
}
}
return timeline_items_data;
private async loadLipsyncClips(items: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new LipSyncClip(
element.version,
element.media_id,
element.volume,
);
}
}
return timeline_items_data;
}
private async loadAnimationClips(items: { [key: string]: any }): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new AnimationClip(element.version, element.media_id, "remote", element.object_uuid, element.speed, element.length, element.clip_name);
}
}
return timeline_items_data;
private async loadEmotionClips(items: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new EmotionClip(
element.version,
element.media_id,
);
}
}
return timeline_items_data;
}
private async loadLipsyncClips(items: { [key: string]: any }): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new LipSyncClip(element.version, element.media_id, element.volume);
}
}
return timeline_items_data;
}
public async loadFromJson(timeline: any) {
this.timeline.timeline_items = await this.loadTimelineClips(
timeline["timeline"],
);
this.transform_engine.clips = await this.loadTransformClips(
timeline["transform"],
);
this.audio_engine.clips = await this.loadAudioClips(timeline["audio"]);
this.lipsync_engine.clips = await this.loadLipsyncClips(
timeline["lipsync"],
);
this.animation_engine.clips = await this.loadAnimationClips(
timeline["animation"],
);
this.emotion_engine.clips = await this.loadEmotionClips(
timeline["emotion"],
);
private async loadEmotionClips(items: { [key: string]: any }): Promise<{ [key: string]: any }> {
let timeline_items_data: { [key: string]: any } = {};
for (const key in items) {
if (items.hasOwnProperty(key)) {
const element = items[key];
timeline_items_data[key] = new EmotionClip(element.version, element.media_id);
}
}
return timeline_items_data;
}
public async loadFromJson(timeline: any) {
this.timeline.timeline_items = await this.loadTimelineClips(timeline['timeline']);
this.transform_engine.clips = await this.loadTransformClips(timeline['transform']);
this.audio_engine.clips = await this.loadAudioClips(timeline['audio']);
this.lipsync_engine.clips = await this.loadLipsyncClips(timeline['lipsync']);
this.animation_engine.clips = await this.loadAnimationClips(timeline['animation']);
this.emotion_engine.clips = await this.loadEmotionClips(timeline['emotion']);
console.log(this.timeline.timeline_items)
this.timeline.updateUI()
}
console.log(this.timeline.timeline_items);
this.timeline.updateUI();
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
import { ArtStyle } from "~/pages/PageEnigma/Editor/api_manager";
export const styleList = [
{