import gradio as gr import os import time from pathlib import Path from PIL import Image import base64 from io import BytesIO # --- Setup --- # Create a directory to store uploaded images IMAGE_DIR = Path("gallery_images") IMAGE_DIR.mkdir(exist_ok=True) # --- Backend Functions --- def cleanup_old_images(): """Deletes images older than 60 minutes to save space.""" now = time.time() for f in IMAGE_DIR.iterdir(): if f.is_file(): # Check if file is older than 3600 seconds (60 minutes) if now - f.stat().st_mtime > 3600: print(f"Cleaning up old image: {f.name}") f.unlink() def show_gallery(): """ Cleans up old files and returns a sorted list of all current image paths. This is called by the frontend to refresh the gallery view. """ cleanup_old_images() # Sort files by creation time, newest first files = sorted(IMAGE_DIR.iterdir(), key=os.path.getmtime, reverse=True) return [str(f) for f in files] def clear_gallery(): """Deletes all files in the image directory.""" if IMAGE_DIR.exists(): for file_path in IMAGE_DIR.iterdir(): if file_path.is_file(): file_path.unlink() # Deletes the file print("Gallery cleared successfully.") # Return an empty list to update the gallery UI return [] def upload_via_ui(image_from_ui): """ Handles image uploads directly from the Gradio UI interface. The 'image_from_ui' is a PIL Image object. """ if image_from_ui is not None: timestamp = int(time.time()) filename = f"ui_{timestamp}.png" path = IMAGE_DIR / filename image_from_ui.save(path) print(f"Image saved from UI to {path}") return show_gallery() def upload_via_data_url(data_url_string): """ Handles image uploads from our JavaScript frontend. The 'data_url_string' is a base64 encoded string. """ if not data_url_string: raise gr.Error("No image data received.") try: # The string is "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..." # We need to strip the header to get just the base64 part. header, encoded = data_url_string.split(",", 1) # Decode the base64 string img_data = base64.b64decode(encoded) # Open the image using Pillow image = Image.open(BytesIO(img_data)) # Generate a unique filename timestamp = int(time.time()) filename = f"capture_{timestamp}.jpeg" path = IMAGE_DIR / filename # Save the image image.save(path, "JPEG") print(f"Image saved from camera to {path}") except Exception as e: print(f"Error processing image: {e}") raise gr.Error("Server failed to process image.") # After saving, return the updated list of all images return show_gallery() # --- Gradio UI & API --- with gr.Blocks(title="Shekar's Portfolio Gallery") as demo: gr.Markdown("## 📸 Portfolio Image Gallery") # Main gallery display gallery = gr.Gallery( label="Uploaded Images", show_label=False, elem_id="gallery_component", # Use a unique elem_id columns=4, height="auto" ) # --- Section for manual uploads via the Gradio UI --- with gr.Accordion("Manual Upload (for testing)", open=False): image_input = gr.Image(type="pil", label="Upload an Image") upload_button = gr.Button("Upload from UI") upload_button.click( fn=upload_via_ui, inputs=image_input, outputs=gallery ) with gr.Accordion("Admin Controls", open=False): clear_btn = gr.Button("⚠️ Clear All Gallery Images") # --- Hidden components to create the API endpoints --- with gr.Row(visible=False): # Textbox to receive the base64 data URL from the JS client data_url_input = gr.Textbox() # The upload function will output the new gallery list to our main gallery component # This creates the /upload_via_data_url endpoint data_url_input.submit( fn=upload_via_data_url, inputs=data_url_input, outputs=gallery, api_name="upload_via_data_url" ) clear_btn.click(fn=clear_gallery, inputs=None, outputs=gallery) # Load and refresh the gallery when the app starts. # This also creates the /show_gallery endpoint for the JS client. demo.load( fn=show_gallery, inputs=None, outputs=gallery, api_name="show_gallery" ) # --- Launch the App --- demo.launch()