@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.
- package/CHANGELOG.md +17 -0
- package/Video.test.ts +2 -2
- 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 +34 -15
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script>import { createEventDispatcher, afterUpdate, tick } from "svelte";
|
|
2
|
+
import { BlockLabel, Empty, IconButton, ShareButton } from "@gradio/atoms";
|
|
3
|
+
import { Video, Download } from "@gradio/icons";
|
|
4
|
+
import { uploadToHuggingFace } from "@gradio/utils";
|
|
5
|
+
import { DownloadLink } from "@gradio/wasm/svelte";
|
|
6
|
+
import Player from "./Player.svelte";
|
|
7
|
+
export let value = null;
|
|
8
|
+
export let subtitle = null;
|
|
9
|
+
export let label = void 0;
|
|
10
|
+
export let show_label = true;
|
|
11
|
+
export let autoplay;
|
|
12
|
+
export let show_share_button = true;
|
|
13
|
+
export let show_download_button = true;
|
|
14
|
+
export let loop;
|
|
15
|
+
export let i18n;
|
|
16
|
+
export let upload;
|
|
17
|
+
let old_value = null;
|
|
18
|
+
let old_subtitle = null;
|
|
19
|
+
const dispatch = createEventDispatcher();
|
|
20
|
+
$:
|
|
21
|
+
value && dispatch("change", value);
|
|
22
|
+
afterUpdate(async () => {
|
|
23
|
+
if (value !== old_value && subtitle !== old_subtitle && old_subtitle !== null) {
|
|
24
|
+
old_value = value;
|
|
25
|
+
value = null;
|
|
26
|
+
await tick();
|
|
27
|
+
value = old_value;
|
|
28
|
+
}
|
|
29
|
+
old_value = value;
|
|
30
|
+
old_subtitle = subtitle;
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<BlockLabel {show_label} Icon={Video} label={label || "Video"} />
|
|
35
|
+
{#if value === null || value.url === undefined}
|
|
36
|
+
<Empty unpadded_box={true} size="large"><Video /></Empty>
|
|
37
|
+
{:else}
|
|
38
|
+
{#key value.url}
|
|
39
|
+
<Player
|
|
40
|
+
src={value.url}
|
|
41
|
+
subtitle={subtitle?.url}
|
|
42
|
+
{autoplay}
|
|
43
|
+
on:play
|
|
44
|
+
on:pause
|
|
45
|
+
on:stop
|
|
46
|
+
on:end
|
|
47
|
+
on:load
|
|
48
|
+
mirror={false}
|
|
49
|
+
{label}
|
|
50
|
+
{loop}
|
|
51
|
+
interactive={false}
|
|
52
|
+
{upload}
|
|
53
|
+
/>
|
|
54
|
+
{/key}
|
|
55
|
+
<div class="icon-buttons" data-testid="download-div">
|
|
56
|
+
{#if show_download_button}
|
|
57
|
+
<DownloadLink href={value.url} download={value.orig_name || value.path}>
|
|
58
|
+
<IconButton Icon={Download} label="Download" />
|
|
59
|
+
</DownloadLink>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if show_share_button}
|
|
62
|
+
<ShareButton
|
|
63
|
+
{i18n}
|
|
64
|
+
on:error
|
|
65
|
+
on:share
|
|
66
|
+
{value}
|
|
67
|
+
formatter={async (value) => {
|
|
68
|
+
if (!value) return "";
|
|
69
|
+
let url = await uploadToHuggingFace(value.data, "url");
|
|
70
|
+
return url;
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
{/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>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { FileData, Client } from "@gradio/client";
|
|
3
|
+
import type { I18nFormatter } from "js/core/src/gradio_helper";
|
|
4
|
+
declare const __propDef: {
|
|
5
|
+
props: {
|
|
6
|
+
value?: (FileData | null) | undefined;
|
|
7
|
+
subtitle?: (FileData | null) | undefined;
|
|
8
|
+
label?: string | undefined;
|
|
9
|
+
show_label?: boolean | undefined;
|
|
10
|
+
autoplay: boolean;
|
|
11
|
+
show_share_button?: boolean | undefined;
|
|
12
|
+
show_download_button?: boolean | undefined;
|
|
13
|
+
loop: boolean;
|
|
14
|
+
i18n: I18nFormatter;
|
|
15
|
+
upload: Client["upload"];
|
|
16
|
+
};
|
|
17
|
+
events: {
|
|
18
|
+
play: CustomEvent<any>;
|
|
19
|
+
pause: CustomEvent<any>;
|
|
20
|
+
stop: CustomEvent<any>;
|
|
21
|
+
end: CustomEvent<any>;
|
|
22
|
+
load: Event;
|
|
23
|
+
error: CustomEvent<string>;
|
|
24
|
+
share: CustomEvent<import("@gradio/utils").ShareData>;
|
|
25
|
+
change: CustomEvent<FileData>;
|
|
26
|
+
} & {
|
|
27
|
+
[evt: string]: CustomEvent<any>;
|
|
28
|
+
};
|
|
29
|
+
slots: {};
|
|
30
|
+
};
|
|
31
|
+
export type VideoPreviewProps = typeof __propDef.props;
|
|
32
|
+
export type VideoPreviewEvents = typeof __propDef.events;
|
|
33
|
+
export type VideoPreviewSlots = typeof __propDef.slots;
|
|
34
|
+
export default class VideoPreview extends SvelteComponent<VideoPreviewProps, VideoPreviewEvents, VideoPreviewSlots> {
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<script>import { onMount, onDestroy } from "svelte";
|
|
2
|
+
export let videoElement;
|
|
3
|
+
export let trimmedDuration;
|
|
4
|
+
export let dragStart;
|
|
5
|
+
export let dragEnd;
|
|
6
|
+
export let loadingTimeline;
|
|
7
|
+
let thumbnails = [];
|
|
8
|
+
let numberOfThumbnails = 10;
|
|
9
|
+
let intervalId;
|
|
10
|
+
let videoDuration;
|
|
11
|
+
let leftHandlePosition = 0;
|
|
12
|
+
let rightHandlePosition = 100;
|
|
13
|
+
let dragging = null;
|
|
14
|
+
const startDragging = (side) => {
|
|
15
|
+
dragging = side;
|
|
16
|
+
};
|
|
17
|
+
$:
|
|
18
|
+
loadingTimeline = thumbnails.length !== numberOfThumbnails;
|
|
19
|
+
const stopDragging = () => {
|
|
20
|
+
dragging = null;
|
|
21
|
+
};
|
|
22
|
+
const drag = (event, distance) => {
|
|
23
|
+
if (dragging) {
|
|
24
|
+
const timeline = document.getElementById("timeline");
|
|
25
|
+
if (!timeline)
|
|
26
|
+
return;
|
|
27
|
+
const rect = timeline.getBoundingClientRect();
|
|
28
|
+
let newPercentage = (event.clientX - rect.left) / rect.width * 100;
|
|
29
|
+
if (distance) {
|
|
30
|
+
newPercentage = dragging === "left" ? leftHandlePosition + distance : rightHandlePosition + distance;
|
|
31
|
+
} else {
|
|
32
|
+
newPercentage = (event.clientX - rect.left) / rect.width * 100;
|
|
33
|
+
}
|
|
34
|
+
newPercentage = Math.max(0, Math.min(newPercentage, 100));
|
|
35
|
+
if (dragging === "left") {
|
|
36
|
+
leftHandlePosition = Math.min(newPercentage, rightHandlePosition);
|
|
37
|
+
const newTimeLeft = leftHandlePosition / 100 * videoDuration;
|
|
38
|
+
videoElement.currentTime = newTimeLeft;
|
|
39
|
+
dragStart = newTimeLeft;
|
|
40
|
+
} else if (dragging === "right") {
|
|
41
|
+
rightHandlePosition = Math.max(newPercentage, leftHandlePosition);
|
|
42
|
+
const newTimeRight = rightHandlePosition / 100 * videoDuration;
|
|
43
|
+
videoElement.currentTime = newTimeRight;
|
|
44
|
+
dragEnd = newTimeRight;
|
|
45
|
+
}
|
|
46
|
+
const startTime = leftHandlePosition / 100 * videoDuration;
|
|
47
|
+
const endTime = rightHandlePosition / 100 * videoDuration;
|
|
48
|
+
trimmedDuration = endTime - startTime;
|
|
49
|
+
leftHandlePosition = leftHandlePosition;
|
|
50
|
+
rightHandlePosition = rightHandlePosition;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const moveHandle = (e) => {
|
|
54
|
+
if (dragging) {
|
|
55
|
+
const distance = 1 / videoDuration * 100;
|
|
56
|
+
if (e.key === "ArrowLeft") {
|
|
57
|
+
drag({ clientX: 0 }, -distance);
|
|
58
|
+
} else if (e.key === "ArrowRight") {
|
|
59
|
+
drag({ clientX: 0 }, distance);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const generateThumbnail = () => {
|
|
64
|
+
const canvas = document.createElement("canvas");
|
|
65
|
+
const ctx = canvas.getContext("2d");
|
|
66
|
+
if (!ctx)
|
|
67
|
+
return;
|
|
68
|
+
canvas.width = videoElement.videoWidth;
|
|
69
|
+
canvas.height = videoElement.videoHeight;
|
|
70
|
+
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
|
71
|
+
const thumbnail = canvas.toDataURL("image/jpeg", 0.7);
|
|
72
|
+
thumbnails = [...thumbnails, thumbnail];
|
|
73
|
+
};
|
|
74
|
+
onMount(() => {
|
|
75
|
+
const loadMetadata = () => {
|
|
76
|
+
videoDuration = videoElement.duration;
|
|
77
|
+
const interval = videoDuration / numberOfThumbnails;
|
|
78
|
+
let captures = 0;
|
|
79
|
+
const onSeeked = () => {
|
|
80
|
+
generateThumbnail();
|
|
81
|
+
captures++;
|
|
82
|
+
if (captures < numberOfThumbnails) {
|
|
83
|
+
videoElement.currentTime += interval;
|
|
84
|
+
} else {
|
|
85
|
+
videoElement.removeEventListener("seeked", onSeeked);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
videoElement.addEventListener("seeked", onSeeked);
|
|
89
|
+
videoElement.currentTime = 0;
|
|
90
|
+
};
|
|
91
|
+
if (videoElement.readyState >= 1) {
|
|
92
|
+
loadMetadata();
|
|
93
|
+
} else {
|
|
94
|
+
videoElement.addEventListener("loadedmetadata", loadMetadata);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
onDestroy(() => {
|
|
98
|
+
window.removeEventListener("mousemove", drag);
|
|
99
|
+
window.removeEventListener("mouseup", stopDragging);
|
|
100
|
+
window.removeEventListener("keydown", moveHandle);
|
|
101
|
+
if (intervalId !== void 0) {
|
|
102
|
+
clearInterval(intervalId);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
onMount(() => {
|
|
106
|
+
window.addEventListener("mousemove", drag);
|
|
107
|
+
window.addEventListener("mouseup", stopDragging);
|
|
108
|
+
window.addEventListener("keydown", moveHandle);
|
|
109
|
+
});
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<div class="container">
|
|
113
|
+
{#if loadingTimeline}
|
|
114
|
+
<div class="load-wrap">
|
|
115
|
+
<span aria-label="loading timeline" class="loader" />
|
|
116
|
+
</div>
|
|
117
|
+
{:else}
|
|
118
|
+
<div id="timeline" class="thumbnail-wrapper">
|
|
119
|
+
<button
|
|
120
|
+
aria-label="start drag handle for trimming video"
|
|
121
|
+
class="handle left"
|
|
122
|
+
on:mousedown={() => startDragging("left")}
|
|
123
|
+
on:blur={stopDragging}
|
|
124
|
+
on:keydown={(e) => {
|
|
125
|
+
if (e.key === "ArrowLeft" || e.key == "ArrowRight") {
|
|
126
|
+
startDragging("left");
|
|
127
|
+
}
|
|
128
|
+
}}
|
|
129
|
+
style="left: {leftHandlePosition}%;"
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
<div
|
|
133
|
+
class="opaque-layer"
|
|
134
|
+
style="left: {leftHandlePosition}%; right: {100 - rightHandlePosition}%"
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
{#each thumbnails as thumbnail, i (i)}
|
|
138
|
+
<img src={thumbnail} alt={`frame-${i}`} draggable="false" />
|
|
139
|
+
{/each}
|
|
140
|
+
<button
|
|
141
|
+
aria-label="end drag handle for trimming video"
|
|
142
|
+
class="handle right"
|
|
143
|
+
on:mousedown={() => startDragging("right")}
|
|
144
|
+
on:blur={stopDragging}
|
|
145
|
+
on:keydown={(e) => {
|
|
146
|
+
if (e.key === "ArrowLeft" || e.key == "ArrowRight") {
|
|
147
|
+
startDragging("right");
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
style="left: {rightHandlePosition}%;"
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
{/if}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<style>
|
|
157
|
+
.load-wrap {
|
|
158
|
+
display: flex;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
align-items: center;
|
|
161
|
+
height: 100%;
|
|
162
|
+
}
|
|
163
|
+
.loader {
|
|
164
|
+
display: flex;
|
|
165
|
+
position: relative;
|
|
166
|
+
background-color: var(--border-color-accent-subdued);
|
|
167
|
+
animation: shadowPulse 2s linear infinite;
|
|
168
|
+
box-shadow:
|
|
169
|
+
-24px 0 var(--border-color-accent-subdued),
|
|
170
|
+
24px 0 var(--border-color-accent-subdued);
|
|
171
|
+
margin: var(--spacing-md);
|
|
172
|
+
border-radius: 50%;
|
|
173
|
+
width: 10px;
|
|
174
|
+
height: 10px;
|
|
175
|
+
scale: 0.5;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes shadowPulse {
|
|
179
|
+
33% {
|
|
180
|
+
box-shadow:
|
|
181
|
+
-24px 0 var(--border-color-accent-subdued),
|
|
182
|
+
24px 0 #fff;
|
|
183
|
+
background: #fff;
|
|
184
|
+
}
|
|
185
|
+
66% {
|
|
186
|
+
box-shadow:
|
|
187
|
+
-24px 0 #fff,
|
|
188
|
+
24px 0 #fff;
|
|
189
|
+
background: var(--border-color-accent-subdued);
|
|
190
|
+
}
|
|
191
|
+
100% {
|
|
192
|
+
box-shadow:
|
|
193
|
+
-24px 0 #fff,
|
|
194
|
+
24px 0 var(--border-color-accent-subdued);
|
|
195
|
+
background: #fff;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.container {
|
|
200
|
+
display: flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
margin: var(--spacing-lg) var(--spacing-lg) 0 var(--spacing-lg);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#timeline {
|
|
208
|
+
display: flex;
|
|
209
|
+
height: var(--size-10);
|
|
210
|
+
flex: 1;
|
|
211
|
+
position: relative;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
img {
|
|
215
|
+
flex: 1 1 auto;
|
|
216
|
+
min-width: 0;
|
|
217
|
+
object-fit: cover;
|
|
218
|
+
height: var(--size-12);
|
|
219
|
+
border: 1px solid var(--block-border-color);
|
|
220
|
+
user-select: none;
|
|
221
|
+
z-index: 1;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.handle {
|
|
225
|
+
width: 3px;
|
|
226
|
+
background-color: var(--color-accent);
|
|
227
|
+
cursor: ew-resize;
|
|
228
|
+
height: var(--size-12);
|
|
229
|
+
z-index: 3;
|
|
230
|
+
position: absolute;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.opaque-layer {
|
|
234
|
+
background-color: rgba(230, 103, 40, 0.25);
|
|
235
|
+
border: 1px solid var(--color-accent);
|
|
236
|
+
height: var(--size-12);
|
|
237
|
+
position: absolute;
|
|
238
|
+
z-index: 2;
|
|
239
|
+
}
|
|
240
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
videoElement: HTMLVideoElement;
|
|
5
|
+
trimmedDuration: number | null;
|
|
6
|
+
dragStart: number;
|
|
7
|
+
dragEnd: number;
|
|
8
|
+
loadingTimeline: boolean;
|
|
9
|
+
};
|
|
10
|
+
events: {
|
|
11
|
+
[evt: string]: CustomEvent<any>;
|
|
12
|
+
};
|
|
13
|
+
slots: {};
|
|
14
|
+
};
|
|
15
|
+
export type VideoTimelineProps = typeof __propDef.props;
|
|
16
|
+
export type VideoTimelineEvents = typeof __propDef.events;
|
|
17
|
+
export type VideoTimelineSlots = typeof __propDef.slots;
|
|
18
|
+
export default class VideoTimeline extends SvelteComponent<VideoTimelineProps, VideoTimelineEvents, VideoTimelineSlots> {
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Video } from "./Video.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Video } from "./Video.svelte";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
|
2
|
+
export declare const prettyBytes: (bytes: number) => string;
|
|
3
|
+
export declare const playable: () => boolean;
|
|
4
|
+
export declare function loaded(node: HTMLVideoElement, { autoplay }: {
|
|
5
|
+
autoplay: boolean;
|
|
6
|
+
}): any;
|
|
7
|
+
export default function loadFfmpeg(): Promise<FFmpeg>;
|
|
8
|
+
export declare function blob_to_data_url(blob: Blob): Promise<string>;
|
|
9
|
+
export declare function trimVideo(ffmpeg: FFmpeg, startTime: number, endTime: number, videoElement: HTMLVideoElement): Promise<any>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { toBlobURL } from "@ffmpeg/util";
|
|
2
|
+
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
|
3
|
+
import { lookup } from "mrmime";
|
|
4
|
+
export const prettyBytes = (bytes) => {
|
|
5
|
+
let units = ["B", "KB", "MB", "GB", "PB"];
|
|
6
|
+
let i = 0;
|
|
7
|
+
while (bytes > 1024) {
|
|
8
|
+
bytes /= 1024;
|
|
9
|
+
i++;
|
|
10
|
+
}
|
|
11
|
+
let unit = units[i];
|
|
12
|
+
return bytes.toFixed(1) + " " + unit;
|
|
13
|
+
};
|
|
14
|
+
export const playable = () => {
|
|
15
|
+
// TODO: Fix this
|
|
16
|
+
// let video_element = document.createElement("video");
|
|
17
|
+
// let mime_type = mime.lookup(filename);
|
|
18
|
+
// return video_element.canPlayType(mime_type) != "";
|
|
19
|
+
return true; // FIX BEFORE COMMIT - mime import causing issues
|
|
20
|
+
};
|
|
21
|
+
export function loaded(node, { autoplay }) {
|
|
22
|
+
async function handle_playback() {
|
|
23
|
+
if (!autoplay)
|
|
24
|
+
return;
|
|
25
|
+
await node.play();
|
|
26
|
+
}
|
|
27
|
+
node.addEventListener("loadeddata", handle_playback);
|
|
28
|
+
return {
|
|
29
|
+
destroy() {
|
|
30
|
+
node.removeEventListener("loadeddata", handle_playback);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export default async function loadFfmpeg() {
|
|
35
|
+
const ffmpeg = new FFmpeg();
|
|
36
|
+
const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.4/dist/esm";
|
|
37
|
+
await ffmpeg.load({
|
|
38
|
+
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
|
|
39
|
+
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm")
|
|
40
|
+
});
|
|
41
|
+
return ffmpeg;
|
|
42
|
+
}
|
|
43
|
+
export function blob_to_data_url(blob) {
|
|
44
|
+
return new Promise((fulfill, reject) => {
|
|
45
|
+
let reader = new FileReader();
|
|
46
|
+
reader.onerror = reject;
|
|
47
|
+
reader.onload = () => fulfill(reader.result);
|
|
48
|
+
reader.readAsDataURL(blob);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function trimVideo(ffmpeg, startTime, endTime, videoElement) {
|
|
52
|
+
const videoUrl = videoElement.src;
|
|
53
|
+
const mimeType = lookup(videoElement.src) || "video/mp4";
|
|
54
|
+
const blobUrl = await toBlobURL(videoUrl, mimeType);
|
|
55
|
+
const response = await fetch(blobUrl);
|
|
56
|
+
const vidBlob = await response.blob();
|
|
57
|
+
const type = getVideoExtensionFromMimeType(mimeType) || "mp4";
|
|
58
|
+
const inputName = `input.${type}`;
|
|
59
|
+
const outputName = `output.${type}`;
|
|
60
|
+
try {
|
|
61
|
+
if (startTime === 0 && endTime === 0) {
|
|
62
|
+
return vidBlob;
|
|
63
|
+
}
|
|
64
|
+
await ffmpeg.writeFile(inputName, new Uint8Array(await vidBlob.arrayBuffer()));
|
|
65
|
+
let command = [
|
|
66
|
+
"-i",
|
|
67
|
+
inputName,
|
|
68
|
+
...(startTime !== 0 ? ["-ss", startTime.toString()] : []),
|
|
69
|
+
...(endTime !== 0 ? ["-to", endTime.toString()] : []),
|
|
70
|
+
"-c:a",
|
|
71
|
+
"copy",
|
|
72
|
+
outputName
|
|
73
|
+
];
|
|
74
|
+
await ffmpeg.exec(command);
|
|
75
|
+
const outputData = await ffmpeg.readFile(outputName);
|
|
76
|
+
const outputBlob = new Blob([outputData], {
|
|
77
|
+
type: `video/${type}`
|
|
78
|
+
});
|
|
79
|
+
return outputBlob;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error("Error initializing FFmpeg:", error);
|
|
83
|
+
return vidBlob;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const getVideoExtensionFromMimeType = (mimeType) => {
|
|
87
|
+
const videoMimeToExtensionMap = {
|
|
88
|
+
"video/mp4": "mp4",
|
|
89
|
+
"video/webm": "webm",
|
|
90
|
+
"video/ogg": "ogv",
|
|
91
|
+
"video/quicktime": "mov",
|
|
92
|
+
"video/x-msvideo": "avi",
|
|
93
|
+
"video/x-matroska": "mkv",
|
|
94
|
+
"video/mpeg": "mpeg",
|
|
95
|
+
"video/3gpp": "3gp",
|
|
96
|
+
"video/3gpp2": "3g2",
|
|
97
|
+
"video/h261": "h261",
|
|
98
|
+
"video/h263": "h263",
|
|
99
|
+
"video/h264": "h264",
|
|
100
|
+
"video/jpeg": "jpgv",
|
|
101
|
+
"video/jpm": "jpm",
|
|
102
|
+
"video/mj2": "mj2",
|
|
103
|
+
"video/mpv": "mpv",
|
|
104
|
+
"video/vnd.ms-playready.media.pyv": "pyv",
|
|
105
|
+
"video/vnd.uvvu.mp4": "uvu",
|
|
106
|
+
"video/vnd.vivo": "viv",
|
|
107
|
+
"video/x-f4v": "f4v",
|
|
108
|
+
"video/x-fli": "fli",
|
|
109
|
+
"video/x-flv": "flv",
|
|
110
|
+
"video/x-m4v": "m4v",
|
|
111
|
+
"video/x-ms-asf": "asf",
|
|
112
|
+
"video/x-ms-wm": "wm",
|
|
113
|
+
"video/x-ms-wmv": "wmv",
|
|
114
|
+
"video/x-ms-wmx": "wmx",
|
|
115
|
+
"video/x-ms-wvx": "wvx",
|
|
116
|
+
"video/x-sgi-movie": "movie",
|
|
117
|
+
"video/x-smv": "smv"
|
|
118
|
+
};
|
|
119
|
+
return videoMimeToExtensionMap[mimeType] || null;
|
|
120
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/video",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -10,24 +10,43 @@
|
|
|
10
10
|
"@ffmpeg/ffmpeg": "^0.12.7",
|
|
11
11
|
"@ffmpeg/util": "^0.12.1",
|
|
12
12
|
"mrmime": "^2.0.0",
|
|
13
|
-
"@gradio/atoms": "^0.8.
|
|
14
|
-
"@gradio/
|
|
15
|
-
"@gradio/
|
|
16
|
-
"@gradio/
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/utils": "^0.6.
|
|
19
|
-
"@gradio/
|
|
20
|
-
"@gradio/
|
|
13
|
+
"@gradio/atoms": "^0.8.1",
|
|
14
|
+
"@gradio/icons": "^0.7.2",
|
|
15
|
+
"@gradio/image": "^0.15.1",
|
|
16
|
+
"@gradio/client": "^1.5.2",
|
|
17
|
+
"@gradio/statustracker": "^0.7.6",
|
|
18
|
+
"@gradio/utils": "^0.6.1",
|
|
19
|
+
"@gradio/upload": "^0.12.4",
|
|
20
|
+
"@gradio/wasm": "^0.13.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@gradio/preview": "^0.11.
|
|
23
|
+
"@gradio/preview": "^0.11.1"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
|
-
".": "./
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
".": {
|
|
28
|
+
"gradio": "./index.ts",
|
|
29
|
+
"svelte": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./example": {
|
|
33
|
+
"gradio": "./Example.svelte",
|
|
34
|
+
"svelte": "./dist/Example.svelte",
|
|
35
|
+
"types": "./dist/Example.svelte.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./shared": {
|
|
38
|
+
"gradio": "./shared/index.ts",
|
|
39
|
+
"svelte": "./dist/shared/index.js",
|
|
40
|
+
"types": "./dist/shared/index.d.ts"
|
|
41
|
+
},
|
|
42
|
+
"./base": {
|
|
43
|
+
"gradio": "./shared/VideoPreview.svelte",
|
|
44
|
+
"svelte": "./dist/shared/VideoPreview.svelte",
|
|
45
|
+
"types": "./dist/shared/VideoPreview.svelte.d.ts"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"svelte": "^4.0.0"
|
|
31
50
|
},
|
|
32
51
|
"main": "index.ts",
|
|
33
52
|
"main_changeset": true,
|