@gradio/video 0.10.3 → 0.10.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.
@@ -0,0 +1,229 @@
1
+ <script>import { createEventDispatcher } from "svelte";
2
+ import { Play, Pause, Maximise, Undo } from "@gradio/icons";
3
+ import Video from "./Video.svelte";
4
+ import VideoControls from "./VideoControls.svelte";
5
+ import { prepare_files } from "@gradio/client";
6
+ import { format_time } from "@gradio/utils";
7
+ export let root = "";
8
+ export let src;
9
+ export let subtitle = null;
10
+ export let mirror;
11
+ export let autoplay;
12
+ export let loop;
13
+ export let label = "test";
14
+ export let interactive = false;
15
+ export let handle_change = () => {
16
+ };
17
+ export let handle_reset_value = () => {
18
+ };
19
+ export let upload;
20
+ const dispatch = createEventDispatcher();
21
+ let time = 0;
22
+ let duration;
23
+ let paused = true;
24
+ let video;
25
+ let processingVideo = false;
26
+ function handleMove(e) {
27
+ if (!duration)
28
+ return;
29
+ if (e.type === "click") {
30
+ handle_click(e);
31
+ return;
32
+ }
33
+ if (e.type !== "touchmove" && !(e.buttons & 1))
34
+ return;
35
+ const clientX = e.type === "touchmove" ? e.touches[0].clientX : e.clientX;
36
+ const { left, right } = e.currentTarget.getBoundingClientRect();
37
+ time = duration * (clientX - left) / (right - left);
38
+ }
39
+ async function play_pause() {
40
+ if (document.fullscreenElement != video) {
41
+ const isPlaying = video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
42
+ if (!isPlaying) {
43
+ await video.play();
44
+ } else
45
+ video.pause();
46
+ }
47
+ }
48
+ function handle_click(e) {
49
+ const { left, right } = e.currentTarget.getBoundingClientRect();
50
+ time = duration * (e.clientX - left) / (right - left);
51
+ }
52
+ function handle_end() {
53
+ dispatch("stop");
54
+ dispatch("end");
55
+ }
56
+ const handle_trim_video = async (videoBlob) => {
57
+ let _video_blob = new File([videoBlob], "video.mp4");
58
+ const val = await prepare_files([_video_blob]);
59
+ let value = ((await upload(val, root))?.filter(Boolean))[0];
60
+ handle_change(value);
61
+ };
62
+ function open_full_screen() {
63
+ video.requestFullscreen();
64
+ }
65
+ </script>
66
+
67
+ <div class="wrap">
68
+ <div class="mirror-wrap" class:mirror>
69
+ <Video
70
+ {src}
71
+ preload="auto"
72
+ {autoplay}
73
+ {loop}
74
+ on:click={play_pause}
75
+ on:play
76
+ on:pause
77
+ on:ended={handle_end}
78
+ bind:currentTime={time}
79
+ bind:duration
80
+ bind:paused
81
+ bind:node={video}
82
+ data-testid={`${label}-player`}
83
+ {processingVideo}
84
+ on:load
85
+ >
86
+ <track kind="captions" src={subtitle} default />
87
+ </Video>
88
+ </div>
89
+
90
+ <div class="controls">
91
+ <div class="inner">
92
+ <span
93
+ role="button"
94
+ tabindex="0"
95
+ class="icon"
96
+ aria-label="play-pause-replay-button"
97
+ on:click={play_pause}
98
+ on:keydown={play_pause}
99
+ >
100
+ {#if time === duration}
101
+ <Undo />
102
+ {:else if paused}
103
+ <Play />
104
+ {:else}
105
+ <Pause />
106
+ {/if}
107
+ </span>
108
+
109
+ <span class="time">{format_time(time)} / {format_time(duration)}</span>
110
+
111
+ <!-- TODO: implement accessible video timeline for 4.0 -->
112
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
113
+ <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
114
+ <progress
115
+ value={time / duration || 0}
116
+ on:mousemove={handleMove}
117
+ on:touchmove|preventDefault={handleMove}
118
+ on:click|stopPropagation|preventDefault={handle_click}
119
+ />
120
+
121
+ <div
122
+ role="button"
123
+ tabindex="0"
124
+ class="icon"
125
+ aria-label="full-screen"
126
+ on:click={open_full_screen}
127
+ on:keypress={open_full_screen}
128
+ >
129
+ <Maximise />
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ {#if interactive}
135
+ <VideoControls
136
+ videoElement={video}
137
+ showRedo
138
+ {handle_trim_video}
139
+ {handle_reset_value}
140
+ bind:processingVideo
141
+ />
142
+ {/if}
143
+
144
+ <style>
145
+ span {
146
+ text-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
147
+ }
148
+
149
+ progress {
150
+ margin-right: var(--size-3);
151
+ border-radius: var(--radius-sm);
152
+ width: var(--size-full);
153
+ height: var(--size-2);
154
+ }
155
+
156
+ progress::-webkit-progress-bar {
157
+ border-radius: 2px;
158
+ background-color: rgba(255, 255, 255, 0.2);
159
+ overflow: hidden;
160
+ }
161
+
162
+ progress::-webkit-progress-value {
163
+ background-color: rgba(255, 255, 255, 0.9);
164
+ }
165
+
166
+ .mirror {
167
+ transform: scaleX(-1);
168
+ }
169
+
170
+ .mirror-wrap {
171
+ position: relative;
172
+ height: 100%;
173
+ width: 100%;
174
+ }
175
+
176
+ .controls {
177
+ position: absolute;
178
+ bottom: 0;
179
+ opacity: 0;
180
+ transition: 500ms;
181
+ margin: var(--size-2);
182
+ border-radius: var(--radius-md);
183
+ background: var(--color-grey-800);
184
+ padding: var(--size-2) var(--size-1);
185
+ width: calc(100% - 0.375rem * 2);
186
+ width: calc(100% - var(--size-2) * 2);
187
+ }
188
+ .wrap:hover .controls {
189
+ opacity: 1;
190
+ }
191
+
192
+ .inner {
193
+ display: flex;
194
+ justify-content: space-between;
195
+ align-items: center;
196
+ padding-right: var(--size-2);
197
+ padding-left: var(--size-2);
198
+ width: var(--size-full);
199
+ height: var(--size-full);
200
+ }
201
+
202
+ .icon {
203
+ display: flex;
204
+ justify-content: center;
205
+ cursor: pointer;
206
+ width: var(--size-6);
207
+ color: white;
208
+ }
209
+
210
+ .time {
211
+ flex-shrink: 0;
212
+ margin-right: var(--size-3);
213
+ margin-left: var(--size-3);
214
+ color: white;
215
+ font-size: var(--text-sm);
216
+ font-family: var(--font-mono);
217
+ }
218
+ .wrap {
219
+ position: relative;
220
+ background-color: var(--background-fill-secondary);
221
+ height: var(--size-full);
222
+ width: var(--size-full);
223
+ border-radius: var(--radius-xl);
224
+ }
225
+ .wrap :global(video) {
226
+ height: var(--size-full);
227
+ width: var(--size-full);
228
+ }
229
+ </style>
@@ -0,0 +1,33 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { FileData, Client } from "@gradio/client";
3
+ declare const __propDef: {
4
+ props: {
5
+ root?: string | undefined;
6
+ src: string;
7
+ subtitle?: (string | null) | undefined;
8
+ mirror: boolean;
9
+ autoplay: boolean;
10
+ loop: boolean;
11
+ label?: string | undefined;
12
+ interactive?: boolean | undefined;
13
+ handle_change?: ((video: FileData) => void) | undefined;
14
+ handle_reset_value?: (() => void) | undefined;
15
+ upload: Client["upload"];
16
+ };
17
+ events: {
18
+ play: CustomEvent<any>;
19
+ pause: CustomEvent<any>;
20
+ load: Event;
21
+ stop: CustomEvent<undefined>;
22
+ end: CustomEvent<undefined>;
23
+ } & {
24
+ [evt: string]: CustomEvent<any>;
25
+ };
26
+ slots: {};
27
+ };
28
+ export type PlayerProps = typeof __propDef.props;
29
+ export type PlayerEvents = typeof __propDef.events;
30
+ export type PlayerSlots = typeof __propDef.slots;
31
+ export default class Player extends SvelteComponent<PlayerProps, PlayerEvents, PlayerSlots> {
32
+ }
33
+ export {};
@@ -0,0 +1,126 @@
1
+ <script>import { createEventDispatcher } from "svelte";
2
+ import { loaded } from "./utils";
3
+ import { resolve_wasm_src } from "@gradio/wasm/svelte";
4
+ export let src = void 0;
5
+ export let muted = void 0;
6
+ export let playsinline = void 0;
7
+ export let preload = void 0;
8
+ export let autoplay = void 0;
9
+ export let controls = void 0;
10
+ export let currentTime = void 0;
11
+ export let duration = void 0;
12
+ export let paused = void 0;
13
+ export let node = void 0;
14
+ export let loop;
15
+ export let processingVideo = false;
16
+ let resolved_src;
17
+ let latest_src;
18
+ $: {
19
+ resolved_src = src;
20
+ latest_src = src;
21
+ const resolving_src = src;
22
+ resolve_wasm_src(resolving_src).then((s) => {
23
+ if (latest_src === resolving_src) {
24
+ resolved_src = s;
25
+ }
26
+ });
27
+ }
28
+ const dispatch = createEventDispatcher();
29
+ </script>
30
+
31
+ <!--
32
+ The spread operator with `$$props` or `$$restProps` can't be used here
33
+ to pass props from the parent component to the <video> element
34
+ because of its unexpected behavior: https://github.com/sveltejs/svelte/issues/7404
35
+ For example, if we add {...$$props} or {...$$restProps}, the boolean props aside it like `controls` will be compiled as string "true" or "false" on the actual DOM.
36
+ Then, even when `controls` is false, the compiled DOM would be `<video controls="false">` which is equivalent to `<video controls>` since the string "false" is even truthy.
37
+ -->
38
+ <div class:hidden={!processingVideo} class="overlay">
39
+ <span class="load-wrap">
40
+ <span class="loader" />
41
+ </span>
42
+ </div>
43
+ <video
44
+ src={resolved_src}
45
+ {muted}
46
+ {playsinline}
47
+ {preload}
48
+ {autoplay}
49
+ {controls}
50
+ {loop}
51
+ on:loadeddata={dispatch.bind(null, "loadeddata")}
52
+ on:click={dispatch.bind(null, "click")}
53
+ on:play={dispatch.bind(null, "play")}
54
+ on:pause={dispatch.bind(null, "pause")}
55
+ on:ended={dispatch.bind(null, "ended")}
56
+ on:mouseover={dispatch.bind(null, "mouseover")}
57
+ on:mouseout={dispatch.bind(null, "mouseout")}
58
+ on:focus={dispatch.bind(null, "focus")}
59
+ on:blur={dispatch.bind(null, "blur")}
60
+ on:load
61
+ bind:currentTime
62
+ bind:duration
63
+ bind:paused
64
+ bind:this={node}
65
+ use:loaded={{ autoplay: autoplay ?? false }}
66
+ data-testid={$$props["data-testid"]}
67
+ crossorigin="anonymous"
68
+ >
69
+ <slot />
70
+ </video>
71
+
72
+ <style>
73
+ .overlay {
74
+ position: absolute;
75
+ background-color: rgba(0, 0, 0, 0.4);
76
+ width: 100%;
77
+ height: 100%;
78
+ }
79
+
80
+ .hidden {
81
+ display: none;
82
+ }
83
+
84
+ .load-wrap {
85
+ display: flex;
86
+ justify-content: center;
87
+ align-items: center;
88
+ height: 100%;
89
+ }
90
+
91
+ .loader {
92
+ display: flex;
93
+ position: relative;
94
+ background-color: var(--border-color-accent-subdued);
95
+ animation: shadowPulse 2s linear infinite;
96
+ box-shadow:
97
+ -24px 0 var(--border-color-accent-subdued),
98
+ 24px 0 var(--border-color-accent-subdued);
99
+ margin: var(--spacing-md);
100
+ border-radius: 50%;
101
+ width: 10px;
102
+ height: 10px;
103
+ scale: 0.5;
104
+ }
105
+
106
+ @keyframes shadowPulse {
107
+ 33% {
108
+ box-shadow:
109
+ -24px 0 var(--border-color-accent-subdued),
110
+ 24px 0 #fff;
111
+ background: #fff;
112
+ }
113
+ 66% {
114
+ box-shadow:
115
+ -24px 0 #fff,
116
+ 24px 0 #fff;
117
+ background: var(--border-color-accent-subdued);
118
+ }
119
+ 100% {
120
+ box-shadow:
121
+ -24px 0 #fff,
122
+ 24px 0 var(--border-color-accent-subdued);
123
+ background: #fff;
124
+ }
125
+ }
126
+ </style>
@@ -0,0 +1,33 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { HTMLVideoAttributes } from "svelte/elements";
3
+ declare const __propDef: {
4
+ props: {
5
+ [x: string]: any;
6
+ src?: HTMLVideoAttributes["src"];
7
+ muted?: HTMLVideoAttributes["muted"];
8
+ playsinline?: HTMLVideoAttributes["playsinline"];
9
+ preload?: HTMLVideoAttributes["preload"];
10
+ autoplay?: HTMLVideoAttributes["autoplay"];
11
+ controls?: HTMLVideoAttributes["controls"];
12
+ currentTime?: number | undefined;
13
+ duration?: number | undefined;
14
+ paused?: boolean | undefined;
15
+ node?: HTMLVideoElement | undefined;
16
+ loop: boolean;
17
+ processingVideo?: boolean | undefined;
18
+ };
19
+ events: {
20
+ load: Event;
21
+ } & {
22
+ [evt: string]: CustomEvent<any>;
23
+ };
24
+ slots: {
25
+ default: {};
26
+ };
27
+ };
28
+ export type VideoProps = typeof __propDef.props;
29
+ export type VideoEvents = typeof __propDef.events;
30
+ export type VideoSlots = typeof __propDef.slots;
31
+ export default class Video extends SvelteComponent<VideoProps, VideoEvents, VideoSlots> {
32
+ }
33
+ export {};
@@ -0,0 +1,200 @@
1
+ <script>import { Undo, Trim } from "@gradio/icons";
2
+ import VideoTimeline from "./VideoTimeline.svelte";
3
+ import { trimVideo } from "./utils";
4
+ import { FFmpeg } from "@ffmpeg/ffmpeg";
5
+ import loadFfmpeg from "./utils";
6
+ import { onMount } from "svelte";
7
+ import { format_time } from "@gradio/utils";
8
+ export let videoElement;
9
+ export let showRedo = false;
10
+ export let interactive = true;
11
+ export let mode = "";
12
+ export let handle_reset_value;
13
+ export let handle_trim_video;
14
+ export let processingVideo = false;
15
+ let ffmpeg;
16
+ onMount(async () => {
17
+ ffmpeg = await loadFfmpeg();
18
+ });
19
+ $:
20
+ if (mode === "edit" && trimmedDuration === null && videoElement)
21
+ trimmedDuration = videoElement.duration;
22
+ let trimmedDuration = null;
23
+ let dragStart = 0;
24
+ let dragEnd = 0;
25
+ let loadingTimeline = false;
26
+ const toggleTrimmingMode = () => {
27
+ if (mode === "edit") {
28
+ mode = "";
29
+ trimmedDuration = videoElement.duration;
30
+ } else {
31
+ mode = "edit";
32
+ }
33
+ };
34
+ </script>
35
+
36
+ <div class="container">
37
+ {#if mode === "edit"}
38
+ <div class="timeline-wrapper">
39
+ <VideoTimeline
40
+ {videoElement}
41
+ bind:dragStart
42
+ bind:dragEnd
43
+ bind:trimmedDuration
44
+ bind:loadingTimeline
45
+ />
46
+ </div>
47
+ {/if}
48
+
49
+ <div class="controls" data-testid="waveform-controls">
50
+ {#if mode === "edit" && trimmedDuration !== null}
51
+ <time
52
+ aria-label="duration of selected region in seconds"
53
+ class:hidden={loadingTimeline}>{format_time(trimmedDuration)}</time
54
+ >
55
+ {:else}
56
+ <div />
57
+ {/if}
58
+
59
+ <div class="settings-wrapper">
60
+ {#if showRedo && mode === ""}
61
+ <button
62
+ class="action icon"
63
+ disabled={processingVideo}
64
+ aria-label="Reset video to initial value"
65
+ on:click={() => {
66
+ handle_reset_value();
67
+ mode = "";
68
+ }}
69
+ >
70
+ <Undo />
71
+ </button>
72
+ {/if}
73
+
74
+ {#if interactive}
75
+ {#if mode === ""}
76
+ <button
77
+ disabled={processingVideo}
78
+ class="action icon"
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>
108
+ </div>
109
+ </div>
110
+
111
+ <style>
112
+ .container {
113
+ width: 100%;
114
+ }
115
+ time {
116
+ color: var(--color-accent);
117
+ font-weight: bold;
118
+ padding-left: var(--spacing-xs);
119
+ }
120
+
121
+ .timeline-wrapper {
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ width: 100%;
126
+ }
127
+ .settings-wrapper {
128
+ display: flex;
129
+ justify-self: self-end;
130
+ }
131
+ .text-button {
132
+ border: 1px solid var(--neutral-400);
133
+ border-radius: var(--radius-sm);
134
+ font-weight: 300;
135
+ font-size: var(--size-3);
136
+ text-align: center;
137
+ color: var(--neutral-400);
138
+ height: var(--size-5);
139
+ font-weight: bold;
140
+ padding: 0 5px;
141
+ margin-left: 5px;
142
+ }
143
+ .hidden {
144
+ display: none;
145
+ }
146
+
147
+ .text-button:hover,
148
+ .text-button:focus {
149
+ color: var(--color-accent);
150
+ border-color: var(--color-accent);
151
+ }
152
+
153
+ .controls {
154
+ display: grid;
155
+ grid-template-columns: 1fr 1fr;
156
+ margin: var(--spacing-lg);
157
+ overflow: hidden;
158
+ text-align: left;
159
+ }
160
+
161
+ @media (max-width: 320px) {
162
+ .controls {
163
+ display: flex;
164
+ flex-wrap: wrap;
165
+ }
166
+
167
+ .controls * {
168
+ margin: var(--spacing-sm);
169
+ }
170
+
171
+ .controls .text-button {
172
+ margin-left: 0;
173
+ }
174
+ }
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
+
196
+ .container {
197
+ display: flex;
198
+ flex-direction: column;
199
+ }
200
+ </style>
@@ -0,0 +1,22 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ videoElement: HTMLVideoElement;
5
+ showRedo?: boolean | undefined;
6
+ interactive?: boolean | undefined;
7
+ mode?: string | undefined;
8
+ handle_reset_value: () => void;
9
+ handle_trim_video: (videoBlob: Blob) => void;
10
+ processingVideo?: boolean | undefined;
11
+ };
12
+ events: {
13
+ [evt: string]: CustomEvent<any>;
14
+ };
15
+ slots: {};
16
+ };
17
+ export type VideoControlsProps = typeof __propDef.props;
18
+ export type VideoControlsEvents = typeof __propDef.events;
19
+ export type VideoControlsSlots = typeof __propDef.slots;
20
+ export default class VideoControls extends SvelteComponent<VideoControlsProps, VideoControlsEvents, VideoControlsSlots> {
21
+ }
22
+ export {};