upscalev1 / app.py
torinriley's picture
Switch to Docker SDK: fix build order so basicsr_vendor exists before pip install
87596d9
"""
AI Image & Video Upscaler – Gradio UI
Powered by Real-ESRGAN
"""
import os
import tempfile
import cv2
import gradio as gr
import numpy as np
from PIL import Image
from upscaler import Upscaler
# Try to import the interactive slider; fall back to plain images
try:
from gradio_imageslider import ImageSlider
HAS_SLIDER = True
except ImportError:
HAS_SLIDER = False
engine = Upscaler()
# ── helpers ────────────────────────────────────────────────────────────
def _pil_from_bgr(arr: np.ndarray) -> Image.Image:
return Image.fromarray(cv2.cvtColor(arr, cv2.COLOR_BGR2RGB))
def _parse_inputs(scale_str: str, model_str: str):
scale = int(scale_str.replace("x", ""))
mtype = "anime" if "Anime" in model_str else "photo"
return scale, mtype
# ── image handler ──────────────────────────────────────────────────────
def upscale_image(img_path, scale_str, model_str, progress=gr.Progress()):
if img_path is None:
gr.Warning("Upload an image first.")
return None, None, None, ""
scale, mtype = _parse_inputs(scale_str, model_str)
progress(0.10, desc="Loading model…")
progress(0.25, desc="Upscaling – this may take a moment…")
output_bgr = engine.upscale_image(img_path, scale=scale, model_type=mtype)
progress(0.90, desc="Preparing preview…")
original_bgr = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
# Flatten alpha channel so cvtColor always gets a 3-channel BGR image
if original_bgr.ndim == 3 and original_bgr.shape[2] == 4:
original_bgr = cv2.cvtColor(original_bgr, cv2.COLOR_BGRA2BGR)
oh, ow = original_bgr.shape[:2]
nh, nw = output_bgr.shape[:2]
# save full-res result for download
out_file = os.path.join(tempfile.gettempdir(), "upscaled_output.png")
cv2.imwrite(out_file, output_bgr)
upscaled_pil = _pil_from_bgr(output_bgr)
original_pil = _pil_from_bgr(original_bgr).resize((nw, nh), Image.LANCZOS)
progress(1.0, desc="Done!")
status = f"βœ… {ow}Γ—{oh} β†’ {nw}Γ—{nh} ({scale}x {mtype})"
if HAS_SLIDER:
return upscaled_pil, (original_pil, upscaled_pil), out_file, status
return upscaled_pil, None, out_file, status
# ── video handler ──────────────────────────────────────────────────────
def upscale_video(video_path, scale_str, model_str, progress=gr.Progress()):
if video_path is None:
gr.Warning("Upload a video first.")
return None, ""
scale, mtype = _parse_inputs(scale_str, model_str)
out_file = os.path.join(tempfile.gettempdir(), "upscaled_video.mp4")
def on_progress(pct):
progress(pct, desc=f"Upscaling frames… {int(pct * 100)}%")
progress(0.05, desc="Loading model…")
engine.upscale_video(
video_path, out_file, scale=scale, model_type=mtype, progress_cb=on_progress
)
progress(1.0, desc="Done!")
return out_file, "βœ… Video upscaled successfully!"
# ── UI ─────────────────────────────────────────────────────────────────
custom_css = """
#title { text-align: center; }
.gr-button-primary { min-height: 48px !important; font-size: 1.1em !important; }
"""
with gr.Blocks(title="AI Upscaler") as demo:
gr.Markdown(
"# πŸ” AI Image & Video Upscaler\n"
"*Powered by Real-ESRGAN β€’ 100 % Local & Private*",
elem_id="title",
)
with gr.Tabs():
# ─── Image tab ─────────────────────────────────────────────
with gr.Tab("πŸ–ΌοΈ Image"):
with gr.Row(equal_height=True):
with gr.Column(scale=1, min_width=300):
img_input = gr.Image(
label="Upload Image",
type="filepath",
sources=["upload", "clipboard"],
height=300,
)
img_scale = gr.Radio(
["2x", "4x"],
label="Upscale Factor",
value="4x",
info="Higher = bigger output & longer processing",
)
img_model = gr.Dropdown(
["General (Photo)", "Anime / Illustration"],
label="AI Model",
value="General (Photo)",
info="Pick the best match for your content",
)
img_btn = gr.Button("πŸš€ Upscale Image", variant="primary", size="lg")
img_status = gr.Textbox(label="Status", interactive=False, lines=1)
with gr.Column(scale=2, min_width=400):
img_output = gr.Image(
label="Upscaled Result",
height=400,
)
img_download = gr.File(label="Download Full Resolution")
# before / after comparison
gr.Markdown("### πŸ”„ Before / After Comparison")
if HAS_SLIDER:
img_slider = ImageSlider(
label="Drag the slider to compare",
type="pil",
)
else:
gr.Markdown(
"*Install `gradio_imageslider` for an interactive comparison slider.*"
)
img_slider = gr.Image(label="Comparison (install gradio_imageslider for slider)", visible=False)
img_btn.click(
fn=upscale_image,
inputs=[img_input, img_scale, img_model],
outputs=[img_output, img_slider, img_download, img_status],
)
# ─── Video tab ─────────────────────────────────────────────
with gr.Tab("🎬 Video"):
with gr.Row(equal_height=True):
with gr.Column(scale=1, min_width=300):
vid_input = gr.Video(label="Upload Video", height=300)
vid_scale = gr.Radio(
["2x", "4x"],
label="Upscale Factor",
value="2x",
info="2x recommended for video (much faster)",
)
vid_model = gr.Dropdown(
["General (Photo)", "Anime / Illustration"],
label="AI Model",
value="General (Photo)",
)
vid_btn = gr.Button("πŸš€ Upscale Video", variant="primary", size="lg")
vid_status = gr.Textbox(label="Status", interactive=False, lines=1)
with gr.Column(scale=2, min_width=400):
vid_output = gr.Video(label="Upscaled Video", height=400)
vid_btn.click(
fn=upscale_video,
inputs=[vid_input, vid_scale, vid_model],
outputs=[vid_output, vid_status],
)
gr.Markdown(
"---\n"
"Models are downloaded automatically on first use (~65 MB each). \n"
"GPU (CUDA) is used when available; otherwise falls back to CPU."
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)