From 8cdacb4d052ae59bde8afaddd1dc24ba8e009f27 Mon Sep 17 00:00:00 2001 From: Quicksticks-oss Date: Wed, 20 Mar 2024 21:07:24 -0400 Subject: [PATCH] updated with ffmpeg. --- app/pages/PageEnigma/PageEnigma.tsx | 3 +- app/pages/PageEnigma/js/editor.js | 73 +++++++++++++++++++---- app/pages/PageEnigma/js/serialization.js | 22 +++++-- app/routes/IdealEnigma.tsx | 20 ++++--- package-lock.json | 29 +++++++++ package.json | 2 + public/resources/skybox/black.jpg | Bin 0 -> 9388 bytes 7 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 public/resources/skybox/black.jpg diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 0f02cf72fd..2645398601 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -41,7 +41,7 @@ export const PageEnigma = () => { editorRef.current?.save(); } const handleButtonLoad = ()=>{ - + document.getElementById("load-upload")?.click(); } const handleButtonRender = ()=>{ editorRef.current?.togglePlayback(); @@ -58,6 +58,7 @@ export const PageEnigma = () => { +
Timeline Panel
diff --git a/app/pages/PageEnigma/js/editor.js b/app/pages/PageEnigma/js/editor.js index 62c8813f98..6e962f2ffb 100644 --- a/app/pages/PageEnigma/js/editor.js +++ b/app/pages/PageEnigma/js/editor.js @@ -15,6 +15,7 @@ import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js'; import { SAOPass } from 'three/addons/postprocessing/SAOPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { BokehPass } from 'three/addons/postprocessing/BokehPass.js'; +import { FFmpeg } from '@ffmpeg/ffmpeg'; if (typeof window !== 'undefined') { import('ccapture.js').then(module => { @@ -66,7 +67,10 @@ class Editor { // Recording params. this.capturer = null; + this.frame_buffer = []; + this.render_timer = 0; this.cap_fps = 60; + this.ffmpeg = null; // Timeline settings. this.playback = false; @@ -94,7 +98,7 @@ class Editor { this.camera.position.x = 2; // Base WebGL render and clock for delta time. - this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvReference }); + this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvReference, preserveDrawingBuffer: true }); this.renderer.shadowMap.enabled = true; this.clock = new THREE.Clock(); @@ -106,7 +110,7 @@ class Editor { // Current scene for saving and loading. this.activeScene = new Scene(); - //this._configure_post_pro(); + this._configure_post_pro(); this.activeScene.initialize(); @@ -145,6 +149,8 @@ class Editor { this.renderer.setAnimationLoop(this.update_loop.bind(this)); //this.create_geometry("Box"); + + this.ffmpeg = new FFmpeg(); } // Configure post processing. @@ -191,6 +197,11 @@ class Editor { this.outputPass = new OutputPass(); this.composer.addPass(this.outputPass); + + + + + document.getElementById('load-upload').addEventListener('change', this.load.bind(this)); } _add_post_processing() { @@ -333,6 +344,9 @@ class Editor { this.capturer.stop(); this.capturer.save(); } + this.render_mode(); + this.ffmpeg.writeFile('input.webm', fetchFile('https://raw.githubusercontent.com/ffmpegwasm/testdata/master/Big_Buck_Bunny_180_10s.webm')); + this.ffmpeg.exec(['-i', 'input.webm', 'output.mp4']); } frameCounter() { @@ -376,16 +390,18 @@ class Editor { // Initializes CCapture for capturing the scene to send over to backend. _initialize_recording() { - this.capturer = new CCapture({ - verbose: false, - display: false, - framerate: this.cap_fps, - quality: 100, - format: 'webm-mediarecorder', - frameLimit: 0, - autoSaveTime: 0 - }); - this.capturer.start(); + this.frame_buffer = []; + this.render_timer = 0; + //this.capturer = new CCapture({ + // verbose: true, + // display: true, + // framerate: this.cap_fps, + // quality: 100, + // format: 'webm', + // frameLimit: 0, + // autoSaveTime: 0 + //}); + //this.capturer.start(); } // Debug stats using ThreJS built in FPS and MS counter. @@ -410,12 +426,43 @@ class Editor { // Render the scene to the camera. render_scene() { - if (this.composer != null && this.rendering == false) { + if (this.composer != null) { this.composer.render(); } else { this.renderer.render(this.activeScene.scene, this.camera); } + + if (this.rendering) { + let imgData = this.renderer.domElement.toDataURL(); + this.frame_buffer.push(imgData); + this.render_timer += this.clock.getDelta(); + + //const base64Data = imgData.split(',')[1]; + //const contentType = imgData.split(',')[0].split(':')[1].split(';')[0]; + //const byteCharacters = atob(base64Data); + //const byteArrays = []; + //for (let offset = 0; offset < byteCharacters.length; offset += 1024) { + // const slice = byteCharacters.slice(offset, offset + 1024); + // const byteNumbers = new Array(slice.length); + // for (let i = 0; i < slice.length; i++) { + // byteNumbers[i] = slice.charCodeAt(i); + // } + // const byteArray = new Uint8Array(byteNumbers); + // byteArrays.push(byteArray); + //} + //const blob = new Blob(byteArrays, {type: contentType}); + //const link = document.createElement('a'); + //link.href = URL.createObjectURL(blob); + //link.download = 'image.png'; // Set the file name for the download + //document.body.appendChild(link); + //link.click(); + //document.body.removeChild(link); + } + + if(this.render_timer >= this.fps_number) { + this.stopPlayback(); + } } update_properties() { diff --git a/app/pages/PageEnigma/js/serialization.js b/app/pages/PageEnigma/js/serialization.js index 3879c4845d..5fc32ee4d7 100644 --- a/app/pages/PageEnigma/js/serialization.js +++ b/app/pages/PageEnigma/js/serialization.js @@ -47,12 +47,22 @@ class SaveManager { } load(uploadedFile, load_callback) { - // I dont know if this works yet without a load file UI. - console.log(uploadedFile); - const loader = new GLTFLoader(); - loader.load(uploadedFile, function ( gltf ) { - load_callback(gltf.scene); - }); + const reader = new FileReader(); + reader.onload = function(e) { + const contents = e.target.result; + + const loader = new THREE.GLTFLoader(); + loader.parse(contents, '', function(gltf) { + scene.add(gltf.scene); + }); + }; + reader.readAsArrayBuffer(file); + + //const loader = new GLTFLoader(); + //loader.load(uploadedFile, function ( gltf ) { + // console.log(gltf); + // load_callback(gltf.scene); + //}); } download(data, filename, type) { diff --git a/app/routes/IdealEnigma.tsx b/app/routes/IdealEnigma.tsx index e5d7442632..9606f9fe14 100644 --- a/app/routes/IdealEnigma.tsx +++ b/app/routes/IdealEnigma.tsx @@ -4,18 +4,20 @@ import { PageEnigma } from "../pages/PageEnigma"; import { Button } from '~/components/Button'; export default function IdealEnigma(){ - const [riddle, setRiddle] = useState(""); - const [submitted, setSubmitted] = useState(false); + //const [riddle, setRiddle] = useState(""); + //const [submitted, setSubmitted] = useState(false); - const handleInput = (event:React.ChangeEvent)=>{ - setRiddle(event.target.value); - } + //const handleInput = (event:React.ChangeEvent)=>{ + // setRiddle(event.target.value); + //} - const handleSubmit = () => { - const state = riddle.length > 5 && riddle[riddle.length-1] === "!"; - setSubmitted(state); - } + //const handleSubmit = () => { + // const state = riddle.length > 5 && riddle[riddle.length-1] === "!"; + // setSubmitted(state); + //} + + return if(submitted){ return }else{ diff --git a/package-lock.json b/package-lock.json index 0c061b7ebe..dc68f11dea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,8 @@ "": { "name": "gottagofast_v0.0.0", "dependencies": { + "@ffmpeg/core": "^0.12.6", + "@ffmpeg/ffmpeg": "^0.12.10", "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", @@ -1276,6 +1278,33 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@ffmpeg/core": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.6.tgz", + "integrity": "sha512-PrjWBTfGn2WVn9T7wGnzfFwChbqWeZc7tM9vvJZVRadYFUDakfzy7W0LpYC0cvvK0xT82qlBsk38lQhJ/Hps5A==", + "engines": { + "node": ">=16.x" + } + }, + "node_modules/@ffmpeg/ffmpeg": { + "version": "0.12.10", + "resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.12.10.tgz", + "integrity": "sha512-lVtk8PW8e+NUzGZhPTWj2P1J4/NyuCrbDD3O9IGpSeLYtUZKBqZO8CNj1WYGghep/MXoM8e1qVY1GztTkf8YYQ==", + "dependencies": { + "@ffmpeg/types": "^0.12.2" + }, + "engines": { + "node": ">=18.x" + } + }, + "node_modules/@ffmpeg/types": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@ffmpeg/types/-/types-0.12.2.tgz", + "integrity": "sha512-NJtxwPoLb60/z1Klv0ueshguWQ/7mNm106qdHkB4HL49LXszjhjCCiL+ldHJGQ9ai2Igx0s4F24ghigy//ERdA==", + "engines": { + "node": ">=16.x" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", diff --git a/package.json b/package.json index 73b2d733f6..17d31ba3e7 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "typecheck": "tsc" }, "dependencies": { + "@ffmpeg/core": "^0.12.6", + "@ffmpeg/ffmpeg": "^0.12.10", "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", diff --git a/public/resources/skybox/black.jpg b/public/resources/skybox/black.jpg new file mode 100644 index 0000000000000000000000000000000000000000..01e7d8a48d1debfea47709006b46449dd0c4d3fd GIT binary patch literal 9388 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 z zmpN~FKKOjz`6K81&ObZ9(#_Sa*6rLK+g;Y()_t@4%?0>^=mo$B zzc2A$(z)b$DdkedrH)G@mp)x4US?jlz8rSB=<>14gO_J6ulEV|>GgT{rS(K=Z)Rz^g$pC^~2~=s%b>ST}fSaB^_<8uvAgYwp*Qt{u46aqZ!? zr6KZ=!jSz?)KKYA+t96{ch}L^C9j)b55As%{pj_<>oeCkZV2BnxZ!sr>qgy;OE)HO ztPb-IYYlH1P93fqK0iD*{Ou;~rpis1oAEdI-8_5q!Oc&%NVgPj+24x3wfokoTX$|P z-p1XQy={Fv{PxbD{2a`FES{4&I%; z2ktTMncNG!mwT`A-oU+?dz<$~?;GC_xWD86k^BAkr|)k(5P4wqAmG7{2S*i;z7>5-=cPiH2;gxG}HM94(Z zM9aj@iTO#~By-YkGJ0~)e+>7PoMpK&i`ERx!?2b=SQ9o zJfD35y%2w4@q+bY=Zm%%cV4`oB2FnzIZee+9hmBxdOWrKlINw)OP`lnFB@L=znqzd zrp2c%rdiXwrrW0PPJf&s&nVBh%p}cJ&0Lt7nE5p;Fl#UyFq=EuG<$va?JLYH*;jV2 zVqWcib?()pSKnWAzt(>3^LqR1hS&YCXXoHK$vLaJh`HT!?Qhxi?WM$i?NIQ7P}T7FRs4le{b+U;C=r4=Jz+>FMc3?Q2OBd zA^F3>50^enec1RY_R->F*vHb3?H?b0{Pv0KllCXyPdT5Ce!BkY?GkQDe#vPmaj9zQ z;?nb__0NpY=AXkpmws;l{P6R)FWg^rzW9CF@ulg@@Rx-#WiN`S$a>;CJKi!QYF&pZtFR`b^!^y9{lg=ONh^0M1< z+VY|0tIM-1s1=zN`<3{Wij|&~XDjQgVyhOb5vzMvJ66Y6SAPorH2N9*bLY=fKSzFk z{l)!D_t(~6`M+9z-TC!#jblw?&3i3-?daN#wT0iL-zvX7erNn{_&xah%^%z!g+DHT zQvV$KbM?=wb@V!O-Elo>{owlL_2~_GLuSK%BYvZ5qjzI!6KqOt+HS^fR&4fczS!LS zr>}(V0zm#+0gg`q;E@f0!vg>g)d3(K&h`=bZ@ysvbN|_1rAe~*0*NC?{-K|^S4`$FT- Kf7GXLKKl>ARJ`y2 literal 0 HcmV?d00001