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>