@gradio/video 0.20.2 → 0.20.4
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 +30 -0
- package/Example.svelte +14 -8
- package/Index.svelte +19 -19
- package/dist/Example.svelte +14 -8
- package/dist/Example.svelte.d.ts +4 -18
- package/dist/Index.svelte +19 -19
- package/dist/shared/InteractiveVideo.svelte +111 -59
- package/dist/shared/InteractiveVideo.svelte.d.ts +21 -40
- package/dist/shared/Player.svelte +130 -78
- package/dist/shared/Player.svelte.d.ts +13 -29
- package/dist/shared/Video.svelte +86 -40
- package/dist/shared/Video.svelte.d.ts +32 -44
- package/dist/shared/VideoControls.svelte +45 -23
- package/dist/shared/VideoControls.svelte.d.ts +5 -19
- package/dist/shared/VideoPreview.svelte +75 -44
- package/dist/shared/VideoPreview.svelte.d.ts +13 -28
- package/dist/shared/VideoTimeline.svelte +34 -20
- package/dist/shared/VideoTimeline.svelte.d.ts +8 -22
- package/dist/shared/VolumeControl.svelte +16 -6
- package/dist/shared/VolumeControl.svelte.d.ts +4 -18
- package/package.json +8 -8
- package/shared/InteractiveVideo.svelte +111 -59
- package/shared/Player.svelte +130 -78
- package/shared/Video.svelte +86 -40
- package/shared/VideoControls.svelte +45 -23
- package/shared/VideoPreview.svelte +75 -44
- package/shared/VideoTimeline.svelte +34 -20
- package/shared/VolumeControl.svelte +16 -6
|
@@ -10,19 +10,35 @@
|
|
|
10
10
|
import { ModifyUpload } from "@gradio/upload";
|
|
11
11
|
import type { FileData } from "@gradio/client";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
interface Props {
|
|
14
|
+
videoElement?: HTMLVideoElement;
|
|
15
|
+
showRedo?: boolean;
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
mode?: string;
|
|
18
|
+
handle_reset_value: () => void;
|
|
19
|
+
handle_trim_video: (videoBlob: Blob) => void;
|
|
20
|
+
processingVideo?: boolean;
|
|
21
|
+
i18n: (key: string) => string;
|
|
22
|
+
value?: FileData | null;
|
|
23
|
+
show_download_button?: boolean;
|
|
24
|
+
handle_clear?: () => void;
|
|
25
|
+
has_change_history?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
videoElement = undefined,
|
|
30
|
+
showRedo = false,
|
|
31
|
+
interactive = true,
|
|
32
|
+
mode = $bindable(""),
|
|
33
|
+
handle_reset_value,
|
|
34
|
+
handle_trim_video,
|
|
35
|
+
processingVideo = $bindable(false),
|
|
36
|
+
i18n,
|
|
37
|
+
value = null,
|
|
38
|
+
show_download_button = false,
|
|
39
|
+
handle_clear = () => {},
|
|
40
|
+
has_change_history = false
|
|
41
|
+
}: Props = $props();
|
|
26
42
|
|
|
27
43
|
let ffmpeg: FFmpeg;
|
|
28
44
|
|
|
@@ -30,19 +46,24 @@
|
|
|
30
46
|
ffmpeg = await loadFfmpeg();
|
|
31
47
|
});
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
trimmedDuration
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (mode === "edit" && trimmedDuration === null && videoElement) {
|
|
51
|
+
trimmedDuration = videoElement.duration;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
35
54
|
|
|
36
|
-
let trimmedDuration
|
|
37
|
-
let dragStart = 0;
|
|
38
|
-
let dragEnd = 0;
|
|
55
|
+
let trimmedDuration = $state<number | null>(null);
|
|
56
|
+
let dragStart = $state(0);
|
|
57
|
+
let dragEnd = $state(0);
|
|
39
58
|
|
|
40
|
-
let loadingTimeline = false;
|
|
59
|
+
let loadingTimeline = $state(false);
|
|
41
60
|
|
|
42
61
|
const toggleTrimmingMode = (): void => {
|
|
43
62
|
if (mode === "edit") {
|
|
44
63
|
mode = "";
|
|
45
|
-
|
|
64
|
+
if (videoElement) {
|
|
65
|
+
trimmedDuration = videoElement.duration;
|
|
66
|
+
}
|
|
46
67
|
} else {
|
|
47
68
|
mode = "edit";
|
|
48
69
|
}
|
|
@@ -50,7 +71,7 @@
|
|
|
50
71
|
</script>
|
|
51
72
|
|
|
52
73
|
<div class="container" class:hidden={mode !== "edit"}>
|
|
53
|
-
{#if mode === "edit"}
|
|
74
|
+
{#if mode === "edit" && videoElement}
|
|
54
75
|
<div class="timeline-wrapper">
|
|
55
76
|
<VideoTimeline
|
|
56
77
|
{videoElement}
|
|
@@ -72,7 +93,8 @@
|
|
|
72
93
|
<button
|
|
73
94
|
class:hidden={loadingTimeline}
|
|
74
95
|
class="text-button"
|
|
75
|
-
|
|
96
|
+
onclick={() => {
|
|
97
|
+
if (!videoElement) return;
|
|
76
98
|
mode = "";
|
|
77
99
|
processingVideo = true;
|
|
78
100
|
trimVideo(ffmpeg, dragStart, dragEnd, videoElement)
|
|
@@ -87,7 +109,7 @@
|
|
|
87
109
|
<button
|
|
88
110
|
class="text-button"
|
|
89
111
|
class:hidden={loadingTimeline}
|
|
90
|
-
|
|
112
|
+
onclick={toggleTrimmingMode}>Cancel</button
|
|
91
113
|
>
|
|
92
114
|
</div>
|
|
93
115
|
{:else}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { tick } from "svelte";
|
|
3
3
|
import {
|
|
4
4
|
BlockLabel,
|
|
5
5
|
Empty,
|
|
@@ -16,47 +16,78 @@
|
|
|
16
16
|
import Player from "./Player.svelte";
|
|
17
17
|
import type { I18nFormatter } from "js/core/src/gradio_helper";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
interface Props {
|
|
20
|
+
value?: FileData | null;
|
|
21
|
+
subtitle?: FileData | null;
|
|
22
|
+
label?: string;
|
|
23
|
+
show_label?: boolean;
|
|
24
|
+
autoplay: boolean;
|
|
25
|
+
buttons?: (string | CustomButtonType)[] | null;
|
|
26
|
+
on_custom_button_click?: ((id: number) => void) | null;
|
|
27
|
+
loop: boolean;
|
|
28
|
+
i18n: I18nFormatter;
|
|
29
|
+
upload: Client["upload"];
|
|
30
|
+
display_icon_button_wrapper_top_corner?: boolean;
|
|
31
|
+
playback_position?: number;
|
|
32
|
+
onplay?: () => void;
|
|
33
|
+
onpause?: () => void;
|
|
34
|
+
onend?: () => void;
|
|
35
|
+
onstop?: () => void;
|
|
36
|
+
onload?: () => void;
|
|
37
|
+
onchange?: (value: FileData) => void;
|
|
38
|
+
onerror?: (error: string) => void;
|
|
39
|
+
onshare?: (detail: unknown) => void;
|
|
40
|
+
}
|
|
31
41
|
|
|
32
|
-
let
|
|
33
|
-
|
|
42
|
+
let {
|
|
43
|
+
value = $bindable(null),
|
|
44
|
+
subtitle = null,
|
|
45
|
+
label = undefined,
|
|
46
|
+
show_label = true,
|
|
47
|
+
autoplay,
|
|
48
|
+
buttons = null,
|
|
49
|
+
on_custom_button_click = null,
|
|
50
|
+
loop,
|
|
51
|
+
i18n,
|
|
52
|
+
upload,
|
|
53
|
+
display_icon_button_wrapper_top_corner = false,
|
|
54
|
+
playback_position = $bindable(),
|
|
55
|
+
onplay,
|
|
56
|
+
onpause,
|
|
57
|
+
onend,
|
|
58
|
+
onstop,
|
|
59
|
+
onload,
|
|
60
|
+
onchange,
|
|
61
|
+
onerror,
|
|
62
|
+
onshare
|
|
63
|
+
}: Props = $props();
|
|
34
64
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
play: undefined;
|
|
38
|
-
pause: undefined;
|
|
39
|
-
end: undefined;
|
|
40
|
-
stop: undefined;
|
|
41
|
-
load: undefined;
|
|
42
|
-
}>();
|
|
65
|
+
let old_value = $state<FileData | null>(null);
|
|
66
|
+
let old_subtitle = $state<FileData | null>(null);
|
|
43
67
|
|
|
44
|
-
|
|
68
|
+
$effect(() => {
|
|
69
|
+
if (value) {
|
|
70
|
+
onchange?.(value);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
45
73
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
$effect(() => {
|
|
75
|
+
async function updateValue(): Promise<void> {
|
|
76
|
+
// needed to bust subtitle caching issues on Chrome
|
|
77
|
+
if (
|
|
78
|
+
value !== old_value &&
|
|
79
|
+
subtitle !== old_subtitle &&
|
|
80
|
+
old_subtitle !== null
|
|
81
|
+
) {
|
|
82
|
+
old_value = value;
|
|
83
|
+
value = null;
|
|
84
|
+
await tick();
|
|
85
|
+
value = old_value;
|
|
86
|
+
}
|
|
53
87
|
old_value = value;
|
|
54
|
-
|
|
55
|
-
await tick();
|
|
56
|
-
value = old_value;
|
|
88
|
+
old_subtitle = subtitle;
|
|
57
89
|
}
|
|
58
|
-
|
|
59
|
-
old_subtitle = subtitle;
|
|
90
|
+
updateValue();
|
|
60
91
|
});
|
|
61
92
|
</script>
|
|
62
93
|
|
|
@@ -70,15 +101,15 @@
|
|
|
70
101
|
subtitle={subtitle?.url}
|
|
71
102
|
is_stream={value.is_stream}
|
|
72
103
|
{autoplay}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
onplay={() => onplay?.()}
|
|
105
|
+
onpause={() => onpause?.()}
|
|
106
|
+
onstop={() => onstop?.()}
|
|
107
|
+
onend={() => onend?.()}
|
|
108
|
+
onloadedmetadata={() => {
|
|
78
109
|
// Deal with `<video>`'s `loadedmetadata` event as `VideoPreview`'s `load` event
|
|
79
110
|
// to represent not only the video is loaded but also the metadata is loaded
|
|
80
111
|
// so its dimensions (w/h) are known. This is used for Chatbot's auto scroll.
|
|
81
|
-
|
|
112
|
+
onload?.();
|
|
82
113
|
}}
|
|
83
114
|
mirror={false}
|
|
84
115
|
{label}
|
|
@@ -108,8 +139,8 @@
|
|
|
108
139
|
{#if buttons?.some((btn) => typeof btn === "string" && btn === "share")}
|
|
109
140
|
<ShareButton
|
|
110
141
|
{i18n}
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
onerror={(detail) => onerror?.(detail)}
|
|
143
|
+
onshare={(detail) => onshare?.(detail)}
|
|
113
144
|
{value}
|
|
114
145
|
formatter={async (value) => {
|
|
115
146
|
if (!value) return "";
|
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, onDestroy } from "svelte";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
interface Props {
|
|
5
|
+
videoElement: HTMLVideoElement;
|
|
6
|
+
trimmedDuration?: number | null;
|
|
7
|
+
dragStart?: number;
|
|
8
|
+
dragEnd?: number;
|
|
9
|
+
loadingTimeline?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
videoElement,
|
|
14
|
+
trimmedDuration = $bindable(null),
|
|
15
|
+
dragStart = $bindable(0),
|
|
16
|
+
dragEnd = $bindable(0),
|
|
17
|
+
loadingTimeline = $bindable(false)
|
|
18
|
+
}: Props = $props();
|
|
9
19
|
|
|
10
|
-
let thumbnails
|
|
20
|
+
let thumbnails = $state<string[]>([]);
|
|
11
21
|
let numberOfThumbnails = 10;
|
|
12
22
|
let intervalId: ReturnType<typeof setInterval> | undefined;
|
|
13
23
|
let videoDuration: number;
|
|
14
24
|
|
|
15
|
-
let leftHandlePosition = 0;
|
|
16
|
-
let rightHandlePosition = 100;
|
|
25
|
+
let leftHandlePosition = $state(0);
|
|
26
|
+
let rightHandlePosition = $state(100);
|
|
17
27
|
|
|
18
|
-
let dragging
|
|
28
|
+
let dragging = $state<string | null>(null);
|
|
19
29
|
|
|
20
30
|
const startDragging = (side: string | null): void => {
|
|
21
31
|
dragging = side;
|
|
22
32
|
};
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
let loadingTimelineValue = $derived(thumbnails.length !== numberOfThumbnails);
|
|
35
|
+
|
|
36
|
+
$effect(() => {
|
|
37
|
+
loadingTimeline = loadingTimelineValue;
|
|
38
|
+
});
|
|
25
39
|
|
|
26
40
|
const stopDragging = (): void => {
|
|
27
41
|
dragging = null;
|
|
@@ -149,7 +163,7 @@
|
|
|
149
163
|
</script>
|
|
150
164
|
|
|
151
165
|
<div class="container">
|
|
152
|
-
{#if
|
|
166
|
+
{#if loadingTimelineValue}
|
|
153
167
|
<div class="load-wrap">
|
|
154
168
|
<span aria-label="loading timeline" class="loader" />
|
|
155
169
|
</div>
|
|
@@ -158,20 +172,20 @@
|
|
|
158
172
|
<button
|
|
159
173
|
aria-label="start drag handle for trimming video"
|
|
160
174
|
class="handle left"
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
onmousedown={() => startDragging("left")}
|
|
176
|
+
onblur={stopDragging}
|
|
177
|
+
onkeydown={(e) => {
|
|
164
178
|
if (e.key === "ArrowLeft" || e.key == "ArrowRight") {
|
|
165
179
|
startDragging("left");
|
|
166
180
|
}
|
|
167
181
|
}}
|
|
168
182
|
style="left: {leftHandlePosition}%;"
|
|
169
|
-
|
|
183
|
+
></button>
|
|
170
184
|
|
|
171
185
|
<div
|
|
172
186
|
class="opaque-layer"
|
|
173
187
|
style="left: {leftHandlePosition}%; right: {100 - rightHandlePosition}%"
|
|
174
|
-
|
|
188
|
+
></div>
|
|
175
189
|
|
|
176
190
|
{#each thumbnails as thumbnail, i (i)}
|
|
177
191
|
<img src={thumbnail} alt={`frame-${i}`} draggable="false" />
|
|
@@ -179,15 +193,15 @@
|
|
|
179
193
|
<button
|
|
180
194
|
aria-label="end drag handle for trimming video"
|
|
181
195
|
class="handle right"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
196
|
+
onmousedown={() => startDragging("right")}
|
|
197
|
+
onblur={stopDragging}
|
|
198
|
+
onkeydown={(e) => {
|
|
185
199
|
if (e.key === "ArrowLeft" || e.key == "ArrowRight") {
|
|
186
200
|
startDragging("right");
|
|
187
201
|
}
|
|
188
202
|
}}
|
|
189
203
|
style="left: {rightHandlePosition}%;"
|
|
190
|
-
|
|
204
|
+
></button>
|
|
191
205
|
</div>
|
|
192
206
|
{/if}
|
|
193
207
|
</div>
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
interface Props {
|
|
5
|
+
current_volume?: number;
|
|
6
|
+
show_volume_slider?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
current_volume = $bindable(1),
|
|
11
|
+
show_volume_slider = $bindable(false)
|
|
12
|
+
}: Props = $props();
|
|
6
13
|
|
|
7
|
-
let volume_element: HTMLInputElement;
|
|
14
|
+
let volume_element: HTMLInputElement | undefined = $state();
|
|
8
15
|
|
|
9
16
|
onMount(() => {
|
|
10
17
|
adjustSlider();
|
|
@@ -19,7 +26,10 @@
|
|
|
19
26
|
}%, rgba(255, 255, 255, 0.3) ${current_volume * 100}%)`;
|
|
20
27
|
};
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
$effect(() => {
|
|
30
|
+
current_volume;
|
|
31
|
+
adjustSlider();
|
|
32
|
+
});
|
|
23
33
|
</script>
|
|
24
34
|
|
|
25
35
|
<input
|
|
@@ -31,8 +41,8 @@
|
|
|
31
41
|
max="1"
|
|
32
42
|
step="0.01"
|
|
33
43
|
value={current_volume}
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
onfocusout={() => (show_volume_slider = false)}
|
|
45
|
+
oninput={(e) => {
|
|
36
46
|
if (e.target instanceof HTMLInputElement) {
|
|
37
47
|
current_volume = parseFloat(e.target.value);
|
|
38
48
|
}
|