@gradio/video 0.20.5 → 0.20.6
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 +14 -0
- package/Index.svelte +7 -1
- package/Video.test.ts +450 -254
- package/dist/Index.svelte +7 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @gradio/video
|
|
2
2
|
|
|
3
|
+
## 0.20.6
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- [#13167](https://github.com/gradio-app/gradio/pull/13167) [`a4e1c92`](https://github.com/gradio-app/gradio/commit/a4e1c92c11e05bee332ff69e19b533fbd9abc840) - Audio and Video unit tests. Thanks @freddyaboulton!
|
|
8
|
+
|
|
9
|
+
### Dependency updates
|
|
10
|
+
|
|
11
|
+
- @gradio/utils@0.12.2
|
|
12
|
+
- @gradio/atoms@0.23.0
|
|
13
|
+
- @gradio/statustracker@0.13.1
|
|
14
|
+
- @gradio/upload@0.17.8
|
|
15
|
+
- @gradio/image@0.26.1
|
|
16
|
+
|
|
3
17
|
## 0.20.5
|
|
4
18
|
|
|
5
19
|
### Dependency updates
|
package/Index.svelte
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const gradio = new VideoGradio(props);
|
|
30
|
-
let old_value =
|
|
30
|
+
let old_value = gradio.props.value;
|
|
31
|
+
let mounted = false;
|
|
31
32
|
|
|
32
33
|
let uploading = $state(false);
|
|
33
34
|
let dragging = $state(false);
|
|
@@ -37,6 +38,11 @@
|
|
|
37
38
|
let initial_value: FileData | null = gradio.props.value;
|
|
38
39
|
|
|
39
40
|
$effect(() => {
|
|
41
|
+
if (!mounted) {
|
|
42
|
+
old_value = gradio.props.value;
|
|
43
|
+
mounted = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
40
46
|
if (old_value != gradio.props.value) {
|
|
41
47
|
old_value = gradio.props.value;
|
|
42
48
|
gradio.dispatch("change");
|
package/Video.test.ts
CHANGED
|
@@ -5,10 +5,19 @@ import {
|
|
|
5
5
|
afterEach,
|
|
6
6
|
vi,
|
|
7
7
|
beforeAll,
|
|
8
|
-
beforeEach,
|
|
9
8
|
expect
|
|
10
9
|
} from "vitest";
|
|
11
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
cleanup,
|
|
12
|
+
render,
|
|
13
|
+
fireEvent,
|
|
14
|
+
waitFor,
|
|
15
|
+
upload_file,
|
|
16
|
+
drop_file,
|
|
17
|
+
mock_client,
|
|
18
|
+
TEST_MP4
|
|
19
|
+
} from "@self/tootils/render";
|
|
20
|
+
import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
|
|
12
21
|
import { setupi18n } from "../core/src/i18n";
|
|
13
22
|
|
|
14
23
|
vi.mock("@ffmpeg/ffmpeg", () => ({
|
|
@@ -28,314 +37,501 @@ vi.mock("@ffmpeg/util", () => ({
|
|
|
28
37
|
}));
|
|
29
38
|
|
|
30
39
|
import Video from "./Index.svelte";
|
|
31
|
-
|
|
32
40
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
33
41
|
|
|
34
|
-
const loading_status = {
|
|
42
|
+
const loading_status: LoadingStatus = {
|
|
35
43
|
eta: 0,
|
|
36
44
|
queue_position: 1,
|
|
37
45
|
queue_size: 1,
|
|
38
|
-
status: "complete"
|
|
46
|
+
status: "complete",
|
|
39
47
|
scroll_to_output: false,
|
|
40
48
|
visible: true,
|
|
41
49
|
fn_index: 0,
|
|
42
|
-
show_progress: "full"
|
|
50
|
+
show_progress: "full",
|
|
51
|
+
type: "input" as const,
|
|
52
|
+
stream_state: "closed" as const
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const fake_value = {
|
|
56
|
+
path: "video_sample.mp4",
|
|
57
|
+
url: "https://example.com/video_sample.mp4",
|
|
58
|
+
orig_name: "video_sample.mp4",
|
|
59
|
+
size: 261179,
|
|
60
|
+
mime_type: "video/mp4",
|
|
61
|
+
is_stream: false
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const default_props = {
|
|
65
|
+
loading_status,
|
|
66
|
+
label: "Video",
|
|
67
|
+
show_label: true,
|
|
68
|
+
value: null as any,
|
|
69
|
+
sources: ["upload", "webcam"] as ("upload" | "webcam")[],
|
|
70
|
+
interactive: true,
|
|
71
|
+
streaming: false,
|
|
72
|
+
pending: false,
|
|
73
|
+
autoplay: false,
|
|
74
|
+
loop: false,
|
|
75
|
+
webcam_options: { mirror: false, constraints: {} },
|
|
76
|
+
buttons: [] as (string | { value: string; id: number; icon: null })[]
|
|
43
77
|
};
|
|
44
78
|
|
|
79
|
+
beforeAll(() => {
|
|
80
|
+
window.HTMLMediaElement.prototype.play = vi.fn();
|
|
81
|
+
window.HTMLMediaElement.prototype.pause = vi.fn();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
run_shared_prop_tests({
|
|
85
|
+
component: Video,
|
|
86
|
+
name: "Video",
|
|
87
|
+
base_props: {
|
|
88
|
+
...default_props
|
|
89
|
+
},
|
|
90
|
+
has_label: false,
|
|
91
|
+
has_validation_error: false
|
|
92
|
+
});
|
|
93
|
+
|
|
45
94
|
describe("Video", () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
95
|
+
setupi18n();
|
|
96
|
+
afterEach(() => cleanup());
|
|
97
|
+
|
|
98
|
+
test("renders upload area when value is null", async () => {
|
|
99
|
+
const { getByLabelText } = await render(Video, {
|
|
100
|
+
...default_props,
|
|
101
|
+
sources: ["upload"],
|
|
102
|
+
value: null
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(getByLabelText("video.drop_to_upload")).toBeVisible();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("renders video player when value is set", async () => {
|
|
109
|
+
const { getByTestId } = await render(Video, {
|
|
110
|
+
...default_props,
|
|
111
|
+
label: "Test Video",
|
|
112
|
+
value: fake_value
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const vid = getByTestId("Test Video-player") as HTMLVideoElement;
|
|
116
|
+
expect(vid).toBeTruthy();
|
|
117
|
+
expect(vid.src).toContain("video_sample.mp4");
|
|
49
118
|
});
|
|
50
|
-
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("Props: sources", () => {
|
|
122
|
+
setupi18n();
|
|
51
123
|
afterEach(() => cleanup());
|
|
52
124
|
|
|
53
|
-
test("renders
|
|
54
|
-
const { getByTestId
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
125
|
+
test("multiple sources renders source selection buttons", async () => {
|
|
126
|
+
const { getByTestId } = await render(Video, {
|
|
127
|
+
...default_props,
|
|
128
|
+
sources: ["upload", "webcam"]
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(getByTestId("source-select")).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("single upload source does not render source selection", async () => {
|
|
135
|
+
const { queryByTestId } = await render(Video, {
|
|
136
|
+
...default_props,
|
|
137
|
+
sources: ["upload"]
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(queryByTestId("source-select")).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("upload and webcam sources render corresponding buttons", async () => {
|
|
144
|
+
const { getByLabelText } = await render(Video, {
|
|
145
|
+
...default_props,
|
|
146
|
+
sources: ["upload", "webcam"]
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(getByLabelText("Upload file")).toBeTruthy();
|
|
150
|
+
expect(getByLabelText("Capture from camera")).toBeTruthy();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("Props: interactive", () => {
|
|
155
|
+
setupi18n();
|
|
156
|
+
afterEach(() => cleanup());
|
|
157
|
+
|
|
158
|
+
test("interactive=true shows upload area when value is null", async () => {
|
|
159
|
+
const { getByLabelText } = await render(Video, {
|
|
160
|
+
...default_props,
|
|
161
|
+
interactive: true,
|
|
67
162
|
sources: ["upload"],
|
|
163
|
+
value: null
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(getByLabelText("video.drop_to_upload")).toBeTruthy();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("interactive=false renders the video without upload controls", async () => {
|
|
170
|
+
const { queryByLabelText } = await render(Video, {
|
|
171
|
+
...default_props,
|
|
172
|
+
interactive: false,
|
|
173
|
+
value: fake_value
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(queryByLabelText("video.drop_to_upload")).toBeNull();
|
|
177
|
+
expect(queryByLabelText("Upload file")).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("interactive=false with null value does not show upload area", async () => {
|
|
181
|
+
const { queryByLabelText } = await render(Video, {
|
|
182
|
+
...default_props,
|
|
183
|
+
interactive: false,
|
|
184
|
+
value: null
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(queryByLabelText("video.drop_to_upload")).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("Events: change", () => {
|
|
192
|
+
setupi18n();
|
|
193
|
+
afterEach(() => cleanup());
|
|
194
|
+
|
|
195
|
+
test("setting value triggers change event", async () => {
|
|
196
|
+
const { listen, set_data } = await render(Video, {
|
|
197
|
+
...default_props,
|
|
198
|
+
value: null
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const change = listen("change");
|
|
202
|
+
|
|
203
|
+
await set_data({ value: fake_value });
|
|
204
|
+
|
|
205
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("change event is not triggered on mount with a default value", async () => {
|
|
209
|
+
const { listen } = await render(Video, {
|
|
210
|
+
...default_props,
|
|
211
|
+
value: fake_value
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const change = listen("change", { retrospective: true });
|
|
215
|
+
|
|
216
|
+
expect(change).not.toHaveBeenCalled();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("changing value multiple times triggers change each time", async () => {
|
|
220
|
+
const { listen, set_data } = await render(Video, {
|
|
221
|
+
...default_props,
|
|
222
|
+
value: null
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const change = listen("change");
|
|
226
|
+
|
|
227
|
+
const value_a = { ...fake_value, url: "https://example.com/a.mp4" };
|
|
228
|
+
const value_b = { ...fake_value, url: "https://example.com/b.mp4" };
|
|
229
|
+
|
|
230
|
+
await set_data({ value: value_a });
|
|
231
|
+
await set_data({ value: value_b });
|
|
232
|
+
|
|
233
|
+
expect(change).toHaveBeenCalledTimes(2);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("setting value to null after a value triggers change", async () => {
|
|
237
|
+
const { listen, set_data } = await render(Video, {
|
|
238
|
+
...default_props,
|
|
239
|
+
value: fake_value
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const change = listen("change");
|
|
243
|
+
|
|
244
|
+
await set_data({ value: null });
|
|
245
|
+
|
|
246
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("Props: buttons (static mode)", () => {
|
|
251
|
+
setupi18n();
|
|
252
|
+
afterEach(() => cleanup());
|
|
253
|
+
|
|
254
|
+
test("buttons with download shows download button", async () => {
|
|
255
|
+
const { getAllByTestId } = await render(Video, {
|
|
256
|
+
...default_props,
|
|
257
|
+
interactive: false,
|
|
258
|
+
value: fake_value,
|
|
259
|
+
buttons: ["download"]
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const downloadDiv = getAllByTestId("download-div")[0];
|
|
263
|
+
expect(downloadDiv).toBeTruthy();
|
|
264
|
+
const link = downloadDiv.querySelector("a");
|
|
265
|
+
expect(link?.getAttribute("href")).toContain("video_sample.mp4");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("custom button renders and dispatches custom_button_click", async () => {
|
|
269
|
+
const { listen, getByLabelText } = await render(Video, {
|
|
270
|
+
...default_props,
|
|
271
|
+
interactive: false,
|
|
272
|
+
value: fake_value,
|
|
273
|
+
buttons: [{ value: "Analyze", id: 5, icon: null }]
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const custom = listen("custom_button_click");
|
|
277
|
+
const btn = getByLabelText("Analyze");
|
|
278
|
+
|
|
279
|
+
await fireEvent.click(btn);
|
|
280
|
+
|
|
281
|
+
expect(custom).toHaveBeenCalledTimes(1);
|
|
282
|
+
expect(custom).toHaveBeenCalledWith({ id: 5 });
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("Props: buttons (interactive mode)", () => {
|
|
287
|
+
setupi18n();
|
|
288
|
+
afterEach(() => cleanup());
|
|
289
|
+
|
|
290
|
+
test("clear button appears when video has a value", async () => {
|
|
291
|
+
const { getByLabelText } = await render(Video, {
|
|
292
|
+
...default_props,
|
|
68
293
|
interactive: true,
|
|
69
|
-
|
|
70
|
-
mirror: true,
|
|
71
|
-
constraints: null
|
|
72
|
-
}
|
|
294
|
+
value: fake_value
|
|
73
295
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
78
|
-
);
|
|
79
|
-
assert.equal(queryAllByText("Test Label").length, 1);
|
|
296
|
+
|
|
297
|
+
const clearBtn = getByLabelText("common.clear");
|
|
298
|
+
expect(clearBtn).toBeTruthy();
|
|
80
299
|
});
|
|
81
300
|
|
|
82
|
-
test("
|
|
83
|
-
const {
|
|
84
|
-
|
|
85
|
-
loading_status,
|
|
86
|
-
value: {
|
|
87
|
-
path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
|
|
88
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
89
|
-
},
|
|
90
|
-
label: "Video Component",
|
|
91
|
-
root: "foo",
|
|
92
|
-
proxy_url: null,
|
|
93
|
-
streaming: false,
|
|
94
|
-
pending: false,
|
|
95
|
-
name: "bar",
|
|
96
|
-
sources: ["upload"],
|
|
301
|
+
test("clicking clear button removes the video and dispatches clear and input", async () => {
|
|
302
|
+
const { getByLabelText, listen } = await render(Video, {
|
|
303
|
+
...default_props,
|
|
97
304
|
interactive: true,
|
|
98
|
-
|
|
99
|
-
mirror: true,
|
|
100
|
-
constraints: null
|
|
101
|
-
}
|
|
305
|
+
value: fake_value
|
|
102
306
|
});
|
|
103
|
-
|
|
307
|
+
|
|
308
|
+
const clear = listen("clear");
|
|
309
|
+
const input = listen("input");
|
|
310
|
+
const clearBtn = getByLabelText("common.clear");
|
|
311
|
+
|
|
312
|
+
await fireEvent.click(clearBtn);
|
|
313
|
+
|
|
314
|
+
expect(clear).toHaveBeenCalledTimes(1);
|
|
315
|
+
expect(input).toHaveBeenCalledTimes(1);
|
|
104
316
|
});
|
|
317
|
+
});
|
|
105
318
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
root: "foo",
|
|
115
|
-
proxy_url: null,
|
|
116
|
-
streaming: false,
|
|
117
|
-
pending: false,
|
|
118
|
-
name: "bar",
|
|
119
|
-
sources: ["upload"],
|
|
120
|
-
mode: "static",
|
|
121
|
-
webcam_options: {
|
|
122
|
-
mirror: true,
|
|
123
|
-
constraints: null
|
|
124
|
-
}
|
|
319
|
+
describe("get_data", () => {
|
|
320
|
+
setupi18n();
|
|
321
|
+
afterEach(() => cleanup());
|
|
322
|
+
|
|
323
|
+
test("get_data returns the current value", async () => {
|
|
324
|
+
const { get_data, set_data } = await render(Video, {
|
|
325
|
+
...default_props,
|
|
326
|
+
value: null
|
|
125
327
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
328
|
+
|
|
329
|
+
const initial = await get_data();
|
|
330
|
+
expect(initial.value).toBeNull();
|
|
331
|
+
|
|
332
|
+
await set_data({ value: fake_value });
|
|
333
|
+
|
|
334
|
+
const updated = await get_data();
|
|
335
|
+
expect(updated.value).toEqual(fake_value);
|
|
131
336
|
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe("Autoplay", () => {
|
|
340
|
+
setupi18n();
|
|
341
|
+
afterEach(() => cleanup());
|
|
132
342
|
|
|
133
|
-
test("
|
|
343
|
+
test("autoplay calls media.play in static mode", async () => {
|
|
134
344
|
const { getByTestId } = await render(Video, {
|
|
135
|
-
|
|
136
|
-
loading_status,
|
|
345
|
+
...default_props,
|
|
137
346
|
interactive: false,
|
|
138
|
-
value:
|
|
139
|
-
|
|
140
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
141
|
-
},
|
|
142
|
-
root: "foo",
|
|
143
|
-
proxy_url: null,
|
|
144
|
-
streaming: false,
|
|
145
|
-
pending: false,
|
|
146
|
-
sources: ["upload"],
|
|
147
|
-
autoplay: true,
|
|
148
|
-
webcam_options: {
|
|
149
|
-
mirror: true,
|
|
150
|
-
constraints: null
|
|
151
|
-
}
|
|
347
|
+
value: fake_value,
|
|
348
|
+
autoplay: true
|
|
152
349
|
});
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
350
|
+
|
|
351
|
+
const video = getByTestId("Video-player") as HTMLVideoElement;
|
|
352
|
+
const fn = vi.spyOn(video, "play").mockResolvedValue(undefined);
|
|
353
|
+
video.dispatchEvent(new Event("loadeddata"));
|
|
354
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
157
355
|
});
|
|
158
356
|
|
|
159
|
-
test("
|
|
357
|
+
test("autoplay calls media.play in interactive mode", async () => {
|
|
160
358
|
const { getByTestId } = await render(Video, {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
value:
|
|
164
|
-
|
|
165
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
166
|
-
},
|
|
167
|
-
root: "foo",
|
|
168
|
-
proxy_url: null,
|
|
169
|
-
streaming: false,
|
|
170
|
-
pending: false,
|
|
171
|
-
sources: ["upload"],
|
|
172
|
-
autoplay: true,
|
|
173
|
-
webcam_options: {
|
|
174
|
-
mirror: true,
|
|
175
|
-
constraints: null
|
|
176
|
-
}
|
|
359
|
+
...default_props,
|
|
360
|
+
interactive: true,
|
|
361
|
+
value: fake_value,
|
|
362
|
+
autoplay: true
|
|
177
363
|
});
|
|
178
364
|
|
|
179
|
-
const
|
|
180
|
-
const fn = vi.spyOn(
|
|
181
|
-
|
|
365
|
+
const video = getByTestId("Video-player") as HTMLVideoElement;
|
|
366
|
+
const fn = vi.spyOn(video, "play").mockResolvedValue(undefined);
|
|
367
|
+
video.dispatchEvent(new Event("loadeddata"));
|
|
368
|
+
expect(fn).toHaveBeenCalled();
|
|
182
369
|
});
|
|
370
|
+
});
|
|
183
371
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
root: "foo",
|
|
194
|
-
proxy_url: null,
|
|
195
|
-
streaming: false,
|
|
196
|
-
pending: false,
|
|
197
|
-
sources: ["upload"],
|
|
198
|
-
autoplay: true,
|
|
199
|
-
webcam_options: {
|
|
200
|
-
mirror: true,
|
|
201
|
-
constraints: null
|
|
202
|
-
}
|
|
372
|
+
describe("Player controls", () => {
|
|
373
|
+
setupi18n();
|
|
374
|
+
afterEach(() => cleanup());
|
|
375
|
+
|
|
376
|
+
test("play/pause button is rendered when video has a value", async () => {
|
|
377
|
+
const { getByLabelText } = await render(Video, {
|
|
378
|
+
...default_props,
|
|
379
|
+
interactive: true,
|
|
380
|
+
value: fake_value
|
|
203
381
|
});
|
|
204
|
-
let video_player = getByTestId("test-player") as HTMLVideoElement;
|
|
205
|
-
const fn = vi.spyOn(video_player, "play").mockResolvedValue(undefined);
|
|
206
|
-
assert.equal(fn.mock.calls.length, 1);
|
|
207
|
-
unmount();
|
|
208
382
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
root: "foo",
|
|
218
|
-
proxy_url: null,
|
|
219
|
-
streaming: false,
|
|
220
|
-
pending: false,
|
|
221
|
-
sources: ["upload"],
|
|
222
|
-
autoplay: true,
|
|
223
|
-
webcam_options: {
|
|
224
|
-
mirror: true,
|
|
225
|
-
constraints: null
|
|
226
|
-
}
|
|
383
|
+
expect(getByLabelText("play-pause-replay-button")).toBeTruthy();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("volume button is rendered", async () => {
|
|
387
|
+
const { getByLabelText } = await render(Video, {
|
|
388
|
+
...default_props,
|
|
389
|
+
interactive: true,
|
|
390
|
+
value: fake_value
|
|
227
391
|
});
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
assert.equal(fn2.mock.calls.length, 1);
|
|
392
|
+
|
|
393
|
+
expect(getByLabelText("Adjust volume")).toBeTruthy();
|
|
231
394
|
});
|
|
232
395
|
|
|
233
|
-
test("
|
|
234
|
-
const {
|
|
235
|
-
|
|
236
|
-
loading_status,
|
|
396
|
+
test("fullscreen button is rendered", async () => {
|
|
397
|
+
const { getByLabelText } = await render(Video, {
|
|
398
|
+
...default_props,
|
|
237
399
|
interactive: true,
|
|
238
|
-
value:
|
|
239
|
-
path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
|
|
240
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
241
|
-
},
|
|
242
|
-
root: "foo",
|
|
243
|
-
proxy_url: null,
|
|
244
|
-
streaming: false,
|
|
245
|
-
pending: false,
|
|
246
|
-
sources: ["upload"],
|
|
247
|
-
autoplay: true,
|
|
248
|
-
webcam_options: {
|
|
249
|
-
mirror: true,
|
|
250
|
-
constraints: null
|
|
251
|
-
}
|
|
400
|
+
value: fake_value
|
|
252
401
|
});
|
|
253
|
-
let video_player = getByTestId("test-player") as HTMLVideoElement;
|
|
254
|
-
const fn = vi.spyOn(video_player, "play").mockResolvedValue(undefined);
|
|
255
|
-
assert.equal(fn.mock.calls.length, 1);
|
|
256
|
-
unmount();
|
|
257
402
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
403
|
+
expect(getByLabelText("full-screen")).toBeTruthy();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("trim button is rendered in interactive mode", async () => {
|
|
407
|
+
const { getByLabelText } = await render(Video, {
|
|
408
|
+
...default_props,
|
|
261
409
|
interactive: true,
|
|
262
|
-
value:
|
|
263
|
-
path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
|
|
264
|
-
url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
|
|
265
|
-
},
|
|
266
|
-
root: "foo",
|
|
267
|
-
proxy_url: null,
|
|
268
|
-
streaming: false,
|
|
269
|
-
pending: false,
|
|
270
|
-
sources: ["upload"],
|
|
271
|
-
autoplay: true,
|
|
272
|
-
webcam_options: {
|
|
273
|
-
mirror: true,
|
|
274
|
-
constraints: null
|
|
275
|
-
}
|
|
410
|
+
value: fake_value
|
|
276
411
|
});
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.spyOn(video_player, "play")
|
|
280
|
-
.mockResolvedValue(undefined);
|
|
281
|
-
assert.equal(fnResult.mock.calls.length, 1);
|
|
412
|
+
|
|
413
|
+
expect(getByLabelText("Trim video to selection")).toBeTruthy();
|
|
282
414
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
};
|
|
288
|
-
const results = await render(Video, {
|
|
415
|
+
|
|
416
|
+
test("trim button is not rendered in static mode", async () => {
|
|
417
|
+
const { queryByLabelText } = await render(Video, {
|
|
418
|
+
...default_props,
|
|
289
419
|
interactive: false,
|
|
290
|
-
|
|
291
|
-
show_label: true,
|
|
292
|
-
value: data,
|
|
293
|
-
root: "https://localhost:8000",
|
|
294
|
-
webcam_options: {
|
|
295
|
-
mirror: true,
|
|
296
|
-
constraints: null
|
|
297
|
-
}
|
|
420
|
+
value: fake_value
|
|
298
421
|
});
|
|
299
422
|
|
|
300
|
-
|
|
301
|
-
expect(
|
|
302
|
-
downloadButton.getElementsByTagName("a")[0].getAttribute("href")
|
|
303
|
-
).toBe(data.path);
|
|
304
|
-
expect(
|
|
305
|
-
downloadButton.getElementsByTagName("button").length
|
|
306
|
-
).toBeGreaterThan(0);
|
|
423
|
+
expect(queryByLabelText("Trim video to selection")).toBeNull();
|
|
307
424
|
});
|
|
308
425
|
|
|
309
|
-
test
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
show_label: true,
|
|
313
|
-
loading_status,
|
|
426
|
+
test("clicking trim button enters edit mode with Trim and Cancel buttons", async () => {
|
|
427
|
+
const { getByLabelText, getByText } = await render(Video, {
|
|
428
|
+
...default_props,
|
|
314
429
|
interactive: true,
|
|
315
|
-
value:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
430
|
+
value: fake_value
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await fireEvent.click(getByLabelText("Trim video to selection"));
|
|
434
|
+
|
|
435
|
+
expect(getByText("Trim")).toBeTruthy();
|
|
436
|
+
expect(getByText("Cancel")).toBeTruthy();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("clicking Cancel exits edit mode and restores trim button", async () => {
|
|
440
|
+
const { getByLabelText, getByText, queryByText } = await render(Video, {
|
|
441
|
+
...default_props,
|
|
442
|
+
interactive: true,
|
|
443
|
+
value: fake_value
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await fireEvent.click(getByLabelText("Trim video to selection"));
|
|
447
|
+
expect(getByText("Cancel")).toBeTruthy();
|
|
448
|
+
|
|
449
|
+
await fireEvent.click(getByText("Cancel"));
|
|
450
|
+
|
|
451
|
+
expect(queryByText("Cancel")).toBeNull();
|
|
452
|
+
expect(getByLabelText("Trim video to selection")).toBeTruthy();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("time display is rendered", async () => {
|
|
456
|
+
const { container } = await render(Video, {
|
|
457
|
+
...default_props,
|
|
458
|
+
interactive: true,
|
|
459
|
+
value: fake_value
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const timeDisplay = container.querySelector(".time");
|
|
463
|
+
expect(timeDisplay).toBeTruthy();
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const upload_props = {
|
|
468
|
+
...default_props,
|
|
469
|
+
sources: ["upload"] as "upload"[],
|
|
470
|
+
interactive: true,
|
|
471
|
+
value: null,
|
|
472
|
+
root: "https://example.com",
|
|
473
|
+
client: mock_client()
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
describe("Events: upload via file input", () => {
|
|
477
|
+
setupi18n();
|
|
478
|
+
afterEach(() => cleanup());
|
|
479
|
+
|
|
480
|
+
test("selecting a file triggers upload, change, and input events", async () => {
|
|
481
|
+
const { listen } = await render(Video, upload_props);
|
|
482
|
+
|
|
483
|
+
const upload = listen("upload");
|
|
484
|
+
const change = listen("change");
|
|
485
|
+
const input = listen("input");
|
|
486
|
+
|
|
487
|
+
await upload_file(TEST_MP4);
|
|
488
|
+
|
|
489
|
+
await waitFor(() => {
|
|
490
|
+
expect(upload).toHaveBeenCalledTimes(1);
|
|
330
491
|
});
|
|
492
|
+
expect(input).toHaveBeenCalledTimes(1);
|
|
493
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("drag and drop a file triggers upload, change, and input events", async () => {
|
|
497
|
+
const { listen } = await render(Video, upload_props);
|
|
331
498
|
|
|
332
|
-
const
|
|
499
|
+
const upload = listen("upload");
|
|
500
|
+
const change = listen("change");
|
|
501
|
+
const input = listen("input");
|
|
333
502
|
|
|
334
|
-
(
|
|
335
|
-
|
|
336
|
-
|
|
503
|
+
await drop_file(TEST_MP4, "[aria-label='video.drop_to_upload']");
|
|
504
|
+
|
|
505
|
+
await waitFor(() => {
|
|
506
|
+
expect(upload).toHaveBeenCalledTimes(1);
|
|
507
|
+
});
|
|
508
|
+
expect(input).toHaveBeenCalledTimes(1);
|
|
509
|
+
expect(change).toHaveBeenCalledTimes(1);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("upload failure dispatches error event with the message", async () => {
|
|
513
|
+
const failing_upload = vi
|
|
514
|
+
.fn()
|
|
515
|
+
.mockRejectedValue(new Error("File too large"));
|
|
516
|
+
const { listen } = await render(Video, {
|
|
517
|
+
...upload_props,
|
|
518
|
+
client: {
|
|
519
|
+
upload: failing_upload,
|
|
520
|
+
stream: async () => ({ onmessage: null, close: () => {} })
|
|
337
521
|
}
|
|
338
|
-
|
|
339
|
-
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const error = listen("error");
|
|
525
|
+
|
|
526
|
+
await upload_file(TEST_MP4);
|
|
527
|
+
|
|
528
|
+
await waitFor(() => {
|
|
529
|
+
expect(failing_upload).toHaveBeenCalled();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
await waitFor(() => {
|
|
533
|
+
expect(error).toHaveBeenCalledTimes(1);
|
|
534
|
+
});
|
|
535
|
+
expect(error).toHaveBeenCalledWith("File too large");
|
|
340
536
|
});
|
|
341
537
|
});
|
package/dist/Index.svelte
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const gradio = new VideoGradio(props);
|
|
30
|
-
let old_value =
|
|
30
|
+
let old_value = gradio.props.value;
|
|
31
|
+
let mounted = false;
|
|
31
32
|
|
|
32
33
|
let uploading = $state(false);
|
|
33
34
|
let dragging = $state(false);
|
|
@@ -37,6 +38,11 @@
|
|
|
37
38
|
let initial_value: FileData | null = gradio.props.value;
|
|
38
39
|
|
|
39
40
|
$effect(() => {
|
|
41
|
+
if (!mounted) {
|
|
42
|
+
old_value = gradio.props.value;
|
|
43
|
+
mounted = true;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
40
46
|
if (old_value != gradio.props.value) {
|
|
41
47
|
old_value = gradio.props.value;
|
|
42
48
|
gradio.dispatch("change");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/video",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.6",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"@ffmpeg/util": "^0.12.2",
|
|
12
12
|
"hls.js": "^1.6.13",
|
|
13
13
|
"mrmime": "^2.0.1",
|
|
14
|
-
"@gradio/atoms": "^0.
|
|
14
|
+
"@gradio/atoms": "^0.23.0",
|
|
15
|
+
"@gradio/image": "^0.26.1",
|
|
15
16
|
"@gradio/client": "^2.1.0",
|
|
16
17
|
"@gradio/icons": "^0.15.1",
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/
|
|
19
|
-
"@gradio/utils": "^0.12.
|
|
20
|
-
"@gradio/upload": "^0.17.7"
|
|
18
|
+
"@gradio/statustracker": "^0.13.1",
|
|
19
|
+
"@gradio/upload": "^0.17.8",
|
|
20
|
+
"@gradio/utils": "^0.12.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@gradio/preview": "^0.16.
|
|
23
|
+
"@gradio/preview": "^0.16.2"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
26
|
"./package.json": "./package.json",
|