@gradio/image 0.15.1 → 0.16.0-beta.2
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 +39 -0
- package/Index.svelte +29 -7
- package/dist/Index.svelte +25 -3
- package/dist/Index.svelte.d.ts +27 -5
- package/dist/shared/ClearImage.svelte +3 -15
- package/dist/shared/ImagePreview.svelte +9 -12
- package/dist/shared/ImageUploader.svelte +28 -11
- package/dist/shared/ImageUploader.svelte.d.ts +9 -3
- package/dist/shared/Webcam.svelte +76 -15
- package/dist/shared/Webcam.svelte.d.ts +7 -0
- package/package.json +8 -8
- package/shared/ClearImage.svelte +3 -15
- package/shared/ImagePreview.svelte +9 -12
- package/shared/ImageUploader.svelte +38 -16
- package/shared/Webcam.svelte +81 -15
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,44 @@
|
|
1
1
|
# @gradio/image
|
2
2
|
|
3
|
+
## 0.16.0-beta.2
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
- [#9339](https://github.com/gradio-app/gradio/pull/9339) [`4c8c6f2`](https://github.com/gradio-app/gradio/commit/4c8c6f2fe603081941c5fdc43f48a0632b9f31ad) - Ssr part 2. Thanks @pngwn!
|
8
|
+
- [#9250](https://github.com/gradio-app/gradio/pull/9250) [`350b0a5`](https://github.com/gradio-app/gradio/commit/350b0a5cafb9176f914f62e7c90de51d4352cc77) - Improve Icon Button consistency. Thanks @hannahblair!
|
9
|
+
- [#9253](https://github.com/gradio-app/gradio/pull/9253) [`99648ec`](https://github.com/gradio-app/gradio/commit/99648ec7c4443e74799941e47b0015ac9ca581e1) - Adds ability to block event trigger when file is uploading. Thanks @dawoodkhan82!
|
10
|
+
- [#9270](https://github.com/gradio-app/gradio/pull/9270) [`b0b8500`](https://github.com/gradio-app/gradio/commit/b0b850081d8d10c1287b5d179b8db37482e21c8d) - Fix stop recording button colors. Thanks @freddyaboulton!
|
11
|
+
|
12
|
+
### Dependency updates
|
13
|
+
|
14
|
+
- @gradio/atoms@0.9.0-beta.2
|
15
|
+
- @gradio/upload@0.13.0-beta.2
|
16
|
+
- @gradio/wasm@0.14.0-beta.2
|
17
|
+
- @gradio/client@1.6.0-beta.2
|
18
|
+
- @gradio/icons@0.8.0-beta.2
|
19
|
+
- @gradio/statustracker@0.8.0-beta.2
|
20
|
+
- @gradio/utils@0.7.0-beta.2
|
21
|
+
|
22
|
+
## 0.16.0-beta.1
|
23
|
+
|
24
|
+
### Dependency updates
|
25
|
+
|
26
|
+
- @gradio/atoms@0.8.1-beta.1
|
27
|
+
- @gradio/icons@0.8.0-beta.1
|
28
|
+
- @gradio/statustracker@0.8.0-beta.1
|
29
|
+
- @gradio/utils@0.7.0-beta.1
|
30
|
+
- @gradio/client@1.6.0-beta.1
|
31
|
+
- @gradio/upload@0.12.4-beta.1
|
32
|
+
- @gradio/wasm@0.13.1-beta.1
|
33
|
+
|
34
|
+
## 0.16.0-beta.0
|
35
|
+
|
36
|
+
### Features
|
37
|
+
|
38
|
+
- [#9149](https://github.com/gradio-app/gradio/pull/9149) [`3d7a9b8`](https://github.com/gradio-app/gradio/commit/3d7a9b81f6fef06187eca832471dc1692eb493a0) - Open audio/image input stream only when queue is ready. Thanks @freddyaboulton!
|
39
|
+
- [#9173](https://github.com/gradio-app/gradio/pull/9173) [`66349fe`](https://github.com/gradio-app/gradio/commit/66349fe26827e3a3c15b738a1177e95fec7f5554) - Streaming Guides. Thanks @freddyaboulton!
|
40
|
+
- [#8941](https://github.com/gradio-app/gradio/pull/8941) [`97a7bf6`](https://github.com/gradio-app/gradio/commit/97a7bf66a79179d1b91a3199d68e5c11216ca500) - Streaming inputs for 5.0. Thanks @freddyaboulton!
|
41
|
+
|
3
42
|
## 0.15.1
|
4
43
|
|
5
44
|
### Fixes
|
package/Index.svelte
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
</script>
|
10
10
|
|
11
11
|
<script lang="ts">
|
12
|
-
import type { Gradio, SelectData } from "@gradio/utils";
|
12
|
+
import type { Gradio, SelectData, ValueData } from "@gradio/utils";
|
13
13
|
import StaticImage from "./shared/ImagePreview.svelte";
|
14
14
|
import ImageUploader from "./shared/ImageUploader.svelte";
|
15
15
|
import { afterUpdate } from "svelte";
|
@@ -17,11 +17,21 @@
|
|
17
17
|
import { Block, Empty, UploadText } from "@gradio/atoms";
|
18
18
|
import { Image } from "@gradio/icons";
|
19
19
|
import { StatusTracker } from "@gradio/statustracker";
|
20
|
-
import type
|
20
|
+
import { upload, type FileData } from "@gradio/client";
|
21
21
|
import type { LoadingStatus } from "@gradio/statustracker";
|
22
22
|
|
23
23
|
type sources = "upload" | "webcam" | "clipboard" | null;
|
24
24
|
|
25
|
+
let stream_state = "closed";
|
26
|
+
let _modify_stream: (state: "open" | "closed" | "waiting") => void = () => {};
|
27
|
+
export function modify_stream_state(
|
28
|
+
state: "open" | "closed" | "waiting"
|
29
|
+
): void {
|
30
|
+
stream_state = state;
|
31
|
+
_modify_stream(state);
|
32
|
+
}
|
33
|
+
export const get_stream_state: () => void = () => stream_state;
|
34
|
+
export let set_time_limit: (arg0: number) => void;
|
25
35
|
export let value_is_output = false;
|
26
36
|
export let elem_id = "";
|
27
37
|
export let elem_classes: string[] = [];
|
@@ -35,6 +45,7 @@
|
|
35
45
|
|
36
46
|
export let height: number | undefined;
|
37
47
|
export let width: number | undefined;
|
48
|
+
export let stream_every: number;
|
38
49
|
|
39
50
|
export let _selectable = false;
|
40
51
|
export let container = true;
|
@@ -53,19 +64,22 @@
|
|
53
64
|
export let mirror_webcam: boolean;
|
54
65
|
export let placeholder: string | undefined = undefined;
|
55
66
|
export let show_fullscreen_button: boolean;
|
56
|
-
|
67
|
+
export let input_ready: boolean;
|
68
|
+
let uploading = false;
|
69
|
+
$: input_ready = !uploading;
|
57
70
|
export let gradio: Gradio<{
|
58
71
|
input: never;
|
59
72
|
change: never;
|
60
73
|
error: string;
|
61
74
|
edit: never;
|
62
|
-
stream:
|
75
|
+
stream: ValueData;
|
63
76
|
drag: never;
|
64
77
|
upload: never;
|
65
78
|
clear: never;
|
66
79
|
select: SelectData;
|
67
80
|
share: ShareData;
|
68
81
|
clear_status: LoadingStatus;
|
82
|
+
close_stream: string;
|
69
83
|
}>;
|
70
84
|
|
71
85
|
$: {
|
@@ -77,6 +91,7 @@
|
|
77
91
|
}
|
78
92
|
}
|
79
93
|
}
|
94
|
+
|
80
95
|
afterUpdate(() => {
|
81
96
|
value_is_output = false;
|
82
97
|
});
|
@@ -171,6 +186,7 @@
|
|
171
186
|
|
172
187
|
<ImageUploader
|
173
188
|
bind:this={upload_component}
|
189
|
+
bind:uploading
|
174
190
|
bind:active_source
|
175
191
|
bind:value
|
176
192
|
bind:dragging
|
@@ -181,7 +197,7 @@
|
|
181
197
|
on:clear={() => {
|
182
198
|
gradio.dispatch("clear");
|
183
199
|
}}
|
184
|
-
on:stream={() => gradio.dispatch("stream")}
|
200
|
+
on:stream={({ detail }) => gradio.dispatch("stream", detail)}
|
185
201
|
on:drag={({ detail }) => (dragging = detail)}
|
186
202
|
on:upload={() => gradio.dispatch("upload")}
|
187
203
|
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
@@ -191,15 +207,21 @@
|
|
191
207
|
loading_status.status = "error";
|
192
208
|
gradio.dispatch("error", detail);
|
193
209
|
}}
|
210
|
+
on:close_stream={() => {
|
211
|
+
gradio.dispatch("close_stream", "stream");
|
212
|
+
}}
|
194
213
|
{label}
|
195
214
|
{show_label}
|
196
215
|
{pending}
|
197
216
|
{streaming}
|
198
217
|
{mirror_webcam}
|
218
|
+
{stream_every}
|
219
|
+
bind:modify_stream={_modify_stream}
|
220
|
+
bind:set_time_limit
|
199
221
|
max_file_size={gradio.max_file_size}
|
200
222
|
i18n={gradio.i18n}
|
201
|
-
upload={gradio.client.upload}
|
202
|
-
stream_handler={gradio.client
|
223
|
+
upload={(...args) => gradio.client.upload(...args)}
|
224
|
+
stream_handler={gradio.client?.stream}
|
203
225
|
>
|
204
226
|
{#if active_source === "upload" || !active_source}
|
205
227
|
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
|
package/dist/Index.svelte
CHANGED
@@ -13,6 +13,16 @@ import { afterUpdate } from "svelte";
|
|
13
13
|
import { Block, Empty, UploadText } from "@gradio/atoms";
|
14
14
|
import { Image } from "@gradio/icons";
|
15
15
|
import { StatusTracker } from "@gradio/statustracker";
|
16
|
+
import { upload } from "@gradio/client";
|
17
|
+
let stream_state = "closed";
|
18
|
+
let _modify_stream = () => {
|
19
|
+
};
|
20
|
+
export function modify_stream_state(state) {
|
21
|
+
stream_state = state;
|
22
|
+
_modify_stream(state);
|
23
|
+
}
|
24
|
+
export const get_stream_state = () => stream_state;
|
25
|
+
export let set_time_limit;
|
16
26
|
export let value_is_output = false;
|
17
27
|
export let elem_id = "";
|
18
28
|
export let elem_classes = [];
|
@@ -25,6 +35,7 @@ export let show_download_button;
|
|
25
35
|
export let root;
|
26
36
|
export let height;
|
27
37
|
export let width;
|
38
|
+
export let stream_every;
|
28
39
|
export let _selectable = false;
|
29
40
|
export let container = true;
|
30
41
|
export let scale = null;
|
@@ -42,6 +53,10 @@ export let pending;
|
|
42
53
|
export let mirror_webcam;
|
43
54
|
export let placeholder = void 0;
|
44
55
|
export let show_fullscreen_button;
|
56
|
+
export let input_ready;
|
57
|
+
let uploading = false;
|
58
|
+
$:
|
59
|
+
input_ready = !uploading;
|
45
60
|
export let gradio;
|
46
61
|
$: {
|
47
62
|
if (JSON.stringify(value) !== JSON.stringify(old_value)) {
|
@@ -143,6 +158,7 @@ const handle_drop = (event) => {
|
|
143
158
|
|
144
159
|
<ImageUploader
|
145
160
|
bind:this={upload_component}
|
161
|
+
bind:uploading
|
146
162
|
bind:active_source
|
147
163
|
bind:value
|
148
164
|
bind:dragging
|
@@ -153,7 +169,7 @@ const handle_drop = (event) => {
|
|
153
169
|
on:clear={() => {
|
154
170
|
gradio.dispatch("clear");
|
155
171
|
}}
|
156
|
-
on:stream={() => gradio.dispatch("stream")}
|
172
|
+
on:stream={({ detail }) => gradio.dispatch("stream", detail)}
|
157
173
|
on:drag={({ detail }) => (dragging = detail)}
|
158
174
|
on:upload={() => gradio.dispatch("upload")}
|
159
175
|
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
@@ -163,15 +179,21 @@ const handle_drop = (event) => {
|
|
163
179
|
loading_status.status = "error";
|
164
180
|
gradio.dispatch("error", detail);
|
165
181
|
}}
|
182
|
+
on:close_stream={() => {
|
183
|
+
gradio.dispatch("close_stream", "stream");
|
184
|
+
}}
|
166
185
|
{label}
|
167
186
|
{show_label}
|
168
187
|
{pending}
|
169
188
|
{streaming}
|
170
189
|
{mirror_webcam}
|
190
|
+
{stream_every}
|
191
|
+
bind:modify_stream={_modify_stream}
|
192
|
+
bind:set_time_limit
|
171
193
|
max_file_size={gradio.max_file_size}
|
172
194
|
i18n={gradio.i18n}
|
173
|
-
upload={gradio.client.upload}
|
174
|
-
stream_handler={gradio.client
|
195
|
+
upload={(...args) => gradio.client.upload(...args)}
|
196
|
+
stream_handler={gradio.client?.stream}
|
175
197
|
>
|
176
198
|
{#if active_source === "upload" || !active_source}
|
177
199
|
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
|
package/dist/Index.svelte.d.ts
CHANGED
@@ -4,11 +4,14 @@ export { default as BaseImageUploader } from "./shared/ImageUploader.svelte";
|
|
4
4
|
export { default as BaseStaticImage } from "./shared/ImagePreview.svelte";
|
5
5
|
export { default as BaseExample } from "./Example.svelte";
|
6
6
|
export { default as BaseImage } from "./shared/Image.svelte";
|
7
|
-
import type { Gradio, SelectData } from "@gradio/utils";
|
8
|
-
import type
|
7
|
+
import type { Gradio, SelectData, ValueData } from "@gradio/utils";
|
8
|
+
import { type FileData } from "@gradio/client";
|
9
9
|
import type { LoadingStatus } from "@gradio/statustracker";
|
10
10
|
declare const __propDef: {
|
11
11
|
props: {
|
12
|
+
modify_stream_state?: ((state: "open" | "closed" | "waiting") => void) | undefined;
|
13
|
+
get_stream_state?: (() => void) | undefined;
|
14
|
+
set_time_limit: (arg0: number) => void;
|
12
15
|
value_is_output?: boolean | undefined;
|
13
16
|
elem_id?: string | undefined;
|
14
17
|
elem_classes?: string[] | undefined;
|
@@ -20,6 +23,7 @@ declare const __propDef: {
|
|
20
23
|
root: string;
|
21
24
|
height: number | undefined;
|
22
25
|
width: number | undefined;
|
26
|
+
stream_every: number;
|
23
27
|
_selectable?: boolean | undefined;
|
24
28
|
container?: boolean | undefined;
|
25
29
|
scale?: (number | null) | undefined;
|
@@ -33,18 +37,20 @@ declare const __propDef: {
|
|
33
37
|
mirror_webcam: boolean;
|
34
38
|
placeholder?: string | undefined;
|
35
39
|
show_fullscreen_button: boolean;
|
40
|
+
input_ready: boolean;
|
36
41
|
gradio: Gradio<{
|
37
42
|
input: never;
|
38
43
|
change: never;
|
39
44
|
error: string;
|
40
45
|
edit: never;
|
41
|
-
stream:
|
46
|
+
stream: ValueData;
|
42
47
|
drag: never;
|
43
48
|
upload: never;
|
44
49
|
clear: never;
|
45
50
|
select: SelectData;
|
46
51
|
share: ShareData;
|
47
52
|
clear_status: LoadingStatus;
|
53
|
+
close_stream: string;
|
48
54
|
}>;
|
49
55
|
};
|
50
56
|
events: {
|
@@ -56,6 +62,14 @@ export type IndexProps = typeof __propDef.props;
|
|
56
62
|
export type IndexEvents = typeof __propDef.events;
|
57
63
|
export type IndexSlots = typeof __propDef.slots;
|
58
64
|
export default class Index extends SvelteComponent<IndexProps, IndexEvents, IndexSlots> {
|
65
|
+
get modify_stream_state(): (state: "open" | "closed" | "waiting") => void;
|
66
|
+
get get_stream_state(): () => void;
|
67
|
+
get undefined(): any;
|
68
|
+
/**accessor*/
|
69
|
+
set undefined(_: any);
|
70
|
+
get set_time_limit(): (arg0: number) => void;
|
71
|
+
/**accessor*/
|
72
|
+
set set_time_limit(_: (arg0: number) => void);
|
59
73
|
get value_is_output(): boolean | undefined;
|
60
74
|
/**accessor*/
|
61
75
|
set value_is_output(_: boolean | undefined);
|
@@ -89,6 +103,9 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
89
103
|
get width(): number | undefined;
|
90
104
|
/**accessor*/
|
91
105
|
set width(_: number | undefined);
|
106
|
+
get stream_every(): number;
|
107
|
+
/**accessor*/
|
108
|
+
set stream_every(_: number);
|
92
109
|
get _selectable(): boolean | undefined;
|
93
110
|
/**accessor*/
|
94
111
|
set _selectable(_: boolean | undefined);
|
@@ -128,18 +145,22 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
128
145
|
get show_fullscreen_button(): boolean;
|
129
146
|
/**accessor*/
|
130
147
|
set show_fullscreen_button(_: boolean);
|
148
|
+
get input_ready(): boolean;
|
149
|
+
/**accessor*/
|
150
|
+
set input_ready(_: boolean);
|
131
151
|
get gradio(): Gradio<{
|
132
152
|
input: never;
|
133
153
|
change: never;
|
134
154
|
error: string;
|
135
155
|
edit: never;
|
136
|
-
stream:
|
156
|
+
stream: ValueData;
|
137
157
|
drag: never;
|
138
158
|
upload: never;
|
139
159
|
clear: never;
|
140
160
|
select: SelectData;
|
141
161
|
share: ShareData;
|
142
162
|
clear_status: LoadingStatus;
|
163
|
+
close_stream: string;
|
143
164
|
}>;
|
144
165
|
/**accessor*/
|
145
166
|
set gradio(_: Gradio<{
|
@@ -147,12 +168,13 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
147
168
|
change: never;
|
148
169
|
error: string;
|
149
170
|
edit: never;
|
150
|
-
stream:
|
171
|
+
stream: ValueData;
|
151
172
|
drag: never;
|
152
173
|
upload: never;
|
153
174
|
clear: never;
|
154
175
|
select: SelectData;
|
155
176
|
share: ShareData;
|
156
177
|
clear_status: LoadingStatus;
|
178
|
+
close_stream: string;
|
157
179
|
}>);
|
158
180
|
}
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<script>import { createEventDispatcher } from "svelte";
|
2
|
-
import { IconButton } from "@gradio/atoms";
|
2
|
+
import { IconButton, IconButtonWrapper } from "@gradio/atoms";
|
3
3
|
import { Clear } from "@gradio/icons";
|
4
4
|
const dispatch = createEventDispatcher();
|
5
5
|
</script>
|
6
6
|
|
7
|
-
<
|
7
|
+
<IconButtonWrapper>
|
8
8
|
<IconButton
|
9
9
|
Icon={Clear}
|
10
10
|
label="Remove Image"
|
@@ -13,16 +13,4 @@ const dispatch = createEventDispatcher();
|
|
13
13
|
event.stopPropagation();
|
14
14
|
}}
|
15
15
|
/>
|
16
|
-
</
|
17
|
-
|
18
|
-
<style>
|
19
|
-
div {
|
20
|
-
display: flex;
|
21
|
-
position: absolute;
|
22
|
-
top: var(--size-2);
|
23
|
-
right: var(--size-2);
|
24
|
-
justify-content: flex-end;
|
25
|
-
gap: var(--spacing-sm);
|
26
|
-
z-index: var(--layer-5);
|
27
|
-
}
|
28
|
-
</style>
|
16
|
+
</IconButtonWrapper>
|
@@ -1,6 +1,12 @@
|
|
1
1
|
<script>import { createEventDispatcher, onMount } from "svelte";
|
2
2
|
import { uploadToHuggingFace } from "@gradio/utils";
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
BlockLabel,
|
5
|
+
Empty,
|
6
|
+
IconButton,
|
7
|
+
ShareButton,
|
8
|
+
IconButtonWrapper
|
9
|
+
} from "@gradio/atoms";
|
4
10
|
import { Download } from "@gradio/icons";
|
5
11
|
import { get_coordinates_of_clicked_image } from "./utils";
|
6
12
|
import Image from "./Image.svelte";
|
@@ -49,7 +55,7 @@ const toggle_full_screen = async () => {
|
|
49
55
|
<Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
|
50
56
|
{:else}
|
51
57
|
<div class="image-container" bind:this={image_container}>
|
52
|
-
<
|
58
|
+
<IconButtonWrapper>
|
53
59
|
{#if !is_full_screen && show_fullscreen_button}
|
54
60
|
<IconButton
|
55
61
|
Icon={Maximize}
|
@@ -84,7 +90,7 @@ const toggle_full_screen = async () => {
|
|
84
90
|
{value}
|
85
91
|
/>
|
86
92
|
{/if}
|
87
|
-
</
|
93
|
+
</IconButtonWrapper>
|
88
94
|
<button on:click={handle_click}>
|
89
95
|
<div class:selectable class="image-frame">
|
90
96
|
<Image src={value.url} alt="" loading="lazy" on:load />
|
@@ -126,15 +132,6 @@ const toggle_full_screen = async () => {
|
|
126
132
|
cursor: crosshair;
|
127
133
|
}
|
128
134
|
|
129
|
-
.icon-buttons {
|
130
|
-
display: flex;
|
131
|
-
position: absolute;
|
132
|
-
top: 6px;
|
133
|
-
right: 6px;
|
134
|
-
gap: var(--size-1);
|
135
|
-
z-index: 1;
|
136
|
-
}
|
137
|
-
|
138
135
|
:global(.fullscreen-controls svg) {
|
139
136
|
position: relative;
|
140
137
|
top: 0px;
|
@@ -1,9 +1,12 @@
|
|
1
1
|
<script>import { createEventDispatcher, tick } from "svelte";
|
2
2
|
import { BlockLabel } from "@gradio/atoms";
|
3
3
|
import { Image as ImageIcon } from "@gradio/icons";
|
4
|
+
import {
|
5
|
+
} from "@gradio/utils";
|
4
6
|
import { get_coordinates_of_clicked_image } from "./utils";
|
5
7
|
import Webcam from "./Webcam.svelte";
|
6
8
|
import { Upload } from "@gradio/upload";
|
9
|
+
import { FileData } from "@gradio/client";
|
7
10
|
import ClearImage from "./ClearImage.svelte";
|
8
11
|
import { SelectSource } from "@gradio/atoms";
|
9
12
|
import Image from "./Image.svelte";
|
@@ -20,26 +23,35 @@ export let i18n;
|
|
20
23
|
export let max_file_size = null;
|
21
24
|
export let upload;
|
22
25
|
export let stream_handler;
|
26
|
+
export let stream_every;
|
27
|
+
export let modify_stream;
|
28
|
+
export let set_time_limit;
|
23
29
|
let upload_input;
|
24
|
-
let uploading = false;
|
30
|
+
export let uploading = false;
|
25
31
|
export let active_source = null;
|
26
32
|
function handle_upload({ detail }) {
|
27
|
-
|
28
|
-
|
33
|
+
if (!streaming) {
|
34
|
+
value = detail;
|
35
|
+
dispatch("upload");
|
36
|
+
}
|
29
37
|
}
|
30
38
|
function handle_clear() {
|
31
39
|
value = null;
|
32
40
|
dispatch("clear");
|
33
41
|
dispatch("change", null);
|
34
42
|
}
|
35
|
-
async function handle_save(img_blob) {
|
43
|
+
async function handle_save(img_blob, event) {
|
36
44
|
pending = true;
|
37
45
|
const f = await upload_input.load_files([
|
38
|
-
new File([img_blob], `
|
46
|
+
new File([img_blob], `image/${streaming ? "jpeg" : "png"}`)
|
39
47
|
]);
|
40
|
-
|
41
|
-
|
42
|
-
|
48
|
+
if (event === "change" || event === "upload") {
|
49
|
+
value = f?.[0] || null;
|
50
|
+
await tick();
|
51
|
+
dispatch("change");
|
52
|
+
} else {
|
53
|
+
dispatch("stream", { value: f?.[0] || null, is_value_data: true });
|
54
|
+
}
|
43
55
|
pending = false;
|
44
56
|
}
|
45
57
|
$:
|
@@ -109,17 +121,22 @@ async function handle_select_source(source) {
|
|
109
121
|
{#if active_source === "webcam" && (streaming || (!streaming && !value))}
|
110
122
|
<Webcam
|
111
123
|
{root}
|
112
|
-
|
113
|
-
on:
|
124
|
+
{value}
|
125
|
+
on:capture={(e) => handle_save(e.detail, "change")}
|
126
|
+
on:stream={(e) => handle_save(e.detail, "stream")}
|
114
127
|
on:error
|
115
128
|
on:drag
|
116
|
-
on:upload={(e) => handle_save(e.detail)}
|
129
|
+
on:upload={(e) => handle_save(e.detail, "upload")}
|
130
|
+
on:close_stream
|
117
131
|
{mirror_webcam}
|
132
|
+
{stream_every}
|
118
133
|
{streaming}
|
119
134
|
mode="image"
|
120
135
|
include_audio={false}
|
121
136
|
{i18n}
|
122
137
|
{upload}
|
138
|
+
bind:modify_stream
|
139
|
+
bind:set_time_limit
|
123
140
|
/>
|
124
141
|
{:else if value !== null && !streaming}
|
125
142
|
<!-- svelte-ignore a11y-click-events-have-key-events-->
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
|
-
import type
|
3
|
-
import
|
2
|
+
import { type SelectData, type I18nFormatter, type ValueData } from "@gradio/utils";
|
3
|
+
import { FileData, type Client } from "@gradio/client";
|
4
4
|
declare const __propDef: {
|
5
5
|
props: {
|
6
6
|
value: null | FileData;
|
@@ -16,17 +16,23 @@ declare const __propDef: {
|
|
16
16
|
max_file_size?: (number | null) | undefined;
|
17
17
|
upload: Client["upload"];
|
18
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
|
+
uploading?: boolean | undefined;
|
19
23
|
active_source?: ("upload" | "clipboard" | "microphone" | "webcam" | null) | undefined;
|
20
24
|
dragging?: boolean | undefined;
|
21
25
|
};
|
22
26
|
events: {
|
23
27
|
error: CustomEvent<string> | CustomEvent<any>;
|
24
28
|
drag: CustomEvent<any>;
|
29
|
+
close_stream: CustomEvent<undefined>;
|
25
30
|
change?: CustomEvent<undefined> | undefined;
|
26
|
-
stream
|
31
|
+
stream: CustomEvent<ValueData>;
|
27
32
|
clear?: CustomEvent<undefined> | undefined;
|
28
33
|
upload?: CustomEvent<undefined> | undefined;
|
29
34
|
select: CustomEvent<SelectData>;
|
35
|
+
end_stream: CustomEvent<never>;
|
30
36
|
} & {
|
31
37
|
[evt: string]: CustomEvent<any>;
|
32
38
|
};
|
@@ -1,5 +1,12 @@
|
|
1
1
|
<script>import { createEventDispatcher, onMount } from "svelte";
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
Camera,
|
4
|
+
Circle,
|
5
|
+
Square,
|
6
|
+
DropdownArrow,
|
7
|
+
Spinner
|
8
|
+
} from "@gradio/icons";
|
9
|
+
import { StreamingBar } from "@gradio/statustracker";
|
3
10
|
import { prepare_files } from "@gradio/client";
|
4
11
|
import WebcamPermissions from "./WebcamPermissions.svelte";
|
5
12
|
import { fade } from "svelte/transition";
|
@@ -11,15 +18,34 @@ import {
|
|
11
18
|
let video_source;
|
12
19
|
let available_video_devices = [];
|
13
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
|
+
};
|
14
38
|
let canvas;
|
15
39
|
export let streaming = false;
|
16
40
|
export let pending = false;
|
17
41
|
export let root = "";
|
42
|
+
export let stream_every = 1;
|
18
43
|
export let mode = "image";
|
19
44
|
export let mirror_webcam;
|
20
45
|
export let include_audio;
|
21
46
|
export let i18n;
|
22
47
|
export let upload;
|
48
|
+
export let value = null;
|
23
49
|
const dispatch = createEventDispatcher();
|
24
50
|
onMount(() => canvas = document.createElement("canvas"));
|
25
51
|
const handle_device_change = async (event) => {
|
@@ -73,11 +99,14 @@ function take_picture() {
|
|
73
99
|
context.scale(-1, 1);
|
74
100
|
context.drawImage(video_source, -video_source.videoWidth, 0);
|
75
101
|
}
|
102
|
+
if (streaming && (!recording || stream_state === "waiting")) {
|
103
|
+
return;
|
104
|
+
}
|
76
105
|
canvas.toBlob(
|
77
106
|
(blob) => {
|
78
107
|
dispatch(streaming ? "stream" : "capture", blob);
|
79
108
|
},
|
80
|
-
"
|
109
|
+
`image/${streaming ? "jpeg" : "png"}`,
|
81
110
|
0.8
|
82
111
|
);
|
83
112
|
}
|
@@ -99,8 +128,8 @@ function take_recording() {
|
|
99
128
|
"sample." + mimeType.substring(6)
|
100
129
|
);
|
101
130
|
const val = await prepare_files([_video_blob]);
|
102
|
-
let
|
103
|
-
dispatch("capture",
|
131
|
+
let val_ = ((await upload(val, root))?.filter(Boolean))[0];
|
132
|
+
dispatch("capture", val_);
|
104
133
|
dispatch("stop_recording");
|
105
134
|
}
|
106
135
|
};
|
@@ -140,9 +169,14 @@ function record_video_or_photo() {
|
|
140
169
|
take_recording();
|
141
170
|
}
|
142
171
|
if (!recording && stream) {
|
172
|
+
dispatch("close_stream");
|
143
173
|
stream.getTracks().forEach((track) => track.stop());
|
144
174
|
video_source.srcObject = null;
|
145
175
|
webcam_accessed = false;
|
176
|
+
window.setTimeout(() => {
|
177
|
+
value = null;
|
178
|
+
}, 500);
|
179
|
+
value = null;
|
146
180
|
}
|
147
181
|
}
|
148
182
|
if (streaming && mode === "image") {
|
@@ -150,7 +184,7 @@ if (streaming && mode === "image") {
|
|
150
184
|
if (video_source && !pending) {
|
151
185
|
take_picture();
|
152
186
|
}
|
153
|
-
},
|
187
|
+
}, stream_every * 1e3);
|
154
188
|
}
|
155
189
|
let options_open = false;
|
156
190
|
export function click_outside(node, cb) {
|
@@ -174,12 +208,18 @@ function handle_click_outside(event) {
|
|
174
208
|
</script>
|
175
209
|
|
176
210
|
<div class="wrap">
|
211
|
+
<StreamingBar {time_limit} />
|
177
212
|
<!-- svelte-ignore a11y-media-has-caption -->
|
178
213
|
<!-- need to suppress for video streaming https://github.com/sveltejs/svelte/issues/5967 -->
|
179
214
|
<video
|
180
215
|
bind:this={video_source}
|
181
216
|
class:flip={mirror_webcam}
|
182
|
-
class:hide={!webcam_accessed}
|
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)}
|
183
223
|
/>
|
184
224
|
{#if !webcam_accessed}
|
185
225
|
<div
|
@@ -196,13 +236,26 @@ function handle_click_outside(event) {
|
|
196
236
|
aria-label={mode === "image" ? "capture photo" : "start recording"}
|
197
237
|
>
|
198
238
|
{#if mode === "video" || streaming}
|
199
|
-
{#if
|
200
|
-
<div class="icon
|
201
|
-
<
|
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")}
|
202
252
|
</div>
|
203
253
|
{:else}
|
204
|
-
<div class="icon
|
205
|
-
<
|
254
|
+
<div class="icon-with-text">
|
255
|
+
<div class="icon color-primary" title="start recording">
|
256
|
+
<Circle />
|
257
|
+
</div>
|
258
|
+
{i18n("audio.record")}
|
206
259
|
</div>
|
207
260
|
{/if}
|
208
261
|
{:else}
|
@@ -284,6 +337,14 @@ function handle_click_outside(event) {
|
|
284
337
|
color: var(--button-secondary-text-color);
|
285
338
|
}
|
286
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
|
+
|
287
348
|
@media (--screen-md) {
|
288
349
|
button {
|
289
350
|
bottom: var(--size-4);
|
@@ -297,7 +358,6 @@ function handle_click_outside(event) {
|
|
297
358
|
}
|
298
359
|
|
299
360
|
.icon {
|
300
|
-
opacity: 0.8;
|
301
361
|
width: 18px;
|
302
362
|
height: 18px;
|
303
363
|
display: flex;
|
@@ -305,9 +365,10 @@ function handle_click_outside(event) {
|
|
305
365
|
align-items: center;
|
306
366
|
}
|
307
367
|
|
308
|
-
.
|
309
|
-
fill:
|
310
|
-
stroke:
|
368
|
+
.color-primary {
|
369
|
+
fill: var(--primary-600);
|
370
|
+
stroke: var(--primary-600);
|
371
|
+
color: var(--primary-600);
|
311
372
|
}
|
312
373
|
|
313
374
|
.flip {
|
@@ -3,14 +3,18 @@ import type { I18nFormatter } from "@gradio/utils";
|
|
3
3
|
import { type FileData, type Client } from "@gradio/client";
|
4
4
|
declare const __propDef: {
|
5
5
|
props: {
|
6
|
+
modify_stream?: ((state: "open" | "closed" | "waiting") => void) | undefined;
|
7
|
+
set_time_limit?: ((time: number) => void) | undefined;
|
6
8
|
streaming?: boolean | undefined;
|
7
9
|
pending?: boolean | undefined;
|
8
10
|
root?: string | undefined;
|
11
|
+
stream_every?: number | undefined;
|
9
12
|
mode?: ("image" | "video") | undefined;
|
10
13
|
mirror_webcam: boolean;
|
11
14
|
include_audio: boolean;
|
12
15
|
i18n: I18nFormatter;
|
13
16
|
upload: Client["upload"];
|
17
|
+
value?: (FileData | null) | undefined;
|
14
18
|
click_outside?: ((node: Node, cb: any) => any) | undefined;
|
15
19
|
};
|
16
20
|
events: {
|
@@ -19,6 +23,7 @@ declare const __propDef: {
|
|
19
23
|
error: CustomEvent<string>;
|
20
24
|
start_recording: CustomEvent<undefined>;
|
21
25
|
stop_recording: CustomEvent<undefined>;
|
26
|
+
close_stream: CustomEvent<undefined>;
|
22
27
|
} & {
|
23
28
|
[evt: string]: CustomEvent<any>;
|
24
29
|
};
|
@@ -28,6 +33,8 @@ export type WebcamProps = typeof __propDef.props;
|
|
28
33
|
export type WebcamEvents = typeof __propDef.events;
|
29
34
|
export type WebcamSlots = typeof __propDef.slots;
|
30
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;
|
31
38
|
get click_outside(): (node: Node, cb: any) => any;
|
32
39
|
}
|
33
40
|
export {};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@gradio/image",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.16.0-beta.2",
|
4
4
|
"description": "Gradio UI packages",
|
5
5
|
"type": "module",
|
6
6
|
"author": "",
|
@@ -10,13 +10,13 @@
|
|
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.
|
14
|
-
"@gradio/client": "^1.
|
15
|
-
"@gradio/icons": "^0.
|
16
|
-
"@gradio/statustracker": "^0.
|
17
|
-
"@gradio/utils": "^0.
|
18
|
-
"@gradio/
|
19
|
-
"@gradio/
|
13
|
+
"@gradio/atoms": "^0.9.0-beta.2",
|
14
|
+
"@gradio/client": "^1.6.0-beta.2",
|
15
|
+
"@gradio/icons": "^0.8.0-beta.2",
|
16
|
+
"@gradio/statustracker": "^0.8.0-beta.2",
|
17
|
+
"@gradio/utils": "^0.7.0-beta.2",
|
18
|
+
"@gradio/wasm": "^0.14.0-beta.2",
|
19
|
+
"@gradio/upload": "^0.13.0-beta.2"
|
20
20
|
},
|
21
21
|
"devDependencies": {
|
22
22
|
"@gradio/preview": "^0.11.1"
|
package/shared/ClearImage.svelte
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
<script lang="ts">
|
2
2
|
import { createEventDispatcher } from "svelte";
|
3
|
-
import { IconButton } from "@gradio/atoms";
|
3
|
+
import { IconButton, IconButtonWrapper } from "@gradio/atoms";
|
4
4
|
import { Clear } from "@gradio/icons";
|
5
5
|
|
6
6
|
const dispatch = createEventDispatcher();
|
7
7
|
</script>
|
8
8
|
|
9
|
-
<
|
9
|
+
<IconButtonWrapper>
|
10
10
|
<IconButton
|
11
11
|
Icon={Clear}
|
12
12
|
label="Remove Image"
|
@@ -15,16 +15,4 @@
|
|
15
15
|
event.stopPropagation();
|
16
16
|
}}
|
17
17
|
/>
|
18
|
-
</
|
19
|
-
|
20
|
-
<style>
|
21
|
-
div {
|
22
|
-
display: flex;
|
23
|
-
position: absolute;
|
24
|
-
top: var(--size-2);
|
25
|
-
right: var(--size-2);
|
26
|
-
justify-content: flex-end;
|
27
|
-
gap: var(--spacing-sm);
|
28
|
-
z-index: var(--layer-5);
|
29
|
-
}
|
30
|
-
</style>
|
18
|
+
</IconButtonWrapper>
|
@@ -2,7 +2,13 @@
|
|
2
2
|
import { createEventDispatcher, onMount } from "svelte";
|
3
3
|
import type { SelectData } from "@gradio/utils";
|
4
4
|
import { uploadToHuggingFace } from "@gradio/utils";
|
5
|
-
import {
|
5
|
+
import {
|
6
|
+
BlockLabel,
|
7
|
+
Empty,
|
8
|
+
IconButton,
|
9
|
+
ShareButton,
|
10
|
+
IconButtonWrapper
|
11
|
+
} from "@gradio/atoms";
|
6
12
|
import { Download } from "@gradio/icons";
|
7
13
|
import { get_coordinates_of_clicked_image } from "./utils";
|
8
14
|
import Image from "./Image.svelte";
|
@@ -62,7 +68,7 @@
|
|
62
68
|
<Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
|
63
69
|
{:else}
|
64
70
|
<div class="image-container" bind:this={image_container}>
|
65
|
-
<
|
71
|
+
<IconButtonWrapper>
|
66
72
|
{#if !is_full_screen && show_fullscreen_button}
|
67
73
|
<IconButton
|
68
74
|
Icon={Maximize}
|
@@ -97,7 +103,7 @@
|
|
97
103
|
{value}
|
98
104
|
/>
|
99
105
|
{/if}
|
100
|
-
</
|
106
|
+
</IconButtonWrapper>
|
101
107
|
<button on:click={handle_click}>
|
102
108
|
<div class:selectable class="image-frame">
|
103
109
|
<Image src={value.url} alt="" loading="lazy" on:load />
|
@@ -139,15 +145,6 @@
|
|
139
145
|
cursor: crosshair;
|
140
146
|
}
|
141
147
|
|
142
|
-
.icon-buttons {
|
143
|
-
display: flex;
|
144
|
-
position: absolute;
|
145
|
-
top: 6px;
|
146
|
-
right: 6px;
|
147
|
-
gap: var(--size-1);
|
148
|
-
z-index: 1;
|
149
|
-
}
|
150
|
-
|
151
148
|
:global(.fullscreen-controls svg) {
|
152
149
|
position: relative;
|
153
150
|
top: 0px;
|
@@ -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
|
-
let uploading = false;
|
41
|
+
export 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,10 @@
|
|
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);
|
427
|
+
color: var(--primary-600);
|
362
428
|
}
|
363
429
|
|
364
430
|
.flip {
|