@gradio/video 0.10.4 → 0.11.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 +37 -0
- package/Example.svelte +1 -0
- package/Index.svelte +7 -3
- package/dist/Example.svelte +1 -0
- package/dist/Index.svelte +8 -3
- package/dist/Index.svelte.d.ts +4 -0
- package/dist/shared/InteractiveVideo.svelte +36 -32
- package/dist/shared/InteractiveVideo.svelte.d.ts +1 -0
- package/dist/shared/Player.svelte +15 -2
- package/dist/shared/Player.svelte.d.ts +8 -0
- package/dist/shared/Video.svelte +50 -0
- package/dist/shared/Video.svelte.d.ts +1 -0
- package/dist/shared/VideoControls.svelte +77 -83
- package/dist/shared/VideoControls.svelte.d.ts +6 -0
- package/dist/shared/VideoPreview.svelte +36 -31
- package/package.json +10 -9
- package/shared/InteractiveVideo.svelte +37 -32
- package/shared/Player.svelte +14 -0
- package/shared/Video.svelte +51 -0
- package/shared/VideoControls.svelte +77 -83
- package/shared/VideoPreview.svelte +36 -31
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
# @gradio/video
|
2
2
|
|
3
|
+
## 0.11.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
|
+
|
11
|
+
### Dependency updates
|
12
|
+
|
13
|
+
- @gradio/atoms@0.9.0-beta.2
|
14
|
+
- @gradio/upload@0.13.0-beta.2
|
15
|
+
- @gradio/wasm@0.14.0-beta.2
|
16
|
+
- @gradio/client@1.6.0-beta.2
|
17
|
+
- @gradio/icons@0.8.0-beta.2
|
18
|
+
- @gradio/statustracker@0.8.0-beta.2
|
19
|
+
- @gradio/utils@0.7.0-beta.2
|
20
|
+
- @gradio/image@0.16.0-beta.2
|
21
|
+
|
22
|
+
## 0.11.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/image@0.16.0-beta.1
|
32
|
+
- @gradio/upload@0.12.4-beta.1
|
33
|
+
- @gradio/wasm@0.13.1-beta.1
|
34
|
+
|
35
|
+
## 0.11.0
|
36
|
+
|
37
|
+
### Features
|
38
|
+
|
39
|
+
- [#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!
|
3
40
|
## 0.10.4
|
4
41
|
|
5
42
|
### Fixes
|
package/Example.svelte
CHANGED
package/Index.svelte
CHANGED
@@ -54,6 +54,9 @@
|
|
54
54
|
export let mirror_webcam: boolean;
|
55
55
|
export let include_audio: boolean;
|
56
56
|
export let loop = false;
|
57
|
+
export let input_ready: boolean;
|
58
|
+
let uploading = false;
|
59
|
+
$: input_ready = !uploading;
|
57
60
|
|
58
61
|
let _video: FileData | null = null;
|
59
62
|
let _subtitle: FileData | null = null;
|
@@ -158,7 +161,7 @@
|
|
158
161
|
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
159
162
|
on:error={({ detail }) => gradio.dispatch("error", detail)}
|
160
163
|
i18n={gradio.i18n}
|
161
|
-
upload={gradio.client.upload}
|
164
|
+
upload={(...args) => gradio.client.upload(...args)}
|
162
165
|
/>
|
163
166
|
</Block>
|
164
167
|
{:else}
|
@@ -189,6 +192,7 @@
|
|
189
192
|
on:change={handle_change}
|
190
193
|
on:drag={({ detail }) => (dragging = detail)}
|
191
194
|
on:error={handle_error}
|
195
|
+
bind:uploading
|
192
196
|
{label}
|
193
197
|
{show_label}
|
194
198
|
{show_download_button}
|
@@ -210,8 +214,8 @@
|
|
210
214
|
on:stop_recording={() => gradio.dispatch("stop_recording")}
|
211
215
|
i18n={gradio.i18n}
|
212
216
|
max_file_size={gradio.max_file_size}
|
213
|
-
upload={gradio.client.upload}
|
214
|
-
stream_handler={gradio.client.stream}
|
217
|
+
upload={(...args) => gradio.client.upload(...args)}
|
218
|
+
stream_handler={(...args) => gradio.client.stream(...args)}
|
215
219
|
>
|
216
220
|
<UploadText i18n={gradio.i18n} type="video" />
|
217
221
|
</Video>
|
package/dist/Example.svelte
CHANGED
package/dist/Index.svelte
CHANGED
@@ -27,6 +27,10 @@ export let interactive;
|
|
27
27
|
export let mirror_webcam;
|
28
28
|
export let include_audio;
|
29
29
|
export let loop = false;
|
30
|
+
export let input_ready;
|
31
|
+
let uploading = false;
|
32
|
+
$:
|
33
|
+
input_ready = !uploading;
|
30
34
|
let _video = null;
|
31
35
|
let _subtitle = null;
|
32
36
|
let active_source;
|
@@ -115,7 +119,7 @@ function handle_error({ detail }) {
|
|
115
119
|
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
116
120
|
on:error={({ detail }) => gradio.dispatch("error", detail)}
|
117
121
|
i18n={gradio.i18n}
|
118
|
-
upload={gradio.client.upload}
|
122
|
+
upload={(...args) => gradio.client.upload(...args)}
|
119
123
|
/>
|
120
124
|
</Block>
|
121
125
|
{:else}
|
@@ -146,6 +150,7 @@ function handle_error({ detail }) {
|
|
146
150
|
on:change={handle_change}
|
147
151
|
on:drag={({ detail }) => (dragging = detail)}
|
148
152
|
on:error={handle_error}
|
153
|
+
bind:uploading
|
149
154
|
{label}
|
150
155
|
{show_label}
|
151
156
|
{show_download_button}
|
@@ -167,8 +172,8 @@ function handle_error({ detail }) {
|
|
167
172
|
on:stop_recording={() => gradio.dispatch("stop_recording")}
|
168
173
|
i18n={gradio.i18n}
|
169
174
|
max_file_size={gradio.max_file_size}
|
170
|
-
upload={gradio.client.upload}
|
171
|
-
stream_handler={gradio.client.stream}
|
175
|
+
upload={(...args) => gradio.client.upload(...args)}
|
176
|
+
stream_handler={(...args) => gradio.client.stream(...args)}
|
172
177
|
>
|
173
178
|
<UploadText i18n={gradio.i18n} type="video" />
|
174
179
|
</Video>
|
package/dist/Index.svelte.d.ts
CHANGED
@@ -43,6 +43,7 @@ declare const __propDef: {
|
|
43
43
|
mirror_webcam: boolean;
|
44
44
|
include_audio: boolean;
|
45
45
|
loop?: boolean | undefined;
|
46
|
+
input_ready: boolean;
|
46
47
|
};
|
47
48
|
events: {
|
48
49
|
[evt: string]: CustomEvent<any>;
|
@@ -153,5 +154,8 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
153
154
|
get loop(): boolean | undefined;
|
154
155
|
/**accessor*/
|
155
156
|
set loop(_: boolean | undefined);
|
157
|
+
get input_ready(): boolean;
|
158
|
+
/**accessor*/
|
159
|
+
set input_ready(_: boolean);
|
156
160
|
}
|
157
161
|
export {};
|
@@ -24,6 +24,8 @@ export let max_file_size = null;
|
|
24
24
|
export let upload;
|
25
25
|
export let stream_handler;
|
26
26
|
export let loop;
|
27
|
+
export let uploading = false;
|
28
|
+
let has_change_history = false;
|
27
29
|
const dispatch = createEventDispatcher();
|
28
30
|
function handle_load({ detail }) {
|
29
31
|
value = detail;
|
@@ -36,6 +38,7 @@ function handle_clear() {
|
|
36
38
|
dispatch("clear");
|
37
39
|
}
|
38
40
|
function handle_change(video) {
|
41
|
+
has_change_history = true;
|
39
42
|
dispatch("change", video);
|
40
43
|
}
|
41
44
|
function handle_capture({
|
@@ -55,6 +58,7 @@ $:
|
|
55
58
|
{#if active_source === "upload"}
|
56
59
|
<Upload
|
57
60
|
bind:dragging
|
61
|
+
bind:uploading
|
58
62
|
filetype="video/x-m4v,video/*"
|
59
63
|
on:load={handle_load}
|
60
64
|
{max_file_size}
|
@@ -77,41 +81,41 @@ $:
|
|
77
81
|
on:stop_recording
|
78
82
|
{i18n}
|
79
83
|
{upload}
|
84
|
+
stream_every={1}
|
80
85
|
/>
|
81
86
|
{/if}
|
82
87
|
</div>
|
83
|
-
{:else}
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
{/if}
|
88
|
+
{:else if playable()}
|
89
|
+
{#key value?.url}
|
90
|
+
<Player
|
91
|
+
{upload}
|
92
|
+
{root}
|
93
|
+
interactive
|
94
|
+
{autoplay}
|
95
|
+
src={value.url}
|
96
|
+
subtitle={subtitle?.url}
|
97
|
+
is_stream={false}
|
98
|
+
on:play
|
99
|
+
on:pause
|
100
|
+
on:stop
|
101
|
+
on:end
|
102
|
+
mirror={mirror_webcam && active_source === "webcam"}
|
103
|
+
{label}
|
104
|
+
{handle_change}
|
105
|
+
{handle_reset_value}
|
106
|
+
{loop}
|
107
|
+
{value}
|
108
|
+
{i18n}
|
109
|
+
{show_download_button}
|
110
|
+
{handle_clear}
|
111
|
+
{has_change_history}
|
112
|
+
/>
|
113
|
+
{/key}
|
114
|
+
{:else if value.size}
|
115
|
+
<div class="file-name">{value.orig_name || value.url}</div>
|
116
|
+
<div class="file-size">
|
117
|
+
{prettyBytes(value.size)}
|
118
|
+
</div>
|
115
119
|
{/if}
|
116
120
|
|
117
121
|
<SelectSource {sources} bind:active_source {handle_clear} />
|
@@ -17,6 +17,13 @@ export let handle_change = () => {
|
|
17
17
|
export let handle_reset_value = () => {
|
18
18
|
};
|
19
19
|
export let upload;
|
20
|
+
export let is_stream;
|
21
|
+
export let i18n;
|
22
|
+
export let show_download_button = false;
|
23
|
+
export let value = null;
|
24
|
+
export let handle_clear = () => {
|
25
|
+
};
|
26
|
+
export let has_change_history = false;
|
20
27
|
const dispatch = createEventDispatcher();
|
21
28
|
let time = 0;
|
22
29
|
let duration;
|
@@ -56,8 +63,8 @@ function handle_end() {
|
|
56
63
|
const handle_trim_video = async (videoBlob) => {
|
57
64
|
let _video_blob = new File([videoBlob], "video.mp4");
|
58
65
|
const val = await prepare_files([_video_blob]);
|
59
|
-
let
|
60
|
-
handle_change(
|
66
|
+
let value2 = ((await upload(val, root))?.filter(Boolean))[0];
|
67
|
+
handle_change(value2);
|
61
68
|
};
|
62
69
|
function open_full_screen() {
|
63
70
|
video.requestFullscreen();
|
@@ -71,6 +78,7 @@ function open_full_screen() {
|
|
71
78
|
preload="auto"
|
72
79
|
{autoplay}
|
73
80
|
{loop}
|
81
|
+
{is_stream}
|
74
82
|
on:click={play_pause}
|
75
83
|
on:play
|
76
84
|
on:pause
|
@@ -138,6 +146,11 @@ function open_full_screen() {
|
|
138
146
|
{handle_trim_video}
|
139
147
|
{handle_reset_value}
|
140
148
|
bind:processingVideo
|
149
|
+
{value}
|
150
|
+
{i18n}
|
151
|
+
{show_download_button}
|
152
|
+
{handle_clear}
|
153
|
+
{has_change_history}
|
141
154
|
/>
|
142
155
|
{/if}
|
143
156
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
2
|
import type { FileData, Client } from "@gradio/client";
|
3
|
+
import type { I18nFormatter } from "@gradio/utils";
|
3
4
|
declare const __propDef: {
|
4
5
|
props: {
|
5
6
|
root?: string | undefined;
|
@@ -13,6 +14,12 @@ declare const __propDef: {
|
|
13
14
|
handle_change?: ((video: FileData) => void) | undefined;
|
14
15
|
handle_reset_value?: (() => void) | undefined;
|
15
16
|
upload: Client["upload"];
|
17
|
+
is_stream: boolean | undefined;
|
18
|
+
i18n: I18nFormatter;
|
19
|
+
show_download_button?: boolean | undefined;
|
20
|
+
value?: (FileData | null) | undefined;
|
21
|
+
handle_clear?: (() => void) | undefined;
|
22
|
+
has_change_history?: boolean | undefined;
|
16
23
|
};
|
17
24
|
events: {
|
18
25
|
play: CustomEvent<any>;
|
@@ -20,6 +27,7 @@ declare const __propDef: {
|
|
20
27
|
load: Event;
|
21
28
|
stop: CustomEvent<undefined>;
|
22
29
|
end: CustomEvent<undefined>;
|
30
|
+
clear: CustomEvent<undefined>;
|
23
31
|
} & {
|
24
32
|
[evt: string]: CustomEvent<any>;
|
25
33
|
};
|
package/dist/shared/Video.svelte
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
<script>import { createEventDispatcher } from "svelte";
|
2
2
|
import { loaded } from "./utils";
|
3
3
|
import { resolve_wasm_src } from "@gradio/wasm/svelte";
|
4
|
+
import Hls from "hls.js";
|
4
5
|
export let src = void 0;
|
5
6
|
export let muted = void 0;
|
6
7
|
export let playsinline = void 0;
|
@@ -12,8 +13,10 @@ export let duration = void 0;
|
|
12
13
|
export let paused = void 0;
|
13
14
|
export let node = void 0;
|
14
15
|
export let loop;
|
16
|
+
export let is_stream;
|
15
17
|
export let processingVideo = false;
|
16
18
|
let resolved_src;
|
19
|
+
let stream_active = false;
|
17
20
|
let latest_src;
|
18
21
|
$: {
|
19
22
|
resolved_src = src;
|
@@ -26,6 +29,53 @@ $: {
|
|
26
29
|
});
|
27
30
|
}
|
28
31
|
const dispatch = createEventDispatcher();
|
32
|
+
function load_stream(src2, is_stream2, node2) {
|
33
|
+
if (!src2 || !is_stream2)
|
34
|
+
return;
|
35
|
+
if (!node2)
|
36
|
+
return;
|
37
|
+
if (Hls.isSupported() && !stream_active) {
|
38
|
+
const hls = new Hls({
|
39
|
+
maxBufferLength: 1,
|
40
|
+
// 0.5 seconds (500 ms)
|
41
|
+
maxMaxBufferLength: 1,
|
42
|
+
// Maximum max buffer length in seconds
|
43
|
+
lowLatencyMode: true
|
44
|
+
// Enable low latency mode
|
45
|
+
});
|
46
|
+
hls.loadSource(src2);
|
47
|
+
hls.attachMedia(node2);
|
48
|
+
hls.on(Hls.Events.MANIFEST_PARSED, function() {
|
49
|
+
node2.play();
|
50
|
+
});
|
51
|
+
hls.on(Hls.Events.ERROR, function(event, data) {
|
52
|
+
console.error("HLS error:", event, data);
|
53
|
+
if (data.fatal) {
|
54
|
+
switch (data.type) {
|
55
|
+
case Hls.ErrorTypes.NETWORK_ERROR:
|
56
|
+
console.error(
|
57
|
+
"Fatal network error encountered, trying to recover"
|
58
|
+
);
|
59
|
+
hls.startLoad();
|
60
|
+
break;
|
61
|
+
case Hls.ErrorTypes.MEDIA_ERROR:
|
62
|
+
console.error("Fatal media error encountered, trying to recover");
|
63
|
+
hls.recoverMediaError();
|
64
|
+
break;
|
65
|
+
default:
|
66
|
+
console.error("Fatal error, cannot recover");
|
67
|
+
hls.destroy();
|
68
|
+
break;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
});
|
72
|
+
stream_active = true;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
$:
|
76
|
+
src, stream_active = false;
|
77
|
+
$:
|
78
|
+
load_stream(src, is_stream, node);
|
29
79
|
</script>
|
30
80
|
|
31
81
|
<!--
|
@@ -1,10 +1,12 @@
|
|
1
|
-
<script>import { Undo, Trim } from "@gradio/icons";
|
1
|
+
<script>import { Undo, Trim, Clear } from "@gradio/icons";
|
2
2
|
import VideoTimeline from "./VideoTimeline.svelte";
|
3
3
|
import { trimVideo } from "./utils";
|
4
4
|
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
5
5
|
import loadFfmpeg from "./utils";
|
6
6
|
import { onMount } from "svelte";
|
7
7
|
import { format_time } from "@gradio/utils";
|
8
|
+
import { IconButton } from "@gradio/atoms";
|
9
|
+
import { ModifyUpload } from "@gradio/upload";
|
8
10
|
export let videoElement;
|
9
11
|
export let showRedo = false;
|
10
12
|
export let interactive = true;
|
@@ -12,6 +14,12 @@ export let mode = "";
|
|
12
14
|
export let handle_reset_value;
|
13
15
|
export let handle_trim_video;
|
14
16
|
export let processingVideo = false;
|
17
|
+
export let i18n;
|
18
|
+
export let value = null;
|
19
|
+
export let show_download_button = false;
|
20
|
+
export let handle_clear = () => {
|
21
|
+
};
|
22
|
+
export let has_change_history = false;
|
15
23
|
let ffmpeg;
|
16
24
|
onMount(async () => {
|
17
25
|
ffmpeg = await loadFfmpeg();
|
@@ -33,7 +41,7 @@ const toggleTrimmingMode = () => {
|
|
33
41
|
};
|
34
42
|
</script>
|
35
43
|
|
36
|
-
<div class="container">
|
44
|
+
<div class="container" class:hidden={mode !== "edit"}>
|
37
45
|
{#if mode === "edit"}
|
38
46
|
<div class="timeline-wrapper">
|
39
47
|
<VideoTimeline
|
@@ -52,62 +60,61 @@ const toggleTrimmingMode = () => {
|
|
52
60
|
aria-label="duration of selected region in seconds"
|
53
61
|
class:hidden={loadingTimeline}>{format_time(trimmedDuration)}</time
|
54
62
|
>
|
55
|
-
|
56
|
-
<div />
|
57
|
-
{/if}
|
58
|
-
|
59
|
-
<div class="settings-wrapper">
|
60
|
-
{#if showRedo && mode === ""}
|
63
|
+
<div class="edit-buttons">
|
61
64
|
<button
|
62
|
-
class=
|
63
|
-
|
64
|
-
aria-label="Reset video to initial value"
|
65
|
+
class:hidden={loadingTimeline}
|
66
|
+
class="text-button"
|
65
67
|
on:click={() => {
|
66
|
-
handle_reset_value();
|
67
68
|
mode = "";
|
68
|
-
|
69
|
+
processingVideo = true;
|
70
|
+
trimVideo(ffmpeg, dragStart, dragEnd, videoElement)
|
71
|
+
.then((videoBlob) => {
|
72
|
+
handle_trim_video(videoBlob);
|
73
|
+
})
|
74
|
+
.then(() => {
|
75
|
+
processingVideo = false;
|
76
|
+
});
|
77
|
+
}}>Trim</button
|
69
78
|
>
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
aria-label="Trim video to selection"
|
80
|
-
on:click={toggleTrimmingMode}
|
81
|
-
>
|
82
|
-
<Trim />
|
83
|
-
</button>
|
84
|
-
{:else}
|
85
|
-
<button
|
86
|
-
class:hidden={loadingTimeline}
|
87
|
-
class="text-button"
|
88
|
-
on:click={() => {
|
89
|
-
mode = "";
|
90
|
-
processingVideo = true;
|
91
|
-
trimVideo(ffmpeg, dragStart, dragEnd, videoElement)
|
92
|
-
.then((videoBlob) => {
|
93
|
-
handle_trim_video(videoBlob);
|
94
|
-
})
|
95
|
-
.then(() => {
|
96
|
-
processingVideo = false;
|
97
|
-
});
|
98
|
-
}}>Trim</button
|
99
|
-
>
|
100
|
-
<button
|
101
|
-
class="text-button"
|
102
|
-
class:hidden={loadingTimeline}
|
103
|
-
on:click={toggleTrimmingMode}>Cancel</button
|
104
|
-
>
|
105
|
-
{/if}
|
106
|
-
{/if}
|
107
|
-
</div>
|
79
|
+
<button
|
80
|
+
class="text-button"
|
81
|
+
class:hidden={loadingTimeline}
|
82
|
+
on:click={toggleTrimmingMode}>Cancel</button
|
83
|
+
>
|
84
|
+
</div>
|
85
|
+
{:else}
|
86
|
+
<div />
|
87
|
+
{/if}
|
108
88
|
</div>
|
109
89
|
</div>
|
110
90
|
|
91
|
+
<ModifyUpload
|
92
|
+
{i18n}
|
93
|
+
on:clear={() => handle_clear()}
|
94
|
+
download={show_download_button ? value?.url : null}
|
95
|
+
>
|
96
|
+
{#if showRedo && mode === ""}
|
97
|
+
<IconButton
|
98
|
+
Icon={Undo}
|
99
|
+
label="Reset video to initial value"
|
100
|
+
disabled={processingVideo || !has_change_history}
|
101
|
+
on:click={() => {
|
102
|
+
handle_reset_value();
|
103
|
+
mode = "";
|
104
|
+
}}
|
105
|
+
/>
|
106
|
+
{/if}
|
107
|
+
|
108
|
+
{#if interactive && mode === ""}
|
109
|
+
<IconButton
|
110
|
+
Icon={Trim}
|
111
|
+
label="Trim video to selection"
|
112
|
+
disabled={processingVideo}
|
113
|
+
on:click={toggleTrimmingMode}
|
114
|
+
/>
|
115
|
+
{/if}
|
116
|
+
</ModifyUpload>
|
117
|
+
|
111
118
|
<style>
|
112
119
|
.container {
|
113
120
|
width: 100%;
|
@@ -124,10 +131,7 @@ const toggleTrimmingMode = () => {
|
|
124
131
|
justify-content: center;
|
125
132
|
width: 100%;
|
126
133
|
}
|
127
|
-
|
128
|
-
display: flex;
|
129
|
-
justify-self: self-end;
|
130
|
-
}
|
134
|
+
|
131
135
|
.text-button {
|
132
136
|
border: 1px solid var(--neutral-400);
|
133
137
|
border-radius: var(--radius-sm);
|
@@ -140,9 +144,6 @@ const toggleTrimmingMode = () => {
|
|
140
144
|
padding: 0 5px;
|
141
145
|
margin-left: 5px;
|
142
146
|
}
|
143
|
-
.hidden {
|
144
|
-
display: none;
|
145
|
-
}
|
146
147
|
|
147
148
|
.text-button:hover,
|
148
149
|
.text-button:focus {
|
@@ -151,17 +152,26 @@ const toggleTrimmingMode = () => {
|
|
151
152
|
}
|
152
153
|
|
153
154
|
.controls {
|
154
|
-
display:
|
155
|
-
|
155
|
+
display: flex;
|
156
|
+
justify-content: space-between;
|
157
|
+
align-items: center;
|
156
158
|
margin: var(--spacing-lg);
|
157
159
|
overflow: hidden;
|
158
|
-
|
160
|
+
}
|
161
|
+
|
162
|
+
.edit-buttons {
|
163
|
+
display: flex;
|
164
|
+
gap: var(--spacing-sm);
|
159
165
|
}
|
160
166
|
|
161
167
|
@media (max-width: 320px) {
|
162
168
|
.controls {
|
163
|
-
|
164
|
-
|
169
|
+
flex-direction: column;
|
170
|
+
align-items: flex-start;
|
171
|
+
}
|
172
|
+
|
173
|
+
.edit-buttons {
|
174
|
+
margin-top: var(--spacing-sm);
|
165
175
|
}
|
166
176
|
|
167
177
|
.controls * {
|
@@ -172,29 +182,13 @@ const toggleTrimmingMode = () => {
|
|
172
182
|
margin-left: 0;
|
173
183
|
}
|
174
184
|
}
|
175
|
-
.action {
|
176
|
-
width: var(--size-5);
|
177
|
-
width: var(--size-5);
|
178
|
-
color: var(--neutral-400);
|
179
|
-
margin-left: var(--spacing-md);
|
180
|
-
}
|
181
|
-
|
182
|
-
.action:disabled {
|
183
|
-
cursor: not-allowed;
|
184
|
-
color: var(--border-color-accent-subdued);
|
185
|
-
}
|
186
|
-
|
187
|
-
.action:disabled:hover {
|
188
|
-
color: var(--border-color-accent-subdued);
|
189
|
-
}
|
190
|
-
|
191
|
-
.icon:hover,
|
192
|
-
.icon:focus {
|
193
|
-
color: var(--color-accent);
|
194
|
-
}
|
195
185
|
|
196
186
|
.container {
|
197
187
|
display: flex;
|
198
188
|
flex-direction: column;
|
199
189
|
}
|
190
|
+
|
191
|
+
.hidden {
|
192
|
+
display: none;
|
193
|
+
}
|
200
194
|
</style>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
2
|
+
import type { FileData } from "@gradio/client";
|
2
3
|
declare const __propDef: {
|
3
4
|
props: {
|
4
5
|
videoElement: HTMLVideoElement;
|
@@ -8,6 +9,11 @@ declare const __propDef: {
|
|
8
9
|
handle_reset_value: () => void;
|
9
10
|
handle_trim_video: (videoBlob: Blob) => void;
|
10
11
|
processingVideo?: boolean | undefined;
|
12
|
+
i18n: (key: string) => string;
|
13
|
+
value?: (FileData | null) | undefined;
|
14
|
+
show_download_button?: boolean | undefined;
|
15
|
+
handle_clear?: (() => void) | undefined;
|
16
|
+
has_change_history?: boolean | undefined;
|
11
17
|
};
|
12
18
|
events: {
|
13
19
|
[evt: string]: CustomEvent<any>;
|
@@ -1,5 +1,11 @@
|
|
1
1
|
<script>import { createEventDispatcher, afterUpdate, tick } from "svelte";
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
BlockLabel,
|
4
|
+
Empty,
|
5
|
+
IconButton,
|
6
|
+
ShareButton,
|
7
|
+
IconButtonWrapper
|
8
|
+
} from "@gradio/atoms";
|
3
9
|
import { Video, Download } from "@gradio/icons";
|
4
10
|
import { uploadToHuggingFace } from "@gradio/utils";
|
5
11
|
import { DownloadLink } from "@gradio/wasm/svelte";
|
@@ -32,13 +38,14 @@ afterUpdate(async () => {
|
|
32
38
|
</script>
|
33
39
|
|
34
40
|
<BlockLabel {show_label} Icon={Video} label={label || "Video"} />
|
35
|
-
{#if value
|
41
|
+
{#if !value || value.url === undefined}
|
36
42
|
<Empty unpadded_box={true} size="large"><Video /></Empty>
|
37
43
|
{:else}
|
38
44
|
{#key value.url}
|
39
45
|
<Player
|
40
46
|
src={value.url}
|
41
47
|
subtitle={subtitle?.url}
|
48
|
+
is_stream={value.is_stream}
|
42
49
|
{autoplay}
|
43
50
|
on:play
|
44
51
|
on:pause
|
@@ -50,36 +57,34 @@ afterUpdate(async () => {
|
|
50
57
|
{loop}
|
51
58
|
interactive={false}
|
52
59
|
{upload}
|
60
|
+
{i18n}
|
53
61
|
/>
|
54
62
|
{/key}
|
55
|
-
<div
|
56
|
-
|
57
|
-
|
58
|
-
<
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
63
|
+
<div data-testid="download-div">
|
64
|
+
<IconButtonWrapper>
|
65
|
+
{#if show_download_button}
|
66
|
+
<DownloadLink
|
67
|
+
href={value.is_stream
|
68
|
+
? value.url?.replace("playlist.m3u8", "playlist-file")
|
69
|
+
: value.url}
|
70
|
+
download={value.orig_name || value.path}
|
71
|
+
>
|
72
|
+
<IconButton Icon={Download} label="Download" />
|
73
|
+
</DownloadLink>
|
74
|
+
{/if}
|
75
|
+
{#if show_share_button}
|
76
|
+
<ShareButton
|
77
|
+
{i18n}
|
78
|
+
on:error
|
79
|
+
on:share
|
80
|
+
{value}
|
81
|
+
formatter={async (value) => {
|
82
|
+
if (!value) return "";
|
83
|
+
let url = await uploadToHuggingFace(value.data, "url");
|
84
|
+
return url;
|
85
|
+
}}
|
86
|
+
/>
|
87
|
+
{/if}
|
88
|
+
</IconButtonWrapper>
|
74
89
|
</div>
|
75
90
|
{/if}
|
76
|
-
|
77
|
-
<style>
|
78
|
-
.icon-buttons {
|
79
|
-
display: flex;
|
80
|
-
position: absolute;
|
81
|
-
top: 6px;
|
82
|
-
right: 6px;
|
83
|
-
gap: var(--size-1);
|
84
|
-
}
|
85
|
-
</style>
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@gradio/video",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.11.0-beta.2",
|
4
4
|
"description": "Gradio UI packages",
|
5
5
|
"type": "module",
|
6
6
|
"author": "",
|
@@ -9,15 +9,16 @@
|
|
9
9
|
"dependencies": {
|
10
10
|
"@ffmpeg/ffmpeg": "^0.12.7",
|
11
11
|
"@ffmpeg/util": "^0.12.1",
|
12
|
+
"hls.js": "^1.5.13",
|
12
13
|
"mrmime": "^2.0.0",
|
13
|
-
"@gradio/atoms": "^0.
|
14
|
-
"@gradio/
|
15
|
-
"@gradio/
|
16
|
-
"@gradio/
|
17
|
-
"@gradio/
|
18
|
-
"@gradio/
|
19
|
-
"@gradio/
|
20
|
-
"@gradio/wasm": "^0.
|
14
|
+
"@gradio/atoms": "^0.9.0-beta.2",
|
15
|
+
"@gradio/client": "^1.6.0-beta.2",
|
16
|
+
"@gradio/icons": "^0.8.0-beta.2",
|
17
|
+
"@gradio/statustracker": "^0.8.0-beta.2",
|
18
|
+
"@gradio/image": "^0.16.0-beta.2",
|
19
|
+
"@gradio/upload": "^0.13.0-beta.2",
|
20
|
+
"@gradio/utils": "^0.7.0-beta.2",
|
21
|
+
"@gradio/wasm": "^0.14.0-beta.2"
|
21
22
|
},
|
22
23
|
"devDependencies": {
|
23
24
|
"@gradio/preview": "^0.11.1"
|
@@ -32,6 +32,9 @@
|
|
32
32
|
export let upload: Client["upload"];
|
33
33
|
export let stream_handler: Client["stream"];
|
34
34
|
export let loop: boolean;
|
35
|
+
export let uploading = false;
|
36
|
+
|
37
|
+
let has_change_history = false;
|
35
38
|
|
36
39
|
const dispatch = createEventDispatcher<{
|
37
40
|
change: FileData | null;
|
@@ -59,6 +62,7 @@
|
|
59
62
|
}
|
60
63
|
|
61
64
|
function handle_change(video: FileData): void {
|
65
|
+
has_change_history = true;
|
62
66
|
dispatch("change", video);
|
63
67
|
}
|
64
68
|
|
@@ -79,6 +83,7 @@
|
|
79
83
|
{#if active_source === "upload"}
|
80
84
|
<Upload
|
81
85
|
bind:dragging
|
86
|
+
bind:uploading
|
82
87
|
filetype="video/x-m4v,video/*"
|
83
88
|
on:load={handle_load}
|
84
89
|
{max_file_size}
|
@@ -101,41 +106,41 @@
|
|
101
106
|
on:stop_recording
|
102
107
|
{i18n}
|
103
108
|
{upload}
|
109
|
+
stream_every={1}
|
104
110
|
/>
|
105
111
|
{/if}
|
106
112
|
</div>
|
107
|
-
{:else}
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
{/if}
|
113
|
+
{:else if playable()}
|
114
|
+
{#key value?.url}
|
115
|
+
<Player
|
116
|
+
{upload}
|
117
|
+
{root}
|
118
|
+
interactive
|
119
|
+
{autoplay}
|
120
|
+
src={value.url}
|
121
|
+
subtitle={subtitle?.url}
|
122
|
+
is_stream={false}
|
123
|
+
on:play
|
124
|
+
on:pause
|
125
|
+
on:stop
|
126
|
+
on:end
|
127
|
+
mirror={mirror_webcam && active_source === "webcam"}
|
128
|
+
{label}
|
129
|
+
{handle_change}
|
130
|
+
{handle_reset_value}
|
131
|
+
{loop}
|
132
|
+
{value}
|
133
|
+
{i18n}
|
134
|
+
{show_download_button}
|
135
|
+
{handle_clear}
|
136
|
+
{has_change_history}
|
137
|
+
/>
|
138
|
+
{/key}
|
139
|
+
{:else if value.size}
|
140
|
+
<div class="file-name">{value.orig_name || value.url}</div>
|
141
|
+
<div class="file-size">
|
142
|
+
{prettyBytes(value.size)}
|
143
|
+
</div>
|
139
144
|
{/if}
|
140
145
|
|
141
146
|
<SelectSource {sources} bind:active_source {handle_clear} />
|
package/shared/Player.svelte
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
import type { FileData, Client } from "@gradio/client";
|
7
7
|
import { prepare_files } from "@gradio/client";
|
8
8
|
import { format_time } from "@gradio/utils";
|
9
|
+
import type { I18nFormatter } from "@gradio/utils";
|
9
10
|
|
10
11
|
export let root = "";
|
11
12
|
export let src: string;
|
@@ -18,12 +19,19 @@
|
|
18
19
|
export let handle_change: (video: FileData) => void = () => {};
|
19
20
|
export let handle_reset_value: () => void = () => {};
|
20
21
|
export let upload: Client["upload"];
|
22
|
+
export let is_stream: boolean | undefined;
|
23
|
+
export let i18n: I18nFormatter;
|
24
|
+
export let show_download_button = false;
|
25
|
+
export let value: FileData | null = null;
|
26
|
+
export let handle_clear: () => void = () => {};
|
27
|
+
export let has_change_history = false;
|
21
28
|
|
22
29
|
const dispatch = createEventDispatcher<{
|
23
30
|
play: undefined;
|
24
31
|
pause: undefined;
|
25
32
|
stop: undefined;
|
26
33
|
end: undefined;
|
34
|
+
clear: undefined;
|
27
35
|
}>();
|
28
36
|
|
29
37
|
let time = 0;
|
@@ -98,6 +106,7 @@
|
|
98
106
|
preload="auto"
|
99
107
|
{autoplay}
|
100
108
|
{loop}
|
109
|
+
{is_stream}
|
101
110
|
on:click={play_pause}
|
102
111
|
on:play
|
103
112
|
on:pause
|
@@ -165,6 +174,11 @@
|
|
165
174
|
{handle_trim_video}
|
166
175
|
{handle_reset_value}
|
167
176
|
bind:processingVideo
|
177
|
+
{value}
|
178
|
+
{i18n}
|
179
|
+
{show_download_button}
|
180
|
+
{handle_clear}
|
181
|
+
{has_change_history}
|
168
182
|
/>
|
169
183
|
{/if}
|
170
184
|
|
package/shared/Video.svelte
CHANGED
@@ -5,6 +5,8 @@
|
|
5
5
|
|
6
6
|
import { resolve_wasm_src } from "@gradio/wasm/svelte";
|
7
7
|
|
8
|
+
import Hls from "hls.js";
|
9
|
+
|
8
10
|
export let src: HTMLVideoAttributes["src"] = undefined;
|
9
11
|
|
10
12
|
export let muted: HTMLVideoAttributes["muted"] = undefined;
|
@@ -19,10 +21,12 @@
|
|
19
21
|
|
20
22
|
export let node: HTMLVideoElement | undefined = undefined;
|
21
23
|
export let loop: boolean;
|
24
|
+
export let is_stream;
|
22
25
|
|
23
26
|
export let processingVideo = false;
|
24
27
|
|
25
28
|
let resolved_src: typeof src;
|
29
|
+
let stream_active = false;
|
26
30
|
|
27
31
|
// The `src` prop can be updated before the Promise from `resolve_wasm_src` is resolved.
|
28
32
|
// In such a case, the resolved value for the old `src` has to be discarded,
|
@@ -46,6 +50,53 @@
|
|
46
50
|
}
|
47
51
|
|
48
52
|
const dispatch = createEventDispatcher();
|
53
|
+
|
54
|
+
function load_stream(
|
55
|
+
src: string | null | undefined,
|
56
|
+
is_stream: boolean,
|
57
|
+
node: HTMLVideoElement | undefined
|
58
|
+
): void {
|
59
|
+
if (!src || !is_stream) return;
|
60
|
+
if (!node) return;
|
61
|
+
if (Hls.isSupported() && !stream_active) {
|
62
|
+
const hls = new Hls({
|
63
|
+
maxBufferLength: 1, // 0.5 seconds (500 ms)
|
64
|
+
maxMaxBufferLength: 1, // Maximum max buffer length in seconds
|
65
|
+
lowLatencyMode: true // Enable low latency mode
|
66
|
+
});
|
67
|
+
hls.loadSource(src);
|
68
|
+
hls.attachMedia(node);
|
69
|
+
hls.on(Hls.Events.MANIFEST_PARSED, function () {
|
70
|
+
(node as HTMLVideoElement).play();
|
71
|
+
});
|
72
|
+
hls.on(Hls.Events.ERROR, function (event, data) {
|
73
|
+
console.error("HLS error:", event, data);
|
74
|
+
if (data.fatal) {
|
75
|
+
switch (data.type) {
|
76
|
+
case Hls.ErrorTypes.NETWORK_ERROR:
|
77
|
+
console.error(
|
78
|
+
"Fatal network error encountered, trying to recover"
|
79
|
+
);
|
80
|
+
hls.startLoad();
|
81
|
+
break;
|
82
|
+
case Hls.ErrorTypes.MEDIA_ERROR:
|
83
|
+
console.error("Fatal media error encountered, trying to recover");
|
84
|
+
hls.recoverMediaError();
|
85
|
+
break;
|
86
|
+
default:
|
87
|
+
console.error("Fatal error, cannot recover");
|
88
|
+
hls.destroy();
|
89
|
+
break;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
});
|
93
|
+
stream_active = true;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
$: src, (stream_active = false);
|
98
|
+
|
99
|
+
$: load_stream(src, is_stream, node);
|
49
100
|
</script>
|
50
101
|
|
51
102
|
<!--
|
@@ -1,11 +1,14 @@
|
|
1
1
|
<script lang="ts">
|
2
|
-
import { Undo, Trim } from "@gradio/icons";
|
2
|
+
import { Undo, Trim, Clear } from "@gradio/icons";
|
3
3
|
import VideoTimeline from "./VideoTimeline.svelte";
|
4
4
|
import { trimVideo } from "./utils";
|
5
5
|
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
6
6
|
import loadFfmpeg from "./utils";
|
7
7
|
import { onMount } from "svelte";
|
8
8
|
import { format_time } from "@gradio/utils";
|
9
|
+
import { IconButton } from "@gradio/atoms";
|
10
|
+
import { ModifyUpload } from "@gradio/upload";
|
11
|
+
import type { FileData } from "@gradio/client";
|
9
12
|
|
10
13
|
export let videoElement: HTMLVideoElement;
|
11
14
|
|
@@ -15,6 +18,11 @@
|
|
15
18
|
export let handle_reset_value: () => void;
|
16
19
|
export let handle_trim_video: (videoBlob: Blob) => void;
|
17
20
|
export let processingVideo = false;
|
21
|
+
export let i18n: (key: string) => string;
|
22
|
+
export let value: FileData | null = null;
|
23
|
+
export let show_download_button = false;
|
24
|
+
export let handle_clear: () => void = () => {};
|
25
|
+
export let has_change_history = false;
|
18
26
|
|
19
27
|
let ffmpeg: FFmpeg;
|
20
28
|
|
@@ -41,7 +49,7 @@
|
|
41
49
|
};
|
42
50
|
</script>
|
43
51
|
|
44
|
-
<div class="container">
|
52
|
+
<div class="container" class:hidden={mode !== "edit"}>
|
45
53
|
{#if mode === "edit"}
|
46
54
|
<div class="timeline-wrapper">
|
47
55
|
<VideoTimeline
|
@@ -60,62 +68,61 @@
|
|
60
68
|
aria-label="duration of selected region in seconds"
|
61
69
|
class:hidden={loadingTimeline}>{format_time(trimmedDuration)}</time
|
62
70
|
>
|
63
|
-
|
64
|
-
<div />
|
65
|
-
{/if}
|
66
|
-
|
67
|
-
<div class="settings-wrapper">
|
68
|
-
{#if showRedo && mode === ""}
|
71
|
+
<div class="edit-buttons">
|
69
72
|
<button
|
70
|
-
class=
|
71
|
-
|
72
|
-
aria-label="Reset video to initial value"
|
73
|
+
class:hidden={loadingTimeline}
|
74
|
+
class="text-button"
|
73
75
|
on:click={() => {
|
74
|
-
handle_reset_value();
|
75
76
|
mode = "";
|
76
|
-
|
77
|
+
processingVideo = true;
|
78
|
+
trimVideo(ffmpeg, dragStart, dragEnd, videoElement)
|
79
|
+
.then((videoBlob) => {
|
80
|
+
handle_trim_video(videoBlob);
|
81
|
+
})
|
82
|
+
.then(() => {
|
83
|
+
processingVideo = false;
|
84
|
+
});
|
85
|
+
}}>Trim</button
|
77
86
|
>
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
aria-label="Trim video to selection"
|
88
|
-
on:click={toggleTrimmingMode}
|
89
|
-
>
|
90
|
-
<Trim />
|
91
|
-
</button>
|
92
|
-
{:else}
|
93
|
-
<button
|
94
|
-
class:hidden={loadingTimeline}
|
95
|
-
class="text-button"
|
96
|
-
on:click={() => {
|
97
|
-
mode = "";
|
98
|
-
processingVideo = true;
|
99
|
-
trimVideo(ffmpeg, dragStart, dragEnd, videoElement)
|
100
|
-
.then((videoBlob) => {
|
101
|
-
handle_trim_video(videoBlob);
|
102
|
-
})
|
103
|
-
.then(() => {
|
104
|
-
processingVideo = false;
|
105
|
-
});
|
106
|
-
}}>Trim</button
|
107
|
-
>
|
108
|
-
<button
|
109
|
-
class="text-button"
|
110
|
-
class:hidden={loadingTimeline}
|
111
|
-
on:click={toggleTrimmingMode}>Cancel</button
|
112
|
-
>
|
113
|
-
{/if}
|
114
|
-
{/if}
|
115
|
-
</div>
|
87
|
+
<button
|
88
|
+
class="text-button"
|
89
|
+
class:hidden={loadingTimeline}
|
90
|
+
on:click={toggleTrimmingMode}>Cancel</button
|
91
|
+
>
|
92
|
+
</div>
|
93
|
+
{:else}
|
94
|
+
<div />
|
95
|
+
{/if}
|
116
96
|
</div>
|
117
97
|
</div>
|
118
98
|
|
99
|
+
<ModifyUpload
|
100
|
+
{i18n}
|
101
|
+
on:clear={() => handle_clear()}
|
102
|
+
download={show_download_button ? value?.url : null}
|
103
|
+
>
|
104
|
+
{#if showRedo && mode === ""}
|
105
|
+
<IconButton
|
106
|
+
Icon={Undo}
|
107
|
+
label="Reset video to initial value"
|
108
|
+
disabled={processingVideo || !has_change_history}
|
109
|
+
on:click={() => {
|
110
|
+
handle_reset_value();
|
111
|
+
mode = "";
|
112
|
+
}}
|
113
|
+
/>
|
114
|
+
{/if}
|
115
|
+
|
116
|
+
{#if interactive && mode === ""}
|
117
|
+
<IconButton
|
118
|
+
Icon={Trim}
|
119
|
+
label="Trim video to selection"
|
120
|
+
disabled={processingVideo}
|
121
|
+
on:click={toggleTrimmingMode}
|
122
|
+
/>
|
123
|
+
{/if}
|
124
|
+
</ModifyUpload>
|
125
|
+
|
119
126
|
<style>
|
120
127
|
.container {
|
121
128
|
width: 100%;
|
@@ -132,10 +139,7 @@
|
|
132
139
|
justify-content: center;
|
133
140
|
width: 100%;
|
134
141
|
}
|
135
|
-
|
136
|
-
display: flex;
|
137
|
-
justify-self: self-end;
|
138
|
-
}
|
142
|
+
|
139
143
|
.text-button {
|
140
144
|
border: 1px solid var(--neutral-400);
|
141
145
|
border-radius: var(--radius-sm);
|
@@ -148,9 +152,6 @@
|
|
148
152
|
padding: 0 5px;
|
149
153
|
margin-left: 5px;
|
150
154
|
}
|
151
|
-
.hidden {
|
152
|
-
display: none;
|
153
|
-
}
|
154
155
|
|
155
156
|
.text-button:hover,
|
156
157
|
.text-button:focus {
|
@@ -159,17 +160,26 @@
|
|
159
160
|
}
|
160
161
|
|
161
162
|
.controls {
|
162
|
-
display:
|
163
|
-
|
163
|
+
display: flex;
|
164
|
+
justify-content: space-between;
|
165
|
+
align-items: center;
|
164
166
|
margin: var(--spacing-lg);
|
165
167
|
overflow: hidden;
|
166
|
-
|
168
|
+
}
|
169
|
+
|
170
|
+
.edit-buttons {
|
171
|
+
display: flex;
|
172
|
+
gap: var(--spacing-sm);
|
167
173
|
}
|
168
174
|
|
169
175
|
@media (max-width: 320px) {
|
170
176
|
.controls {
|
171
|
-
|
172
|
-
|
177
|
+
flex-direction: column;
|
178
|
+
align-items: flex-start;
|
179
|
+
}
|
180
|
+
|
181
|
+
.edit-buttons {
|
182
|
+
margin-top: var(--spacing-sm);
|
173
183
|
}
|
174
184
|
|
175
185
|
.controls * {
|
@@ -180,29 +190,13 @@
|
|
180
190
|
margin-left: 0;
|
181
191
|
}
|
182
192
|
}
|
183
|
-
.action {
|
184
|
-
width: var(--size-5);
|
185
|
-
width: var(--size-5);
|
186
|
-
color: var(--neutral-400);
|
187
|
-
margin-left: var(--spacing-md);
|
188
|
-
}
|
189
|
-
|
190
|
-
.action:disabled {
|
191
|
-
cursor: not-allowed;
|
192
|
-
color: var(--border-color-accent-subdued);
|
193
|
-
}
|
194
|
-
|
195
|
-
.action:disabled:hover {
|
196
|
-
color: var(--border-color-accent-subdued);
|
197
|
-
}
|
198
|
-
|
199
|
-
.icon:hover,
|
200
|
-
.icon:focus {
|
201
|
-
color: var(--color-accent);
|
202
|
-
}
|
203
193
|
|
204
194
|
.container {
|
205
195
|
display: flex;
|
206
196
|
flex-direction: column;
|
207
197
|
}
|
198
|
+
|
199
|
+
.hidden {
|
200
|
+
display: none;
|
201
|
+
}
|
208
202
|
</style>
|
@@ -1,6 +1,12 @@
|
|
1
1
|
<script lang="ts">
|
2
2
|
import { createEventDispatcher, afterUpdate, tick } from "svelte";
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
BlockLabel,
|
5
|
+
Empty,
|
6
|
+
IconButton,
|
7
|
+
ShareButton,
|
8
|
+
IconButtonWrapper
|
9
|
+
} from "@gradio/atoms";
|
4
10
|
import type { FileData, Client } from "@gradio/client";
|
5
11
|
import { Video, Download } from "@gradio/icons";
|
6
12
|
import { uploadToHuggingFace } from "@gradio/utils";
|
@@ -51,13 +57,14 @@
|
|
51
57
|
</script>
|
52
58
|
|
53
59
|
<BlockLabel {show_label} Icon={Video} label={label || "Video"} />
|
54
|
-
{#if value
|
60
|
+
{#if !value || value.url === undefined}
|
55
61
|
<Empty unpadded_box={true} size="large"><Video /></Empty>
|
56
62
|
{:else}
|
57
63
|
{#key value.url}
|
58
64
|
<Player
|
59
65
|
src={value.url}
|
60
66
|
subtitle={subtitle?.url}
|
67
|
+
is_stream={value.is_stream}
|
61
68
|
{autoplay}
|
62
69
|
on:play
|
63
70
|
on:pause
|
@@ -69,36 +76,34 @@
|
|
69
76
|
{loop}
|
70
77
|
interactive={false}
|
71
78
|
{upload}
|
79
|
+
{i18n}
|
72
80
|
/>
|
73
81
|
{/key}
|
74
|
-
<div
|
75
|
-
|
76
|
-
|
77
|
-
<
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
82
|
+
<div data-testid="download-div">
|
83
|
+
<IconButtonWrapper>
|
84
|
+
{#if show_download_button}
|
85
|
+
<DownloadLink
|
86
|
+
href={value.is_stream
|
87
|
+
? value.url?.replace("playlist.m3u8", "playlist-file")
|
88
|
+
: value.url}
|
89
|
+
download={value.orig_name || value.path}
|
90
|
+
>
|
91
|
+
<IconButton Icon={Download} label="Download" />
|
92
|
+
</DownloadLink>
|
93
|
+
{/if}
|
94
|
+
{#if show_share_button}
|
95
|
+
<ShareButton
|
96
|
+
{i18n}
|
97
|
+
on:error
|
98
|
+
on:share
|
99
|
+
{value}
|
100
|
+
formatter={async (value) => {
|
101
|
+
if (!value) return "";
|
102
|
+
let url = await uploadToHuggingFace(value.data, "url");
|
103
|
+
return url;
|
104
|
+
}}
|
105
|
+
/>
|
106
|
+
{/if}
|
107
|
+
</IconButtonWrapper>
|
93
108
|
</div>
|
94
109
|
{/if}
|
95
|
-
|
96
|
-
<style>
|
97
|
-
.icon-buttons {
|
98
|
-
display: flex;
|
99
|
-
position: absolute;
|
100
|
-
top: 6px;
|
101
|
-
right: 6px;
|
102
|
-
gap: var(--size-1);
|
103
|
-
}
|
104
|
-
</style>
|