Spaces:
Runtime error
Runtime error
queue
Browse files- app.py +2 -2
- app_init.py +27 -30
- frontend/src/lib/components/Button.svelte +1 -1
- frontend/src/lib/components/ImagePlayer.svelte +6 -5
- frontend/src/lib/components/PipelineOptions.svelte +9 -9
- frontend/src/lib/components/VideoInput.svelte +16 -21
- frontend/src/lib/lcmLive.ts +18 -50
- frontend/src/lib/mediaStream.ts +18 -43
- frontend/src/lib/store.ts +1 -1
- frontend/src/lib/types.ts +7 -7
- frontend/src/routes/+page.svelte +30 -15
- frontend/svelte.config.js +1 -3
- user_queue.py +42 -19
- util.py +5 -0
app.py
CHANGED
|
@@ -3,7 +3,7 @@ from fastapi import FastAPI
|
|
| 3 |
from config import args
|
| 4 |
from device import device, torch_dtype
|
| 5 |
from app_init import init_app
|
| 6 |
-
from user_queue import
|
| 7 |
from util import get_pipeline_class
|
| 8 |
|
| 9 |
|
|
@@ -11,4 +11,4 @@ app = FastAPI()
|
|
| 11 |
|
| 12 |
pipeline_class = get_pipeline_class(args.pipeline)
|
| 13 |
pipeline = pipeline_class(args, device, torch_dtype)
|
| 14 |
-
init_app(app,
|
|
|
|
| 3 |
from config import args
|
| 4 |
from device import device, torch_dtype
|
| 5 |
from app_init import init_app
|
| 6 |
+
from user_queue import user_data
|
| 7 |
from util import get_pipeline_class
|
| 8 |
|
| 9 |
|
|
|
|
| 11 |
|
| 12 |
pipeline_class = get_pipeline_class(args.pipeline)
|
| 13 |
pipeline = pipeline_class(args, device, torch_dtype)
|
| 14 |
+
init_app(app, user_data, args, pipeline)
|
app_init.py
CHANGED
|
@@ -7,16 +7,15 @@ from fastapi import Request
|
|
| 7 |
import logging
|
| 8 |
import traceback
|
| 9 |
from config import Args
|
| 10 |
-
from user_queue import
|
| 11 |
import uuid
|
| 12 |
-
from asyncio import Event, sleep
|
| 13 |
import time
|
| 14 |
-
from PIL import Image
|
| 15 |
from types import SimpleNamespace
|
| 16 |
-
from util import pil_to_frame, is_firefox
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
-
def init_app(app: FastAPI,
|
| 20 |
app.add_middleware(
|
| 21 |
CORSMiddleware,
|
| 22 |
allow_origins=["*"],
|
|
@@ -28,44 +27,42 @@ def init_app(app: FastAPI, user_data_events: UserDataEventMap, args: Args, pipel
|
|
| 28 |
@app.websocket("/ws")
|
| 29 |
async def websocket_endpoint(websocket: WebSocket):
|
| 30 |
await websocket.accept()
|
| 31 |
-
|
|
|
|
| 32 |
print("Server is full")
|
| 33 |
await websocket.send_json({"status": "error", "message": "Server is full"})
|
| 34 |
await websocket.close()
|
| 35 |
return
|
| 36 |
-
|
| 37 |
try:
|
| 38 |
-
|
| 39 |
-
print(f"New user connected: {
|
| 40 |
-
await
|
| 41 |
-
{"status": "success", "message": "Connected", "userId": uid}
|
| 42 |
-
)
|
| 43 |
-
user_data_events[uid] = UserDataEvent()
|
| 44 |
await websocket.send_json(
|
| 45 |
-
{"status": "
|
| 46 |
)
|
| 47 |
-
await handle_websocket_data(
|
| 48 |
except WebSocketDisconnect as e:
|
| 49 |
-
logging.error(f"WebSocket Error: {e}, {
|
| 50 |
traceback.print_exc()
|
| 51 |
finally:
|
| 52 |
-
print(f"User disconnected: {
|
| 53 |
-
|
| 54 |
|
| 55 |
@app.get("/queue_size")
|
| 56 |
async def get_queue_size():
|
| 57 |
-
queue_size =
|
| 58 |
return JSONResponse({"queue_size": queue_size})
|
| 59 |
|
| 60 |
@app.get("/stream/{user_id}")
|
| 61 |
async def stream(user_id: uuid.UUID, request: Request):
|
| 62 |
-
uid = str(user_id)
|
| 63 |
try:
|
|
|
|
| 64 |
|
| 65 |
async def generate():
|
| 66 |
while True:
|
| 67 |
-
|
| 68 |
-
|
|
|
|
| 69 |
image = pipeline.predict(params)
|
| 70 |
if image is None:
|
| 71 |
continue
|
|
@@ -81,13 +78,12 @@ def init_app(app: FastAPI, user_data_events: UserDataEventMap, args: Args, pipel
|
|
| 81 |
headers={"Cache-Control": "no-cache"},
|
| 82 |
)
|
| 83 |
except Exception as e:
|
| 84 |
-
logging.error(f"Streaming Error: {e}, {
|
| 85 |
traceback.print_exc()
|
| 86 |
return HTTPException(status_code=404, detail="User not found")
|
| 87 |
|
| 88 |
-
async def handle_websocket_data(
|
| 89 |
-
|
| 90 |
-
if uid not in user_data_events:
|
| 91 |
return HTTPException(status_code=404, detail="User not found")
|
| 92 |
last_time = time.time()
|
| 93 |
try:
|
|
@@ -98,19 +94,20 @@ def init_app(app: FastAPI, user_data_events: UserDataEventMap, args: Args, pipel
|
|
| 98 |
params = SimpleNamespace(**params.dict())
|
| 99 |
if info.input_mode == "image":
|
| 100 |
image_data = await websocket.receive_bytes()
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
if args.timeout > 0 and time.time() - last_time > args.timeout:
|
| 105 |
await websocket.send_json(
|
| 106 |
{
|
| 107 |
"status": "timeout",
|
| 108 |
"message": "Your session has ended",
|
| 109 |
-
"userId":
|
| 110 |
}
|
| 111 |
)
|
| 112 |
await websocket.close()
|
| 113 |
return
|
|
|
|
| 114 |
|
| 115 |
except Exception as e:
|
| 116 |
logging.error(f"Error: {e}")
|
|
|
|
| 7 |
import logging
|
| 8 |
import traceback
|
| 9 |
from config import Args
|
| 10 |
+
from user_queue import UserData
|
| 11 |
import uuid
|
|
|
|
| 12 |
import time
|
|
|
|
| 13 |
from types import SimpleNamespace
|
| 14 |
+
from util import pil_to_frame, bytes_to_pil, is_firefox
|
| 15 |
+
import asyncio
|
| 16 |
|
| 17 |
|
| 18 |
+
def init_app(app: FastAPI, user_data: UserData, args: Args, pipeline):
|
| 19 |
app.add_middleware(
|
| 20 |
CORSMiddleware,
|
| 21 |
allow_origins=["*"],
|
|
|
|
| 27 |
@app.websocket("/ws")
|
| 28 |
async def websocket_endpoint(websocket: WebSocket):
|
| 29 |
await websocket.accept()
|
| 30 |
+
user_count = user_data.get_user_count()
|
| 31 |
+
if args.max_queue_size > 0 and user_count >= args.max_queue_size:
|
| 32 |
print("Server is full")
|
| 33 |
await websocket.send_json({"status": "error", "message": "Server is full"})
|
| 34 |
await websocket.close()
|
| 35 |
return
|
|
|
|
| 36 |
try:
|
| 37 |
+
user_id = uuid.uuid4()
|
| 38 |
+
print(f"New user connected: {user_id}")
|
| 39 |
+
await user_data.create_user(user_id, websocket)
|
|
|
|
|
|
|
|
|
|
| 40 |
await websocket.send_json(
|
| 41 |
+
{"status": "connected", "message": "Connected", "userId": str(user_id)}
|
| 42 |
)
|
| 43 |
+
await handle_websocket_data(user_id, websocket)
|
| 44 |
except WebSocketDisconnect as e:
|
| 45 |
+
logging.error(f"WebSocket Error: {e}, {user_id}")
|
| 46 |
traceback.print_exc()
|
| 47 |
finally:
|
| 48 |
+
print(f"User disconnected: {user_id}")
|
| 49 |
+
user_data.delete_user(user_id)
|
| 50 |
|
| 51 |
@app.get("/queue_size")
|
| 52 |
async def get_queue_size():
|
| 53 |
+
queue_size = user_data.get_user_count()
|
| 54 |
return JSONResponse({"queue_size": queue_size})
|
| 55 |
|
| 56 |
@app.get("/stream/{user_id}")
|
| 57 |
async def stream(user_id: uuid.UUID, request: Request):
|
|
|
|
| 58 |
try:
|
| 59 |
+
print(f"New stream request: {user_id}")
|
| 60 |
|
| 61 |
async def generate():
|
| 62 |
while True:
|
| 63 |
+
params = await user_data.get_latest_data(user_id)
|
| 64 |
+
if not params:
|
| 65 |
+
continue
|
| 66 |
image = pipeline.predict(params)
|
| 67 |
if image is None:
|
| 68 |
continue
|
|
|
|
| 78 |
headers={"Cache-Control": "no-cache"},
|
| 79 |
)
|
| 80 |
except Exception as e:
|
| 81 |
+
logging.error(f"Streaming Error: {e}, {user_id} ")
|
| 82 |
traceback.print_exc()
|
| 83 |
return HTTPException(status_code=404, detail="User not found")
|
| 84 |
|
| 85 |
+
async def handle_websocket_data(user_id: uuid.UUID, websocket: WebSocket):
|
| 86 |
+
if not user_data.check_user(user_id):
|
|
|
|
| 87 |
return HTTPException(status_code=404, detail="User not found")
|
| 88 |
last_time = time.time()
|
| 89 |
try:
|
|
|
|
| 94 |
params = SimpleNamespace(**params.dict())
|
| 95 |
if info.input_mode == "image":
|
| 96 |
image_data = await websocket.receive_bytes()
|
| 97 |
+
params.image = bytes_to_pil(image_data)
|
| 98 |
+
|
| 99 |
+
await user_data.update_data(user_id, params)
|
| 100 |
if args.timeout > 0 and time.time() - last_time > args.timeout:
|
| 101 |
await websocket.send_json(
|
| 102 |
{
|
| 103 |
"status": "timeout",
|
| 104 |
"message": "Your session has ended",
|
| 105 |
+
"userId": user_id,
|
| 106 |
}
|
| 107 |
)
|
| 108 |
await websocket.close()
|
| 109 |
return
|
| 110 |
+
await asyncio.sleep(1.0 / 24)
|
| 111 |
|
| 112 |
except Exception as e:
|
| 113 |
logging.error(f"Error: {e}")
|
frontend/src/lib/components/Button.svelte
CHANGED
|
@@ -7,7 +7,7 @@
|
|
| 7 |
<slot />
|
| 8 |
</button>
|
| 9 |
|
| 10 |
-
<style lang="postcss">
|
| 11 |
.button {
|
| 12 |
@apply rounded bg-gray-700 p-2 font-normal text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:bg-gray-300 dark:disabled:bg-gray-700 dark:disabled:text-black;
|
| 13 |
}
|
|
|
|
| 7 |
<slot />
|
| 8 |
</button>
|
| 9 |
|
| 10 |
+
<style lang="postcss" scoped>
|
| 11 |
.button {
|
| 12 |
@apply rounded bg-gray-700 p-2 font-normal text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:bg-gray-300 dark:disabled:bg-gray-700 dark:disabled:text-black;
|
| 13 |
}
|
frontend/src/lib/components/ImagePlayer.svelte
CHANGED
|
@@ -1,18 +1,19 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import {
|
| 3 |
import { onFrameChangeStore } from '$lib/mediaStream';
|
| 4 |
import { PUBLIC_BASE_URL } from '$env/static/public';
|
| 5 |
|
| 6 |
-
$: streamId = $lcmLiveState?.streamId;
|
| 7 |
$: {
|
| 8 |
-
console.log('streamId', streamId);
|
| 9 |
}
|
|
|
|
|
|
|
| 10 |
</script>
|
| 11 |
|
| 12 |
<div class="relative overflow-hidden rounded-lg border border-slate-300">
|
| 13 |
<!-- svelte-ignore a11y-missing-attribute -->
|
| 14 |
-
{#if
|
| 15 |
-
<img class="aspect-square w-full rounded-lg" src={PUBLIC_BASE_URL + '/stream/' + streamId} />
|
| 16 |
{:else}
|
| 17 |
<div class="aspect-square w-full rounded-lg" />
|
| 18 |
{/if}
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { lcmLiveStatus, LCMLiveStatus, streamId } from '$lib/lcmLive';
|
| 3 |
import { onFrameChangeStore } from '$lib/mediaStream';
|
| 4 |
import { PUBLIC_BASE_URL } from '$env/static/public';
|
| 5 |
|
|
|
|
| 6 |
$: {
|
| 7 |
+
console.log('streamId', $streamId);
|
| 8 |
}
|
| 9 |
+
$: isLCMRunning = $lcmLiveStatus !== LCMLiveStatus.DISCONNECTED;
|
| 10 |
+
$: console.log('isLCMRunning', isLCMRunning);
|
| 11 |
</script>
|
| 12 |
|
| 13 |
<div class="relative overflow-hidden rounded-lg border border-slate-300">
|
| 14 |
<!-- svelte-ignore a11y-missing-attribute -->
|
| 15 |
+
{#if isLCMRunning}
|
| 16 |
+
<img class="aspect-square w-full rounded-lg" src={PUBLIC_BASE_URL + '/stream/' + $streamId} />
|
| 17 |
{:else}
|
| 18 |
<div class="aspect-square w-full rounded-lg" />
|
| 19 |
{/if}
|
frontend/src/lib/components/PipelineOptions.svelte
CHANGED
|
@@ -17,13 +17,13 @@
|
|
| 17 |
<div>
|
| 18 |
{#if featuredOptions}
|
| 19 |
{#each featuredOptions as params}
|
| 20 |
-
{#if params.field === FieldType.
|
| 21 |
<InputRange {params} bind:value={$pipelineValues[params.id]}></InputRange>
|
| 22 |
-
{:else if params.field === FieldType.
|
| 23 |
<SeedInput bind:value={$pipelineValues[params.id]}></SeedInput>
|
| 24 |
-
{:else if params.field === FieldType.
|
| 25 |
<TextArea {params} bind:value={$pipelineValues[params.id]}></TextArea>
|
| 26 |
-
{:else if params.field === FieldType.
|
| 27 |
<Checkbox {params} bind:value={$pipelineValues[params.id]}></Checkbox>
|
| 28 |
{/if}
|
| 29 |
{/each}
|
|
@@ -33,17 +33,17 @@
|
|
| 33 |
<details open>
|
| 34 |
<summary class="cursor-pointer font-medium">Advanced Options</summary>
|
| 35 |
<div
|
| 36 |
-
class="grid grid-cols-1 items-center gap-3 {
|
| 37 |
>
|
| 38 |
{#if advanceOptions}
|
| 39 |
{#each advanceOptions as params}
|
| 40 |
-
{#if params.field === FieldType.
|
| 41 |
<InputRange {params} bind:value={$pipelineValues[params.id]}></InputRange>
|
| 42 |
-
{:else if params.field === FieldType.
|
| 43 |
<SeedInput bind:value={$pipelineValues[params.id]}></SeedInput>
|
| 44 |
-
{:else if params.field === FieldType.
|
| 45 |
<TextArea {params} bind:value={$pipelineValues[params.id]}></TextArea>
|
| 46 |
-
{:else if params.field === FieldType.
|
| 47 |
<Checkbox {params} bind:value={$pipelineValues[params.id]}></Checkbox>
|
| 48 |
{/if}
|
| 49 |
{/each}
|
|
|
|
| 17 |
<div>
|
| 18 |
{#if featuredOptions}
|
| 19 |
{#each featuredOptions as params}
|
| 20 |
+
{#if params.field === FieldType.RANGE}
|
| 21 |
<InputRange {params} bind:value={$pipelineValues[params.id]}></InputRange>
|
| 22 |
+
{:else if params.field === FieldType.SEED}
|
| 23 |
<SeedInput bind:value={$pipelineValues[params.id]}></SeedInput>
|
| 24 |
+
{:else if params.field === FieldType.TEXTAREA}
|
| 25 |
<TextArea {params} bind:value={$pipelineValues[params.id]}></TextArea>
|
| 26 |
+
{:else if params.field === FieldType.CHECKBOX}
|
| 27 |
<Checkbox {params} bind:value={$pipelineValues[params.id]}></Checkbox>
|
| 28 |
{/if}
|
| 29 |
{/each}
|
|
|
|
| 33 |
<details open>
|
| 34 |
<summary class="cursor-pointer font-medium">Advanced Options</summary>
|
| 35 |
<div
|
| 36 |
+
class="grid grid-cols-1 items-center gap-3 {pipelineParams.length > 5 ? 'sm:grid-cols-2' : ''}"
|
| 37 |
>
|
| 38 |
{#if advanceOptions}
|
| 39 |
{#each advanceOptions as params}
|
| 40 |
+
{#if params.field === FieldType.RANGE}
|
| 41 |
<InputRange {params} bind:value={$pipelineValues[params.id]}></InputRange>
|
| 42 |
+
{:else if params.field === FieldType.SEED}
|
| 43 |
<SeedInput bind:value={$pipelineValues[params.id]}></SeedInput>
|
| 44 |
+
{:else if params.field === FieldType.TEXTAREA}
|
| 45 |
<TextArea {params} bind:value={$pipelineValues[params.id]}></TextArea>
|
| 46 |
+
{:else if params.field === FieldType.CHECKBOX}
|
| 47 |
<Checkbox {params} bind:value={$pipelineValues[params.id]}></Checkbox>
|
| 48 |
{/if}
|
| 49 |
{/each}
|
frontend/src/lib/components/VideoInput.svelte
CHANGED
|
@@ -1,42 +1,38 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
import 'rvfc-polyfill';
|
| 3 |
-
import {
|
| 4 |
import {
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
onFrameChangeStore
|
| 10 |
} from '$lib/mediaStream';
|
| 11 |
|
| 12 |
-
$: mediaStream = $mediaStreamState.mediaStream;
|
| 13 |
-
|
| 14 |
let videoEl: HTMLVideoElement;
|
| 15 |
let videoFrameCallbackId: number;
|
| 16 |
const WIDTH = 512;
|
| 17 |
const HEIGHT = 512;
|
|
|
|
| 18 |
|
| 19 |
onDestroy(() => {
|
| 20 |
if (videoFrameCallbackId) videoEl.cancelVideoFrameCallback(videoFrameCallbackId);
|
| 21 |
});
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
return {
|
| 26 |
-
update(newStream: MediaStream) {
|
| 27 |
-
if (node.srcObject != newStream) {
|
| 28 |
-
node.srcObject = newStream;
|
| 29 |
-
}
|
| 30 |
-
}
|
| 31 |
-
};
|
| 32 |
}
|
|
|
|
|
|
|
| 33 |
async function onFrameChange(now: DOMHighResTimeStamp, metadata: VideoFrameCallbackMetadata) {
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
| 36 |
videoFrameCallbackId = videoEl.requestVideoFrameCallback(onFrameChange);
|
| 37 |
}
|
| 38 |
|
| 39 |
-
$: if ($
|
| 40 |
videoFrameCallbackId = videoEl.requestVideoFrameCallback(onFrameChange);
|
| 41 |
}
|
| 42 |
async function grapBlobImg() {
|
|
@@ -70,7 +66,6 @@
|
|
| 70 |
autoplay
|
| 71 |
muted
|
| 72 |
loop
|
| 73 |
-
use:srcObject={mediaStream}
|
| 74 |
></video>
|
| 75 |
</div>
|
| 76 |
<svg
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
import 'rvfc-polyfill';
|
| 3 |
+
import { onDestroy } from 'svelte';
|
| 4 |
import {
|
| 5 |
+
mediaStreamStatus,
|
| 6 |
+
MediaStreamStatusEnum,
|
| 7 |
+
onFrameChangeStore,
|
| 8 |
+
mediaStream
|
|
|
|
| 9 |
} from '$lib/mediaStream';
|
| 10 |
|
|
|
|
|
|
|
| 11 |
let videoEl: HTMLVideoElement;
|
| 12 |
let videoFrameCallbackId: number;
|
| 13 |
const WIDTH = 512;
|
| 14 |
const HEIGHT = 512;
|
| 15 |
+
const THROTTLE_FPS = 10;
|
| 16 |
|
| 17 |
onDestroy(() => {
|
| 18 |
if (videoFrameCallbackId) videoEl.cancelVideoFrameCallback(videoFrameCallbackId);
|
| 19 |
});
|
| 20 |
|
| 21 |
+
$: if (videoEl) {
|
| 22 |
+
videoEl.srcObject = $mediaStream;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
+
|
| 25 |
+
let last_millis = 0;
|
| 26 |
async function onFrameChange(now: DOMHighResTimeStamp, metadata: VideoFrameCallbackMetadata) {
|
| 27 |
+
if (now - last_millis > 1000 / THROTTLE_FPS) {
|
| 28 |
+
const blob = await grapBlobImg();
|
| 29 |
+
onFrameChangeStore.set({ now, metadata, blob });
|
| 30 |
+
last_millis = now;
|
| 31 |
+
}
|
| 32 |
videoFrameCallbackId = videoEl.requestVideoFrameCallback(onFrameChange);
|
| 33 |
}
|
| 34 |
|
| 35 |
+
$: if ($mediaStreamStatus == MediaStreamStatusEnum.CONNECTED) {
|
| 36 |
videoFrameCallbackId = videoEl.requestVideoFrameCallback(onFrameChange);
|
| 37 |
}
|
| 38 |
async function grapBlobImg() {
|
|
|
|
| 66 |
autoplay
|
| 67 |
muted
|
| 68 |
loop
|
|
|
|
| 69 |
></video>
|
| 70 |
</div>
|
| 71 |
<svg
|
frontend/src/lib/lcmLive.ts
CHANGED
|
@@ -1,27 +1,17 @@
|
|
| 1 |
import { writable } from 'svelte/store';
|
| 2 |
import { PUBLIC_WSS_URL } from '$env/static/public';
|
| 3 |
|
| 4 |
-
export const isStreaming = writable(false);
|
| 5 |
-
export const isLCMRunning = writable(false);
|
| 6 |
-
|
| 7 |
|
| 8 |
export enum LCMLiveStatus {
|
| 9 |
-
INIT = "init",
|
| 10 |
CONNECTED = "connected",
|
| 11 |
DISCONNECTED = "disconnected",
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
-
|
| 15 |
-
streamId: string | null;
|
| 16 |
-
status: LCMLiveStatus
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
const initialState: lcmLive = {
|
| 20 |
-
streamId: null,
|
| 21 |
-
status: LCMLiveStatus.INIT
|
| 22 |
-
};
|
| 23 |
|
| 24 |
-
export const
|
|
|
|
| 25 |
|
| 26 |
let websocket: WebSocket | null = null;
|
| 27 |
export const lcmLiveActions = {
|
|
@@ -37,12 +27,8 @@ export const lcmLiveActions = {
|
|
| 37 |
console.log("Connected to websocket");
|
| 38 |
};
|
| 39 |
websocket.onclose = () => {
|
| 40 |
-
|
| 41 |
-
...state,
|
| 42 |
-
status: LCMLiveStatus.DISCONNECTED
|
| 43 |
-
}));
|
| 44 |
console.log("Disconnected from websocket");
|
| 45 |
-
isLCMRunning.set(false);
|
| 46 |
};
|
| 47 |
websocket.onerror = (err) => {
|
| 48 |
console.error(err);
|
|
@@ -51,47 +37,29 @@ export const lcmLiveActions = {
|
|
| 51 |
const data = JSON.parse(event.data);
|
| 52 |
console.log("WS: ", data);
|
| 53 |
switch (data.status) {
|
| 54 |
-
case "
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
lcmLiveState.update((state) => ({
|
| 59 |
-
...state,
|
| 60 |
-
status: LCMLiveStatus.CONNECTED,
|
| 61 |
-
streamId: streamId,
|
| 62 |
-
}));
|
| 63 |
-
isLCMRunning.set(true);
|
| 64 |
-
resolve(streamId);
|
| 65 |
break;
|
| 66 |
case "timeout":
|
| 67 |
console.log("timeout");
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
...state,
|
| 71 |
-
status: LCMLiveStatus.DISCONNECTED,
|
| 72 |
-
streamId: null,
|
| 73 |
-
}));
|
| 74 |
reject("timeout");
|
| 75 |
case "error":
|
| 76 |
console.log(data.message);
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
...state,
|
| 80 |
-
status: LCMLiveStatus.DISCONNECTED,
|
| 81 |
-
streamId: null,
|
| 82 |
-
}));
|
| 83 |
reject(data.message);
|
| 84 |
}
|
| 85 |
};
|
| 86 |
|
| 87 |
} catch (err) {
|
| 88 |
console.error(err);
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
status: LCMLiveStatus.DISCONNECTED,
|
| 93 |
-
streamId: null,
|
| 94 |
-
}));
|
| 95 |
reject(err);
|
| 96 |
}
|
| 97 |
});
|
|
@@ -113,7 +81,7 @@ export const lcmLiveActions = {
|
|
| 113 |
websocket.close();
|
| 114 |
}
|
| 115 |
websocket = null;
|
| 116 |
-
|
| 117 |
-
|
| 118 |
},
|
| 119 |
};
|
|
|
|
| 1 |
import { writable } from 'svelte/store';
|
| 2 |
import { PUBLIC_WSS_URL } from '$env/static/public';
|
| 3 |
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
export enum LCMLiveStatus {
|
|
|
|
| 6 |
CONNECTED = "connected",
|
| 7 |
DISCONNECTED = "disconnected",
|
| 8 |
+
WAIT = "wait",
|
| 9 |
}
|
| 10 |
|
| 11 |
+
const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
export const lcmLiveStatus = writable<LCMLiveStatus>(initStatus);
|
| 14 |
+
export const streamId = writable<string | null>(null);
|
| 15 |
|
| 16 |
let websocket: WebSocket | null = null;
|
| 17 |
export const lcmLiveActions = {
|
|
|
|
| 27 |
console.log("Connected to websocket");
|
| 28 |
};
|
| 29 |
websocket.onclose = () => {
|
| 30 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
|
|
|
|
|
|
|
|
|
| 31 |
console.log("Disconnected from websocket");
|
|
|
|
| 32 |
};
|
| 33 |
websocket.onerror = (err) => {
|
| 34 |
console.error(err);
|
|
|
|
| 37 |
const data = JSON.parse(event.data);
|
| 38 |
console.log("WS: ", data);
|
| 39 |
switch (data.status) {
|
| 40 |
+
case "connected":
|
| 41 |
+
const userId = data.userId;
|
| 42 |
+
lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
|
| 43 |
+
streamId.set(userId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
break;
|
| 45 |
case "timeout":
|
| 46 |
console.log("timeout");
|
| 47 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 48 |
+
streamId.set(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
reject("timeout");
|
| 50 |
case "error":
|
| 51 |
console.log(data.message);
|
| 52 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 53 |
+
streamId.set(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
reject(data.message);
|
| 55 |
}
|
| 56 |
};
|
| 57 |
|
| 58 |
} catch (err) {
|
| 59 |
console.error(err);
|
| 60 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 61 |
+
streamId.set(null);
|
| 62 |
+
|
|
|
|
|
|
|
|
|
|
| 63 |
reject(err);
|
| 64 |
}
|
| 65 |
});
|
|
|
|
| 81 |
websocket.close();
|
| 82 |
}
|
| 83 |
websocket = null;
|
| 84 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 85 |
+
streamId.set(null);
|
| 86 |
},
|
| 87 |
};
|
frontend/src/lib/mediaStream.ts
CHANGED
|
@@ -1,38 +1,23 @@
|
|
| 1 |
import { writable, type Writable } from 'svelte/store';
|
| 2 |
|
| 3 |
-
export enum
|
| 4 |
INIT = "init",
|
| 5 |
CONNECTED = "connected",
|
| 6 |
DISCONNECTED = "disconnected",
|
| 7 |
}
|
| 8 |
export const onFrameChangeStore: Writable<{ now: Number, metadata: VideoFrameCallbackMetadata, blob: Blob }> = writable();
|
| 9 |
-
export const isMediaStreaming = writable(MediaStreamStatus.INIT);
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
devices: MediaDeviceInfo[];
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
const initialState: mediaStream = {
|
| 18 |
-
mediaStream: null,
|
| 19 |
-
status: MediaStreamStatus.INIT,
|
| 20 |
-
devices: [],
|
| 21 |
-
};
|
| 22 |
-
|
| 23 |
-
export const mediaStreamState = writable(initialState);
|
| 24 |
|
| 25 |
export const mediaStreamActions = {
|
| 26 |
async enumerateDevices() {
|
| 27 |
-
console.log("Enumerating devices");
|
| 28 |
await navigator.mediaDevices.enumerateDevices()
|
| 29 |
.then(devices => {
|
| 30 |
const cameras = devices.filter(device => device.kind === 'videoinput');
|
| 31 |
-
|
| 32 |
-
mediaStreamState.update((state) => ({
|
| 33 |
-
...state,
|
| 34 |
-
devices: cameras,
|
| 35 |
-
}));
|
| 36 |
})
|
| 37 |
.catch(err => {
|
| 38 |
console.error(err);
|
|
@@ -48,17 +33,14 @@ export const mediaStreamActions = {
|
|
| 48 |
|
| 49 |
await navigator.mediaDevices
|
| 50 |
.getUserMedia(constraints)
|
| 51 |
-
.then((
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
mediaStream: mediaStream,
|
| 55 |
-
status: MediaStreamStatus.CONNECTED,
|
| 56 |
-
}));
|
| 57 |
-
isMediaStreaming.set(MediaStreamStatus.CONNECTED);
|
| 58 |
})
|
| 59 |
.catch((err) => {
|
| 60 |
console.error(`${err.name}: ${err.message}`);
|
| 61 |
-
|
|
|
|
| 62 |
});
|
| 63 |
},
|
| 64 |
async switchCamera(mediaDevicedID: string) {
|
|
@@ -68,26 +50,19 @@ export const mediaStreamActions = {
|
|
| 68 |
};
|
| 69 |
await navigator.mediaDevices
|
| 70 |
.getUserMedia(constraints)
|
| 71 |
-
.then((
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
mediaStream: mediaStream,
|
| 75 |
-
status: MediaStreamStatus.CONNECTED,
|
| 76 |
-
}));
|
| 77 |
})
|
| 78 |
.catch((err) => {
|
| 79 |
console.error(`${err.name}: ${err.message}`);
|
| 80 |
});
|
| 81 |
},
|
| 82 |
async stop() {
|
| 83 |
-
navigator.mediaDevices.getUserMedia({ video: true }).then((
|
| 84 |
-
|
| 85 |
});
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
mediaStream: null,
|
| 89 |
-
status: MediaStreamStatus.DISCONNECTED,
|
| 90 |
-
}));
|
| 91 |
-
isMediaStreaming.set(MediaStreamStatus.DISCONNECTED);
|
| 92 |
},
|
| 93 |
};
|
|
|
|
| 1 |
import { writable, type Writable } from 'svelte/store';
|
| 2 |
|
| 3 |
+
export enum MediaStreamStatusEnum {
|
| 4 |
INIT = "init",
|
| 5 |
CONNECTED = "connected",
|
| 6 |
DISCONNECTED = "disconnected",
|
| 7 |
}
|
| 8 |
export const onFrameChangeStore: Writable<{ now: Number, metadata: VideoFrameCallbackMetadata, blob: Blob }> = writable();
|
|
|
|
| 9 |
|
| 10 |
+
export const mediaDevices = writable<MediaDeviceInfo[]>([]);
|
| 11 |
+
export const mediaStreamStatus = writable(MediaStreamStatusEnum.INIT);
|
| 12 |
+
export const mediaStream = writable<MediaStream | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
export const mediaStreamActions = {
|
| 15 |
async enumerateDevices() {
|
| 16 |
+
// console.log("Enumerating devices");
|
| 17 |
await navigator.mediaDevices.enumerateDevices()
|
| 18 |
.then(devices => {
|
| 19 |
const cameras = devices.filter(device => device.kind === 'videoinput');
|
| 20 |
+
mediaDevices.set(cameras);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
})
|
| 22 |
.catch(err => {
|
| 23 |
console.error(err);
|
|
|
|
| 33 |
|
| 34 |
await navigator.mediaDevices
|
| 35 |
.getUserMedia(constraints)
|
| 36 |
+
.then((stream) => {
|
| 37 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
| 38 |
+
mediaStream.set(stream);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
})
|
| 40 |
.catch((err) => {
|
| 41 |
console.error(`${err.name}: ${err.message}`);
|
| 42 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
|
| 43 |
+
mediaStream.set(null);
|
| 44 |
});
|
| 45 |
},
|
| 46 |
async switchCamera(mediaDevicedID: string) {
|
|
|
|
| 50 |
};
|
| 51 |
await navigator.mediaDevices
|
| 52 |
.getUserMedia(constraints)
|
| 53 |
+
.then((stream) => {
|
| 54 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
| 55 |
+
mediaStream.set(stream)
|
|
|
|
|
|
|
|
|
|
| 56 |
})
|
| 57 |
.catch((err) => {
|
| 58 |
console.error(`${err.name}: ${err.message}`);
|
| 59 |
});
|
| 60 |
},
|
| 61 |
async stop() {
|
| 62 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
|
| 63 |
+
stream.getTracks().forEach((track) => track.stop());
|
| 64 |
});
|
| 65 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
|
| 66 |
+
mediaStream.set(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
},
|
| 68 |
};
|
frontend/src/lib/store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
|
| 2 |
import { writable, type Writable } from 'svelte/store';
|
| 3 |
|
| 4 |
-
export const pipelineValues = writable({});
|
|
|
|
| 1 |
|
| 2 |
import { writable, type Writable } from 'svelte/store';
|
| 3 |
|
| 4 |
+
export const pipelineValues = writable({} as Record<string, any>);
|
frontend/src/lib/types.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
export const enum FieldType {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
}
|
| 7 |
export const enum PipelineMode {
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
}
|
| 12 |
|
| 13 |
export interface FieldProps {
|
|
|
|
| 1 |
export const enum FieldType {
|
| 2 |
+
RANGE = "range",
|
| 3 |
+
SEED = "seed",
|
| 4 |
+
TEXTAREA = "textarea",
|
| 5 |
+
CHECKBOX = "checkbox",
|
| 6 |
}
|
| 7 |
export const enum PipelineMode {
|
| 8 |
+
IMAGE = "image",
|
| 9 |
+
VIDEO = "video",
|
| 10 |
+
TEXT = "text",
|
| 11 |
}
|
| 12 |
|
| 13 |
export interface FieldProps {
|
frontend/src/routes/+page.svelte
CHANGED
|
@@ -8,12 +8,12 @@
|
|
| 8 |
import Button from '$lib/components/Button.svelte';
|
| 9 |
import PipelineOptions from '$lib/components/PipelineOptions.svelte';
|
| 10 |
import Spinner from '$lib/icons/spinner.svelte';
|
| 11 |
-
import {
|
| 12 |
import {
|
| 13 |
-
mediaStreamState,
|
| 14 |
mediaStreamActions,
|
| 15 |
-
|
| 16 |
-
onFrameChangeStore
|
|
|
|
| 17 |
} from '$lib/mediaStream';
|
| 18 |
import { pipelineValues } from '$lib/store';
|
| 19 |
|
|
@@ -30,7 +30,7 @@
|
|
| 30 |
const settings = await fetch(`${PUBLIC_BASE_URL}/settings`).then((r) => r.json());
|
| 31 |
pipelineParams = Object.values(settings.input_params.properties);
|
| 32 |
pipelineInfo = settings.info.properties;
|
| 33 |
-
isImageMode = pipelineInfo.input_mode.default === PipelineMode.
|
| 34 |
maxQueueSize = settings.max_queue_size;
|
| 35 |
pipelineParams = pipelineParams.filter((e) => e?.disabled !== true);
|
| 36 |
console.log('PARAMS', pipelineParams);
|
|
@@ -38,22 +38,37 @@
|
|
| 38 |
}
|
| 39 |
console.log('isImageMode', isImageMode);
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
// send Webcam stream to LCM if image mode
|
| 42 |
$: {
|
| 43 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
lcmLiveActions.send($pipelineValues);
|
| 45 |
lcmLiveActions.send($onFrameChangeStore.blob);
|
| 46 |
}
|
| 47 |
}
|
| 48 |
|
| 49 |
-
|
| 50 |
-
$: {
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
async function toggleLcmLive() {
|
| 56 |
-
if (
|
| 57 |
if (isImageMode) {
|
| 58 |
await mediaStreamActions.enumerateDevices();
|
| 59 |
await mediaStreamActions.start();
|
|
@@ -112,13 +127,13 @@
|
|
| 112 |
<PipelineOptions {pipelineParams}></PipelineOptions>
|
| 113 |
<div class="flex gap-3">
|
| 114 |
<Button on:click={toggleLcmLive}>
|
| 115 |
-
{#if
|
| 116 |
Stop
|
| 117 |
{:else}
|
| 118 |
Start
|
| 119 |
{/if}
|
| 120 |
</Button>
|
| 121 |
-
<Button disabled={
|
| 122 |
</div>
|
| 123 |
|
| 124 |
<ImagePlayer>
|
|
|
|
| 8 |
import Button from '$lib/components/Button.svelte';
|
| 9 |
import PipelineOptions from '$lib/components/PipelineOptions.svelte';
|
| 10 |
import Spinner from '$lib/icons/spinner.svelte';
|
| 11 |
+
import { lcmLiveStatus, lcmLiveActions, LCMLiveStatus } from '$lib/lcmLive';
|
| 12 |
import {
|
|
|
|
| 13 |
mediaStreamActions,
|
| 14 |
+
mediaStreamStatus,
|
| 15 |
+
onFrameChangeStore,
|
| 16 |
+
MediaStreamStatusEnum
|
| 17 |
} from '$lib/mediaStream';
|
| 18 |
import { pipelineValues } from '$lib/store';
|
| 19 |
|
|
|
|
| 30 |
const settings = await fetch(`${PUBLIC_BASE_URL}/settings`).then((r) => r.json());
|
| 31 |
pipelineParams = Object.values(settings.input_params.properties);
|
| 32 |
pipelineInfo = settings.info.properties;
|
| 33 |
+
isImageMode = pipelineInfo.input_mode.default === PipelineMode.IMAGE;
|
| 34 |
maxQueueSize = settings.max_queue_size;
|
| 35 |
pipelineParams = pipelineParams.filter((e) => e?.disabled !== true);
|
| 36 |
console.log('PARAMS', pipelineParams);
|
|
|
|
| 38 |
}
|
| 39 |
console.log('isImageMode', isImageMode);
|
| 40 |
|
| 41 |
+
$: {
|
| 42 |
+
console.log('lcmLiveState', $lcmLiveStatus);
|
| 43 |
+
}
|
| 44 |
+
$: {
|
| 45 |
+
console.log('mediaStreamState', $mediaStreamStatus);
|
| 46 |
+
}
|
| 47 |
// send Webcam stream to LCM if image mode
|
| 48 |
$: {
|
| 49 |
+
if (
|
| 50 |
+
isImageMode &&
|
| 51 |
+
$lcmLiveStatus === LCMLiveStatus.CONNECTED &&
|
| 52 |
+
$mediaStreamStatus === MediaStreamStatusEnum.CONNECTED
|
| 53 |
+
) {
|
| 54 |
lcmLiveActions.send($pipelineValues);
|
| 55 |
lcmLiveActions.send($onFrameChangeStore.blob);
|
| 56 |
}
|
| 57 |
}
|
| 58 |
|
| 59 |
+
$: isLCMRunning = $lcmLiveStatus !== LCMLiveStatus.DISCONNECTED;
|
| 60 |
+
// $: {
|
| 61 |
+
// console.log('onFrameChangeStore', $onFrameChangeStore);
|
| 62 |
+
// }
|
| 63 |
+
|
| 64 |
+
// // send Webcam stream to LCM
|
| 65 |
+
// $: {
|
| 66 |
+
// if ($lcmLiveState.status === LCMLiveStatus.CONNECTED) {
|
| 67 |
+
// lcmLiveActions.send($pipelineValues);
|
| 68 |
+
// }
|
| 69 |
+
// }
|
| 70 |
async function toggleLcmLive() {
|
| 71 |
+
if (!isLCMRunning) {
|
| 72 |
if (isImageMode) {
|
| 73 |
await mediaStreamActions.enumerateDevices();
|
| 74 |
await mediaStreamActions.start();
|
|
|
|
| 127 |
<PipelineOptions {pipelineParams}></PipelineOptions>
|
| 128 |
<div class="flex gap-3">
|
| 129 |
<Button on:click={toggleLcmLive}>
|
| 130 |
+
{#if isLCMRunning}
|
| 131 |
Stop
|
| 132 |
{:else}
|
| 133 |
Start
|
| 134 |
{/if}
|
| 135 |
</Button>
|
| 136 |
+
<Button disabled={isLCMRunning} classList={'ml-auto'}>Snapshot</Button>
|
| 137 |
</div>
|
| 138 |
|
| 139 |
<ImagePlayer>
|
frontend/svelte.config.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
| 1 |
import adapter from '@sveltejs/adapter-static';
|
| 2 |
import { vitePreprocess } from '@sveltejs/kit/vite';
|
| 3 |
-
|
| 4 |
/** @type {import('@sveltejs/kit').Config} */
|
| 5 |
const config = {
|
| 6 |
-
preprocess: vitePreprocess(),
|
| 7 |
-
|
| 8 |
kit: {
|
| 9 |
adapter: adapter({
|
| 10 |
pages: '../public',
|
|
|
|
| 1 |
import adapter from '@sveltejs/adapter-static';
|
| 2 |
import { vitePreprocess } from '@sveltejs/kit/vite';
|
|
|
|
| 3 |
/** @type {import('@sveltejs/kit').Config} */
|
| 4 |
const config = {
|
| 5 |
+
preprocess: vitePreprocess({ postcss: true }),
|
|
|
|
| 6 |
kit: {
|
| 7 |
adapter: adapter({
|
| 8 |
pages: '../public',
|
user_queue.py
CHANGED
|
@@ -1,29 +1,52 @@
|
|
| 1 |
-
from typing import Dict
|
| 2 |
from uuid import UUID
|
| 3 |
import asyncio
|
| 4 |
-
from
|
| 5 |
-
from
|
| 6 |
-
from
|
|
|
|
| 7 |
|
| 8 |
-
|
| 9 |
-
UserId = UUID
|
| 10 |
-
EventDataContent = Dict[str, InputParams]
|
| 11 |
|
| 12 |
|
| 13 |
-
class
|
| 14 |
def __init__(self):
|
| 15 |
-
self.
|
| 16 |
-
self.data_content: EventDataContent = {}
|
| 17 |
|
| 18 |
-
def
|
| 19 |
-
self.data_content =
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
self.data_event.clear()
|
| 25 |
-
return self.data_content
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict
|
| 2 |
from uuid import UUID
|
| 3 |
import asyncio
|
| 4 |
+
from fastapi import WebSocket
|
| 5 |
+
from types import SimpleNamespace
|
| 6 |
+
from typing import Dict
|
| 7 |
+
from typing import Union
|
| 8 |
|
| 9 |
+
UserDataContent = Dict[UUID, Dict[str, Union[WebSocket, asyncio.Queue]]]
|
|
|
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
+
class UserData:
|
| 13 |
def __init__(self):
|
| 14 |
+
self.data_content: Dict[UUID, UserDataContent] = {}
|
|
|
|
| 15 |
|
| 16 |
+
async def create_user(self, user_id: UUID, websocket: WebSocket):
|
| 17 |
+
self.data_content[user_id] = {
|
| 18 |
+
"websocket": websocket,
|
| 19 |
+
"queue": asyncio.Queue(),
|
| 20 |
+
}
|
| 21 |
+
await asyncio.sleep(1)
|
| 22 |
|
| 23 |
+
def check_user(self, user_id: UUID) -> bool:
|
| 24 |
+
return user_id in self.data_content
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
async def update_data(self, user_id: UUID, new_data: SimpleNamespace):
|
| 27 |
+
user_session = self.data_content[user_id]
|
| 28 |
+
queue = user_session["queue"]
|
| 29 |
+
while not queue.empty():
|
| 30 |
+
try:
|
| 31 |
+
queue.get_nowait()
|
| 32 |
+
except asyncio.QueueEmpty:
|
| 33 |
+
continue
|
| 34 |
+
await queue.put(new_data)
|
| 35 |
|
| 36 |
+
async def get_latest_data(self, user_id: UUID) -> SimpleNamespace:
|
| 37 |
+
user_session = self.data_content[user_id]
|
| 38 |
+
queue = user_session["queue"]
|
| 39 |
+
try:
|
| 40 |
+
return await queue.get()
|
| 41 |
+
except asyncio.QueueEmpty:
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
def delete_user(self, user_id: UUID):
|
| 45 |
+
if user_id in self.data_content:
|
| 46 |
+
del self.data_content[user_id]
|
| 47 |
+
|
| 48 |
+
def get_user_count(self) -> int:
|
| 49 |
+
return len(self.data_content)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
user_data = UserData()
|
util.py
CHANGED
|
@@ -20,6 +20,11 @@ def get_pipeline_class(pipeline_name: str) -> ModuleType:
|
|
| 20 |
return pipeline_class
|
| 21 |
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def pil_to_frame(image: Image.Image) -> bytes:
|
| 24 |
frame_data = io.BytesIO()
|
| 25 |
image.save(frame_data, format="JPEG")
|
|
|
|
| 20 |
return pipeline_class
|
| 21 |
|
| 22 |
|
| 23 |
+
def bytes_to_pil(image_bytes: bytes) -> Image.Image:
|
| 24 |
+
image = Image.open(io.BytesIO(image_bytes))
|
| 25 |
+
return image
|
| 26 |
+
|
| 27 |
+
|
| 28 |
def pil_to_frame(image: Image.Image) -> bytes:
|
| 29 |
frame_data = io.BytesIO()
|
| 30 |
image.save(frame_data, format="JPEG")
|