import gradio as gr import requests import zipfile import io import os import warnings from pathlib import Path from PIL import Image import json from typing import List, Tuple # Suppress insecure request warnings warnings.filterwarnings('ignore', message='Unverified HTTPS request') # Try to import huggingface_hub and wd_tagger try: from huggingface_hub import login, logout, HfApi HAS_HF_HUB = True except ImportError: HAS_HF_HUB = False print("Warning: huggingface_hub not installed. HF dataset features disabled.") try: from wd_tagger import ( get_loaded_models, load, tag ) HAS_WD_TAGGER = True except ImportError: HAS_WD_TAGGER = False print("Warning: wd_tagger not installed. Tagging features disabled.") # Global state for selected images selected_images_state = {"images": [], "file_names": []} def download_and_extract_images(zip_url, hf_token=None, verify_ssl=False): """ Download a deflate zip file from URL and extract images. Args: zip_url: URL to the zip file or HuggingFace dataset hf_token: Huggingface authentication token verify_ssl: Whether to verify SSL certificates Returns: Tuple of (images list, file names list, status message) """ try: # Handle HuggingFace dataset URLs if 'huggingface.co' in zip_url or zip_url.startswith('datasets/'): if not HAS_HF_HUB: return None, [], "❌ Error: huggingface_hub not installed" if hf_token: try: login(token=hf_token) print("✅ Logged into Huggingface") except Exception as e: return None, [], f"❌ Error: Failed to login to HF - {str(e)}" try: # Parse dataset name if zip_url.startswith('datasets/'): dataset_name = zip_url.replace('datasets/', '') else: dataset_name = zip_url.split('/')[-1] print(f"Loading dataset: {dataset_name}") # Load dataset from datasets import load_dataset dataset = load_dataset(dataset_name) images = [] file_names = [] # Extract images from dataset for split_name, split in dataset.items(): for idx, example in enumerate(split): if 'image' in example: img = example['image'] if isinstance(img, Image.Image): images.append(img) file_names.append(f"{split_name}_{idx}.png") if not images: return None, [], "❌ No images found in dataset" return images, file_names, f"✅ Loaded {len(images)} images from HF dataset" except Exception as e: return None, [], f"❌ Error loading dataset: {str(e)}" # Handle regular zip URLs print(f"Downloading from: {zip_url}") headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } response = requests.get( zip_url, verify=verify_ssl, allow_redirects=True, timeout=30, headers=headers ) response.raise_for_status() print(f"Response status: {response.status_code}") print(f"Content length: {len(response.content)} bytes") print(f"Content type: {response.headers.get('content-type', 'unknown')}") # Check if content is actually a zip file if not response.content.startswith(b'PK'): try: import gzip decompressed = gzip.decompress(response.content) if decompressed.startswith(b'PK'): print("Content was gzip-compressed, decompressed successfully") response.content = decompressed else: return None, [], f"❌ Error: Downloaded file is not a zip file.\n\nContent-Type: {response.headers.get('content-type')}\n\nTip: Use a direct .zip link" except Exception as e: return None, [], f"❌ Error: Downloaded file is not a zip file.\n\nContent-Type: {response.headers.get('content-type')}\n\nTip: Use a direct .zip link" zip_buffer = io.BytesIO(response.content) images = [] file_names = [] supported_formats = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'} try: with zipfile.ZipFile(zip_buffer, 'r') as zip_file: file_list = zip_file.namelist() print(f"Found {len(file_list)} files in zip") image_files = [f for f in file_list if Path(f).suffix.lower() in supported_formats] print(f"Found {len(image_files)} image files") for file_name in image_files: try: image_data = zip_file.read(file_name) image = Image.open(io.BytesIO(image_data)) images.append(image) file_names.append(file_name) print(f"Loaded: {file_name}") except Exception as e: print(f"Error loading {file_name}: {e}") continue except zipfile.BadZipFile as e: print(f"BadZipFile error: {e}") return None, [], f"❌ Error: Invalid or corrupted zip file" except Exception as e: print(f"Zip extraction error: {e}") return None, [], f"❌ Error: Failed to extract zip - {str(e)}" if not images: return None, [], "❌ No images found in the zip file" print(f"Successfully extracted {len(images)} images") return images, file_names, f"✅ Loaded {len(images)} images" except requests.exceptions.RequestException as e: return None, [], f"❌ Download error: {str(e)}" except Exception as e: return None, [], f"❌ Error: {str(e)}" def resize_image(image: Image.Image, target_height: int) -> Image.Image: """Resize image maintaining aspect ratio based on target height""" if target_height <= 0: return image ratio = target_height / image.height new_width = int(image.width * ratio) return image.resize((new_width, target_height), Image.Resampling.LANCZOS) def tag_images_wd(selected_indices: List[int], images_list: List[Image.Image], file_names_list: List[str]) -> Tuple[str, dict]: """Tag selected images using WD-Tagger""" if not HAS_WD_TAGGER: return "❌ Error: wd_tagger not installed", {} if not selected_indices: return "❌ Error: No images selected", {} try: print("Loading WD-Tagger model...") model_name = list(get_loaded_models())[0] if get_loaded_models() else None if not model_name: load("wd14-vit") model_name = "wd14-vit" captions = {} for idx in selected_indices: if idx < len(images_list): image = images_list[idx] file_name = file_names_list[idx] print(f"Tagging: {file_name}") results, ratings = tag(image, model_name) # Combine tags with confidence scores tags_str = ", ".join([f"{tag} ({conf:.2f})" for tag, conf in results.items()]) captions[file_name] = tags_str return f"✅ Tagged {len(captions)} images", captions except Exception as e: print(f"Error tagging: {e}") return f"❌ Error tagging images: {str(e)}", {} def export_selected(selected_indices: List[int], images_list: List[Image.Image], file_names_list: List[str], captions_dict: dict, target_height: int): """Export selected images and captions as zip""" if not selected_indices: return None, "❌ Error: No images selected" try: if target_height <= 0: return None, "❌ Error: Invalid height value" zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: for idx in selected_indices: if idx < len(images_list): image = images_list[idx] file_name = file_names_list[idx] # Resize image resized = resize_image(image, target_height) # Save image to zip img_buffer = io.BytesIO() resized.save(img_buffer, format='PNG') zip_file.writestr(f"images/{Path(file_name).stem}.png", img_buffer.getvalue()) # Save caption if available if file_name in captions_dict: caption_name = f"captions/{Path(file_name).stem}.txt" zip_file.writestr(caption_name, captions_dict[file_name]) zip_buffer.seek(0) return zip_buffer, f"✅ Exported {len(selected_indices)} images with captions" except Exception as e: print(f"Export error: {e}") return None, f"❌ Error exporting: {str(e)}" def create_gallery_with_checkboxes(images_list: List[Image.Image], file_names_list: List[str], target_height: int): """Create gallery display with resized images""" if not images_list: return [], "No images loaded" resized_images = [] for img in images_list: resized = resize_image(img, target_height) resized_images.append(resized) return resized_images, f"Displaying {len(resized_images)} images (resized to height: {target_height}px)" # Create Gradio interface with gr.Blocks(title="Zip Image Gallery Pro") as demo: gr.Markdown("# 📸 Zip Image Gallery Pro") gr.Markdown("Download, tag, and export images with captions") # State to store images and file names images_state = gr.State([]) file_names_state = gr.State([]) captions_state = gr.State({}) with gr.Tabs(): # Tab 1: Download with gr.Tab("📥 Download"): gr.Markdown("### Load Images from Zip or HuggingFace Dataset") with gr.Row(): with gr.Column(scale=3): url_input = gr.Textbox( label="Zip File URL or HF Dataset", placeholder="Enter .zip URL or datasets/username/dataset-name", lines=1 ) with gr.Column(scale=1): hf_token_input = gr.Textbox( label="HF Token (optional)", placeholder="Your HF token", type="password", lines=1 ) download_btn = gr.Button("Download & Extract", variant="primary", scale=1) status_text = gr.Textbox(label="Status", interactive=False) # Tab 2: Selection & Tagging with gr.Tab("🏷️ Select & Tag"): gr.Markdown("### Select Images and Tag with WD-Tagger") with gr.Row(): with gr.Column(scale=2): height_input = gr.Slider( label="Image Height (pixels)", minimum=256, maximum=2048, value=1024, step=64 ) with gr.Column(scale=1): refresh_gallery_btn = gr.Button("Refresh Gallery", scale=1) image_gallery = gr.Gallery( label="Images (Click to select)", show_label=True, elem_id="gallery", columns=3, rows=2, object_fit="scale-down", height="auto" ) gallery_status = gr.Textbox(label="Gallery Status", interactive=False) with gr.Row(): tag_btn = gr.Button("🏷️ Tag Selected Images (WD-Tagger)", variant="primary") tag_status = gr.Textbox(label="Tag Status", interactive=False) captions_display = gr.Textbox( label="Captions (JSON format)", interactive=False, lines=10, max_lines=20 ) # Tab 3: Export with gr.Tab("💾 Export"): gr.Markdown("### Export Selected Images with Captions") with gr.Row(): with gr.Column(): export_status = gr.Textbox(label="Export Status", interactive=False) with gr.Column(): export_btn = gr.Button("📦 Export as ZIP", variant="primary", size="lg") export_file = gr.File(label="Download ZIP") gr.Markdown("**Instructions:**\n1. Load images from a zip file or HF dataset\n2. Select images in the gallery\n3. Optionally tag images with WD-Tagger\n4. Export selected images with captions as ZIP") # Event handlers def download_click(url, hf_token): images, file_names, status = download_and_extract_images(url, hf_token) return images, file_names, status, [], "No images loaded", {} download_btn.click( fn=download_click, inputs=[url_input, hf_token_input], outputs=[images_state, file_names_state, status_text, image_gallery, gallery_status, captions_state] ) def refresh_gallery(images_list, height): gallery_imgs, gallery_status_txt = create_gallery_with_checkboxes(images_list, [], height) return gallery_imgs, gallery_status_txt refresh_gallery_btn.click( fn=refresh_gallery, inputs=[images_state, height_input], outputs=[image_gallery, gallery_status] ) def tag_click(selected_indices, images_list, file_names_list): if not selected_indices: return "", "❌ No images selected" tag_status_msg, captions = tag_images_wd(selected_indices, images_list, file_names_list) captions_json = json.dumps(captions, indent=2) return captions_json, tag_status_msg tag_btn.click( fn=tag_click, inputs=[image_gallery, images_state, file_names_state], outputs=[captions_display, tag_status] ) def export_click(selected_indices, images_list, file_names_list, captions_dict, height): file_data, export_msg = export_selected(selected_indices, images_list, file_names_list, captions_dict, height) return file_data, export_msg export_btn.click( fn=export_click, inputs=[image_gallery, images_state, file_names_state, captions_state, height_input], outputs=[export_file, export_status] ) if __name__ == "__main__": demo.launch(share=False)