@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
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@gradio/image",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.16.0-beta.1",
|
4
4
|
"description": "Gradio UI packages",
|
5
5
|
"type": "module",
|
6
6
|
"author": "",
|
@@ -10,25 +10,44 @@
|
|
10
10
|
"cropperjs": "^1.5.12",
|
11
11
|
"lazy-brush": "^1.0.1",
|
12
12
|
"resize-observer-polyfill": "^1.5.1",
|
13
|
-
"@gradio/atoms": "^0.8.
|
14
|
-
"@gradio/icons": "^0.
|
15
|
-
"@gradio/
|
16
|
-
"@gradio/
|
17
|
-
"@gradio/
|
18
|
-
"@gradio/
|
19
|
-
"@gradio/
|
13
|
+
"@gradio/atoms": "^0.8.1-beta.1",
|
14
|
+
"@gradio/icons": "^0.8.0-beta.1",
|
15
|
+
"@gradio/client": "^1.6.0-beta.1",
|
16
|
+
"@gradio/utils": "^0.7.0-beta.1",
|
17
|
+
"@gradio/wasm": "^0.13.1-beta.1",
|
18
|
+
"@gradio/upload": "^0.12.4-beta.1",
|
19
|
+
"@gradio/statustracker": "^0.8.0-beta.1"
|
20
20
|
},
|
21
21
|
"devDependencies": {
|
22
|
-
"@gradio/preview": "^0.11.0"
|
22
|
+
"@gradio/preview": "^0.11.1-beta.0"
|
23
23
|
},
|
24
24
|
"main_changeset": true,
|
25
25
|
"main": "./Index.svelte",
|
26
26
|
"exports": {
|
27
|
-
".": "./
|
28
|
-
"
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
"./package.json": "./package.json",
|
28
|
+
".": {
|
29
|
+
"gradio": "./Index.svelte",
|
30
|
+
"svelte": "./dist/Index.svelte",
|
31
|
+
"types": "./dist/Index.svelte.d.ts"
|
32
|
+
},
|
33
|
+
"./example": {
|
34
|
+
"gradio": "./Example.svelte",
|
35
|
+
"svelte": "./dist/Example.svelte",
|
36
|
+
"types": "./dist/Example.svelte.d.ts"
|
37
|
+
},
|
38
|
+
"./base": {
|
39
|
+
"gradio": "./shared/ImagePreview.svelte",
|
40
|
+
"svelte": "./dist/shared/ImagePreview.svelte",
|
41
|
+
"types": "./dist/shared/ImagePreview.svelte.d.ts"
|
42
|
+
},
|
43
|
+
"./shared": {
|
44
|
+
"gradio": "./shared/index.ts",
|
45
|
+
"svelte": "./dist/shared/index.js",
|
46
|
+
"types": "./dist/shared/index.d.ts"
|
47
|
+
}
|
48
|
+
},
|
49
|
+
"peerDependencies": {
|
50
|
+
"svelte": "^4.0.0"
|
32
51
|
},
|
33
52
|
"repository": {
|
34
53
|
"type": "git",
|
@@ -2,12 +2,16 @@
|
|
2
2
|
import { createEventDispatcher, tick } from "svelte";
|
3
3
|
import { BlockLabel } from "@gradio/atoms";
|
4
4
|
import { Image as ImageIcon } from "@gradio/icons";
|
5
|
-
import
|
5
|
+
import {
|
6
|
+
type SelectData,
|
7
|
+
type I18nFormatter,
|
8
|
+
type ValueData
|
9
|
+
} from "@gradio/utils";
|
6
10
|
import { get_coordinates_of_clicked_image } from "./utils";
|
7
11
|
import Webcam from "./Webcam.svelte";
|
8
12
|
|
9
13
|
import { Upload } from "@gradio/upload";
|
10
|
-
import
|
14
|
+
import { FileData, type Client } from "@gradio/client";
|
11
15
|
import ClearImage from "./ClearImage.svelte";
|
12
16
|
import { SelectSource } from "@gradio/atoms";
|
13
17
|
import Image from "./Image.svelte";
|
@@ -28,14 +32,21 @@
|
|
28
32
|
export let max_file_size: number | null = null;
|
29
33
|
export let upload: Client["upload"];
|
30
34
|
export let stream_handler: Client["stream"];
|
35
|
+
export let stream_every: number;
|
36
|
+
|
37
|
+
export let modify_stream: (state: "open" | "closed" | "waiting") => void;
|
38
|
+
export let set_time_limit: (arg0: number) => void;
|
31
39
|
|
32
40
|
let upload_input: Upload;
|
33
41
|
let uploading = false;
|
34
42
|
export let active_source: source_type = null;
|
35
43
|
|
36
44
|
function handle_upload({ detail }: CustomEvent<FileData>): void {
|
37
|
-
|
38
|
-
|
45
|
+
// only trigger streaming event if streaming
|
46
|
+
if (!streaming) {
|
47
|
+
value = detail;
|
48
|
+
dispatch("upload");
|
49
|
+
}
|
39
50
|
}
|
40
51
|
|
41
52
|
function handle_clear(): void {
|
@@ -44,17 +55,22 @@
|
|
44
55
|
dispatch("change", null);
|
45
56
|
}
|
46
57
|
|
47
|
-
async function handle_save(
|
58
|
+
async function handle_save(
|
59
|
+
img_blob: Blob | any,
|
60
|
+
event: "change" | "stream" | "upload"
|
61
|
+
): Promise<void> {
|
48
62
|
pending = true;
|
49
63
|
const f = await upload_input.load_files([
|
50
|
-
new File([img_blob], `
|
64
|
+
new File([img_blob], `image/${streaming ? "jpeg" : "png"}`)
|
51
65
|
]);
|
52
66
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
if (event === "change" || event === "upload") {
|
68
|
+
value = f?.[0] || null;
|
69
|
+
await tick();
|
70
|
+
dispatch("change");
|
71
|
+
} else {
|
72
|
+
dispatch("stream", { value: f?.[0] || null, is_value_data: true });
|
73
|
+
}
|
58
74
|
pending = false;
|
59
75
|
}
|
60
76
|
|
@@ -63,11 +79,12 @@
|
|
63
79
|
|
64
80
|
const dispatch = createEventDispatcher<{
|
65
81
|
change?: never;
|
66
|
-
stream
|
82
|
+
stream: ValueData;
|
67
83
|
clear?: never;
|
68
84
|
drag: boolean;
|
69
85
|
upload?: never;
|
70
86
|
select: SelectData;
|
87
|
+
end_stream: never;
|
71
88
|
}>();
|
72
89
|
|
73
90
|
export let dragging = false;
|
@@ -135,17 +152,22 @@
|
|
135
152
|
{#if active_source === "webcam" && (streaming || (!streaming && !value))}
|
136
153
|
<Webcam
|
137
154
|
{root}
|
138
|
-
|
139
|
-
on:
|
155
|
+
{value}
|
156
|
+
on:capture={(e) => handle_save(e.detail, "change")}
|
157
|
+
on:stream={(e) => handle_save(e.detail, "stream")}
|
140
158
|
on:error
|
141
159
|
on:drag
|
142
|
-
on:upload={(e) => handle_save(e.detail)}
|
160
|
+
on:upload={(e) => handle_save(e.detail, "upload")}
|
161
|
+
on:close_stream
|
143
162
|
{mirror_webcam}
|
163
|
+
{stream_every}
|
144
164
|
{streaming}
|
145
165
|
mode="image"
|
146
166
|
include_audio={false}
|
147
167
|
{i18n}
|
148
168
|
{upload}
|
169
|
+
bind:modify_stream
|
170
|
+
bind:set_time_limit
|
149
171
|
/>
|
150
172
|
{:else if value !== null && !streaming}
|
151
173
|
<!-- svelte-ignore a11y-click-events-have-key-events-->
|
package/shared/Webcam.svelte
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
<script lang="ts">
|
2
2
|
import { createEventDispatcher, onMount } from "svelte";
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
Camera,
|
5
|
+
Circle,
|
6
|
+
Square,
|
7
|
+
DropdownArrow,
|
8
|
+
Spinner
|
9
|
+
} from "@gradio/icons";
|
4
10
|
import type { I18nFormatter } from "@gradio/utils";
|
11
|
+
import { StreamingBar } from "@gradio/statustracker";
|
5
12
|
import { type FileData, type Client, prepare_files } from "@gradio/client";
|
6
13
|
import WebcamPermissions from "./WebcamPermissions.svelte";
|
7
14
|
import { fade } from "svelte/transition";
|
@@ -14,17 +21,39 @@
|
|
14
21
|
let video_source: HTMLVideoElement;
|
15
22
|
let available_video_devices: MediaDeviceInfo[] = [];
|
16
23
|
let selected_device: MediaDeviceInfo | null = null;
|
24
|
+
let time_limit: number | null = null;
|
25
|
+
let stream_state: "open" | "waiting" | "closed" = "closed";
|
26
|
+
|
27
|
+
export const modify_stream: (state: "open" | "closed" | "waiting") => void = (
|
28
|
+
state: "open" | "closed" | "waiting"
|
29
|
+
) => {
|
30
|
+
if (state === "closed") {
|
31
|
+
time_limit = null;
|
32
|
+
stream_state = "closed";
|
33
|
+
value = null;
|
34
|
+
} else if (state === "waiting") {
|
35
|
+
stream_state = "waiting";
|
36
|
+
} else {
|
37
|
+
stream_state = "open";
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
export const set_time_limit = (time: number): void => {
|
42
|
+
if (recording) time_limit = time;
|
43
|
+
};
|
17
44
|
|
18
45
|
let canvas: HTMLCanvasElement;
|
19
46
|
export let streaming = false;
|
20
47
|
export let pending = false;
|
21
48
|
export let root = "";
|
49
|
+
export let stream_every = 1;
|
22
50
|
|
23
51
|
export let mode: "image" | "video" = "image";
|
24
52
|
export let mirror_webcam: boolean;
|
25
53
|
export let include_audio: boolean;
|
26
54
|
export let i18n: I18nFormatter;
|
27
55
|
export let upload: Client["upload"];
|
56
|
+
export let value: FileData | null = null;
|
28
57
|
|
29
58
|
const dispatch = createEventDispatcher<{
|
30
59
|
stream: undefined;
|
@@ -32,6 +61,7 @@
|
|
32
61
|
error: string;
|
33
62
|
start_recording: undefined;
|
34
63
|
stop_recording: undefined;
|
64
|
+
close_stream: undefined;
|
35
65
|
}>();
|
36
66
|
|
37
67
|
onMount(() => (canvas = document.createElement("canvas")));
|
@@ -108,11 +138,15 @@
|
|
108
138
|
context.drawImage(video_source, -video_source.videoWidth, 0);
|
109
139
|
}
|
110
140
|
|
141
|
+
if (streaming && (!recording || stream_state === "waiting")) {
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
111
145
|
canvas.toBlob(
|
112
146
|
(blob) => {
|
113
147
|
dispatch(streaming ? "stream" : "capture", blob);
|
114
148
|
},
|
115
|
-
"
|
149
|
+
`image/${streaming ? "jpeg" : "png"}`,
|
116
150
|
0.8
|
117
151
|
);
|
118
152
|
}
|
@@ -136,10 +170,10 @@
|
|
136
170
|
"sample." + mimeType.substring(6)
|
137
171
|
);
|
138
172
|
const val = await prepare_files([_video_blob]);
|
139
|
-
let
|
173
|
+
let val_ = (
|
140
174
|
(await upload(val, root))?.filter(Boolean) as FileData[]
|
141
175
|
)[0];
|
142
|
-
dispatch("capture",
|
176
|
+
dispatch("capture", val_);
|
143
177
|
dispatch("stop_recording");
|
144
178
|
}
|
145
179
|
};
|
@@ -181,9 +215,14 @@
|
|
181
215
|
take_recording();
|
182
216
|
}
|
183
217
|
if (!recording && stream) {
|
218
|
+
dispatch("close_stream");
|
184
219
|
stream.getTracks().forEach((track) => track.stop());
|
185
220
|
video_source.srcObject = null;
|
186
221
|
webcam_accessed = false;
|
222
|
+
window.setTimeout(() => {
|
223
|
+
value = null;
|
224
|
+
}, 500);
|
225
|
+
value = null;
|
187
226
|
}
|
188
227
|
}
|
189
228
|
|
@@ -192,7 +231,7 @@
|
|
192
231
|
if (video_source && !pending) {
|
193
232
|
take_picture();
|
194
233
|
}
|
195
|
-
},
|
234
|
+
}, stream_every * 1000);
|
196
235
|
}
|
197
236
|
|
198
237
|
let options_open = false;
|
@@ -225,12 +264,18 @@
|
|
225
264
|
</script>
|
226
265
|
|
227
266
|
<div class="wrap">
|
267
|
+
<StreamingBar {time_limit} />
|
228
268
|
<!-- svelte-ignore a11y-media-has-caption -->
|
229
269
|
<!-- need to suppress for video streaming https://github.com/sveltejs/svelte/issues/5967 -->
|
230
270
|
<video
|
231
271
|
bind:this={video_source}
|
232
272
|
class:flip={mirror_webcam}
|
233
|
-
class:hide={!webcam_accessed}
|
273
|
+
class:hide={!webcam_accessed || (webcam_accessed && !!value)}
|
274
|
+
/>
|
275
|
+
<!-- svelte-ignore a11y-missing-attribute -->
|
276
|
+
<img
|
277
|
+
src={value?.url}
|
278
|
+
class:hide={!webcam_accessed || (webcam_accessed && !value)}
|
234
279
|
/>
|
235
280
|
{#if !webcam_accessed}
|
236
281
|
<div
|
@@ -247,13 +292,26 @@
|
|
247
292
|
aria-label={mode === "image" ? "capture photo" : "start recording"}
|
248
293
|
>
|
249
294
|
{#if mode === "video" || streaming}
|
250
|
-
{#if
|
251
|
-
<div class="icon
|
252
|
-
<
|
295
|
+
{#if streaming && stream_state === "waiting"}
|
296
|
+
<div class="icon-with-text" style="width:var(--size-24);">
|
297
|
+
<div class="icon color-primary" title="spinner">
|
298
|
+
<Spinner />
|
299
|
+
</div>
|
300
|
+
{i18n("audio.waiting")}
|
301
|
+
</div>
|
302
|
+
{:else if (streaming && stream_state === "open") || (!streaming && recording)}
|
303
|
+
<div class="icon-with-text">
|
304
|
+
<div class="icon color-primary" title="stop recording">
|
305
|
+
<Square />
|
306
|
+
</div>
|
307
|
+
{i18n("audio.stop")}
|
253
308
|
</div>
|
254
309
|
{:else}
|
255
|
-
<div class="icon
|
256
|
-
<
|
310
|
+
<div class="icon-with-text">
|
311
|
+
<div class="icon color-primary" title="start recording">
|
312
|
+
<Circle />
|
313
|
+
</div>
|
314
|
+
{i18n("audio.record")}
|
257
315
|
</div>
|
258
316
|
{/if}
|
259
317
|
{:else}
|
@@ -335,6 +393,14 @@
|
|
335
393
|
color: var(--button-secondary-text-color);
|
336
394
|
}
|
337
395
|
|
396
|
+
.icon-with-text {
|
397
|
+
width: var(--size-20);
|
398
|
+
align-items: center;
|
399
|
+
margin: 0 var(--spacing-xl);
|
400
|
+
display: flex;
|
401
|
+
justify-content: space-evenly;
|
402
|
+
}
|
403
|
+
|
338
404
|
@media (--screen-md) {
|
339
405
|
button {
|
340
406
|
bottom: var(--size-4);
|
@@ -348,7 +414,6 @@
|
|
348
414
|
}
|
349
415
|
|
350
416
|
.icon {
|
351
|
-
opacity: 0.8;
|
352
417
|
width: 18px;
|
353
418
|
height: 18px;
|
354
419
|
display: flex;
|
@@ -356,9 +421,9 @@
|
|
356
421
|
align-items: center;
|
357
422
|
}
|
358
423
|
|
359
|
-
.
|
360
|
-
fill:
|
361
|
-
stroke:
|
424
|
+
.color-primary {
|
425
|
+
fill: var(--primary-600);
|
426
|
+
stroke: var(--primary-600);
|
362
427
|
}
|
363
428
|
|
364
429
|
.flip {
|