HTMLviewer / app.py
tomo2chin2's picture
Update app.py
4f4db76 verified
raw
history blame
10.7 kB
import gradio as gr
import os
import tempfile
import time
import base64
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from PIL import Image
import io
# Setup directories
TEMP_DIR = os.path.join(tempfile.gettempdir(), "html-screenshots")
os.makedirs(TEMP_DIR, exist_ok=True)
def setup_driver():
"""Setup and return a headless Chrome driver"""
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--window-size=1280,1024")
# Use ChromeDriverManager to automatically download the appropriate driver
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
return driver
def get_screenshot(html_code, width=1280, height=800, full_page=False, wait_time=1):
"""Take a screenshot of the HTML using headless Chrome"""
if not html_code.strip():
return None, "Please enter some HTML code first."
# Create a temporary HTML file
timestamp = int(time.time())
html_file = os.path.join(TEMP_DIR, f"temp_{timestamp}.html")
try:
# Add doctype and viewport meta if not present
if "<!DOCTYPE" not in html_code:
html_code = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {{
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: #333;
max-width: 100%;
}}
</style>
</head>
<body>
{html_code}
</body>
</html>"""
# Write HTML to temporary file
with open(html_file, "w", encoding="utf-8") as f:
f.write(html_code)
# Initialize the driver
driver = setup_driver()
try:
# Navigate to the HTML file
driver.get(f"file://{html_file}")
# Wait for any JavaScript to load
time.sleep(wait_time)
# Set window size
driver.set_window_size(width, height)
# Take screenshot
if full_page:
# Get the height of the page
height = driver.execute_script("return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);")
driver.set_window_size(width, height)
screenshot = driver.get_screenshot_as_png()
# Convert to PIL Image for processing
img = Image.open(io.BytesIO(screenshot))
# Create an in-memory buffer for the image
buffer = io.BytesIO()
img.save(buffer, format="PNG")
img_str = base64.b64encode(buffer.getvalue()).decode("utf-8")
return f"data:image/png;base64,{img_str}", None
finally:
driver.quit()
except Exception as e:
return None, f"Error taking screenshot: {str(e)}"
finally:
# Clean up temporary file
if os.path.exists(html_file):
try:
os.remove(html_file)
except:
pass
return None, "Failed to generate screenshot."
def render_screenshot(html_code, width, height, full_page, wait_time):
"""Process HTML and return screenshot with potential error message"""
screenshot, error = get_screenshot(
html_code,
width=width,
height=height,
full_page=full_page,
wait_time=wait_time
)
if error:
return None, error
return screenshot, None
# Create elegant theme inspired by Intercontinental Hotel Ishigaki
theme = gr.themes.Soft(
primary_hue="indigo",
secondary_hue="blue",
neutral_hue="slate",
font=gr.themes.GoogleFont("Inter"),
font_mono=gr.themes.GoogleFont("IBM Plex Mono"),
).set(
button_primary_background_fill="*primary_500",
button_primary_background_fill_hover="*primary_600",
button_primary_text_color="white",
button_secondary_background_fill="white",
button_secondary_background_fill_hover="*neutral_100",
button_secondary_text_color="*neutral_800",
block_title_text_color="*primary_700",
block_label_text_color="*neutral_600",
background_fill_primary="white"
)
# Custom CSS for elegant styling
css = """
.gradio-container {
max-width: 1200px !important;
margin: 0 auto !important;
background-color: #ffffff !important;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.03) !important;
border-radius: 16px !important;
overflow: hidden !important;
}
.header-container {
background: linear-gradient(135deg, #e0f2fe, #f0f9ff) !important;
padding: 2rem 1.5rem 1.5rem !important;
border-bottom: 1px solid rgba(0, 0, 0, 0.03) !important;
margin-bottom: 1.5rem !important;
}
.header-title {
font-weight: 300 !important;
color: #1e40af !important;
font-size: 2rem !important;
margin: 0 !important;
letter-spacing: -0.02em !important;
}
.header-subtitle {
color: #475569 !important;
font-weight: 400 !important;
margin-top: 0.5rem !important;
}
.input-panel {
background-color: white !important;
border-radius: 10px !important;
padding: 0.5rem !important;
border: 1px solid #e2e8f0 !important;
}
.screenshot-container {
background-color: white !important;
border: 1px solid #e2e8f0 !important;
border-radius: 10px !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
min-height: 400px !important;
}
.screenshot-container img {
max-width: 100% !important;
height: auto !important;
object-fit: contain !important;
margin: 0 auto !important;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05) !important;
}
.error-message {
color: #ef4444 !important;
background-color: #fef2f2 !important;
padding: 1rem !important;
border-radius: 8px !important;
margin-top: 1rem !important;
font-size: 0.9rem !important;
border-left: 3px solid #ef4444 !important;
}
.footer {
text-align: center !important;
padding: 1.5rem !important;
color: #94a3b8 !important;
font-size: 0.8rem !important;
border-top: 1px solid #f1f5f9 !important;
margin-top: 2rem !important;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.header-title {
font-size: 1.5rem !important;
}
.two-column {
flex-direction: column !important;
}
}
"""
# Create the Gradio interface
with gr.Blocks(theme=theme, css=css) as demo:
# Header
with gr.Column(elem_classes=["header-container"]):
gr.Markdown("# HTML Screenshot Generator", elem_classes=["header-title"])
gr.Markdown("Capture beautiful screenshots of your HTML using headless Chrome",
elem_classes=["header-subtitle"])
# State for storing error messages
error_message = gr.State("")
# Main content with responsive layout
with gr.Row(equal_height=True, elem_classes=["two-column"]) as layout_row:
# Input panel
with gr.Column(scale=1, elem_classes=["input-panel"]):
html_input = gr.Code(
label="HTML Code",
language="html",
value="<!-- Enter your HTML here -->\n<h1>Hello, World!</h1>\n<p>This is a sample HTML page.</p>",
lines=15,
elem_id="html-input"
)
with gr.Accordion("Screenshot Options", open=False):
with gr.Row():
width_input = gr.Slider(
minimum=320, maximum=2560, value=1280, step=10,
label="Width (px)"
)
height_input = gr.Slider(
minimum=240, maximum=1600, value=800, step=10,
label="Height (px)"
)
with gr.Row():
full_page = gr.Checkbox(
label="Capture Full Page Height",
value=False
)
wait_time = gr.Slider(
minimum=0.1, maximum=5.0, value=1.0, step=0.1,
label="Wait Time (seconds)"
)
with gr.Row():
clear_btn = gr.Button("Clear", variant="secondary")
screenshot_btn = gr.Button("Take Screenshot", variant="primary")
# Error message display
error_display = gr.Markdown(
visible=False,
elem_classes=["error-message"]
)
# Screenshot panel
with gr.Column(scale=1, elem_classes=["screenshot-container"]):
screenshot_output = gr.Image(
label="Screenshot Preview",
type="filepath",
height=600
)
download_btn = gr.Button("Download Screenshot", visible=False)
# Footer
with gr.Column(elem_classes=["footer"]):
gr.Markdown("*Designed with the elegant simplicity of Intercontinental Hotel Ishigaki's Club Lounge in mind*")
# Event handlers
def process_screenshot(html_code, width, height, full_page, wait_time):
screenshot, error = render_screenshot(html_code, width, height, full_page, wait_time)
if error:
return None, error, gr.update(visible=True), gr.update(visible=False)
return screenshot, None, gr.update(visible=False), gr.update(visible=True)
screenshot_btn.click(
fn=process_screenshot,
inputs=[html_input, width_input, height_input, full_page, wait_time],
outputs=[screenshot_output, error_display, error_display, download_btn]
)
clear_btn.click(
fn=lambda: (
"<!-- Enter your HTML here -->\n<h1>Hello, World!</h1>\n<p>This is a sample HTML page.</p>",
None,
gr.update(visible=False),
gr.update(visible=False)
),
inputs=None,
outputs=[html_input, screenshot_output, error_display, download_btn]
)
# Launch the app
if __name__ == "__main__":
demo.launch()