mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Removed docs and playground, wizard.
Readded the strange login.
This commit is contained in:
1 parent
e8d3b0bef9
commit
28b6034cfb
18 files changed
+316
-664
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import { Fragment, useCallback } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { DoNotShow } from "~/constants";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
title: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onOk?: () => void;
|
||||
okText?: string;
|
||||
okColor?: string;
|
||||
onCancel?: () => void;
|
||||
cancelText?: string;
|
||||
canHide?: boolean;
|
||||
}
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
text,
|
||||
title,
|
||||
open,
|
||||
onClose,
|
||||
onOk,
|
||||
okText = "OK",
|
||||
okColor = "bg-brand-success",
|
||||
onCancel,
|
||||
cancelText = "Cancel",
|
||||
canHide,
|
||||
}: Props) => {
|
||||
const handleOk = useCallback(() => {
|
||||
const element = document.getElementById("hide-dialog");
|
||||
if (element) {
|
||||
if ((element as HTMLInputElement).checked) {
|
||||
localStorage.setItem(title.replace(" ", "-"), DoNotShow);
|
||||
}
|
||||
}
|
||||
|
||||
onOk!();
|
||||
}, [onOk, title]);
|
||||
|
||||
return (
|
||||
<Transition appear show={open} as={Fragment}>
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/40" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-xl border border-ui-panel-border bg-ui-panel p-5 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h4"
|
||||
className="mb-4 text-xl font-bold text-white"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
|
||||
<div className="mt-2">{text}</div>
|
||||
{canHide && (
|
||||
<div className="mt-2">
|
||||
<label>
|
||||
<input id="hide-dialog" type="checkbox" />
|
||||
Do not show this again
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
{!!onCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="rounded-lg px-3 py-2"
|
||||
variant="secondary"
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
)}
|
||||
{!!onOk && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleOk}
|
||||
className={[okColor, "rounded-lg px-3 py-2"].join(" ")}
|
||||
>
|
||||
{okText}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,204 @@
|
||||
import { useRef, StrictMode } from "react";
|
||||
import { useNavigate, useSearchParams, BrowserRouter } from "react-router-dom";
|
||||
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
faKey,
|
||||
faSpinnerThird,
|
||||
faUser,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { AUTH_STATUS } from "~/enums";
|
||||
import { authentication, login, logout } from "~/signals";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import { ConfirmationModal } from "~/components/reusable/ConfirmationModal";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
|
||||
import "./styles/normalize.css";
|
||||
import "./styles/tailwind.css";
|
||||
import "./styles/base.css";
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
|
||||
import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
import { GlobalSettingsManager } from "./pages/PageEnigma/GlobalSettingsManager";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
config.autoAddCss = false; /* eslint-disable import/first */
|
||||
|
||||
// TODO(bt,2025-04-19): Make these configurable
|
||||
const ENV = {
|
||||
BASE_API: "https://api.storyteller.ai",
|
||||
GOOGLE_API: "https://studio.storyteller.ai",
|
||||
FUNNEL_API: "https://studio.storyteller.ai",
|
||||
CDN_API: "https://cdn-2.fakeyou.com",
|
||||
GRAVATAR_API: "https://studio.storyteller.ai",
|
||||
DEPLOY_PRIME_URL: "https://studio.storyteller.ai",
|
||||
};
|
||||
|
||||
export default function LoginScreen() {
|
||||
useSignals();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const { status: authStatus } = authentication;
|
||||
const authLoaderMessage = getAuthLoaderMessage();
|
||||
const shouldShowLoader = checkShouldShowLoader();
|
||||
|
||||
const showNoAccessModal = authStatus.value === AUTH_STATUS.NO_ACCESS;
|
||||
|
||||
const handleOnSumbit = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (formRef.current) {
|
||||
const form = new FormData(formRef.current);
|
||||
const usernameOrEmail = form.get("usernameOrEmail")?.toString();
|
||||
const password = form.get("password")?.toString();
|
||||
if (usernameOrEmail && password && login) {
|
||||
login({
|
||||
usernameOrEmail,
|
||||
password,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useSignalEffect(() => {
|
||||
const redirectPath = searchParams.get("redirect");
|
||||
if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
|
||||
navigate(redirectPath ? redirectPath : "/");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed flex h-full w-full flex-col items-center justify-center bg-[#1b1b1f] bg-center bg-repeat">
|
||||
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<img
|
||||
src="/resources/images/artcraft-logo-3.png"
|
||||
alt="ArtCraft Logo"
|
||||
className="h-9 select-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full max-w-xl rounded-xl shadow-lg">
|
||||
<div className="glass glass-no-hover relative mx-auto w-full rounded-none rounded-t-xl p-6 shadow-none">
|
||||
<h1 className="mb-8 text-center text-2xl font-bold">
|
||||
Log in to 3D Stage Editor
|
||||
</h1>
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleOnSumbit}
|
||||
className={twMerge(
|
||||
"flex flex-col gap-4",
|
||||
shouldShowLoader && "opacity-0",
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
label="Username or Email"
|
||||
icon={faUser}
|
||||
name="usernameOrEmail"
|
||||
placeholder="Username or Email"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
icon={faKey}
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<Button className="mt-4 py-3">Continue</Button>
|
||||
</form>
|
||||
{shouldShowLoader && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-black/50">
|
||||
<div className="flex items-center text-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<p className="text-sm text-white">{authLoaderMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
|
||||
{/* <a
|
||||
href="https://storyteller.ai/password-reset"
|
||||
className="grow text-sm font-medium !text-brand-primary-400 transition-all duration-150 hover:!text-brand-primary-300"
|
||||
>
|
||||
Forgot your password?
|
||||
</a> */}
|
||||
<a
|
||||
href="/"
|
||||
className="grow text-sm font-medium !text-brand-primary-400 transition-all duration-150 hover:!text-brand-primary-300"
|
||||
>
|
||||
Back
|
||||
</a>
|
||||
<div className="flex justify-end gap-1 text-sm">
|
||||
<p className="opacity-75">Don't have an account?</p>
|
||||
<a
|
||||
href="/signup"
|
||||
className="font-medium text-brand-primary-400 hover:text-brand-primary-300"
|
||||
>
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
text="We're in a closed beta and you'll need a beta key to use this app."
|
||||
title="Unauthorized"
|
||||
open={showNoAccessModal}
|
||||
onClose={handleClose}
|
||||
cancelText="Close"
|
||||
onCancel={handleClose}
|
||||
okText="Get Beta Key"
|
||||
onOk={() => {
|
||||
handleClose();
|
||||
if (window) {
|
||||
window.open("https://storyteller.ai/beta-key/redeem", "_blank");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
if (authentication.status.value !== AUTH_STATUS.LOGGED_OUT) {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
const checkShouldShowLoader = () => {
|
||||
return (
|
||||
authentication.status.value === AUTH_STATUS.LOGGING ||
|
||||
authentication.status.value === AUTH_STATUS.LOGGED_IN
|
||||
);
|
||||
};
|
||||
|
||||
const getAuthLoaderMessage = () => {
|
||||
if (authentication.status.value === AUTH_STATUS.LOGGED_IN) {
|
||||
return "Authenticated, Redirecting...";
|
||||
}
|
||||
if (authentication.status.value === AUTH_STATUS.GET_USER_INFO) {
|
||||
return "Getting User Info...";
|
||||
}
|
||||
return "Getting Session...";
|
||||
};
|
||||
|
||||
// TODO: Replace environment variables from `root.tsx`
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<>
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<GlobalSettingsManager env={ENV} />
|
||||
<LoginScreen />
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
</>,
|
||||
);
|
||||
@@ -3,7 +3,7 @@ import EnvironmentVariables from "~/Classes/EnvironmentVariables";
|
||||
import { persistLogin } from "~/signals";
|
||||
import { pageHeight, pageWidth } from "~/signals";
|
||||
import { useEffect } from "react";
|
||||
import { showWizard } from "~/pages/PageEnigma/Wizard/signals/wizard";
|
||||
|
||||
|
||||
|
||||
export const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {
|
||||
@@ -35,17 +35,7 @@ export const GlobalSettingsManager = ({ env }: { env: Record<string, string> })
|
||||
pageWidth.value = window.innerWidth;
|
||||
}
|
||||
|
||||
function initWizard() {
|
||||
if (showWizard.value) {
|
||||
return;
|
||||
}
|
||||
const wizard = localStorage.getItem("storyteller-wizard");
|
||||
showWizard.value = wizard ? "" : "initial";
|
||||
localStorage.setItem("storyteller-wizard", "shown");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initWizard();
|
||||
setPage();
|
||||
window.addEventListener("resize", setPage);
|
||||
return () => {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# STORYTELLER STUDIO ENGINE DOCS
|
||||
|
||||
This is the official documentation for the storyteller studio engine.
|
||||
|
||||
[Back](../README.md)
|
||||
|
||||
## Copy & Paste
|
||||
|
||||
## Documentation
|
||||
|
||||
- Copy paste is done in the `SceneManager` and `MouseControls` classes.
|
||||
- In `SceneManager` the async `copy` function sets the copied object to whatever the `MouseControls` class has selected.
|
||||
- In `SceneManager` the async `paste` function duplicates the threejs object and updates the outliner while coping the newly created object.
|
||||
|
||||
## Hacks
|
||||
|
||||
- Tempararly disabled copy and paste of characters because the UI on the timeline is not updateing properly.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Fix characters.
|
||||
@@ -1,21 +0,0 @@
|
||||
# STORYTELLER STUDIO ENGINE DOCS
|
||||
|
||||
This is the official documentation for the storyteller studio engine.
|
||||
|
||||
[Back](../README.md)
|
||||
|
||||
## MMD IK
|
||||
|
||||
## Documentation
|
||||
|
||||
- All IK is handled by the `ikHelper` which is appart of `THREE.js`'s `MMDAnimation` lib.
|
||||
- IK is updated by the timeline by setting the action of the mixer which was assigned to it by the animation clip.
|
||||
|
||||
## Hacks
|
||||
|
||||
- Loads `Ammo.js` using js on line `180` of `editor` as of `July 25th 2024`.
|
||||
- Ceates an `Ammo.js` instance using code on line `673` of `scene.ts` as of `July 25th 2024`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Fix loading of `ammo.js`.
|
||||
@@ -1,27 +0,0 @@
|
||||
# STORYTELLER STUDIO ENGINE DOCS
|
||||
|
||||
This is the official documentation for the storyteller studio engine.
|
||||
|
||||
[Back](../README.md)
|
||||
|
||||
## Documentation Links
|
||||
|
||||
[threejs skyboxes](https://threejs.org/manual/#en/backgrounds)
|
||||
|
||||
## Documentation
|
||||
|
||||
- Asset type is `AssetType.SKYBOX`
|
||||
- Update skyboxes by calling the `Scene`'s `updateSkybox` function while passing in the media id of te skybox.
|
||||
- You can also update skyboxes by calling the Scene Manages `updateSkybox` function while passing in the media id of te skybox.
|
||||
|
||||
## Hacks
|
||||
|
||||
- Made creation of skyboxs go through the `addObject` function on line 101 of `DndAssets.ts`.
|
||||
- Hard coded 3 skyboxs into the engine in `demoAssts.ts`.
|
||||
- Use hard coded images and media tokens for loading skyboxes. Ex. `Default`, `m_0`, `m_1`...
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Uploading Skyboxes to Backend.
|
||||
|
||||
- Create Panel for Skyboxes
|
||||
@@ -1,25 +0,0 @@
|
||||
# STORYTELLER STUDIO ENGINE DOCS
|
||||
|
||||
This is the official documentation for the storyteller studio engine.
|
||||
|
||||
[Back](../README.md)
|
||||
|
||||
## Video Planes
|
||||
|
||||
## Documentation Links
|
||||
|
||||
[threejs VideoTexture](https://threejs.org/docs/#api/en/textures/VideoTexture)
|
||||
|
||||
## Documentation
|
||||
|
||||
- Suported types: `.mp4`
|
||||
- The loading and instantiating code is all located in scene.ts arround line 140 as of July 22 2024.
|
||||
- The actual code for updating is in the timeline but it runs at 10fps due too buffering issue.
|
||||
|
||||
## Hacks
|
||||
|
||||
- Caped to 10 fps due to buffering issue.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Make render at 30 fps.
|
||||
@@ -1,13 +0,0 @@
|
||||
# STORYTELLER STUDIO ENGINE DOCS
|
||||
|
||||
This is the official documentation for the storyteller studio engine.
|
||||
|
||||
## Documentation Links
|
||||
|
||||
[Skyboxes](./DOCS/SKYBOXES.md)
|
||||
|
||||
[Copy & Paste](./DOCS/COPYPASTE.md)
|
||||
|
||||
[Video Planes](./DOCS/VIDEOPLANES.md)
|
||||
|
||||
[MMD IK](./DOCS/MMD.md)
|
||||
@@ -1,76 +0,0 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
export class FrameCollectorManager {
|
||||
constructor(width, height, expectedFramesLength = 720) {
|
||||
this.worker = new Worker("canvas-queued-worker.js");
|
||||
this.worker.addEventListener("message", this.handleMessage.bind(this));
|
||||
|
||||
this.frames = [];
|
||||
|
||||
this.expectedFramesLength = expectedFramesLength;
|
||||
|
||||
this.offscreenCanvas = new OffscreenCanvas(width, height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.offScreenRenderer = new THREE.WebGLRenderer({
|
||||
canvas: this.offscreenCanvas,
|
||||
});
|
||||
}
|
||||
|
||||
setCanvas(width, height) {
|
||||
this.offscreenCanvas.width = width;
|
||||
this.offscreenCanvas.height = height;
|
||||
}
|
||||
|
||||
handleMessage(event) {
|
||||
console.time("Decode");
|
||||
console.log("Message from Decode:", event.data);
|
||||
const { id, blob } = event.data;
|
||||
console.log(`${id} Frame id: ${blob}`);
|
||||
console.timeEnd("Decode");
|
||||
this.frames.push(blob);
|
||||
console.log(this.frames.length);
|
||||
|
||||
// create shared worker
|
||||
if (this.frames.length === this.expectedFramesLength) {
|
||||
console.log("Complete");
|
||||
}
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async collectedFrames() {
|
||||
while (this.frames.length != this.expectedFramesLength) {
|
||||
await this.sleep(1);
|
||||
}
|
||||
|
||||
return this.frames;
|
||||
}
|
||||
|
||||
terminate() {
|
||||
this.worker.terminate();
|
||||
console.log("Worker Terminated");
|
||||
}
|
||||
|
||||
render(scene, camera, frame) {
|
||||
this.offScreenRenderer.render(scene, camera);
|
||||
const imageBitmap = this.offscreenCanvas.transferToImageBitmap();
|
||||
this.processFrame(imageBitmap, frame);
|
||||
}
|
||||
|
||||
processFrame(imageBitmap, frame) {
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
const action = "process";
|
||||
|
||||
this.worker.postMessage({
|
||||
action,
|
||||
imageBitmap,
|
||||
width,
|
||||
height,
|
||||
frame,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// This is responsible for processing and turning the data into
|
||||
|
||||
class ImageProcessorWorker {
|
||||
constructor() {
|
||||
this.queue = [];
|
||||
this.processing = false;
|
||||
this.holdingCanvas = new OffscreenCanvas(0, 0);
|
||||
this.ctx = this.holdingCanvas.getContext("bitmaprenderer");
|
||||
this.imageType = "image/png";
|
||||
}
|
||||
|
||||
enqueueTask(imageBitmap, width, height, id) {
|
||||
this.queue.push({ imageBitmap, width, height, id });
|
||||
}
|
||||
|
||||
async processQueue() {
|
||||
// If already processing, return to avoid duplicate execution
|
||||
console.log(`Current Queue Length ${this.queue.length}`);
|
||||
if (this.queue.length > 0) {
|
||||
const { imageBitmap, width, height, id } = this.queue.shift();
|
||||
console.log(`Working On an Frame Id ${id}`);
|
||||
|
||||
this.holdingCanvas.width = width;
|
||||
this.holdingCanvas.height = height;
|
||||
|
||||
try {
|
||||
// Create or reuse the offscreen canvas
|
||||
this.ctx.transferFromImageBitmap(imageBitmap);
|
||||
|
||||
// Convert the canvas content to a Blob
|
||||
const blob = await this.holdingCanvas.convertToBlob({
|
||||
quality: 1.0,
|
||||
type: this.imageType,
|
||||
});
|
||||
|
||||
// Send the Blob back to the main thread
|
||||
self.postMessage({ blob, id });
|
||||
} catch (error) {
|
||||
console.error("Error processing image:", error);
|
||||
self.postMessage({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Processing");
|
||||
setTimeout(this.processQueue.bind(this), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the ImageProcessorWorker
|
||||
const runner = new ImageProcessorWorker();
|
||||
let start = false;
|
||||
|
||||
self.addEventListener("message", async (event) => {
|
||||
console.log(event.data);
|
||||
const { type, imageBitmap, width, height, frame } = event.data;
|
||||
console.log(`${type}: ${imageBitmap} ${width} ${height} ${frame}`);
|
||||
|
||||
if (start == false) {
|
||||
start = true;
|
||||
await runner.processQueue();
|
||||
}
|
||||
|
||||
runner.enqueueTask(imageBitmap, width, height, frame);
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// Listen for messages from the main thread
|
||||
self.addEventListener("message", async function (event) {
|
||||
const imageBitmap = event.data.imageBitmap;
|
||||
const width = event.data.width;
|
||||
const height = event.data.height;
|
||||
|
||||
const holdingCanvas = new OffscreenCanvas(width, height);
|
||||
// in the worker use queue and pass the imageBitmap
|
||||
const ctx = holdingCanvas.getContext("bitmaprenderer");
|
||||
ctx.transferFromImageBitmap(imageBitmap);
|
||||
const blob = await holdingCanvas.convertToBlob({
|
||||
quality: 1.0,
|
||||
type: "image/png",
|
||||
});
|
||||
|
||||
self.postMessage(blob); // Send the result back to the main thread
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
class CanvasProcessor {
|
||||
constructor() {
|
||||
this.queue = [];
|
||||
this.totalItems = 0;
|
||||
this.completedItems = 0;
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
// Add a canvas to the processing queue
|
||||
enqueueCanvas(canvas, id) {
|
||||
this.queue.push({ canvas, id });
|
||||
}
|
||||
|
||||
// Set the total number of items to process
|
||||
setTotalItems(count) {
|
||||
this.totalItems = count;
|
||||
}
|
||||
|
||||
// Start the processing loop
|
||||
async processQueue() {
|
||||
while (this.running) {
|
||||
if (this.queue.length > 0) {
|
||||
await this.processNext();
|
||||
}
|
||||
|
||||
// Check if all items have been processed
|
||||
if (this.completedItems >= this.totalItems) {
|
||||
self.postMessage({ command: "done", message: "All items processed." });
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
await this.sleep(100); // Small delay to prevent blocking
|
||||
}
|
||||
}
|
||||
|
||||
// Process the next canvas in the queue
|
||||
async processNext() {
|
||||
if (this.queue.length > 0) {
|
||||
const { canvas, id } = this.queue.shift();
|
||||
|
||||
try {
|
||||
const blob = await canvas.convertToBlob();
|
||||
|
||||
this.completedItems++;
|
||||
const percentageComplete =
|
||||
(this.completedItems / this.totalItems) * 100;
|
||||
|
||||
self.postMessage({
|
||||
id,
|
||||
blob,
|
||||
completedItems: this.completedItems,
|
||||
totalItems: this.totalItems,
|
||||
percentageComplete,
|
||||
});
|
||||
} catch (error) {
|
||||
self.postMessage({ id, error: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to sleep for a specified time
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the processor
|
||||
const processor = new CanvasProcessor();
|
||||
|
||||
// Listen for messages from the main thread
|
||||
self.addEventListener("message", function (event) {
|
||||
const data = event.data;
|
||||
|
||||
if (data.totalItems) {
|
||||
processor.setTotalItems(data.totalItems);
|
||||
}
|
||||
|
||||
if (data.canvas) {
|
||||
processor.enqueueCanvas(data.canvas, data.id);
|
||||
}
|
||||
|
||||
if (data.command === "start") {
|
||||
processor.processQueue();
|
||||
}
|
||||
|
||||
if (data.command === "stop") {
|
||||
processor.running = false;
|
||||
}
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
// if (this.engine_preprocessing) {
|
||||
// // Normals
|
||||
// this.setNormalMap();
|
||||
// this.render_composer?.render();
|
||||
|
||||
// const normalRenderTask = this.videoAudioPreProcessor.retrieveFrame(
|
||||
// this.rawRenderer,
|
||||
// );
|
||||
// this.engineFrameBuffers.normalFrames[this.renderIndex] =
|
||||
// await normalRenderTask;
|
||||
// // Depth
|
||||
// this.setRenderDepth();
|
||||
// this.render_composer?.render();
|
||||
// const depthRenderTask = this.videoAudioPreProcessor.retrieveFrame(
|
||||
// this.rawRenderer,
|
||||
// );
|
||||
// this.engineFrameBuffers.depthFrames[this.renderIndex] =
|
||||
// await depthRenderTask;
|
||||
|
||||
// // Outline
|
||||
// this.setOutlineRender();
|
||||
// this.render_composer?.render();
|
||||
// const outlineRenderTask = this.videoAudioPreProcessor.retrieveFrame(
|
||||
// this.rawRenderer,
|
||||
// );
|
||||
// this.engineFrameBuffers.outlineFrames[this.renderIndex] =
|
||||
// await outlineRenderTask;
|
||||
// this.renderIndex += 1;
|
||||
// } else {
|
||||
@@ -1,121 +0,0 @@
|
||||
// import { VideoGeneration } from "../../app/pages/PageEnigma/Editor/VideoProcessor/video_audio_preprocessor.ts";
|
||||
|
||||
//import { createFFmpeg, fetchFile, FFmpeg } from "@ffmpeg/ffmpeg";
|
||||
self.importScripts("@ffmpeg/ffmpeg");
|
||||
const JPEG = "image/jpeg";
|
||||
const PNG = "image/png";
|
||||
|
||||
// chrome://inspect/#workers debug shared worker.
|
||||
|
||||
async function processVideoWithBuffer(
|
||||
buffer,
|
||||
fileName,
|
||||
outFileName,
|
||||
imagePrefix,
|
||||
) {
|
||||
try {
|
||||
const imageFormat = this.properties.imageFormat;
|
||||
let extension = "";
|
||||
|
||||
if (imageFormat == JPEG) {
|
||||
extension = ".jpeg";
|
||||
} else {
|
||||
extension = ".png";
|
||||
}
|
||||
// console.log("Image Files");
|
||||
// console.log(`Buffer Lenght ${buffer.length}`);
|
||||
for (let index = 0; index < buffer.length; index++) {
|
||||
const element = buffer[index];
|
||||
if (element === null) {
|
||||
continue;
|
||||
}
|
||||
// this can fail so catch any errors.
|
||||
const image = await fetchFile(element);
|
||||
await this.ffmpeg.FS(
|
||||
"writeFile",
|
||||
`${imagePrefix}_image${index}${extension}`,
|
||||
image,
|
||||
);
|
||||
}
|
||||
|
||||
await this.ffmpeg.run(
|
||||
"-framerate",
|
||||
"" + this.properties.frameRate,
|
||||
"-i",
|
||||
`${imagePrefix}_image%d${extension}`,
|
||||
`${fileName}.mp4`,
|
||||
);
|
||||
|
||||
await this.ffmpeg.run(
|
||||
"-i",
|
||||
`${fileName}.mp4`,
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"anullsrc", // This adds a silent audio track
|
||||
"-max_muxing_queue_size",
|
||||
"999999",
|
||||
"-vf", // WE REMOVED THE BLACK FRAME IN FFMEPG INSTEAD OF THE ENGINE
|
||||
"select=gte(n\\,1)", // WE REMOVED THE BLACK FRAME IN FFMEPG INSTEAD OF THE ENGINE Applies a video filter to remove the first frame.
|
||||
// "select=gte(n\\,1),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",
|
||||
`${outFileName}.mp4`,
|
||||
);
|
||||
|
||||
const blob = await this.outputMp4FromMemory(`${outFileName}`);
|
||||
return blob;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener("connect", function (event) {
|
||||
const port = event.ports[0];
|
||||
|
||||
port.addEventListener("message", function (event) {
|
||||
console.log("Message received from main script:", event.data);
|
||||
port.postMessage("Hello from the Shared Worker!");
|
||||
});
|
||||
|
||||
port.start();
|
||||
});
|
||||
|
||||
// const videoGeneration = new VideoGeneration();
|
||||
|
||||
// self.addEventListener("connect", function (event) {
|
||||
// const port = event.ports[0];
|
||||
|
||||
// console.log("WERE IN");
|
||||
// port.start();
|
||||
|
||||
// port.addEventListener("message", async function (event) {
|
||||
// console.log("Message received from main script:", event.data);
|
||||
|
||||
// const { colorFrames } = event.data;
|
||||
// console.log(event.data);
|
||||
|
||||
// try {
|
||||
// console.time("Processing Video Started");
|
||||
// const colorBlob = await videoGeneration.processVideoWithBuffer(
|
||||
// colorFrames,
|
||||
// "color",
|
||||
// "color",
|
||||
// "colorFrame",
|
||||
// );
|
||||
// console.log(colorBlob);
|
||||
// console.time("Processing Video Finished");
|
||||
// port.postMessage({ colorBlob });
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// port.postMessage({ error: error.message });
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
@@ -1,25 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>My first three.js app</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@v0.149.0/build/three.module.js",
|
||||
"three/addons/": "https://cdn.jsdelivr.net/npm/three@v0.149.0/examples/jsm/",
|
||||
"ffmpeg": "https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.12.10/dist/umd/ffmpeg.min.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,80 +0,0 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
import { FrameCollectorManager } from "./ImageProcessingWorker.js";
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000,
|
||||
);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setAnimationLoop(animate);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
scene.add(cube);
|
||||
|
||||
camera.position.z = 5;
|
||||
|
||||
const totalFrames = 720;
|
||||
|
||||
const imageProcessing = new FrameCollectorManager(
|
||||
window.innerWidth,
|
||||
window.innerHeight,
|
||||
totalFrames,
|
||||
);
|
||||
|
||||
let frame = 0;
|
||||
|
||||
// const sharedWorker = new SharedWorker("ffmpeg-worker.js");
|
||||
// // Start the port
|
||||
// sharedWorker.port.start();
|
||||
// const message = { message: "Hello" };
|
||||
// sharedWorker.port.postMessage(message);
|
||||
|
||||
if (typeof SharedWorker !== "undefined") {
|
||||
console.log("Shared Workers are supported.");
|
||||
// You can safely create a Shared Worker here
|
||||
const worker = new SharedWorker("ffmpeg-worker.js", { type: "module" });
|
||||
|
||||
// Get the port for communication
|
||||
const port = worker.port;
|
||||
|
||||
// Listen for messages from the worker
|
||||
port.addEventListener("message", function (event) {
|
||||
console.log("Message received from worker:", event.data);
|
||||
});
|
||||
|
||||
// Start the port
|
||||
port.start();
|
||||
|
||||
// Send a message to the worker
|
||||
port.postMessage("Hello from the main script!");
|
||||
} else {
|
||||
console.log("Shared Workers are not supported in this browser.");
|
||||
}
|
||||
|
||||
async function animate() {
|
||||
cube.rotation.x += 0.01;
|
||||
cube.rotation.y += 0.01;
|
||||
|
||||
frame += 1;
|
||||
// buckets the frames.
|
||||
|
||||
if (frame <= totalFrames) {
|
||||
//console.time("Render");
|
||||
// can use the canvas renderer to enqueue as an option.
|
||||
//imageProcessing.render(scene, camera, frame);
|
||||
//console.timeEnd("Render");
|
||||
}
|
||||
|
||||
// block and wait for all the frames
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
# Run the TypeScript build
|
||||
npm run build
|
||||
|
||||
# Check if the build was successful
|
||||
if ($?) {
|
||||
# Open the index.html file in the default web browser
|
||||
Start-Process "index.html"
|
||||
} else {
|
||||
Write-Host "Build failed. Please check the errors above."
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
class Network {
|
||||
constructor() {}
|
||||
|
||||
async sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
async task1() {
|
||||
console.log("World.");
|
||||
let val = true;
|
||||
while (val) {
|
||||
await this.sleep(1000);
|
||||
console.log("Task1 doing work.");
|
||||
}
|
||||
//throw Error("Ded World.")
|
||||
}
|
||||
|
||||
async task2() {
|
||||
console.log("Hello.");
|
||||
let val = true;
|
||||
while (val) {
|
||||
await this.sleep(1000);
|
||||
console.log("Task2 doing work.");
|
||||
}
|
||||
//throw Error("Ded Hello.")
|
||||
}
|
||||
}
|
||||
|
||||
const network = new Network();
|
||||
|
||||
async function main() {
|
||||
network.task1();
|
||||
network.task2();
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user