hyungonryu's picture
Update app.py
0ea4d28 verified
import gradio as gr
# 1) Your Hugging Face Space name:
SPACE_USERNAME = "hyungonryu"
SPACE_NAME = "GoeStationaryVFI"
# 2) Base URL for raw MP4 files via "resolve/main":
BASE_URL = f"https://huggingface.co/spaces/{SPACE_USERNAME}/{SPACE_NAME}/resolve/main"
html_code = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Video Sync with Custom UI</title>
<style>
body {{ font-family: sans-serif; margin: 20px; }}
table {{ margin: 0 auto; }}
td {{ padding: 5px; vertical-align: middle; }}
.slider, .timeline-slider {{ }}
.video-container-wrapper {{ position: relative; display: inline-block; }}
.video-container {{
position: relative;
width: 640px;
height: 480px;
background: #000;
overflow: hidden;
}}
.video {{
position: absolute;
top: 0;
left: 0;
width: 640px;
height: 480px;
object-fit: cover;
}}
.wipe-line {{
position: absolute;
top: 0;
width: 2px;
height: 100%;
background: blue;
pointer-events: none;
}}
</style>
</head>
<body>
<center>
<h1>Geostationary Satellite Time Interpolation</h1>
<h4>Hyun Gon Ryu | NVIDIA AI Technology Center Korea</h4>
</center>
<table>
<tr>
<td width="700">
<font size="1">
This demo video consists of images generated at one-minute intervals from the KMA GK2A satellite's full-disk images,
originally captured at ten-minute intervals, using the VFI technique.
By using the wipe scrollbar, you can compare the original video (delineated by a blue line) with the enhanced version.
You can adjust the zoom and playback speed, and when played at 0.3x speed, the differences between the two videos
become distinctly apparent.
</font>
</td>
</tr>
</table>
<!-- Video set selection -->
<table id="videoSetTable">
<tr>
<td>
Select Video Set:
<select id="videoSetSelect">
<option value="">No</option>
<option value="0">TongA Case | 20220116 | ir105</option>
<option value="1">SouthAsia | 20240820 | ir105</option>
<option value="2">EastAsia | 20240820 | ir105</option>
<option value="3">SouthWestAsia | 20240820 | ir105</option>
</select>
</td>
</tr>
</table>
<table id="controlTable">
<!-- 1st Row: Play button, Speed, Screen Size -->
<tr>
<td>
<button id="playPauseBtn">PLAY</button>
</td>
<td>
Screen Size:
<select id="scaleSelect">
<option value="0.5">0.5</option>
<option value="1" selected>1</option>
<option value="1.5">1.5</option>
<option value="2">2</option>
</select>
</td>
<td>
Speed:
<select id="speedSelect">
<option value="0.1">0.1</option>
<option value="0.2">0.2</option>
<option value="0.3">0.3</option>
<option value="0.4">0.4</option>
<option value="0.5">0.5</option>
<option value="0.6">0.6</option>
<option value="0.7">0.7</option>
<option value="0.8">0.8</option>
<option value="0.9">0.9</option>
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="4">4</option>
</select>
</td>
</tr>
<!-- 2nd Row: Wipe Slider -->
<tr>
<td colspan="3">
<input type="range" id="wipeRange" class="slider" min="0" max="1" step="0.01" value="0.5">
</td>
</tr>
<!-- 3rd Row: Timeline Slider -->
<tr>
<td colspan="3">
<input type="range" id="timelineRange" class="timeline-slider" min="0" max="100" step="0.1" value="0">
</td>
</tr>
<!-- 4th Row: Video Area -->
<tr>
<td colspan="3">
<div class="video-container-wrapper">
<div class="video-container" id="videoContainer">
<!-- Default placeholders -->
<video id="video1" class="video" src="" preload="auto" muted></video>
<video id="video2" class="video" src="" preload="auto" muted></video>
<div id="wipeLine" class="wipe-line"></div>
</div>
</div>
</td>
</tr>
</table>
<script>
// Base width remains constant.
const baseWidth = 640;
// We'll store the base "resolve/main" URL from Python as a JS var:
const BASE_URL = "{BASE_URL}";
// Predefined video sets.
const videoSets = [
{{ label: "TongA Case", area: "TongaArea", date: "20220116", sensor: "ir105" }},
{{ label: "SouthAsia", area: "SouthAsia", date: "20240820", sensor: "ir105" }},
{{ label: "EastAsia", area: "EastAsia", date: "20240820", sensor: "ir105" }},
{{ label: "SouthWestAsia", area: "WestAsia", date: "20240820", sensor: "ir105" }},
];
// Get DOM elements.
const video1 = document.getElementById("video1");
const video2 = document.getElementById("video2");
const playPauseBtn = document.getElementById("playPauseBtn");
const wipeRange = document.getElementById("wipeRange");
const timelineRange = document.getElementById("timelineRange");
const scaleSelect = document.getElementById("scaleSelect");
const speedSelect = document.getElementById("speedSelect");
const videoContainer = document.getElementById("videoContainer");
const wipeLine = document.getElementById("wipeLine");
const controlTable = document.getElementById("controlTable");
const videoSetSelect = document.getElementById("videoSetSelect");
let isPlaying = false;
// Update playback speed.
function updateSpeed() {{
const speedValue = parseFloat(speedSelect.value);
video1.playbackRate = speedValue;
video2.playbackRate = speedValue;
}}
// Update the wipe effect.
function updateWipe() {{
const wipeValue = parseFloat(wipeRange.value);
const rightPercent = (1 - wipeValue) * 100;
video2.style.clipPath = `inset(0 ${{rightPercent}}% 0 0)`;
// Calculate position using the base width.
const lineX = baseWidth * wipeValue - (wipeLine.clientWidth / 2);
wipeLine.style.left = `${{lineX}}px`;
}}
// Update timeline slider based on video progress.
function updateTimeline() {{
if (video1.duration) {{
const percent = (video1.currentTime / video1.duration) * 100;
timelineRange.value = percent;
}}
}}
video1.addEventListener("timeupdate", updateTimeline);
// Toggle play/pause.
function togglePlayPause() {{
if (!isPlaying) {{
video1.play();
video2.play();
playPauseBtn.textContent = "Pause";
isPlaying = true;
}} else {{
video1.pause();
video2.pause();
playPauseBtn.textContent = "Play";
isPlaying = false;
}}
}}
// Seek both videos.
function seekTimeline() {{
if (video1.duration) {{
const percent = timelineRange.value / 100;
const newTime = video1.duration * percent;
video1.currentTime = newTime;
video2.currentTime = newTime;
}}
}}
// Update scale and adjust related elements.
function updateScale() {{
const scaleValue = parseFloat(scaleSelect.value);
videoContainer.style.transform = `scale(${{scaleValue}})`;
videoContainer.style.transformOrigin = "top left";
// Update slider and table widths to the visually scaled size.
const effectiveWidth = baseWidth * scaleValue;
wipeRange.style.width = effectiveWidth + "px";
timelineRange.style.width = effectiveWidth + "px";
controlTable.style.width = effectiveWidth + "px";
updateWipe();
}}
// Update video sources based on selected video set.
function updateVideoSet() {{
const selectedIndex = videoSetSelect.value;
if (selectedIndex === "") {{
// "No" selected: clear sources or do nothing.
video1.removeAttribute("src");
video2.removeAttribute("src");
video1.load();
video2.load();
return;
}}
const videoSet = videoSets[parseInt(selectedIndex)];
const area = videoSet.area;
const date = videoSet.date;
const sensor = videoSet.sensor;
// Use "resolve/main" URLs:
const originalFile = `${{BASE_URL}}/${{area}}/video_${{area}}_${{sensor}}um_${{date}}_10min_010fps.mp4`;
const enhancedFile = `${{BASE_URL}}/${{area}}/video_${{area}}_${{sensor}}um_${{date}}_01min_100fps.mp4`;
// Update video sources.
video1.setAttribute("src", originalFile);
video2.setAttribute("src", enhancedFile);
// Reload the videos.
video1.load();
video2.load();
}}
// Event listeners.
playPauseBtn.addEventListener("click", togglePlayPause);
wipeRange.addEventListener("input", updateWipe);
timelineRange.addEventListener("input", seekTimeline);
scaleSelect.addEventListener("change", updateScale);
speedSelect.addEventListener("change", updateSpeed);
videoSetSelect.addEventListener("change", updateVideoSet);
// Initial setup.
updateWipe();
updateScale();
updateSpeed();
</script>
</body>
</html>
"""
# Build the Gradio app with just an HTML component.
def build_app():
with gr.Blocks() as demo:
gr.HTML(html_code)
return demo
demo = build_app()
if __name__ == "__main__":
demo.launch()