@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/image",
3
- "version": "0.15.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.0",
14
- "@gradio/icons": "^0.7.1",
15
- "@gradio/statustracker": "^0.7.5",
16
- "@gradio/client": "^1.5.1",
17
- "@gradio/utils": "^0.6.0",
18
- "@gradio/wasm": "^0.13.0",
19
- "@gradio/upload": "^0.12.3"
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
- ".": "./Index.svelte",
28
- "./shared": "./shared/index.ts",
29
- "./example": "./Example.svelte",
30
- "./base": "./shared/ImagePreview.svelte",
31
- "./package.json": "./package.json"
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 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
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
- 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,9 @@
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);
362
427
  }
363
428
 
364
429
  .flip {