@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 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 { FileData } from "@gradio/client";
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: never;
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.stream}
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.stream}
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} />
@@ -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 { FileData } from "@gradio/client";
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: never;
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: never;
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: never;
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
- <div>
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
- </div>
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 { BlockLabel, Empty, IconButton, ShareButton } from "@gradio/atoms";
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
- <div class="icon-buttons">
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
- </div>
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
- value = detail;
28
- dispatch("upload");
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], `webcam.png`)
46
+ new File([img_blob], `image/${streaming ? "jpeg" : "png"}`)
39
47
  ]);
40
- value = f?.[0] || null;
41
- await tick();
42
- dispatch(streaming ? "stream" : "change");
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
- on:capture={(e) => handle_save(e.detail)}
113
- on:stream={(e) => handle_save(e.detail)}
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 { SelectData, I18nFormatter } from "@gradio/utils";
3
- import type { FileData, Client } from "@gradio/client";
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?: CustomEvent<undefined> | undefined;
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 { Camera, Circle, Square, DropdownArrow } from "@gradio/icons";
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
- "image/png",
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 value = ((await upload(val, root))?.filter(Boolean))[0];
103
- dispatch("capture", value);
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
- }, 500);
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 recording}
200
- <div class="icon red" title="stop recording">
201
- <Square />
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 red" title="start recording">
205
- <Circle />
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
- .red {
309
- fill: red;
310
- stroke: red;
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.15.1",
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.8.1",
14
- "@gradio/client": "^1.5.2",
15
- "@gradio/icons": "^0.7.2",
16
- "@gradio/statustracker": "^0.7.6",
17
- "@gradio/utils": "^0.6.1",
18
- "@gradio/upload": "^0.12.4",
19
- "@gradio/wasm": "^0.13.1"
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"
@@ -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
- <div>
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
- </div>
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 { BlockLabel, Empty, IconButton, ShareButton } from "@gradio/atoms";
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
- <div class="icon-buttons">
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
- </div>
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 type { SelectData, I18nFormatter } from "@gradio/utils";
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 type { FileData, Client } from "@gradio/client";
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
- value = detail;
38
- dispatch("upload");
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(img_blob: Blob | any): Promise<void> {
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], `webcam.png`)
64
+ new File([img_blob], `image/${streaming ? "jpeg" : "png"}`)
51
65
  ]);
52
66
 
53
- value = f?.[0] || null;
54
-
55
- await tick();
56
-
57
- dispatch(streaming ? "stream" : "change");
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?: never;
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
- on:capture={(e) => handle_save(e.detail)}
139
- on:stream={(e) => handle_save(e.detail)}
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-->
@@ -1,7 +1,14 @@
1
1
  <script lang="ts">
2
2
  import { createEventDispatcher, onMount } from "svelte";
3
- import { Camera, Circle, Square, DropdownArrow } from "@gradio/icons";
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
- "image/png",
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 value = (
173
+ let val_ = (
140
174
  (await upload(val, root))?.filter(Boolean) as FileData[]
141
175
  )[0];
142
- dispatch("capture", value);
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
- }, 500);
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 recording}
251
- <div class="icon red" title="stop recording">
252
- <Square />
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 red" title="start recording">
256
- <Circle />
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
- .red {
360
- fill: red;
361
- stroke: red;
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 {