@gradio/image 0.15.0 → 0.16.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/Image.test.ts +1 -1
- package/Index.svelte +22 -3
- package/dist/Example.svelte +46 -0
- package/dist/Example.svelte.d.ts +19 -0
- package/dist/Index.svelte +200 -0
- package/dist/Index.svelte.d.ts +176 -0
- package/dist/shared/ClearImage.svelte +28 -0
- package/dist/shared/ClearImage.svelte.d.ts +18 -0
- package/dist/shared/Image.svelte +26 -0
- package/dist/shared/Image.svelte.d.ts +247 -0
- package/dist/shared/ImagePreview.svelte +155 -0
- package/dist/shared/ImagePreview.svelte.d.ts +32 -0
- package/dist/shared/ImageUploader.svelte +198 -0
- package/dist/shared/ImageUploader.svelte.d.ts +47 -0
- package/dist/shared/Webcam.svelte +431 -0
- package/dist/shared/Webcam.svelte.d.ts +40 -0
- package/dist/shared/WebcamPermissions.svelte +42 -0
- package/dist/shared/WebcamPermissions.svelte.d.ts +18 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +2 -0
- package/dist/shared/stream_utils.d.ts +5 -0
- package/dist/shared/stream_utils.js +31 -0
- package/dist/shared/utils.d.ts +1 -0
- package/dist/shared/utils.js +28 -0
- package/package.json +33 -14
- package/shared/ImageUploader.svelte +37 -15
- package/shared/Webcam.svelte +80 -15
@@ -0,0 +1,47 @@
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
2
|
+
import { type SelectData, type I18nFormatter, type ValueData } from "@gradio/utils";
|
3
|
+
import { FileData, type Client } from "@gradio/client";
|
4
|
+
declare const __propDef: {
|
5
|
+
props: {
|
6
|
+
value: null | FileData;
|
7
|
+
label?: string | undefined;
|
8
|
+
show_label: boolean;
|
9
|
+
sources?: ("upload" | "clipboard" | "microphone" | "webcam" | null)[] | undefined;
|
10
|
+
streaming?: boolean | undefined;
|
11
|
+
pending?: boolean | undefined;
|
12
|
+
mirror_webcam: boolean;
|
13
|
+
selectable?: boolean | undefined;
|
14
|
+
root: string;
|
15
|
+
i18n: I18nFormatter;
|
16
|
+
max_file_size?: (number | null) | undefined;
|
17
|
+
upload: Client["upload"];
|
18
|
+
stream_handler: Client["stream"];
|
19
|
+
stream_every: number;
|
20
|
+
modify_stream: (state: "open" | "closed" | "waiting") => void;
|
21
|
+
set_time_limit: (arg0: number) => void;
|
22
|
+
active_source?: ("upload" | "clipboard" | "microphone" | "webcam" | null) | undefined;
|
23
|
+
dragging?: boolean | undefined;
|
24
|
+
};
|
25
|
+
events: {
|
26
|
+
error: CustomEvent<string> | CustomEvent<any>;
|
27
|
+
drag: CustomEvent<any>;
|
28
|
+
close_stream: CustomEvent<undefined>;
|
29
|
+
change?: CustomEvent<undefined> | undefined;
|
30
|
+
stream: CustomEvent<ValueData>;
|
31
|
+
clear?: CustomEvent<undefined> | undefined;
|
32
|
+
upload?: CustomEvent<undefined> | undefined;
|
33
|
+
select: CustomEvent<SelectData>;
|
34
|
+
end_stream: CustomEvent<never>;
|
35
|
+
} & {
|
36
|
+
[evt: string]: CustomEvent<any>;
|
37
|
+
};
|
38
|
+
slots: {
|
39
|
+
default: {};
|
40
|
+
};
|
41
|
+
};
|
42
|
+
export type ImageUploaderProps = typeof __propDef.props;
|
43
|
+
export type ImageUploaderEvents = typeof __propDef.events;
|
44
|
+
export type ImageUploaderSlots = typeof __propDef.slots;
|
45
|
+
export default class ImageUploader extends SvelteComponent<ImageUploaderProps, ImageUploaderEvents, ImageUploaderSlots> {
|
46
|
+
}
|
47
|
+
export {};
|
@@ -0,0 +1,431 @@
|
|
1
|
+
<script>import { createEventDispatcher, onMount } from "svelte";
|
2
|
+
import {
|
3
|
+
Camera,
|
4
|
+
Circle,
|
5
|
+
Square,
|
6
|
+
DropdownArrow,
|
7
|
+
Spinner
|
8
|
+
} from "@gradio/icons";
|
9
|
+
import { StreamingBar } from "@gradio/statustracker";
|
10
|
+
import { prepare_files } from "@gradio/client";
|
11
|
+
import WebcamPermissions from "./WebcamPermissions.svelte";
|
12
|
+
import { fade } from "svelte/transition";
|
13
|
+
import {
|
14
|
+
get_devices,
|
15
|
+
get_video_stream,
|
16
|
+
set_available_devices
|
17
|
+
} from "./stream_utils";
|
18
|
+
let video_source;
|
19
|
+
let available_video_devices = [];
|
20
|
+
let selected_device = null;
|
21
|
+
let time_limit = null;
|
22
|
+
let stream_state = "closed";
|
23
|
+
export const modify_stream = (state) => {
|
24
|
+
if (state === "closed") {
|
25
|
+
time_limit = null;
|
26
|
+
stream_state = "closed";
|
27
|
+
value = null;
|
28
|
+
} else if (state === "waiting") {
|
29
|
+
stream_state = "waiting";
|
30
|
+
} else {
|
31
|
+
stream_state = "open";
|
32
|
+
}
|
33
|
+
};
|
34
|
+
export const set_time_limit = (time) => {
|
35
|
+
if (recording)
|
36
|
+
time_limit = time;
|
37
|
+
};
|
38
|
+
let canvas;
|
39
|
+
export let streaming = false;
|
40
|
+
export let pending = false;
|
41
|
+
export let root = "";
|
42
|
+
export let stream_every = 1;
|
43
|
+
export let mode = "image";
|
44
|
+
export let mirror_webcam;
|
45
|
+
export let include_audio;
|
46
|
+
export let i18n;
|
47
|
+
export let upload;
|
48
|
+
export let value = null;
|
49
|
+
const dispatch = createEventDispatcher();
|
50
|
+
onMount(() => canvas = document.createElement("canvas"));
|
51
|
+
const handle_device_change = async (event) => {
|
52
|
+
const target = event.target;
|
53
|
+
const device_id = target.value;
|
54
|
+
await get_video_stream(include_audio, video_source, device_id).then(
|
55
|
+
async (local_stream) => {
|
56
|
+
stream = local_stream;
|
57
|
+
selected_device = available_video_devices.find(
|
58
|
+
(device) => device.deviceId === device_id
|
59
|
+
) || null;
|
60
|
+
options_open = false;
|
61
|
+
}
|
62
|
+
);
|
63
|
+
};
|
64
|
+
async function access_webcam() {
|
65
|
+
try {
|
66
|
+
get_video_stream(include_audio, video_source).then(async (local_stream) => {
|
67
|
+
webcam_accessed = true;
|
68
|
+
available_video_devices = await get_devices();
|
69
|
+
stream = local_stream;
|
70
|
+
}).then(() => set_available_devices(available_video_devices)).then((devices) => {
|
71
|
+
available_video_devices = devices;
|
72
|
+
const used_devices = stream.getTracks().map((track) => track.getSettings()?.deviceId)[0];
|
73
|
+
selected_device = used_devices ? devices.find((device) => device.deviceId === used_devices) || available_video_devices[0] : available_video_devices[0];
|
74
|
+
});
|
75
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
76
|
+
dispatch("error", i18n("image.no_webcam_support"));
|
77
|
+
}
|
78
|
+
} catch (err) {
|
79
|
+
if (err instanceof DOMException && err.name == "NotAllowedError") {
|
80
|
+
dispatch("error", i18n("image.allow_webcam_access"));
|
81
|
+
} else {
|
82
|
+
throw err;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
function take_picture() {
|
87
|
+
var context = canvas.getContext("2d");
|
88
|
+
if ((!streaming || streaming && recording) && video_source.videoWidth && video_source.videoHeight) {
|
89
|
+
canvas.width = video_source.videoWidth;
|
90
|
+
canvas.height = video_source.videoHeight;
|
91
|
+
context.drawImage(
|
92
|
+
video_source,
|
93
|
+
0,
|
94
|
+
0,
|
95
|
+
video_source.videoWidth,
|
96
|
+
video_source.videoHeight
|
97
|
+
);
|
98
|
+
if (mirror_webcam) {
|
99
|
+
context.scale(-1, 1);
|
100
|
+
context.drawImage(video_source, -video_source.videoWidth, 0);
|
101
|
+
}
|
102
|
+
if (streaming && (!recording || stream_state === "waiting")) {
|
103
|
+
return;
|
104
|
+
}
|
105
|
+
canvas.toBlob(
|
106
|
+
(blob) => {
|
107
|
+
dispatch(streaming ? "stream" : "capture", blob);
|
108
|
+
},
|
109
|
+
`image/${streaming ? "jpeg" : "png"}`,
|
110
|
+
0.8
|
111
|
+
);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
let recording = false;
|
115
|
+
let recorded_blobs = [];
|
116
|
+
let stream;
|
117
|
+
let mimeType;
|
118
|
+
let media_recorder;
|
119
|
+
function take_recording() {
|
120
|
+
if (recording) {
|
121
|
+
media_recorder.stop();
|
122
|
+
let video_blob = new Blob(recorded_blobs, { type: mimeType });
|
123
|
+
let ReaderObj = new FileReader();
|
124
|
+
ReaderObj.onload = async function(e) {
|
125
|
+
if (e.target) {
|
126
|
+
let _video_blob = new File(
|
127
|
+
[video_blob],
|
128
|
+
"sample." + mimeType.substring(6)
|
129
|
+
);
|
130
|
+
const val = await prepare_files([_video_blob]);
|
131
|
+
let val_ = ((await upload(val, root))?.filter(Boolean))[0];
|
132
|
+
dispatch("capture", val_);
|
133
|
+
dispatch("stop_recording");
|
134
|
+
}
|
135
|
+
};
|
136
|
+
ReaderObj.readAsDataURL(video_blob);
|
137
|
+
} else {
|
138
|
+
dispatch("start_recording");
|
139
|
+
recorded_blobs = [];
|
140
|
+
let validMimeTypes = ["video/webm", "video/mp4"];
|
141
|
+
for (let validMimeType of validMimeTypes) {
|
142
|
+
if (MediaRecorder.isTypeSupported(validMimeType)) {
|
143
|
+
mimeType = validMimeType;
|
144
|
+
break;
|
145
|
+
}
|
146
|
+
}
|
147
|
+
if (mimeType === null) {
|
148
|
+
console.error("No supported MediaRecorder mimeType");
|
149
|
+
return;
|
150
|
+
}
|
151
|
+
media_recorder = new MediaRecorder(stream, {
|
152
|
+
mimeType
|
153
|
+
});
|
154
|
+
media_recorder.addEventListener("dataavailable", function(e) {
|
155
|
+
recorded_blobs.push(e.data);
|
156
|
+
});
|
157
|
+
media_recorder.start(200);
|
158
|
+
}
|
159
|
+
recording = !recording;
|
160
|
+
}
|
161
|
+
let webcam_accessed = false;
|
162
|
+
function record_video_or_photo() {
|
163
|
+
if (mode === "image" && streaming) {
|
164
|
+
recording = !recording;
|
165
|
+
}
|
166
|
+
if (mode === "image") {
|
167
|
+
take_picture();
|
168
|
+
} else {
|
169
|
+
take_recording();
|
170
|
+
}
|
171
|
+
if (!recording && stream) {
|
172
|
+
dispatch("close_stream");
|
173
|
+
stream.getTracks().forEach((track) => track.stop());
|
174
|
+
video_source.srcObject = null;
|
175
|
+
webcam_accessed = false;
|
176
|
+
window.setTimeout(() => {
|
177
|
+
value = null;
|
178
|
+
}, 500);
|
179
|
+
value = null;
|
180
|
+
}
|
181
|
+
}
|
182
|
+
if (streaming && mode === "image") {
|
183
|
+
window.setInterval(() => {
|
184
|
+
if (video_source && !pending) {
|
185
|
+
take_picture();
|
186
|
+
}
|
187
|
+
}, stream_every * 1e3);
|
188
|
+
}
|
189
|
+
let options_open = false;
|
190
|
+
export function click_outside(node, cb) {
|
191
|
+
const handle_click = (event) => {
|
192
|
+
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
193
|
+
cb(event);
|
194
|
+
}
|
195
|
+
};
|
196
|
+
document.addEventListener("click", handle_click, true);
|
197
|
+
return {
|
198
|
+
destroy() {
|
199
|
+
document.removeEventListener("click", handle_click, true);
|
200
|
+
}
|
201
|
+
};
|
202
|
+
}
|
203
|
+
function handle_click_outside(event) {
|
204
|
+
event.preventDefault();
|
205
|
+
event.stopPropagation();
|
206
|
+
options_open = false;
|
207
|
+
}
|
208
|
+
</script>
|
209
|
+
|
210
|
+
<div class="wrap">
|
211
|
+
<StreamingBar {time_limit} />
|
212
|
+
<!-- svelte-ignore a11y-media-has-caption -->
|
213
|
+
<!-- need to suppress for video streaming https://github.com/sveltejs/svelte/issues/5967 -->
|
214
|
+
<video
|
215
|
+
bind:this={video_source}
|
216
|
+
class:flip={mirror_webcam}
|
217
|
+
class:hide={!webcam_accessed || (webcam_accessed && !!value)}
|
218
|
+
/>
|
219
|
+
<!-- svelte-ignore a11y-missing-attribute -->
|
220
|
+
<img
|
221
|
+
src={value?.url}
|
222
|
+
class:hide={!webcam_accessed || (webcam_accessed && !value)}
|
223
|
+
/>
|
224
|
+
{#if !webcam_accessed}
|
225
|
+
<div
|
226
|
+
in:fade={{ delay: 100, duration: 200 }}
|
227
|
+
title="grant webcam access"
|
228
|
+
style="height: 100%"
|
229
|
+
>
|
230
|
+
<WebcamPermissions on:click={async () => access_webcam()} />
|
231
|
+
</div>
|
232
|
+
{:else}
|
233
|
+
<div class="button-wrap">
|
234
|
+
<button
|
235
|
+
on:click={record_video_or_photo}
|
236
|
+
aria-label={mode === "image" ? "capture photo" : "start recording"}
|
237
|
+
>
|
238
|
+
{#if mode === "video" || streaming}
|
239
|
+
{#if streaming && stream_state === "waiting"}
|
240
|
+
<div class="icon-with-text" style="width:var(--size-24);">
|
241
|
+
<div class="icon color-primary" title="spinner">
|
242
|
+
<Spinner />
|
243
|
+
</div>
|
244
|
+
{i18n("audio.waiting")}
|
245
|
+
</div>
|
246
|
+
{:else if (streaming && stream_state === "open") || (!streaming && recording)}
|
247
|
+
<div class="icon-with-text">
|
248
|
+
<div class="icon color-primary" title="stop recording">
|
249
|
+
<Square />
|
250
|
+
</div>
|
251
|
+
{i18n("audio.stop")}
|
252
|
+
</div>
|
253
|
+
{:else}
|
254
|
+
<div class="icon-with-text">
|
255
|
+
<div class="icon color-primary" title="start recording">
|
256
|
+
<Circle />
|
257
|
+
</div>
|
258
|
+
{i18n("audio.record")}
|
259
|
+
</div>
|
260
|
+
{/if}
|
261
|
+
{:else}
|
262
|
+
<div class="icon" title="capture photo">
|
263
|
+
<Camera />
|
264
|
+
</div>
|
265
|
+
{/if}
|
266
|
+
</button>
|
267
|
+
{#if !recording}
|
268
|
+
<button
|
269
|
+
class="icon"
|
270
|
+
on:click={() => (options_open = true)}
|
271
|
+
aria-label="select input source"
|
272
|
+
>
|
273
|
+
<DropdownArrow />
|
274
|
+
</button>
|
275
|
+
{/if}
|
276
|
+
</div>
|
277
|
+
{#if options_open && selected_device}
|
278
|
+
<select
|
279
|
+
class="select-wrap"
|
280
|
+
aria-label="select source"
|
281
|
+
use:click_outside={handle_click_outside}
|
282
|
+
on:change={handle_device_change}
|
283
|
+
>
|
284
|
+
<button
|
285
|
+
class="inset-icon"
|
286
|
+
on:click|stopPropagation={() => (options_open = false)}
|
287
|
+
>
|
288
|
+
<DropdownArrow />
|
289
|
+
</button>
|
290
|
+
{#if available_video_devices.length === 0}
|
291
|
+
<option value="">{i18n("common.no_devices")}</option>
|
292
|
+
{:else}
|
293
|
+
{#each available_video_devices as device}
|
294
|
+
<option
|
295
|
+
value={device.deviceId}
|
296
|
+
selected={selected_device.deviceId === device.deviceId}
|
297
|
+
>
|
298
|
+
{device.label}
|
299
|
+
</option>
|
300
|
+
{/each}
|
301
|
+
{/if}
|
302
|
+
</select>
|
303
|
+
{/if}
|
304
|
+
{/if}
|
305
|
+
</div>
|
306
|
+
|
307
|
+
<style>
|
308
|
+
.wrap {
|
309
|
+
position: relative;
|
310
|
+
width: var(--size-full);
|
311
|
+
height: var(--size-full);
|
312
|
+
}
|
313
|
+
|
314
|
+
.hide {
|
315
|
+
display: none;
|
316
|
+
}
|
317
|
+
|
318
|
+
video {
|
319
|
+
width: var(--size-full);
|
320
|
+
height: var(--size-full);
|
321
|
+
object-fit: cover;
|
322
|
+
}
|
323
|
+
|
324
|
+
.button-wrap {
|
325
|
+
position: absolute;
|
326
|
+
background-color: var(--block-background-fill);
|
327
|
+
border: 1px solid var(--border-color-primary);
|
328
|
+
border-radius: var(--radius-xl);
|
329
|
+
padding: var(--size-1-5);
|
330
|
+
display: flex;
|
331
|
+
bottom: var(--size-2);
|
332
|
+
left: 50%;
|
333
|
+
transform: translate(-50%, 0);
|
334
|
+
box-shadow: var(--shadow-drop-lg);
|
335
|
+
border-radius: var(--radius-xl);
|
336
|
+
line-height: var(--size-3);
|
337
|
+
color: var(--button-secondary-text-color);
|
338
|
+
}
|
339
|
+
|
340
|
+
.icon-with-text {
|
341
|
+
width: var(--size-20);
|
342
|
+
align-items: center;
|
343
|
+
margin: 0 var(--spacing-xl);
|
344
|
+
display: flex;
|
345
|
+
justify-content: space-evenly;
|
346
|
+
}
|
347
|
+
|
348
|
+
@media (--screen-md) {
|
349
|
+
button {
|
350
|
+
bottom: var(--size-4);
|
351
|
+
}
|
352
|
+
}
|
353
|
+
|
354
|
+
@media (--screen-xl) {
|
355
|
+
button {
|
356
|
+
bottom: var(--size-8);
|
357
|
+
}
|
358
|
+
}
|
359
|
+
|
360
|
+
.icon {
|
361
|
+
width: 18px;
|
362
|
+
height: 18px;
|
363
|
+
display: flex;
|
364
|
+
justify-content: space-between;
|
365
|
+
align-items: center;
|
366
|
+
}
|
367
|
+
|
368
|
+
.color-primary {
|
369
|
+
fill: var(--primary-600);
|
370
|
+
stroke: var(--primary-600);
|
371
|
+
}
|
372
|
+
|
373
|
+
.flip {
|
374
|
+
transform: scaleX(-1);
|
375
|
+
}
|
376
|
+
|
377
|
+
.select-wrap {
|
378
|
+
-webkit-appearance: none;
|
379
|
+
-moz-appearance: none;
|
380
|
+
appearance: none;
|
381
|
+
color: var(--button-secondary-text-color);
|
382
|
+
background-color: transparent;
|
383
|
+
width: 95%;
|
384
|
+
font-size: var(--text-md);
|
385
|
+
position: absolute;
|
386
|
+
bottom: var(--size-2);
|
387
|
+
background-color: var(--block-background-fill);
|
388
|
+
box-shadow: var(--shadow-drop-lg);
|
389
|
+
border-radius: var(--radius-xl);
|
390
|
+
z-index: var(--layer-top);
|
391
|
+
border: 1px solid var(--border-color-primary);
|
392
|
+
text-align: left;
|
393
|
+
line-height: var(--size-4);
|
394
|
+
white-space: nowrap;
|
395
|
+
text-overflow: ellipsis;
|
396
|
+
left: 50%;
|
397
|
+
transform: translate(-50%, 0);
|
398
|
+
max-width: var(--size-52);
|
399
|
+
}
|
400
|
+
|
401
|
+
.select-wrap > option {
|
402
|
+
padding: 0.25rem 0.5rem;
|
403
|
+
border-bottom: 1px solid var(--border-color-accent);
|
404
|
+
padding-right: var(--size-8);
|
405
|
+
text-overflow: ellipsis;
|
406
|
+
overflow: hidden;
|
407
|
+
}
|
408
|
+
|
409
|
+
.select-wrap > option:hover {
|
410
|
+
background-color: var(--color-accent);
|
411
|
+
}
|
412
|
+
|
413
|
+
.select-wrap > option:last-child {
|
414
|
+
border: none;
|
415
|
+
}
|
416
|
+
|
417
|
+
.inset-icon {
|
418
|
+
position: absolute;
|
419
|
+
top: 5px;
|
420
|
+
right: -6.5px;
|
421
|
+
width: var(--size-10);
|
422
|
+
height: var(--size-5);
|
423
|
+
opacity: 0.8;
|
424
|
+
}
|
425
|
+
|
426
|
+
@media (--screen-md) {
|
427
|
+
.wrap {
|
428
|
+
font-size: var(--text-lg);
|
429
|
+
}
|
430
|
+
}
|
431
|
+
</style>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
2
|
+
import type { I18nFormatter } from "@gradio/utils";
|
3
|
+
import { type FileData, type Client } from "@gradio/client";
|
4
|
+
declare const __propDef: {
|
5
|
+
props: {
|
6
|
+
modify_stream?: ((state: "open" | "closed" | "waiting") => void) | undefined;
|
7
|
+
set_time_limit?: ((time: number) => void) | undefined;
|
8
|
+
streaming?: boolean | undefined;
|
9
|
+
pending?: boolean | undefined;
|
10
|
+
root?: string | undefined;
|
11
|
+
stream_every?: number | undefined;
|
12
|
+
mode?: ("image" | "video") | undefined;
|
13
|
+
mirror_webcam: boolean;
|
14
|
+
include_audio: boolean;
|
15
|
+
i18n: I18nFormatter;
|
16
|
+
upload: Client["upload"];
|
17
|
+
value?: (FileData | null) | undefined;
|
18
|
+
click_outside?: ((node: Node, cb: any) => any) | undefined;
|
19
|
+
};
|
20
|
+
events: {
|
21
|
+
stream: CustomEvent<undefined>;
|
22
|
+
capture: CustomEvent<Blob | FileData | null>;
|
23
|
+
error: CustomEvent<string>;
|
24
|
+
start_recording: CustomEvent<undefined>;
|
25
|
+
stop_recording: CustomEvent<undefined>;
|
26
|
+
close_stream: CustomEvent<undefined>;
|
27
|
+
} & {
|
28
|
+
[evt: string]: CustomEvent<any>;
|
29
|
+
};
|
30
|
+
slots: {};
|
31
|
+
};
|
32
|
+
export type WebcamProps = typeof __propDef.props;
|
33
|
+
export type WebcamEvents = typeof __propDef.events;
|
34
|
+
export type WebcamSlots = typeof __propDef.slots;
|
35
|
+
export default class Webcam extends SvelteComponent<WebcamProps, WebcamEvents, WebcamSlots> {
|
36
|
+
get modify_stream(): (state: "open" | "closed" | "waiting") => void;
|
37
|
+
get set_time_limit(): (time: number) => void;
|
38
|
+
get click_outside(): (node: Node, cb: any) => any;
|
39
|
+
}
|
40
|
+
export {};
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<script>import { Webcam } from "@gradio/icons";
|
2
|
+
import { createEventDispatcher } from "svelte";
|
3
|
+
const dispatch = createEventDispatcher();
|
4
|
+
</script>
|
5
|
+
|
6
|
+
<button style:height="100%" on:click={() => dispatch("click")}>
|
7
|
+
<div class="wrap">
|
8
|
+
<span class="icon-wrap">
|
9
|
+
<Webcam />
|
10
|
+
</span>
|
11
|
+
{"Click to Access Webcam"}
|
12
|
+
</div>
|
13
|
+
</button>
|
14
|
+
|
15
|
+
<style>
|
16
|
+
button {
|
17
|
+
cursor: pointer;
|
18
|
+
width: var(--size-full);
|
19
|
+
}
|
20
|
+
|
21
|
+
.wrap {
|
22
|
+
display: flex;
|
23
|
+
flex-direction: column;
|
24
|
+
justify-content: center;
|
25
|
+
align-items: center;
|
26
|
+
min-height: var(--size-60);
|
27
|
+
color: var(--block-label-text-color);
|
28
|
+
height: 100%;
|
29
|
+
padding-top: var(--size-3);
|
30
|
+
}
|
31
|
+
|
32
|
+
.icon-wrap {
|
33
|
+
width: 30px;
|
34
|
+
margin-bottom: var(--spacing-lg);
|
35
|
+
}
|
36
|
+
|
37
|
+
@media (--screen-md) {
|
38
|
+
.wrap {
|
39
|
+
font-size: var(--text-lg);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
</style>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
2
|
+
declare const __propDef: {
|
3
|
+
props: {
|
4
|
+
[x: string]: never;
|
5
|
+
};
|
6
|
+
events: {
|
7
|
+
click: CustomEvent<undefined>;
|
8
|
+
} & {
|
9
|
+
[evt: string]: CustomEvent<any>;
|
10
|
+
};
|
11
|
+
slots: {};
|
12
|
+
};
|
13
|
+
export type WebcamPermissionsProps = typeof __propDef.props;
|
14
|
+
export type WebcamPermissionsEvents = typeof __propDef.events;
|
15
|
+
export type WebcamPermissionsSlots = typeof __propDef.slots;
|
16
|
+
export default class WebcamPermissions extends SvelteComponent<WebcamPermissionsProps, WebcamPermissionsEvents, WebcamPermissionsSlots> {
|
17
|
+
}
|
18
|
+
export {};
|
@@ -0,0 +1,5 @@
|
|
1
|
+
export declare function get_devices(): Promise<MediaDeviceInfo[]>;
|
2
|
+
export declare function handle_error(error: string): void;
|
3
|
+
export declare function set_local_stream(local_stream: MediaStream | null, video_source: HTMLVideoElement): void;
|
4
|
+
export declare function get_video_stream(include_audio: boolean, video_source: HTMLVideoElement, device_id?: string): Promise<MediaStream>;
|
5
|
+
export declare function set_available_devices(devices: MediaDeviceInfo[]): MediaDeviceInfo[];
|
@@ -0,0 +1,31 @@
|
|
1
|
+
export function get_devices() {
|
2
|
+
return navigator.mediaDevices.enumerateDevices();
|
3
|
+
}
|
4
|
+
export function handle_error(error) {
|
5
|
+
throw new Error(error);
|
6
|
+
}
|
7
|
+
export function set_local_stream(local_stream, video_source) {
|
8
|
+
video_source.srcObject = local_stream;
|
9
|
+
video_source.muted = true;
|
10
|
+
video_source.play();
|
11
|
+
}
|
12
|
+
export async function get_video_stream(include_audio, video_source, device_id) {
|
13
|
+
const size = {
|
14
|
+
width: { ideal: 1920 },
|
15
|
+
height: { ideal: 1440 }
|
16
|
+
};
|
17
|
+
const constraints = {
|
18
|
+
video: device_id ? { deviceId: { exact: device_id }, ...size } : size,
|
19
|
+
audio: include_audio
|
20
|
+
};
|
21
|
+
return navigator.mediaDevices
|
22
|
+
.getUserMedia(constraints)
|
23
|
+
.then((local_stream) => {
|
24
|
+
set_local_stream(local_stream, video_source);
|
25
|
+
return local_stream;
|
26
|
+
});
|
27
|
+
}
|
28
|
+
export function set_available_devices(devices) {
|
29
|
+
const cameras = devices.filter((device) => device.kind === "videoinput");
|
30
|
+
return cameras;
|
31
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const get_coordinates_of_clicked_image: (evt: MouseEvent) => [number, number] | null;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
export const get_coordinates_of_clicked_image = (evt) => {
|
2
|
+
let image;
|
3
|
+
if (evt.currentTarget instanceof Element) {
|
4
|
+
image = evt.currentTarget.querySelector("img");
|
5
|
+
}
|
6
|
+
else {
|
7
|
+
return [NaN, NaN];
|
8
|
+
}
|
9
|
+
const imageRect = image.getBoundingClientRect();
|
10
|
+
const xScale = image.naturalWidth / imageRect.width;
|
11
|
+
const yScale = image.naturalHeight / imageRect.height;
|
12
|
+
if (xScale > yScale) {
|
13
|
+
const displayed_height = image.naturalHeight / xScale;
|
14
|
+
const y_offset = (imageRect.height - displayed_height) / 2;
|
15
|
+
var x = Math.round((evt.clientX - imageRect.left) * xScale);
|
16
|
+
var y = Math.round((evt.clientY - imageRect.top - y_offset) * xScale);
|
17
|
+
}
|
18
|
+
else {
|
19
|
+
const displayed_width = image.naturalWidth / yScale;
|
20
|
+
const x_offset = (imageRect.width - displayed_width) / 2;
|
21
|
+
var x = Math.round((evt.clientX - imageRect.left - x_offset) * yScale);
|
22
|
+
var y = Math.round((evt.clientY - imageRect.top) * yScale);
|
23
|
+
}
|
24
|
+
if (x < 0 || x >= image.naturalWidth || y < 0 || y >= image.naturalHeight) {
|
25
|
+
return null;
|
26
|
+
}
|
27
|
+
return [x, y];
|
28
|
+
};
|