Spaces:
Runtime error
Runtime error
format
Browse files- frontend/src/lib/lcmLive.ts +82 -84
- frontend/src/lib/mediaStream.ts +102 -104
- frontend/src/lib/store.ts +12 -13
- frontend/src/lib/types.ts +28 -29
- frontend/src/lib/utils.ts +42 -36
- frontend/src/routes/+page.ts +1 -1
- frontend/vite.config.ts +2 -2
frontend/src/lib/lcmLive.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
| 1 |
import { writable } from 'svelte/store';
|
| 2 |
|
| 3 |
-
|
| 4 |
export enum LCMLiveStatus {
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
}
|
| 11 |
|
| 12 |
const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
|
|
@@ -16,84 +15,83 @@ export const streamId = writable<string | null>(null);
|
|
| 16 |
|
| 17 |
let websocket: WebSocket | null = null;
|
| 18 |
export const lcmLiveActions = {
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
websocket = new WebSocket(websocketURL);
|
| 28 |
-
websocket.onopen = () => {
|
| 29 |
-
console.log("Connected to websocket");
|
| 30 |
-
};
|
| 31 |
-
websocket.onclose = () => {
|
| 32 |
-
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 33 |
-
console.log("Disconnected from websocket");
|
| 34 |
-
};
|
| 35 |
-
websocket.onerror = (err) => {
|
| 36 |
-
console.error(err);
|
| 37 |
-
};
|
| 38 |
-
websocket.onmessage = (event) => {
|
| 39 |
-
const data = JSON.parse(event.data);
|
| 40 |
-
switch (data.status) {
|
| 41 |
-
case "connected":
|
| 42 |
-
lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
|
| 43 |
-
streamId.set(userId);
|
| 44 |
-
resolve({ status: "connected", userId });
|
| 45 |
-
break;
|
| 46 |
-
case "send_frame":
|
| 47 |
-
lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME);
|
| 48 |
-
const streamData = getSreamdata();
|
| 49 |
-
websocket?.send(JSON.stringify({ status: "next_frame" }));
|
| 50 |
-
for (const d of streamData) {
|
| 51 |
-
this.send(d);
|
| 52 |
-
}
|
| 53 |
-
break;
|
| 54 |
-
case "wait":
|
| 55 |
-
lcmLiveStatus.set(LCMLiveStatus.WAIT);
|
| 56 |
-
break;
|
| 57 |
-
case "timeout":
|
| 58 |
-
console.log("timeout");
|
| 59 |
-
lcmLiveStatus.set(LCMLiveStatus.TIMEOUT);
|
| 60 |
-
streamId.set(null);
|
| 61 |
-
reject(new Error("timeout"));
|
| 62 |
-
break;
|
| 63 |
-
case "error":
|
| 64 |
-
console.log(data.message);
|
| 65 |
-
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 66 |
-
streamId.set(null);
|
| 67 |
-
reject(new Error(data.message));
|
| 68 |
-
break;
|
| 69 |
-
}
|
| 70 |
-
};
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 93 |
-
if (websocket) {
|
| 94 |
-
websocket.close();
|
| 95 |
-
}
|
| 96 |
-
websocket = null;
|
| 97 |
streamId.set(null);
|
| 98 |
-
|
| 99 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { writable } from 'svelte/store';
|
| 2 |
|
|
|
|
| 3 |
export enum LCMLiveStatus {
|
| 4 |
+
CONNECTED = 'connected',
|
| 5 |
+
DISCONNECTED = 'disconnected',
|
| 6 |
+
WAIT = 'wait',
|
| 7 |
+
SEND_FRAME = 'send_frame',
|
| 8 |
+
TIMEOUT = 'timeout'
|
| 9 |
}
|
| 10 |
|
| 11 |
const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
|
|
|
|
| 15 |
|
| 16 |
let websocket: WebSocket | null = null;
|
| 17 |
export const lcmLiveActions = {
|
| 18 |
+
async start(getSreamdata: () => any[]) {
|
| 19 |
+
return new Promise((resolve, reject) => {
|
| 20 |
+
try {
|
| 21 |
+
const userId = crypto.randomUUID();
|
| 22 |
+
const websocketURL = `${
|
| 23 |
+
window.location.protocol === 'https:' ? 'wss' : 'ws'
|
| 24 |
+
}:${window.location.host}/api/ws/${userId}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
websocket = new WebSocket(websocketURL);
|
| 27 |
+
websocket.onopen = () => {
|
| 28 |
+
console.log('Connected to websocket');
|
| 29 |
+
};
|
| 30 |
+
websocket.onclose = () => {
|
| 31 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 32 |
+
console.log('Disconnected from websocket');
|
| 33 |
+
};
|
| 34 |
+
websocket.onerror = (err) => {
|
| 35 |
+
console.error(err);
|
| 36 |
+
};
|
| 37 |
+
websocket.onmessage = (event) => {
|
| 38 |
+
const data = JSON.parse(event.data);
|
| 39 |
+
switch (data.status) {
|
| 40 |
+
case 'connected':
|
| 41 |
+
lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
|
| 42 |
+
streamId.set(userId);
|
| 43 |
+
resolve({ status: 'connected', userId });
|
| 44 |
+
break;
|
| 45 |
+
case 'send_frame':
|
| 46 |
+
lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME);
|
| 47 |
+
const streamData = getSreamdata();
|
| 48 |
+
websocket?.send(JSON.stringify({ status: 'next_frame' }));
|
| 49 |
+
for (const d of streamData) {
|
| 50 |
+
this.send(d);
|
| 51 |
+
}
|
| 52 |
+
break;
|
| 53 |
+
case 'wait':
|
| 54 |
+
lcmLiveStatus.set(LCMLiveStatus.WAIT);
|
| 55 |
+
break;
|
| 56 |
+
case 'timeout':
|
| 57 |
+
console.log('timeout');
|
| 58 |
+
lcmLiveStatus.set(LCMLiveStatus.TIMEOUT);
|
| 59 |
+
streamId.set(null);
|
| 60 |
+
reject(new Error('timeout'));
|
| 61 |
+
break;
|
| 62 |
+
case 'error':
|
| 63 |
+
console.log(data.message);
|
| 64 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 65 |
+
streamId.set(null);
|
| 66 |
+
reject(new Error(data.message));
|
| 67 |
+
break;
|
| 68 |
+
}
|
| 69 |
+
};
|
| 70 |
+
} catch (err) {
|
| 71 |
+
console.error(err);
|
| 72 |
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
streamId.set(null);
|
| 74 |
+
reject(err);
|
| 75 |
+
}
|
| 76 |
+
});
|
| 77 |
+
},
|
| 78 |
+
send(data: Blob | { [key: string]: any }) {
|
| 79 |
+
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
| 80 |
+
if (data instanceof Blob) {
|
| 81 |
+
websocket.send(data);
|
| 82 |
+
} else {
|
| 83 |
+
websocket.send(JSON.stringify(data));
|
| 84 |
+
}
|
| 85 |
+
} else {
|
| 86 |
+
console.log('WebSocket not connected');
|
| 87 |
+
}
|
| 88 |
+
},
|
| 89 |
+
async stop() {
|
| 90 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
| 91 |
+
if (websocket) {
|
| 92 |
+
websocket.close();
|
| 93 |
+
}
|
| 94 |
+
websocket = null;
|
| 95 |
+
streamId.set(null);
|
| 96 |
+
}
|
| 97 |
+
};
|
frontend/src/lib/mediaStream.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
-
import { writable, type Writable
|
| 2 |
|
| 3 |
const BASE_HEIGHT = 720;
|
| 4 |
export enum MediaStreamStatusEnum {
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
}
|
| 9 |
export const onFrameChangeStore: Writable<{ blob: Blob }> = writable({ blob: new Blob() });
|
| 10 |
|
|
@@ -13,108 +13,106 @@ export const mediaStreamStatus = writable(MediaStreamStatusEnum.INIT);
|
|
| 13 |
export const mediaStream = writable<MediaStream | null>(null);
|
| 14 |
|
| 15 |
export const mediaStreamActions = {
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
| 40 |
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
};
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
let captureStream = null;
|
| 65 |
|
| 66 |
-
|
| 67 |
-
captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
|
| 68 |
-
const videoTrack = captureStream.getVideoTracks()[0];
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
|
| 74 |
-
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
| 75 |
-
mediaStream.set(captureStream)
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
});
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { get, writable, type Writable } from 'svelte/store';
|
| 2 |
|
| 3 |
const BASE_HEIGHT = 720;
|
| 4 |
export enum MediaStreamStatusEnum {
|
| 5 |
+
INIT = 'init',
|
| 6 |
+
CONNECTED = 'connected',
|
| 7 |
+
DISCONNECTED = 'disconnected'
|
| 8 |
}
|
| 9 |
export const onFrameChangeStore: Writable<{ blob: Blob }> = writable({ blob: new Blob() });
|
| 10 |
|
|
|
|
| 13 |
export const mediaStream = writable<MediaStream | null>(null);
|
| 14 |
|
| 15 |
export const mediaStreamActions = {
|
| 16 |
+
async enumerateDevices() {
|
| 17 |
+
// console.log("Enumerating devices");
|
| 18 |
+
await navigator.mediaDevices
|
| 19 |
+
.enumerateDevices()
|
| 20 |
+
.then((devices) => {
|
| 21 |
+
const cameras = devices.filter((device) => device.kind === 'videoinput');
|
| 22 |
+
mediaDevices.set(cameras);
|
| 23 |
+
})
|
| 24 |
+
.catch((err) => {
|
| 25 |
+
console.error(err);
|
| 26 |
+
});
|
| 27 |
+
},
|
| 28 |
+
async start(mediaDevicedID?: string, aspectRatio: number = 1) {
|
| 29 |
+
const constraints = {
|
| 30 |
+
audio: false,
|
| 31 |
+
video: {
|
| 32 |
+
width: {
|
| 33 |
+
ideal: BASE_HEIGHT * aspectRatio
|
| 34 |
+
},
|
| 35 |
+
height: {
|
| 36 |
+
ideal: BASE_HEIGHT
|
| 37 |
+
},
|
| 38 |
+
deviceId: mediaDevicedID
|
| 39 |
+
}
|
| 40 |
+
};
|
| 41 |
|
| 42 |
+
await navigator.mediaDevices
|
| 43 |
+
.getUserMedia(constraints)
|
| 44 |
+
.then((stream) => {
|
| 45 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
| 46 |
+
mediaStream.set(stream);
|
| 47 |
+
})
|
| 48 |
+
.catch((err) => {
|
| 49 |
+
console.error(`${err.name}: ${err.message}`);
|
| 50 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
|
| 51 |
+
mediaStream.set(null);
|
| 52 |
+
});
|
| 53 |
+
},
|
| 54 |
+
async startScreenCapture() {
|
| 55 |
+
const displayMediaOptions = {
|
| 56 |
+
video: {
|
| 57 |
+
displaySurface: 'window'
|
| 58 |
+
},
|
| 59 |
+
audio: false,
|
| 60 |
+
surfaceSwitching: 'include'
|
| 61 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
+
let captureStream = null;
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
try {
|
| 66 |
+
captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
|
| 67 |
+
const videoTrack = captureStream.getVideoTracks()[0];
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
console.log('Track settings:');
|
| 70 |
+
console.log(JSON.stringify(videoTrack.getSettings(), null, 2));
|
| 71 |
+
console.log('Track constraints:');
|
| 72 |
+
console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
|
| 73 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
| 74 |
+
mediaStream.set(captureStream);
|
| 75 |
|
| 76 |
+
const capabilities = videoTrack.getCapabilities();
|
| 77 |
+
const aspectRatio = capabilities.aspectRatio;
|
| 78 |
+
console.log('Aspect Ratio Constraints:', aspectRatio);
|
| 79 |
+
} catch (err) {
|
| 80 |
+
console.error(err);
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
async switchCamera(mediaDevicedID: string, aspectRatio: number) {
|
| 84 |
+
console.log('Switching camera');
|
| 85 |
+
if (get(mediaStreamStatus) !== MediaStreamStatusEnum.CONNECTED) {
|
| 86 |
+
return;
|
| 87 |
+
}
|
| 88 |
+
const constraints = {
|
| 89 |
+
audio: false,
|
| 90 |
+
video: {
|
| 91 |
+
width: {
|
| 92 |
+
ideal: BASE_HEIGHT * aspectRatio
|
| 93 |
+
},
|
| 94 |
+
height: {
|
| 95 |
+
ideal: BASE_HEIGHT
|
| 96 |
+
},
|
| 97 |
+
deviceId: mediaDevicedID
|
| 98 |
+
}
|
| 99 |
+
};
|
| 100 |
+
console.log('Switching camera', constraints);
|
| 101 |
+
await navigator.mediaDevices
|
| 102 |
+
.getUserMedia(constraints)
|
| 103 |
+
.then((stream) => {
|
| 104 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
| 105 |
+
mediaStream.set(stream);
|
| 106 |
+
})
|
| 107 |
+
.catch((err) => {
|
| 108 |
+
console.error(`${err.name}: ${err.message}`);
|
| 109 |
+
});
|
| 110 |
+
},
|
| 111 |
+
async stop() {
|
| 112 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
|
| 113 |
+
stream.getTracks().forEach((track) => track.stop());
|
| 114 |
+
});
|
| 115 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
|
| 116 |
+
mediaStream.set(null);
|
| 117 |
+
}
|
| 118 |
+
};
|
frontend/src/lib/store.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
| 1 |
-
|
| 2 |
-
import { derived, writable, get, type Writable, type Readable } from 'svelte/store';
|
| 3 |
|
| 4 |
export const pipelineValues: Writable<Record<string, any>> = writable({});
|
| 5 |
-
export const deboucedPipelineValues: Readable<Record<string, any>>
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
export const getPipelineValues = () => get(pipelineValues);
|
|
|
|
| 1 |
+
import { derived, get, writable, type Readable, type Writable } from 'svelte/store';
|
|
|
|
| 2 |
|
| 3 |
export const pipelineValues: Writable<Record<string, any>> = writable({});
|
| 4 |
+
export const deboucedPipelineValues: Readable<Record<string, any>> = derived(
|
| 5 |
+
pipelineValues,
|
| 6 |
+
($pipelineValues, set) => {
|
| 7 |
+
const debounced = setTimeout(() => {
|
| 8 |
+
set($pipelineValues);
|
| 9 |
+
}, 100);
|
| 10 |
+
return () => clearTimeout(debounced);
|
| 11 |
+
}
|
| 12 |
+
);
|
| 13 |
+
|
| 14 |
+
export const getPipelineValues = () => get(pipelineValues);
|
frontend/src/lib/types.ts
CHANGED
|
@@ -1,40 +1,39 @@
|
|
| 1 |
export const enum FieldType {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
}
|
| 8 |
export const enum PipelineMode {
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
}
|
| 13 |
|
| 14 |
-
|
| 15 |
export interface Fields {
|
| 16 |
-
|
| 17 |
}
|
| 18 |
|
| 19 |
export interface FieldProps {
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
}
|
| 31 |
export interface PipelineInfo {
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
}
|
|
|
|
| 1 |
export const enum FieldType {
|
| 2 |
+
RANGE = 'range',
|
| 3 |
+
SEED = 'seed',
|
| 4 |
+
TEXTAREA = 'textarea',
|
| 5 |
+
CHECKBOX = 'checkbox',
|
| 6 |
+
SELECT = 'select'
|
| 7 |
}
|
| 8 |
export const enum PipelineMode {
|
| 9 |
+
IMAGE = 'image',
|
| 10 |
+
VIDEO = 'video',
|
| 11 |
+
TEXT = 'text'
|
| 12 |
}
|
| 13 |
|
|
|
|
| 14 |
export interface Fields {
|
| 15 |
+
[key: string]: FieldProps;
|
| 16 |
}
|
| 17 |
|
| 18 |
export interface FieldProps {
|
| 19 |
+
default: number | string;
|
| 20 |
+
max?: number;
|
| 21 |
+
min?: number;
|
| 22 |
+
title: string;
|
| 23 |
+
field: FieldType;
|
| 24 |
+
step?: number;
|
| 25 |
+
disabled?: boolean;
|
| 26 |
+
hide?: boolean;
|
| 27 |
+
id: string;
|
| 28 |
+
values?: string[];
|
| 29 |
}
|
| 30 |
export interface PipelineInfo {
|
| 31 |
+
title: {
|
| 32 |
+
default: string;
|
| 33 |
+
};
|
| 34 |
+
name: string;
|
| 35 |
+
description: string;
|
| 36 |
+
input_mode: {
|
| 37 |
+
default: PipelineMode;
|
| 38 |
+
};
|
| 39 |
+
}
|
frontend/src/lib/utils.ts
CHANGED
|
@@ -1,44 +1,46 @@
|
|
| 1 |
-
import * as piexif from
|
| 2 |
|
| 3 |
interface IImageInfo {
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
}
|
| 9 |
|
| 10 |
export function snapImage(imageEl: HTMLImageElement, info: IImageInfo) {
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
}
|
| 39 |
|
| 40 |
-
export function expandWindow(
|
| 41 |
-
|
| 42 |
<html>
|
| 43 |
<head>
|
| 44 |
<title>Real-Time Latent Consistency Model</title>
|
|
@@ -71,11 +73,15 @@ export function expandWindow(steramURL: string): Window {
|
|
| 71 |
}
|
| 72 |
</script>
|
| 73 |
|
| 74 |
-
<img src="${
|
| 75 |
</body>
|
| 76 |
</html>
|
| 77 |
`;
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as piexif from 'piexifjs';
|
| 2 |
|
| 3 |
interface IImageInfo {
|
| 4 |
+
prompt?: string;
|
| 5 |
+
negative_prompt?: string;
|
| 6 |
+
seed?: number;
|
| 7 |
+
guidance_scale?: number;
|
| 8 |
}
|
| 9 |
|
| 10 |
export function snapImage(imageEl: HTMLImageElement, info: IImageInfo) {
|
| 11 |
+
try {
|
| 12 |
+
const zeroth: { [key: string]: any } = {};
|
| 13 |
+
const exif: { [key: string]: any } = {};
|
| 14 |
+
const gps: { [key: string]: any } = {};
|
| 15 |
+
zeroth[piexif.ImageIFD.Make] = 'LCM Image-to-Image ControNet';
|
| 16 |
+
zeroth[piexif.ImageIFD.ImageDescription] =
|
| 17 |
+
`prompt: ${info?.prompt} | negative_prompt: ${info?.negative_prompt} | seed: ${info?.seed} | guidance_scale: ${info?.guidance_scale}`;
|
| 18 |
+
zeroth[piexif.ImageIFD.Software] =
|
| 19 |
+
'https://github.com/radames/Real-Time-Latent-Consistency-Model';
|
| 20 |
+
exif[piexif.ExifIFD.DateTimeOriginal] = new Date().toISOString();
|
| 21 |
|
| 22 |
+
const exifObj = { '0th': zeroth, Exif: exif, GPS: gps };
|
| 23 |
+
const exifBytes = piexif.dump(exifObj);
|
| 24 |
|
| 25 |
+
const canvas = document.createElement('canvas');
|
| 26 |
+
canvas.width = imageEl.naturalWidth;
|
| 27 |
+
canvas.height = imageEl.naturalHeight;
|
| 28 |
+
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
| 29 |
+
ctx.drawImage(imageEl, 0, 0);
|
| 30 |
+
const dataURL = canvas.toDataURL('image/jpeg');
|
| 31 |
+
const withExif = piexif.insert(exifBytes, dataURL);
|
| 32 |
|
| 33 |
+
const a = document.createElement('a');
|
| 34 |
+
a.href = withExif;
|
| 35 |
+
a.download = `lcm_txt_2_img${Date.now()}.png`;
|
| 36 |
+
a.click();
|
| 37 |
+
} catch (err) {
|
| 38 |
+
console.log(err);
|
| 39 |
+
}
|
| 40 |
}
|
| 41 |
|
| 42 |
+
export function expandWindow(streamURL: string): Window {
|
| 43 |
+
const html = `
|
| 44 |
<html>
|
| 45 |
<head>
|
| 46 |
<title>Real-Time Latent Consistency Model</title>
|
|
|
|
| 73 |
}
|
| 74 |
</script>
|
| 75 |
|
| 76 |
+
<img src="${streamURL}" style="width: 100%; height: 100%; object-fit: contain;" />
|
| 77 |
</body>
|
| 78 |
</html>
|
| 79 |
`;
|
| 80 |
+
const newWindow = window.open(
|
| 81 |
+
'',
|
| 82 |
+
'_blank',
|
| 83 |
+
'width=1024,height=1024,scrollbars=0,resizable=1,toolbar=0,menubar=0,location=0,directories=0,status=0'
|
| 84 |
+
) as Window;
|
| 85 |
+
newWindow.document.write(html);
|
| 86 |
+
return newWindow;
|
| 87 |
+
}
|
frontend/src/routes/+page.ts
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
export const prerender = true
|
|
|
|
| 1 |
+
export const prerender = true;
|
frontend/vite.config.ts
CHANGED
|
@@ -10,6 +10,6 @@ export default defineConfig({
|
|
| 10 |
target: 'ws://localhost:7860',
|
| 11 |
ws: true
|
| 12 |
}
|
| 13 |
-
}
|
| 14 |
}
|
| 15 |
-
});
|
|
|
|
| 10 |
target: 'ws://localhost:7860',
|
| 11 |
ws: true
|
| 12 |
}
|
| 13 |
+
}
|
| 14 |
}
|
| 15 |
+
});
|