Spaces:
Sleeping
Sleeping
File size: 3,763 Bytes
bd5fab7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
<!--
@component
This component provides the user interface for uploading two videos side-by-side.
It acts as the "input" mode for the main VideoSlider component. It uses two
instances of the InteractiveVideo component to handle the individual uploads.
-->
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { FileData, Client } from "@gradio/client";
import type { I18nFormatter } from "@gradio/utils";
import type { WebcamOptions } from "./utils";
import InteractiveVideo from "./InteractiveVideo.svelte";
import { BlockLabel } from "@gradio/atoms";
import { Video as VideoIcon } from "@gradio/icons";
// ------------------
// Props
// ------------------
/** The core value: a tuple containing the two uploaded video files. */
export let value: [FileData | null, FileData | null] = [null, null];
export let label: string | undefined = undefined;
export let show_label: boolean;
export let root: string;
export let i18n: I18nFormatter;
export let max_file_size: number | null = null;
export let upload: Client["upload"];
export let stream_handler: Client["stream"];
export let autoplay: boolean;
export let loop: boolean;
const dispatch = createEventDispatcher<{
upload: [FileData | null, FileData | null];
clear: void;
error: string;
}>();
/** Default options for webcam recording, passed down to the child component. */
const webcam_options: WebcamOptions = {
mirror: true,
constraints: {}
};
/**
* Handles the 'change' event from either of the child InteractiveVideo components.
* It updates the correct slot in the `value` tuple and dispatches an event.
* @param detail The new FileData object from the child component.
* @param slot The index (0 for left, 1 for right) of the video that changed.
*/
function handle_change(
detail: FileData | null,
slot: 0 | 1
): void {
const new_value: [FileData | null, FileData | null] = [...value];
new_value[slot] = detail;
value = new_value;
if (value[0] === null && value[1] === null) {
dispatch("clear");
} else {
dispatch("upload", new_value);
}
}
</script>
<BlockLabel {show_label} Icon={VideoIcon} label={label || "Video Slider"} />
<div class="container" data-testid="video-slider-input">
<!-- Left Video Slot -->
<div class="video-slot">
<InteractiveVideo
value={value[0]}
on:change={({ detail }) => handle_change(detail, 0)}
sources={["upload"]}
active_source="upload"
{root}
{upload}
{stream_handler}
{i18n}
{max_file_size}
show_label={false}
{webcam_options}
{autoplay}
{loop}
include_audio={true}
>
<!-- This text is displayed inside the upload box. -->
<p class="upload-text">Upload Video 1</p>
</InteractiveVideo>
</div>
<!-- Right Video Slot -->
<div class="video-slot">
<InteractiveVideo
value={value[1]}
on:change={({ detail }) => handle_change(detail, 1)}
sources={["upload"]}
active_source="upload"
{root}
{upload}
{stream_handler}
{i18n}
{max_file_size}
show_label={false}
{webcam_options}
{autoplay}
{loop}
include_audio={true}
>
<p class="upload-text">Upload Video 2</p>
</InteractiveVideo>
</div>
</div>
<style>
.container {
display: flex;
flex-direction: row;
gap: var(--spacing-lg);
width: 100%;
height: 100%;
}
.video-slot {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: var(--size-60);
border: 1px solid var(--border-color-primary);
border-radius: var(--radius-lg);
overflow: hidden;
position: relative;
}
/* Use :global to style the slotted content passed to the child component. */
:global(.video-slot .upload-text) {
color: var(--body-text-color-subdued);
text-align: center;
}
</style> |