dineth554's picture
Upload frontend/app.py with huggingface_hub
4ef9d83 verified
import os
import sys
import json
import time
import logging
import tempfile
from pathlib import Path
from typing import Optional
from datetime import datetime
import requests
import gradio as gr
from PIL import Image, ImageDraw, ImageFont
import numpy as np
# Backend API URL
BACKEND_URL = os.environ.get("BACKEND_URL", "http://localhost:8081")
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s')
logger = logging.getLogger("LegionFrontend")
# --- Dark Theme CSS ---
LEGION_CSS = """
:root {
--primary: #ff6b35;
--primary-dark: #e55a2b;
--bg-dark: #0a0a0f;
--bg-card: #14141f;
--bg-input: #1a1a2e;
--text-primary: #e0e0e0;
--text-secondary: #8888aa;
--border-color: #2a2a3e;
--accent: #00d4aa;
}
body {
background-color: var(--bg-dark) !important;
color: var(--text-primary) !important;
}
.gradio-container {
background-color: var(--bg-dark) !important;
max-width: 1200px !important;
margin: 0 auto !important;
padding: 20px !important;
}
h1, h2, h3 {
font-family: 'Courier New', monospace !important;
text-transform: uppercase !important;
letter-spacing: 2px !important;
}
h1 {
color: var(--primary) !important;
text-align: center !important;
font-size: 2.5em !important;
text-shadow: 0 0 20px rgba(255, 107, 53, 0.3) !important;
border-bottom: 2px solid var(--border-color) !important;
padding-bottom: 15px !important;
margin-bottom: 25px !important;
}
.tabs {
background: var(--bg-card) !important;
border: 1px solid var(--border-color) !important;
border-radius: 12px !important;
padding: 15px !important;
}
button {
background: linear-gradient(135deg, var(--primary), var(--primary-dark)) !important;
border: none !important;
color: white !important;
font-weight: bold !important;
letter-spacing: 1px !important;
transition: all 0.3s ease !important;
}
button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 5px 20px rgba(255, 107, 53, 0.4) !important;
}
input, textarea, select, .dropdown {
background: var(--bg-input) !important;
border-radius: 8px !important;
}
.slider input[type="range"] {
accent-color: var(--primary) !important;
}
.video-container {
overflow: hidden !important;
}
.status-badge {
background: var(--accent) !important;
color: black !important;
padding: 4px 12px !important;
border-radius: 20px !important;
font-size: 0.8em !important;
}
.footer {
color: var(--text-secondary) !important;
border-top: 1px solid var(--border-color) !important;
margin-top: 30px !important;
}
"""
# ============================================================
# API Helper Functions
# ============================================================
def call_api_status() -> dict:
try:
resp = requests.get(f"{BACKEND_URL}/api/status", timeout=5)
return resp.json()
except Exception as e:
return {"status": "error", "detail": str(e)}
def call_api_models() -> list:
try:
resp = requests.get(f"{BACKEND_URL}/api/models", timeout=5)
data = resp.json()
return data.get("models", [])
except Exception as e:
return [{"name": f"Error: {e}", "type": "error"}]
# ============================================================
# Generation Functions
# ============================================================
def generate_text_video(
prompt: str,
negative_prompt: str,
num_frames: int,
width: int,
height: int,
inference_steps: int,
guidance_scale: float,
watermark_strength: float,
progress=gr.Progress(),
) -> Optional[str]:
try:
progress(0, desc="Connecting to API...")
payload = {
"prompt": prompt,
"negative_prompt": negative_prompt,
"num_frames": num_frames,
"width": width,
"height": height,
"num_inference_steps": inference_steps,
"guidance_scale": guidance_scale,
"watermark_strength": watermark_strength,
}
progress(0.2, desc="Generating video...")
resp = requests.post(
f"{BACKEND_URL}/api/generate/text",
json=payload,
timeout=600,
)
if resp.status_code != 200:
return f"Error: {resp.text}"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"/tmp/legion_t2v_{timestamp}.mp4"
with open(output_path, "wb") as f:
f.write(resp.content)
progress(1.0, desc="Done!")
return output_path
except Exception as e:
return f"Error: {str(e)}"
def generate_image_video(
image_file,
prompt: str,
negative_prompt: str,
num_frames: int,
width: int,
height: int,
inference_steps: int,
guidance_scale: float,
watermark_strength: float,
progress=gr.Progress(),
) -> Optional[str]:
try:
if image_file is None:
return "Error: No image uploaded"
progress(0.2, desc="Uploading image and generating...")
with open(image_file, "rb") as f:
files = {"file": f}
data = {
"prompt": prompt,
"negative_prompt": negative_prompt,
"num_frames": str(num_frames),
"width": str(width),
"height": str(height),
"num_inference_steps": str(inference_steps),
"guidance_scale": str(guidance_scale),
"watermark_strength": str(watermark_strength),
}
resp = requests.post(
f"{BACKEND_URL}/api/generate/image",
files=files,
data=data,
timeout=600,
)
if resp.status_code != 200:
return f"Error: {resp.text}"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"/tmp/legion_i2v_{timestamp}.mp4"
with open(output_path, "wb") as f:
f.write(resp.content)
progress(1.0, desc="Done!")
return output_path
except Exception as e:
return f"Error: {str(e)}"
def preview_watermark(text: str, position: str, font_size: int, opacity: float) -> Optional[str]:
try:
frame = Image.new("RGB", (480, 480), (20, 20, 35))
draw = ImageDraw.Draw(frame)
for i in range(0, 480, 20):
draw.line([(i, 0), (i, 480)], fill=(30, 30, 50), width=1)
draw.line([(0, i), (480, i)], fill=(30, 30, 50), width=1)
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size)
except:
font = ImageFont.load_default()
w, h = frame.size
bbox = draw.textbbox((0, 0), text, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
margin = 15
pos_map = {
"top-left": (margin, margin),
"top-right": (w - text_w - margin, margin),
"bottom-left": (margin, h - text_h - margin),
"center": ((w - text_w) // 2, (h - text_h) // 2),
"bottom-right": (w - text_w - margin, h - text_h - margin),
}
x, y = pos_map.get(position, pos_map["bottom-right"])
draw.rectangle(
[x - 10, y - 10, x + text_w + 10, y + text_h + 10],
fill=(0, 0, 0, int(40 * opacity))
)
draw.text((x, y), text, font=font, fill=(255, 255, 255, int(255 * opacity)))
preview_path = "/tmp/qwatermark_preview.png"
frame.save(preview_path)
return preview_path
except Exception as e:
return None
# ============================================================
# BUILD GRADIO INTERFACE
# ============================================================
with gr.Blocks(
css=LEGION_CSS,
title="LEGION VIDEO GENERATION",
theme=gr.themes.Soft(
primary_hue="orange",
secondary_hue="blue",
neutral_hue="slate",
),
) as app:
# Header
gr.HTML("""
<div style="text-align: center; margin-bottom: 10px;">
<h1 style="font-size: 2.8em; margin-bottom: 0;">
โš”๏ธ LEGION VIDEO GENERATION
</h1>
<p style="color: #8888aa; font-size: 1.1em; margin-top: 5px;">
The Ultimate AI Video Engine โ€” Text-to-Video & Image-to-Video
</p>
<div style="display: flex; justify-content: center; gap: 20px; margin-top: 10px;">
<span class="status-badge">๐Ÿ”ฅ T2V + I2V</span>
</div>
</div>
""")
# Main tabs
with gr.Tabs(elem_classes="tabs"):
# ============ TAB 1: TEXT-TO-VIDEO ============
with gr.TabItem("๐ŸŽฌ Text-to-Video", id="tab_t2v"):
with gr.Row():
with gr.Column(scale=2):
prompt_t2v = gr.Textbox(
label="๐Ÿ“ Prompt",
placeholder="Describe the video you want to generate...",
lines=4,
value="A serene mountain lake at sunset with colorful clouds reflecting on the water, gentle ripples, cinematic quality",
)
neg_prompt_t2v = gr.Textbox(
label="๐Ÿšซ Negative Prompt",
lines=2,
value="warped, distorted, flickering, jittery, low quality, blurry, artifacts, ugly, deformed, bad anatomy, bad proportions",
)
num_frames_t2v = gr.Slider(
label="Frames", minimum=1, maximum=129, value=49, step=1
)
steps_t2v = gr.Slider(
label="Inference Steps", minimum=10, maximum=100, value=50, step=1
)
guidance_t2v = gr.Slider(
label="Guidance Scale", minimum=1.0, maximum=20.0, value=6.0, step=0.5
)
wm_strength_t2v = gr.Slider(
label="QWatermark Strength", minimum=0.0, maximum=1.0, value=0.3, step=0.05
)
width_t2v = gr.Dropdown(
label="Width", choices=[256, 384, 480, 720], value=480
)
height_t2v = gr.Dropdown(
label="Height", choices=[256, 384, 480, 720], value=480
)
gen_btn_t2v = gr.Button("๐Ÿ›ก๏ธ GENERATE VIDEO", variant="primary", size="lg")
with gr.Column(scale=1):
output_t2v = gr.Video(label="Generated Video", show_label=True)
status_t2v = gr.Textbox(label="Status", interactive=False)
gen_btn_t2v.click(
fn=generate_text_video,
inputs=[
prompt_t2v, neg_prompt_t2v, num_frames_t2v,
width_t2v, height_t2v, steps_t2v,
guidance_t2v, wm_strength_t2v,
],
outputs=[output_t2v],
queue=True,
)
# ============ TAB 2: IMAGE-TO-VIDEO ============
with gr.TabItem("๐Ÿ–ผ๏ธ Image-to-Video", id="tab_i2v"):
with gr.Row():
with gr.Column(scale=2):
image_i2v = gr.Image(
label="๐Ÿ“ท Input Image",
type="filepath",
height=300,
)
prompt_i2v = gr.Textbox(
label="๐Ÿ“ Prompt (motion description)",
placeholder="Describe the motion or action...",
lines=3,
value="Gentle motion, cinematic camera movement, atmospheric",
)
neg_prompt_i2v = gr.Textbox(
label="๐Ÿšซ Negative Prompt",
lines=2,
value="warped, distorted, flickering, jittery, low quality, blurry, artifacts",
)
num_frames_i2v = gr.Slider(
label="Frames", minimum=1, maximum=129, value=49, step=1
)
steps_i2v = gr.Slider(
label="Inference Steps", minimum=10, maximum=100, value=50, step=1
)
guidance_i2v = gr.Slider(
label="Guidance Scale", minimum=1.0, maximum=20.0, value=6.0, step=0.5
)
wm_strength_i2v = gr.Slider(
label="QWatermark Strength", minimum=0.0, maximum=1.0, value=0.3, step=0.05
)
width_i2v = gr.Dropdown(
label="Width", choices=[256, 384, 480, 720], value=480
)
height_i2v = gr.Dropdown(
label="Height", choices=[256, 384, 480, 720], value=480
)
gen_btn_i2v = gr.Button("๐Ÿ›ก๏ธ GENERATE VIDEO", variant="primary", size="lg")
with gr.Column(scale=1):
output_i2v = gr.Video(label="Generated Video", show_label=True)
status_i2v = gr.Textbox(label="Status", interactive=False)
gen_btn_i2v.click(
fn=generate_image_video,
inputs=[
image_i2v, prompt_i2v, neg_prompt_i2v, num_frames_i2v,
width_i2v, height_i2v, steps_i2v,
guidance_i2v, wm_strength_i2v,
],
outputs=[output_i2v],
queue=True,
)
# ============ TAB 3: QWATERMARK SETTINGS ============
with gr.TabItem("๐Ÿ’ง QWatermark Settings", id="tab_wm"):
gr.Markdown("""
## ๐Ÿ’ง QWatermark Quality Assurance System
The QWatermark system imprints a semi-transparent quality assurance marker
on every generated video. Configure the watermark appearance below.
""")
wm_text = gr.Textbox(
label="Watermark Text",
value="LEGION",
placeholder="Enter watermark text",
)
wm_position = gr.Dropdown(
label="Position",
choices=["top-left", "top-right", "bottom-left", "bottom-right", "center"],
value="bottom-right",
)
wm_font_size = gr.Slider(
label="Font Size", minimum=16, maximum=72, value=36, step=2
)
wm_opacity = gr.Slider(
label="Opacity", minimum=0.0, maximum=1.0, value=0.3, step=0.05
)
preview_btn = gr.Button("๐Ÿ‘๏ธ PREVIEW WATERMARK", variant="secondary", size="lg")
wm_preview = gr.Image(
label="Watermark Preview",
height=400,
)
wm_info = gr.Markdown("""
**Current Configuration:**
- Text: LEGION
- Position: bottom-right
- System: Semi-transparent overlay with dark background
The QWatermark is applied to every frame during video export.
Adjust strength in generation tabs (0.0 to disable).
""")
preview_btn.click(
fn=preview_watermark,
inputs=[wm_text, wm_position, wm_font_size, wm_opacity],
outputs=[wm_preview],
)
# Footer
gr.HTML("""
<div class="footer">
<p>
<strong>โš”๏ธ LEGION VIDEO GENERATION</strong> โ€” v1.0 | Apache 2.0 License
</p>
<p style="font-size: 0.8em; margin-top: 5px;">
Text-to-Video | Image-to-Video | QWatermark System
</p>
</div>
""")
# ============================================================
# Launch
# ============================================================
if __name__ == "__main__":
port = int(os.environ.get("FRONTEND_PORT", 8080))
logger.info(f"Starting LEGION Frontend on port {port}")
app.launch(
server_name="0.0.0.0",
server_port=port,
share=False,
show_error=True,
quiet=False,
)