Spaces:
Paused
Paused
| 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() |