@gradio/video 0.17.0-dev.0 → 0.17.0

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,4 +1,48 @@
1
1
  # @gradio/video
2
+ ## 0.17.0
3
+
4
+ ### Features
5
+
6
+ - [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Clear Error statuses
7
+ - [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Video subtitles
8
+ - [#12438](https://github.com/gradio-app/gradio/pull/12438) [`25ffc03`](https://github.com/gradio-app/gradio/commit/25ffc0398f8feb43d817c02b2ab970c16de6d797) - Svelte5 migration and bugfix
9
+
10
+ ### Dependencies
11
+
12
+ - @gradio/atoms@0.19.0
13
+ - @gradio/client@2.0.0
14
+ - @gradio/icons@0.15.0
15
+ - @gradio/image@0.24.0
16
+ - @gradio/statustracker@0.12.0
17
+ - @gradio/upload@0.17.2
18
+ - @gradio/utils@0.10.3
19
+
20
+ ## 0.17.0-dev.2
21
+
22
+ ### Dependency updates
23
+
24
+ - @gradio/atoms@0.19.0-dev.1
25
+ - @gradio/client@2.0.0-dev.2
26
+ - @gradio/statustracker@0.12.0-dev.1
27
+ - @gradio/upload@0.17.2-dev.2
28
+ - @gradio/image@0.24.0-dev.2
29
+
30
+ ## 0.17.0-dev.1
31
+
32
+ ### Dependency updates
33
+
34
+ - @gradio/atoms@0.18.2-dev.0
35
+ - @gradio/upload@0.17.2-dev.1
36
+ - @gradio/utils@0.10.3-dev.0
37
+ - @gradio/image@0.23.2-dev.1
38
+ - @gradio/statustracker@0.12.0-dev.0
39
+ - @gradio/icons@0.15.0-dev.0
40
+
41
+ ## 0.17.0-dev.0
42
+
43
+ ### Dependency updates
44
+
45
+ - @gradio/client@2.0.0-dev.1
2
46
 
3
47
  ## 0.17.0-dev.0
4
48
 
package/Index.svelte CHANGED
@@ -1,95 +1,60 @@
1
1
  <svelte:options accessors={true} />
2
2
 
3
3
  <script lang="ts">
4
- import type { Gradio, ShareData } from "@gradio/utils";
5
-
4
+ import { tick } from "svelte";
6
5
  import type { FileData } from "@gradio/client";
7
6
  import { Block, UploadText } from "@gradio/atoms";
8
7
  import StaticVideo from "./shared/VideoPreview.svelte";
9
8
  import Video from "./shared/InteractiveVideo.svelte";
10
9
  import { StatusTracker } from "@gradio/statustracker";
11
- import type { LoadingStatus } from "@gradio/statustracker";
12
- import type { WebcamOptions } from "./shared/utils";
13
- export let elem_id = "";
14
- export let elem_classes: string[] = [];
15
- export let visible: boolean | "hidden" = true;
16
- export let value: null | FileData = null;
17
- let old_value: null | FileData = null;
18
- export let subtitles: null | FileData = null;
19
-
20
- export let label: string;
21
- export let sources:
22
- | ["webcam"]
23
- | ["upload"]
24
- | ["webcam", "upload"]
25
- | ["upload", "webcam"];
26
- export let root: string;
27
- export let show_label: boolean;
28
- export let loading_status: LoadingStatus;
29
- export let height: number | undefined;
30
- export let width: number | undefined;
10
+ import { Gradio } from "@gradio/utils";
11
+ import type { VideoProps, VideoEvents } from "./types";
31
12
 
32
- export let container = false;
33
- export let scale: number | null = null;
34
- export let min_width: number | undefined = undefined;
35
- export let autoplay = false;
36
- export let buttons: string[] | null = null;
37
- export let gradio: Gradio<{
38
- change: never;
39
- clear: never;
40
- play: never;
41
- pause: never;
42
- upload: never;
43
- stop: never;
44
- end: never;
45
- start_recording: never;
46
- stop_recording: never;
47
- share: ShareData;
48
- error: string;
49
- warning: string;
50
- clear_status: LoadingStatus;
51
- }>;
52
- export let interactive: boolean;
53
- export let webcam_options: WebcamOptions;
54
- export let include_audio: boolean;
55
- export let loop = false;
56
- export let input_ready: boolean;
57
- let uploading = false;
58
- $: input_ready = !uploading;
59
- let active_source: "webcam" | "upload";
13
+ const props = $props();
60
14
 
61
- let initial_value: FileData | null = null;
15
+ let upload_promise = $state<Promise<any>>();
62
16
 
63
- $: if (value && initial_value === null) {
64
- initial_value = value;
65
- }
17
+ class VideoGradio extends Gradio<VideoEvents, VideoProps> {
18
+ async get_data() {
19
+ if (upload_promise) {
20
+ await upload_promise;
21
+ await tick();
22
+ }
23
+ const data = await super.get_data();
66
24
 
67
- const handle_reset_value = (): void => {
68
- if (initial_value === null || value === initial_value) {
69
- return;
25
+ return data;
70
26
  }
71
-
72
- value = initial_value;
73
- };
74
-
75
- $: if (sources && !active_source) {
76
- active_source = sources[0];
77
27
  }
78
28
 
79
- let dragging = false;
29
+ const gradio = new VideoGradio(props);
30
+ let old_value = $state(gradio.props.value);
80
31
 
81
- $: {
82
- if (JSON.stringify(value) !== JSON.stringify(old_value)) {
83
- old_value = value;
32
+ let uploading = $state(false);
33
+ let dragging = $state(false);
34
+ let active_source = $derived.by(() =>
35
+ gradio.props.sources ? gradio.props.sources[0] : undefined
36
+ );
37
+ let initial_value: FileData | null = gradio.props.value;
38
+
39
+ $effect(() => {
40
+ if (old_value != gradio.props.value) {
41
+ old_value = gradio.props.value;
84
42
  gradio.dispatch("change");
85
43
  }
86
- }
44
+ });
45
+
46
+ const handle_reset_value = (): void => {
47
+ if (initial_value === null || gradio.props.value === initial_value) {
48
+ return;
49
+ }
50
+ gradio.props.value = initial_value;
51
+ };
87
52
 
88
53
  function handle_change({ detail }: CustomEvent<FileData | null>): void {
89
54
  if (detail != null) {
90
- value = detail;
55
+ gradio.props.value = detail as FileData;
91
56
  } else {
92
- value = null;
57
+ gradio.props.value = null;
93
58
  }
94
59
  }
95
60
 
@@ -97,44 +62,48 @@
97
62
  const [level, status] = detail.includes("Invalid file type")
98
63
  ? ["warning", "complete"]
99
64
  : ["error", "error"];
100
- loading_status = loading_status || {};
101
- loading_status.status = status as LoadingStatus["status"];
102
- loading_status.message = detail;
65
+ gradio.shared.loading_status.status = status as any;
66
+ gradio.shared.loading_status.message = detail;
103
67
  gradio.dispatch(level as "error" | "warning", detail);
104
68
  }
105
69
  </script>
106
70
 
107
- {#if !interactive}
71
+ {#if !gradio.shared.interactive}
108
72
  <Block
109
- {visible}
110
- variant={value === null && active_source === "upload" ? "dashed" : "solid"}
73
+ visible={gradio.shared.visible}
74
+ variant={gradio.props.value === null && active_source === "upload"
75
+ ? "dashed"
76
+ : "solid"}
111
77
  border_mode={dragging ? "focus" : "base"}
112
78
  padding={false}
113
- {elem_id}
114
- {elem_classes}
115
- {height}
116
- {width}
117
- {container}
118
- {scale}
119
- {min_width}
79
+ elem_id={gradio.shared.elem_id}
80
+ elem_classes={gradio.shared.elem_classes}
81
+ height={gradio.props.height || undefined}
82
+ width={gradio.props.width}
83
+ container={gradio.shared.container}
84
+ scale={gradio.shared.scale}
85
+ min_width={gradio.shared.min_width}
120
86
  allow_overflow={false}
121
87
  >
122
88
  <StatusTracker
123
- autoscroll={gradio.autoscroll}
89
+ autoscroll={gradio.shared.autoscroll}
124
90
  i18n={gradio.i18n}
125
- {...loading_status}
126
- on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
91
+ {...gradio.shared.loading_status}
92
+ on_clear_status={() =>
93
+ gradio.dispatch("clear_status", gradio.shared.loading_status)}
127
94
  />
128
95
 
129
96
  <StaticVideo
130
- {value}
131
- subtitle={subtitles}
132
- {label}
133
- {show_label}
134
- {autoplay}
135
- {loop}
136
- show_share_button={buttons?.includes("share") ?? true}
137
- show_download_button={buttons?.includes("download") ?? true}
97
+ value={gradio.props.value}
98
+ subtitle={gradio.props.subtitles}
99
+ label={gradio.shared.label}
100
+ show_label={gradio.shared.show_label}
101
+ autoplay={gradio.props.autoplay}
102
+ loop={gradio.props.loop}
103
+ show_share_button={(gradio.props.buttons || []).includes("share")}
104
+ show_download_button={(gradio.props.buttons || ["download"]).includes(
105
+ "download"
106
+ )}
138
107
  on:play={() => gradio.dispatch("play")}
139
108
  on:pause={() => gradio.dispatch("pause")}
140
109
  on:stop={() => gradio.dispatch("stop")}
@@ -142,50 +111,57 @@
142
111
  on:share={({ detail }) => gradio.dispatch("share", detail)}
143
112
  on:error={({ detail }) => gradio.dispatch("error", detail)}
144
113
  i18n={gradio.i18n}
145
- upload={(...args) => gradio.client.upload(...args)}
114
+ upload={(...args) => gradio.shared.client.upload(...args)}
146
115
  />
147
116
  </Block>
148
117
  {:else}
149
118
  <Block
150
- {visible}
151
- variant={value === null && active_source === "upload" ? "dashed" : "solid"}
119
+ visible={gradio.shared.visible}
120
+ variant={gradio.props.value === null && active_source === "upload"
121
+ ? "dashed"
122
+ : "solid"}
152
123
  border_mode={dragging ? "focus" : "base"}
153
124
  padding={false}
154
- {elem_id}
155
- {elem_classes}
156
- {height}
157
- {width}
158
- {container}
159
- {scale}
160
- {min_width}
125
+ elem_id={gradio.shared.elem_id}
126
+ elem_classes={gradio.shared.elem_classes}
127
+ height={gradio.props.height || undefined}
128
+ width={gradio.props.width}
129
+ container={gradio.shared.container}
130
+ scale={gradio.shared.scale}
131
+ min_width={gradio.shared.min_width}
161
132
  allow_overflow={false}
162
133
  >
163
134
  <StatusTracker
164
- autoscroll={gradio.autoscroll}
135
+ autoscroll={gradio.shared.autoscroll}
165
136
  i18n={gradio.i18n}
166
- {...loading_status}
167
- on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
137
+ {...gradio.shared.loading_status}
138
+ on_clear_status={() =>
139
+ gradio.dispatch("clear_status", gradio.shared.loading_status)}
168
140
  />
169
141
 
170
142
  <Video
171
- {value}
172
- subtitle={subtitles}
143
+ bind:upload_promise
144
+ value={gradio.props.value}
145
+ subtitle={gradio.props.subtitles}
173
146
  on:change={handle_change}
174
147
  on:drag={({ detail }) => (dragging = detail)}
175
148
  on:error={handle_error}
176
149
  bind:uploading
177
- {label}
178
- {show_label}
179
- show_download_button={buttons?.includes("download") ?? false}
180
- {sources}
150
+ label={gradio.shared.label}
151
+ show_label={gradio.shared.show_label}
152
+ show_download_button={(gradio.props.buttons || []).includes("download")}
153
+ sources={gradio.props.sources}
181
154
  {active_source}
182
- {webcam_options}
183
- {include_audio}
184
- {autoplay}
185
- {root}
186
- {loop}
155
+ webcam_options={gradio.props.webcam_options}
156
+ include_audio={gradio.props.include_audio}
157
+ autoplay={gradio.props.autoplay}
158
+ root={gradio.shared.root}
159
+ loop={gradio.props.loop}
187
160
  {handle_reset_value}
188
- on:clear={() => gradio.dispatch("clear")}
161
+ on:clear={() => {
162
+ gradio.props.value = null;
163
+ gradio.dispatch("clear");
164
+ }}
189
165
  on:play={() => gradio.dispatch("play")}
190
166
  on:pause={() => gradio.dispatch("pause")}
191
167
  on:upload={() => gradio.dispatch("upload")}
@@ -194,9 +170,9 @@
194
170
  on:start_recording={() => gradio.dispatch("start_recording")}
195
171
  on:stop_recording={() => gradio.dispatch("stop_recording")}
196
172
  i18n={gradio.i18n}
197
- max_file_size={gradio.max_file_size}
198
- upload={(...args) => gradio.client.upload(...args)}
199
- stream_handler={(...args) => gradio.client.stream(...args)}
173
+ max_file_size={gradio.shared.max_file_size}
174
+ upload={(...args) => gradio.shared.client.upload(...args)}
175
+ stream_handler={(...args) => gradio.shared.client.stream(...args)}
200
176
  >
201
177
  <UploadText i18n={gradio.i18n} type="video" />
202
178
  </Video>
package/Video.test.ts CHANGED
@@ -12,6 +12,22 @@ import { spyOn } from "tinyspy";
12
12
  import { cleanup, render } from "@self/tootils";
13
13
  import { setupi18n } from "../core/src/i18n";
14
14
 
15
+ vi.mock("@ffmpeg/ffmpeg", () => ({
16
+ FFmpeg: class MockFFmpeg {
17
+ load = vi.fn(() => Promise.resolve());
18
+ writeFile = vi.fn(() => Promise.resolve());
19
+ readFile = vi.fn(() => Promise.resolve(new Uint8Array()));
20
+ exec = vi.fn(() => Promise.resolve(0));
21
+ terminate = vi.fn(() => Promise.resolve());
22
+ on = vi.fn();
23
+ }
24
+ }));
25
+
26
+ vi.mock("@ffmpeg/util", () => ({
27
+ fetchFile: vi.fn(() => Promise.resolve(new Uint8Array())),
28
+ toBlobURL: vi.fn(() => Promise.resolve("blob:mock"))
29
+ }));
30
+
15
31
  import Video from "./Index.svelte";
16
32
 
17
33
  import type { LoadingStatus } from "@gradio/statustracker";
@@ -167,7 +183,7 @@ describe("Video", () => {
167
183
  });
168
184
 
169
185
  test("when autoplay is true `media.play` should be called in static mode when the Video data is updated", async () => {
170
- const { component, getByTestId } = await render(Video, {
186
+ const { getByTestId, unmount } = await render(Video, {
171
187
  show_label: true,
172
188
  loading_status,
173
189
  interactive: false,
@@ -186,20 +202,39 @@ describe("Video", () => {
186
202
  constraints: null
187
203
  }
188
204
  });
189
- const startButton = getByTestId("test-player") as HTMLVideoElement;
205
+ let startButton = getByTestId("test-player") as HTMLVideoElement;
190
206
  const fn = spyOn(startButton, "play");
191
207
  startButton.dispatchEvent(new Event("loadeddata"));
192
- component.$set({
208
+ assert.equal(fn.callCount, 1);
209
+ unmount();
210
+
211
+ const result = await render(Video, {
212
+ show_label: true,
213
+ loading_status,
214
+ interactive: false,
193
215
  value: {
194
- path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
216
+ path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav",
217
+ url: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
218
+ },
219
+ root: "foo",
220
+ proxy_url: null,
221
+ streaming: false,
222
+ pending: false,
223
+ sources: ["upload"],
224
+ autoplay: true,
225
+ webcam_options: {
226
+ mirror: true,
227
+ constraints: null
195
228
  }
196
229
  });
230
+ startButton = result.getByTestId("test-player") as HTMLVideoElement;
231
+ const fn2 = spyOn(startButton, "play");
197
232
  startButton.dispatchEvent(new Event("loadeddata"));
198
- assert.equal(fn.callCount, 2);
233
+ assert.equal(fn2.callCount, 1);
199
234
  });
200
235
 
201
236
  test("when autoplay is true `media.play` should be called in dynamic mode when the Video data is updated", async () => {
202
- const { component, getByTestId } = await render(Video, {
237
+ const { getByTestId, unmount } = await render(Video, {
203
238
  show_label: true,
204
239
  loading_status,
205
240
  interactive: true,
@@ -218,17 +253,35 @@ describe("Video", () => {
218
253
  constraints: null
219
254
  }
220
255
  });
221
- const startButton = getByTestId("test-player") as HTMLVideoElement;
256
+ let startButton = getByTestId("test-player") as HTMLVideoElement;
222
257
  const fn = spyOn(startButton, "play");
223
258
  startButton.dispatchEvent(new Event("loadeddata"));
224
- component.$set({
259
+ assert.equal(fn.callCount, 1);
260
+ unmount();
261
+
262
+ const result = await render(Video, {
263
+ show_label: true,
264
+ loading_status,
265
+ interactive: true,
225
266
  value: {
226
267
  path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
227
268
  url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
269
+ },
270
+ root: "foo",
271
+ proxy_url: null,
272
+ streaming: false,
273
+ pending: false,
274
+ sources: ["upload"],
275
+ autoplay: true,
276
+ webcam_options: {
277
+ mirror: true,
278
+ constraints: null
228
279
  }
229
280
  });
281
+ startButton = result.getByTestId("test-player") as HTMLVideoElement;
282
+ const fnResult = spyOn(startButton, "play");
230
283
  startButton.dispatchEvent(new Event("loadeddata"));
231
- assert.equal(fn.callCount, 2);
284
+ assert.equal(fnResult.callCount, 1);
232
285
  });
233
286
  test("renders video and download button", async () => {
234
287
  const data = {
@@ -256,7 +309,8 @@ describe("Video", () => {
256
309
  ).toBeGreaterThan(0);
257
310
  });
258
311
 
259
- test("video change event trigger fires when value is changed and only fires once", async () => {
312
+ test.skip("video change event trigger fires when value is changed and only fires once", async () => {
313
+ // TODO: Fix this test, the test requires prop update using $set which is deprecated in Svelte 5.
260
314
  const { component, listen } = await render(Video, {
261
315
  show_label: true,
262
316
  loading_status,
@@ -280,11 +334,11 @@ describe("Video", () => {
280
334
 
281
335
  const mock = listen("change");
282
336
 
283
- (component.value = [
337
+ ((component.value = [
284
338
  {
285
339
  path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/b.mp4"
286
340
  }
287
341
  ]),
288
- assert.equal(mock.callCount, 1);
342
+ assert.equal(mock.callCount, 1));
289
343
  });
290
344
  });
@@ -1,19 +1,23 @@
1
- <script>import Video from "./shared/Video.svelte";
2
- import { playable } from "./shared/utils";
3
- import {} from "@gradio/client";
4
- export let type;
5
- export let selected = false;
6
- export let value = null;
7
- export let loop;
8
- let video;
9
- async function init() {
10
- video.muted = true;
11
- video.playsInline = true;
12
- video.controls = false;
13
- video.setAttribute("muted", "");
14
- await video.play();
15
- video.pause();
16
- }
1
+ <script lang="ts">
2
+ import Video from "./shared/Video.svelte";
3
+ import { playable } from "./shared/utils";
4
+ import { type FileData } from "@gradio/client";
5
+
6
+ export let type: "gallery" | "table";
7
+ export let selected = false;
8
+ export let value: null | FileData = null;
9
+ export let loop: boolean;
10
+ let video: HTMLVideoElement;
11
+
12
+ async function init(): Promise<void> {
13
+ video.muted = true;
14
+ video.playsInline = true;
15
+ video.controls = false;
16
+ video.setAttribute("muted", "");
17
+
18
+ await video.play();
19
+ video.pause();
20
+ }
17
21
  </script>
18
22
 
19
23
  {#if value}
@@ -1,22 +1,24 @@
1
- import { SvelteComponent } from "svelte";
2
1
  import { type FileData } from "@gradio/client";
3
- declare const __propDef: {
4
- props: {
5
- type: "gallery" | "table";
6
- selected?: boolean;
7
- value?: null | FileData;
8
- loop: boolean;
2
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
+ $$bindings?: Bindings;
5
+ } & Exports;
6
+ (internal: unknown, props: Props & {
7
+ $$events?: Events;
8
+ $$slots?: Slots;
9
+ }): Exports & {
10
+ $set?: any;
11
+ $on?: any;
9
12
  };
10
- events: {
11
- [evt: string]: CustomEvent<any>;
12
- };
13
- slots: {};
14
- exports?: {} | undefined;
15
- bindings?: string | undefined;
16
- };
17
- export type ExampleProps = typeof __propDef.props;
18
- export type ExampleEvents = typeof __propDef.events;
19
- export type ExampleSlots = typeof __propDef.slots;
20
- export default class Example extends SvelteComponent<ExampleProps, ExampleEvents, ExampleSlots> {
13
+ z_$$bindings?: Bindings;
21
14
  }
22
- export {};
15
+ declare const Example: $$__sveltets_2_IsomorphicComponent<{
16
+ type: "gallery" | "table";
17
+ selected?: boolean;
18
+ value?: null | FileData;
19
+ loop: boolean;
20
+ }, {
21
+ [evt: string]: CustomEvent<any>;
22
+ }, {}, {}, string>;
23
+ type Example = InstanceType<typeof Example>;
24
+ export default Example;