From dcc8bd71942cca0cdfe07b2cd5e592ca0045f501 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Tue, 26 Mar 2024 00:03:05 -0400 Subject: [PATCH] Saving some api manager work and beginning to serialize the timeline --- app/pages/PageEnigma/js/api_manager.ts | 163 +++++++++++++++---------- app/pages/PageEnigma/js/audio_clip.js | 34 ------ app/pages/PageEnigma/js/editor.js | 27 +++- app/pages/PageEnigma/js/timeline.ts | 8 +- 4 files changed, 126 insertions(+), 106 deletions(-) delete mode 100644 app/pages/PageEnigma/js/audio_clip.js diff --git a/app/pages/PageEnigma/js/api_manager.ts b/app/pages/PageEnigma/js/api_manager.ts index 6672e6ccce..ba996efb54 100644 --- a/app/pages/PageEnigma/js/api_manager.ts +++ b/app/pages/PageEnigma/js/api_manager.ts @@ -1,107 +1,142 @@ -import { v4 as uuidv4 } from 'uuid' -import { Scene } from '../datastructures/scene/scene_object' -import * as THREE from 'three' -import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js' -import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' -import { TimelineCurrentReactState } from './timeline' +import { v4 as uuidv4 } from "uuid"; +import { Scene } from "../datastructures/scene/scene_object"; +import * as THREE from "three"; +import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { TimelineCurrentReactState } from "./timeline"; -// Basically the data will exist on the database +// Basically the data will exist on the database class APIManager { - baseUrl:String + baseUrl: String; constructor() { - this.baseUrl = "https://api.fakeyou.com" + this.baseUrl = "https://api.fakeyou.com"; //this.baseUrl = "http://localhost:12345" } - // Function to save or update the scene - async saveSceneState(scene:THREE.Scene, - timeLine:TimelineCurrentReactState, - scene_id:string | null =null):Promise { - if (scene_id == null) { + // it will always return a boolean success and the media_file_token + // they take an optional media_file_token as a parameter for the file to overwrite, which must belong to the user. + //If it's an anonymous user, we check the anonymous visitor token/cookie + async saveSceneState( + scene: THREE.Scene, + timeline: TimelineCurrentReactState, + media_file_token: string | null = null, + ): Promise { + if (media_file_token == null) { // create new scene. - let file = await this.gltfExport(scene) - let response = await this.uploadGLB(file) - console.log(response) + let file = await this.gltfExport(scene); + let response = await this.uploadGLB(file); + + let success = response["success"]; + if (success) { + let media_file_token = response["media_file_token"]; + await this._saveSceneAndTimelineToJSONSpec(media_file_token, timeline); + } else { + throw Error("Saving the Scene Resulted in an Error Success False"); + } } else { // update scene or overwrite glb scene. } - return "" + return ""; } - async _saveSceneAndTimelineToJSONSpec() { - + // which will create or update/replace scene descriptor JSON files + async _saveSceneAndTimelineToJSONSpec( + media_file_token: string, + timeline: TimelineCurrentReactState, + ): Promise { + const url = `${this.baseUrl}/v1/media_files/write/scene_file`; + let uuid = uuidv4(); + return ""; } - async loadScene(scene_token:[]):Promise { - - return "" + async loadScene(media_file_token: string): Promise { + let json_response = `{ + + }` + return "" + } + + // Provides bucket path to the actual file to download from the media file + 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 + // TODO get it as binary. + return media_url + } + + async batchGetMedia(media_tokens: []): Promise { + + return ""; } - async batchMedia(media_tokens:[]):Promise { - - return "" - } - - async gltfExport(scene:THREE.Scene) { - let gltfExporter = new GLTFExporter() - let uuid = uuidv4() - // save the scene to keep ids positions etc - let result = await gltfExporter.parseAsync(scene) - const file = new File([JSON.stringify(result)], `${uuid}.glb`, {type: 'application/json'}) - return file + async gltfExport(scene: THREE.Scene) { + let gltfExporter = new GLTFExporter(); + let uuid = uuidv4(); + // save the scene to keep ids positions etc + let result = await gltfExporter.parseAsync(scene); + const file = new File([JSON.stringify(result)], `${uuid}.glb`, { + type: "application/json", + }); + return file; } // two core functions to upload things to the server and get id's - async uploadMedia(blob:any, fileName:string):Promise { - const url = `${this.baseUrl}/v1/media_uploads/upload` - let uuid = uuidv4() - const formData = new FormData() - formData.append('uuid_idempotency_token', uuid) - formData.append('file', blob, fileName) - formData.append('source', 'file') - formData.append('type', 'video') - formData.append('source', 'file') + async uploadMedia(blob: any, fileName: string): Promise { + const url = `${this.baseUrl}/v1/media_uploads/upload`; + let uuid = uuidv4(); + const formData = new FormData(); + formData.append("uuid_idempotency_token", uuid); + formData.append("file", blob, fileName); + formData.append("source", "file"); + formData.append("type", "video"); + formData.append("source", "file"); const response = await fetch(url, { - method: 'POST', + method: "POST", credentials: "include", headers: { - "Accept": "application/json", + Accept: "application/json", }, body: formData, - }) + }); if (!response.ok) { - throw new Error('Failed to Send Data') + throw new Error("Failed to Send Data"); } - return response.json() // or handle the response as appropriate + return response.json(); // or handle the response as appropriate } // to save to a file then upload the scene. - async uploadGLB(file:File):Promise { - const url = `${this.baseUrl}/v1/media_files/upload/engine_asset` - let uuid = uuidv4() - const formData = new FormData() - formData.append('uuid_idempotency_token', uuid) - formData.append('file', file) - formData.append('source','file') - formData.append('media_file_subtype', 'scene_import') - formData.append('media_file_class', 'scene') + async uploadGLB(file: File): Promise { + // /v1/media_files/write/engine_asset REPLACE with this api endpoint + const url = `${this.baseUrl}/v1/media_files/upload/engine_asset`; + let uuid = uuidv4(); + const formData = new FormData(); + formData.append("uuid_idempotency_token", uuid); + formData.append("file", file); + formData.append("source", "file"); + formData.append("media_file_subtype", "scene_import"); + formData.append("media_file_class", "scene"); const response = await fetch(url, { - method: 'POST', + method: "POST", credentials: "include", headers: { - "Accept": "application/json", + Accept: "application/json", }, body: formData, - }) + }); if (!response.ok) { - throw new Error('Failed to Send Data') + throw new Error("Failed to Send Data"); } - return response.json() // or handle the response as appropriate + return response.json(); // or handle the response as appropriate } } -export default APIManager +export default APIManager; diff --git a/app/pages/PageEnigma/js/audio_clip.js b/app/pages/PageEnigma/js/audio_clip.js deleted file mode 100644 index daf72c7143..0000000000 --- a/app/pages/PageEnigma/js/audio_clip.js +++ /dev/null @@ -1,34 +0,0 @@ -import * as THREE from "three"; - -class AudioClip { - - constructor(listener, audioURL) { - this.audioLoader = new THREE.AudioLoader(); - this.sound = new THREE.Audio(listener); - this.audioURL = audioURL; - this.isLoaded = false; - - // Load the audio file asynchronously - this.audioLoader.load(audioURL, (buffer) => { - this.sound.setBuffer(buffer); - this.isLoaded = true; - }); - } - - play() { - if (this.isLoaded) { - this.sound.play(); - this.sound.setLoop(true); - this.sound.setVolume(1); - this.sound.play(); - } else { - console.warn(`AudioClip: Sound not loaded yet (${this.audioURL}).`); - } - } - - stop() { - if (this.isLoaded) { - this.sound.stop(); - } - } -} \ No newline at end of file diff --git a/app/pages/PageEnigma/js/editor.js b/app/pages/PageEnigma/js/editor.js index 77320ecc35..7edf5e7b8a 100644 --- a/app/pages/PageEnigma/js/editor.js +++ b/app/pages/PageEnigma/js/editor.js @@ -165,10 +165,30 @@ class Editor { // This is the default scene. // "m_189p8hj0eyypbg74kkhcpehwpjhnkz" scene with the fox. - let result = await this.api_manager.saveSceneState(this.activeScene.scene) + //let result = await this.api_manager.saveSceneState(this.activeScene.scene) + let result = await this.api_manager.getMediaFile("m_189p8hj0eyypbg74kkhcpehwpjhnkz") console.log(result) } + async _serialize_timeline() { + // note the database from the server is the source of truth for all the data. + // Test code here + let object = await this.activeScene.load_glb("./resources/models/fox/fox.glb") + + // load object into the engine for lip syncing + this.lipsync_engine.load_object(object.uuid, "m_f1jxx4zwy4da2zn0cvdqhha7kqkj72"); + + // create the clip with the same id for a reference to the media + this.timeline.addPlayableClip(new ClipUI(1.0, "lipsync", "clip1", "m_f1jxx4zwy4da2zn0cvdqhha7kqkj72", object.uuid, 150, 400)); + + // media id for this is up in the air but when a path is created you should be able to store and delete it + this.timeline.addPlayableClip(new ClipUI(1.0, "transform", "clip2", object.uuid, object.uuid, 0, 150)); + + // media id for this as well it can be downloaded + this.timeline.addPlayableClip(new ClipUI(1.0, "animation", "clip3", "/resources/models/fox/fox_idle.glb", object.uuid, 0, 400)); + this.animation_engine.load_object(object.uuid, "/resources/models/fox/fox_idle.glb", "clip3"); + } + async _test_demo() { // note the database from the server is the source of truth for all the data. // Test code here @@ -283,7 +303,6 @@ class Editor { //this.transform_engine.clips[this.test_box_uuid].reset(this.activeScene.get_object_by_uuid(this.test_box_uuid)); } - // Basicly Unity 3D's update loop. update_loop(time) { // Updates debug stats. @@ -307,10 +326,6 @@ class Editor { this.timeline.isPlaying = true; } - // _save_to_cloud(blob) { - // this.api_manager.uploadGLB(blob, "test.glb"); - // } - change_mode(type) { this.control.mode = type; this.transform_interaction = true; diff --git a/app/pages/PageEnigma/js/timeline.ts b/app/pages/PageEnigma/js/timeline.ts index fbfa90caa0..4803e30349 100644 --- a/app/pages/PageEnigma/js/timeline.ts +++ b/app/pages/PageEnigma/js/timeline.ts @@ -9,7 +9,7 @@ import AnimationEngine from "./animation_engine"; // Every object uuid / entity has a track. export class TimelineCurrentReactState { - isEditable: boolean +isEditable: boolean selectedObjectID: number constructor() { @@ -19,6 +19,8 @@ export class TimelineCurrentReactState { } export class TimeLine { + + timelineItems: ClipUI[] runningClips: ClipUI[] @@ -45,6 +47,7 @@ export class TimeLine { lipsyncEngine: LipSyncEngine, animationEngine: AnimationEngine, scene: Scene) { + this.timelineItems = [] this.timeLineLimit = 60 * 10 // 10 seconds this.runningClips = [] @@ -67,8 +70,9 @@ export class TimeLine { // when given a media id item it will create the clip. // Then the clip will be loaded by the engines, if they come from outside of the loaded scene. - async createClipOffset(media_id: string, type: string): Promise { + async createClipOffset(media_id: string, object_uuid:string, type: string): Promise { // use engine to load based off media id and type animation | transform | + } // this will update the state of the clips based off uuid easing?