@gradio/video 0.10.2 → 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.
- package/CHANGELOG.md +35 -0
- package/Video.test.ts +3 -3
- package/dist/Example.svelte +73 -0
- package/dist/Example.svelte.d.ts +23 -0
- package/dist/Index.svelte +176 -0
- package/dist/Index.svelte.d.ts +157 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/shared/InteractiveVideo.svelte +144 -0
- package/dist/shared/InteractiveVideo.svelte.d.ts +48 -0
- package/dist/shared/Player.svelte +229 -0
- package/dist/shared/Player.svelte.d.ts +33 -0
- package/dist/shared/Video.svelte +126 -0
- package/dist/shared/Video.svelte.d.ts +33 -0
- package/dist/shared/VideoControls.svelte +200 -0
- package/dist/shared/VideoControls.svelte.d.ts +22 -0
- package/dist/shared/VideoPreview.svelte +85 -0
- package/dist/shared/VideoPreview.svelte.d.ts +36 -0
- package/dist/shared/VideoTimeline.svelte +240 -0
- package/dist/shared/VideoTimeline.svelte.d.ts +20 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/utils.d.ts +9 -0
- package/dist/shared/utils.js +120 -0
- package/package.json +40 -16
- package/shared/VideoPreview.svelte +1 -1
|
@@ -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 {};
|