tomo2chin2 commited on
Commit
48f7c5d
ยท
verified ยท
1 Parent(s): 91c001c

Upload 5 files

Browse files
Files changed (4) hide show
  1. README.md +25 -7
  2. app.py +216 -7
  3. dataset_manager.py +294 -0
  4. requirements.txt +4 -2
README.md CHANGED
@@ -6,27 +6,40 @@ colorTo: purple
6
  sdk: gradio
7
  sdk_version: 4.19.2
8
  app_file: app.py
9
- pinned: true
10
  license: mit
11
  ---
12
 
13
- # ๐ŸŒ NanoBanana Gemini Image Generator
14
 
15
- AI-powered image generation service using Google's Gemini 2.0 Flash model with Gradio UI and FastAPI REST endpoints.
16
 
17
- ## ๐ŸŒŸ Features
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  ### Web Interface (Gradio)
20
- - **Generate**: Create images from text prompts using Gemini 2.0 Flash
21
  - **Edit**: Modify existing images with text instructions
22
  - **Compose**: Combine multiple images into compositions
23
  - **History**: View recent generations with metadata
 
24
 
25
  ### REST API (FastAPI)
26
  - Full REST API with automatic documentation
27
  - JSON request/response format
28
  - Base64 image encoding
29
  - Comprehensive error handling
 
30
 
31
  ## ๐Ÿš€ Quick Start
32
 
@@ -36,6 +49,10 @@ AI-powered image generation service using Google's Gemini 2.0 Flash model with G
36
  - In Hugging Face Spaces: Add `GEMINI_API_KEY` as a secret
37
  - Locally: Create `.env` file with `GEMINI_API_KEY=your_api_key_here`
38
 
 
 
 
 
39
  ### Access Points
40
 
41
  Once deployed:
@@ -67,10 +84,11 @@ GET /api/history?limit=10
67
 
68
  ## ๐Ÿ› ๏ธ Technology Stack
69
 
70
- - **AI Model**: Google Gemini 2.0 Flash (Experimental)
71
  - **Frontend**: Gradio 4.19.2
72
  - **Backend**: FastAPI
73
  - **Server**: Uvicorn (ASGI)
 
74
  - **Runtime**: Hugging Face Spaces (Gradio SDK)
75
  - **Python**: 3.10+
76
 
@@ -80,4 +98,4 @@ MIT License
80
 
81
  ---
82
 
83
- Made with โค๏ธ using Gradio, FastAPI, and Google Gemini
 
6
  sdk: gradio
7
  sdk_version: 4.19.2
8
  app_file: app.py
9
+ pinned: false
10
  license: mit
11
  ---
12
 
13
+ # ๐ŸŒ NanoBanana Gemini Image Generator - Version 3
14
 
15
+ AI-powered image generation service using Google's Gemini 2.5 Flash Image Preview model with dataset repository integration and enhanced API features.
16
 
17
+ ## ๐ŸŒŸ Version 3 Features (Planned)
18
+
19
+ ### Dataset Integration
20
+ - **Hugging Face Dataset Repository**: Automatic saving of generated images to dataset repository
21
+ - **Metadata Tracking**: Store generation parameters and prompts with images
22
+ - **Version History**: Track iterations and improvements
23
+
24
+ ### Enhanced API Features
25
+ - **Batch Processing**: Generate multiple images in one request
26
+ - **Webhooks**: Notify when generation completes
27
+ - **Rate Limiting**: Control API usage
28
+ - **API Keys**: User authentication and tracking
29
 
30
  ### Web Interface (Gradio)
31
+ - **Generate**: Create images from text prompts using Gemini 2.5 Flash Image Preview
32
  - **Edit**: Modify existing images with text instructions
33
  - **Compose**: Combine multiple images into compositions
34
  - **History**: View recent generations with metadata
35
+ - **Gallery**: Browse dataset repository images
36
 
37
  ### REST API (FastAPI)
38
  - Full REST API with automatic documentation
39
  - JSON request/response format
40
  - Base64 image encoding
41
  - Comprehensive error handling
42
+ - Dataset repository integration
43
 
44
  ## ๐Ÿš€ Quick Start
45
 
 
49
  - In Hugging Face Spaces: Add `GEMINI_API_KEY` as a secret
50
  - Locally: Create `.env` file with `GEMINI_API_KEY=your_api_key_here`
51
 
52
+ 2. **Set Hugging Face Token (for dataset access)**
53
+ - Add `HF_TOKEN` as a secret in Spaces
54
+ - Or in `.env` file locally
55
+
56
  ### Access Points
57
 
58
  Once deployed:
 
84
 
85
  ## ๐Ÿ› ๏ธ Technology Stack
86
 
87
+ - **AI Model**: Google Gemini 2.5 Flash Image Preview
88
  - **Frontend**: Gradio 4.19.2
89
  - **Backend**: FastAPI
90
  - **Server**: Uvicorn (ASGI)
91
+ - **Dataset**: Hugging Face Datasets
92
  - **Runtime**: Hugging Face Spaces (Gradio SDK)
93
  - **Python**: 3.10+
94
 
 
98
 
99
  ---
100
 
101
+ Made with โค๏ธ using Gradio, FastAPI, Google Gemini, and Hugging Face
app.py CHANGED
@@ -46,6 +46,24 @@ if GEMINI_API_KEY:
46
  else:
47
  logger.warning("GEMINI_API_KEY not found. Image generation will use placeholder images.")
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  def generate_image_with_gemini(prompt: str, width: int = 1024, height: int = 1024, style: str = "Default") -> Image.Image:
50
  """
51
  Generate image using Gemini 2.5 Flash Image Preview (Nano Banana)
@@ -370,7 +388,9 @@ async def health_check():
370
  async def generate_image_api(
371
  prompt: str,
372
  size: str = "1024x1024",
373
- style: str = "Default"
 
 
374
  ):
375
  """Generate image via API using Gemini Nano Banana"""
376
  try:
@@ -380,18 +400,37 @@ async def generate_image_api(
380
  # Generate image
381
  image = generate_image_with_gemini(prompt, width, height, style)
382
 
383
- # Save image
384
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
385
  filename = f"api_gen_{timestamp}.png"
386
  filepath = GENERATED_DIR / filename
387
  image.save(filepath)
388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  # Convert to base64
390
  buffer = BytesIO()
391
  image.save(buffer, format="PNG")
392
  img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
393
 
394
- return JSONResponse(content={
395
  "success": True,
396
  "filename": filename,
397
  "prompt": prompt,
@@ -399,7 +438,12 @@ async def generate_image_api(
399
  "style": style,
400
  "model": MODEL_NAME if GEMINI_API_KEY else "placeholder",
401
  "image_base64": img_base64
402
- })
 
 
 
 
 
403
 
404
  except Exception as e:
405
  raise HTTPException(status_code=500, detail=str(e))
@@ -460,8 +504,39 @@ async def get_generation_history(limit: int = 10):
460
  except Exception as e:
461
  raise HTTPException(status_code=500, detail=str(e))
462
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  # Gradio Interface functions
464
- def gradio_generate(prompt: str, size: str, style: str, quality: str, negative_prompt: str):
 
465
  """Generate image through Gradio interface using Nano Banana"""
466
  try:
467
  if not prompt:
@@ -485,7 +560,7 @@ def gradio_generate(prompt: str, size: str, style: str, quality: str, negative_p
485
  # Generate image using Gemini
486
  image = generate_image_with_gemini(enhanced_prompt, width, height, style)
487
 
488
- # Save image
489
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
490
  filename = f"gradio_gen_{timestamp}.png"
491
  filepath = GENERATED_DIR / filename
@@ -497,6 +572,33 @@ def gradio_generate(prompt: str, size: str, style: str, quality: str, negative_p
497
  else:
498
  status += f"\n๐ŸŽจ Model: {MODEL_NAME}"
499
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  return image, status
501
 
502
  except Exception as e:
@@ -573,6 +675,40 @@ def gradio_compose(images, compose_prompt):
573
  except Exception as e:
574
  return None, f"โŒ Error: {str(e)}"
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  # Create Gradio interface
577
  with gr.Blocks(title="NanoBanana Gemini Image Generator", theme=gr.themes.Soft()) as demo:
578
  gr.Markdown(
@@ -649,6 +785,23 @@ with gr.Blocks(title="NanoBanana Gemini Image Generator", theme=gr.themes.Soft()
649
  lines=2
650
  )
651
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
  gen_button = gr.Button("๐Ÿš€ Generate with Nano Banana", variant="primary", size="lg")
653
 
654
  with gr.Column():
@@ -671,7 +824,7 @@ with gr.Blocks(title="NanoBanana Gemini Image Generator", theme=gr.themes.Soft()
671
 
672
  gen_button.click(
673
  fn=gradio_generate,
674
- inputs=[gen_prompt, gen_size, gen_style, gen_quality, gen_negative],
675
  outputs=[gen_output, gen_status]
676
  )
677
 
@@ -771,6 +924,62 @@ with gr.Blocks(title="NanoBanana Gemini Image Generator", theme=gr.themes.Soft()
771
  # Auto-load history on tab open
772
  demo.load(fn=get_history, outputs=history_display)
773
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
  # Footer
775
  gr.Markdown(
776
  """
 
46
  else:
47
  logger.warning("GEMINI_API_KEY not found. Image generation will use placeholder images.")
48
 
49
+ # Initialize Dataset Manager
50
+ HF_TOKEN = os.getenv("HF_TOKEN")
51
+ DATASET_REPO_ID = os.getenv("DATASET_REPO_ID")
52
+ dataset_manager = None
53
+
54
+ if HF_TOKEN and DATASET_REPO_ID:
55
+ try:
56
+ from dataset_manager import DatasetManager
57
+ dataset_manager = DatasetManager(DATASET_REPO_ID, HF_TOKEN)
58
+ logger.info(f"Dataset manager initialized for repository: {DATASET_REPO_ID}")
59
+ except Exception as e:
60
+ logger.warning(f"Could not initialize dataset manager: {e}")
61
+ else:
62
+ if not HF_TOKEN:
63
+ logger.info("HF_TOKEN not set. Dataset saving feature disabled.")
64
+ if not DATASET_REPO_ID:
65
+ logger.info("DATASET_REPO_ID not set. Dataset saving feature disabled.")
66
+
67
  def generate_image_with_gemini(prompt: str, width: int = 1024, height: int = 1024, style: str = "Default") -> Image.Image:
68
  """
69
  Generate image using Gemini 2.5 Flash Image Preview (Nano Banana)
 
388
  async def generate_image_api(
389
  prompt: str,
390
  size: str = "1024x1024",
391
+ style: str = "Default",
392
+ save_to_dataset: bool = True,
393
+ dataset_folder: Optional[str] = None
394
  ):
395
  """Generate image via API using Gemini Nano Banana"""
396
  try:
 
400
  # Generate image
401
  image = generate_image_with_gemini(prompt, width, height, style)
402
 
403
+ # Save image locally
404
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
405
  filename = f"api_gen_{timestamp}.png"
406
  filepath = GENERATED_DIR / filename
407
  image.save(filepath)
408
 
409
+ # Save to dataset repository if enabled
410
+ dataset_url = None
411
+ if dataset_manager and save_to_dataset:
412
+ try:
413
+ metadata = {
414
+ "style": style,
415
+ "size": size,
416
+ "model": MODEL_NAME if GEMINI_API_KEY else "placeholder",
417
+ "generation_type": "text-to-image"
418
+ }
419
+ dataset_url = dataset_manager.save_image(
420
+ image=image,
421
+ prompt=prompt,
422
+ folder_name=dataset_folder,
423
+ metadata=metadata
424
+ )
425
+ except Exception as dataset_error:
426
+ logger.error(f"Failed to save to dataset: {dataset_error}")
427
+
428
  # Convert to base64
429
  buffer = BytesIO()
430
  image.save(buffer, format="PNG")
431
  img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
432
 
433
+ response_data = {
434
  "success": True,
435
  "filename": filename,
436
  "prompt": prompt,
 
438
  "style": style,
439
  "model": MODEL_NAME if GEMINI_API_KEY else "placeholder",
440
  "image_base64": img_base64
441
+ }
442
+
443
+ if dataset_url:
444
+ response_data["dataset_url"] = dataset_url
445
+
446
+ return JSONResponse(content=response_data)
447
 
448
  except Exception as e:
449
  raise HTTPException(status_code=500, detail=str(e))
 
504
  except Exception as e:
505
  raise HTTPException(status_code=500, detail=str(e))
506
 
507
+ @app.get("/api/dataset/folders")
508
+ async def get_dataset_folders():
509
+ """Get list of folders in dataset repository"""
510
+ if not dataset_manager:
511
+ return JSONResponse(
512
+ status_code=503,
513
+ content={"error": "Dataset manager not configured"}
514
+ )
515
+
516
+ try:
517
+ folders = dataset_manager.get_folders()
518
+ return JSONResponse(content={"folders": folders, "count": len(folders)})
519
+ except Exception as e:
520
+ raise HTTPException(status_code=500, detail=str(e))
521
+
522
+ @app.get("/api/dataset/images/{folder_name}")
523
+ async def get_dataset_images(folder_name: str):
524
+ """Get list of images in a specific dataset folder"""
525
+ if not dataset_manager:
526
+ return JSONResponse(
527
+ status_code=503,
528
+ content={"error": "Dataset manager not configured"}
529
+ )
530
+
531
+ try:
532
+ images = dataset_manager.get_images_in_folder(folder_name)
533
+ return JSONResponse(content={"images": images, "count": len(images), "folder": folder_name})
534
+ except Exception as e:
535
+ raise HTTPException(status_code=500, detail=str(e))
536
+
537
  # Gradio Interface functions
538
+ def gradio_generate(prompt: str, size: str, style: str, quality: str, negative_prompt: str,
539
+ save_to_dataset: bool = True, dataset_folder: str = ""):
540
  """Generate image through Gradio interface using Nano Banana"""
541
  try:
542
  if not prompt:
 
560
  # Generate image using Gemini
561
  image = generate_image_with_gemini(enhanced_prompt, width, height, style)
562
 
563
+ # Save image locally
564
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
565
  filename = f"gradio_gen_{timestamp}.png"
566
  filepath = GENERATED_DIR / filename
 
572
  else:
573
  status += f"\n๐ŸŽจ Model: {MODEL_NAME}"
574
 
575
+ # Save to dataset if enabled
576
+ if dataset_manager and save_to_dataset:
577
+ try:
578
+ metadata = {
579
+ "style": style,
580
+ "quality": quality,
581
+ "size": f"{width}x{height}",
582
+ "negative_prompt": negative_prompt,
583
+ "model": MODEL_NAME if GEMINI_API_KEY else "placeholder",
584
+ "generation_type": "text-to-image"
585
+ }
586
+
587
+ # Use provided folder or None (will default to date)
588
+ folder_name = dataset_folder if dataset_folder.strip() else None
589
+
590
+ dataset_url = dataset_manager.save_image(
591
+ image=image,
592
+ prompt=prompt,
593
+ folder_name=folder_name,
594
+ metadata=metadata
595
+ )
596
+
597
+ if dataset_url:
598
+ status += f"\n๐Ÿ“ Saved to dataset: {folder_name or datetime.now().strftime('%Y_%m_%d')}"
599
+ except Exception as dataset_error:
600
+ status += f"\nโš ๏ธ Dataset save failed: {str(dataset_error)}"
601
+
602
  return image, status
603
 
604
  except Exception as e:
 
675
  except Exception as e:
676
  return None, f"โŒ Error: {str(e)}"
677
 
678
+ def get_dataset_folders_list():
679
+ """Get list of folders for dropdown"""
680
+ if not dataset_manager:
681
+ return []
682
+ try:
683
+ return dataset_manager.get_folders()
684
+ except:
685
+ return []
686
+
687
+ def load_dataset_gallery(folder_name: str):
688
+ """Load images from dataset folder for gallery"""
689
+ if not dataset_manager or not folder_name:
690
+ return []
691
+
692
+ try:
693
+ images = dataset_manager.get_images_in_folder(folder_name)
694
+ gallery_items = []
695
+
696
+ # Load first few images for preview
697
+ for img_info in images[:20]: # Limit to 20 for performance
698
+ try:
699
+ # Download and display image from URL
700
+ import requests
701
+ response = requests.get(img_info["url"], timeout=10)
702
+ if response.status_code == 200:
703
+ img = Image.open(BytesIO(response.content))
704
+ gallery_items.append(img)
705
+ except:
706
+ continue
707
+
708
+ return gallery_items
709
+ except:
710
+ return []
711
+
712
  # Create Gradio interface
713
  with gr.Blocks(title="NanoBanana Gemini Image Generator", theme=gr.themes.Soft()) as demo:
714
  gr.Markdown(
 
785
  lines=2
786
  )
787
 
788
+ # Dataset save options
789
+ with gr.Accordion("๐Ÿ“ Dataset Options", open=False):
790
+ gen_save_dataset = gr.Checkbox(
791
+ label="Save to Dataset Repository",
792
+ value=True if dataset_manager else False,
793
+ interactive=bool(dataset_manager)
794
+ )
795
+ gen_dataset_folder = gr.Textbox(
796
+ label="Folder Name (leave empty for date-based)",
797
+ placeholder="e.g., 'portraits' or leave empty for YYYY_MM_DD",
798
+ value="",
799
+ interactive=bool(dataset_manager)
800
+ )
801
+
802
+ if not dataset_manager:
803
+ gr.Markdown("โš ๏ธ Dataset saving disabled. Set HF_TOKEN and DATASET_REPO_ID in environment.")
804
+
805
  gen_button = gr.Button("๐Ÿš€ Generate with Nano Banana", variant="primary", size="lg")
806
 
807
  with gr.Column():
 
824
 
825
  gen_button.click(
826
  fn=gradio_generate,
827
+ inputs=[gen_prompt, gen_size, gen_style, gen_quality, gen_negative, gen_save_dataset, gen_dataset_folder],
828
  outputs=[gen_output, gen_status]
829
  )
830
 
 
924
  # Auto-load history on tab open
925
  demo.load(fn=get_history, outputs=history_display)
926
 
927
+ # Dataset Gallery Tab
928
+ if dataset_manager:
929
+ with gr.Tab("๐Ÿ–ผ๏ธ Dataset Gallery"):
930
+ gr.Markdown(
931
+ f"""
932
+ ### Browse Generated Images
933
+ View images saved to the dataset repository: **{DATASET_REPO_ID}**
934
+
935
+ [View on Hugging Face](https://huggingface.co/datasets/{DATASET_REPO_ID})
936
+ """
937
+ )
938
+
939
+ with gr.Row():
940
+ folder_dropdown = gr.Dropdown(
941
+ label="Select Folder",
942
+ choices=get_dataset_folders_list(),
943
+ value=None,
944
+ interactive=True
945
+ )
946
+ refresh_folders_btn = gr.Button("๐Ÿ”„ Refresh Folders")
947
+
948
+ gallery = gr.Gallery(
949
+ label="Images",
950
+ show_label=True,
951
+ elem_id="gallery",
952
+ columns=4,
953
+ rows=3,
954
+ object_fit="contain",
955
+ height="auto"
956
+ )
957
+
958
+ def refresh_folders():
959
+ folders = get_dataset_folders_list()
960
+ return gr.Dropdown(choices=folders, value=folders[0] if folders else None)
961
+
962
+ refresh_folders_btn.click(
963
+ fn=refresh_folders,
964
+ outputs=folder_dropdown
965
+ )
966
+
967
+ folder_dropdown.change(
968
+ fn=load_dataset_gallery,
969
+ inputs=folder_dropdown,
970
+ outputs=gallery
971
+ )
972
+
973
+ # Load first folder on tab open
974
+ demo.load(
975
+ fn=lambda: get_dataset_folders_list()[0] if get_dataset_folders_list() else None,
976
+ outputs=folder_dropdown
977
+ ).then(
978
+ fn=load_dataset_gallery,
979
+ inputs=folder_dropdown,
980
+ outputs=gallery
981
+ )
982
+
983
  # Footer
984
  gr.Markdown(
985
  """
dataset_manager.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Dataset Manager for NanoBanana Image Generator
3
+ Handles saving generated images to Hugging Face Dataset Repository
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import logging
9
+ from typing import Optional, Dict, Any, List
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from PIL import Image
13
+ from io import BytesIO
14
+ import tempfile
15
+
16
+ from huggingface_hub import HfApi, upload_file, upload_folder, create_repo, list_repo_files
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class DatasetManager:
22
+ """
23
+ Manages image uploads to Hugging Face Dataset Repository
24
+ with custom folder structure support
25
+ """
26
+
27
+ def __init__(self, repo_id: str, token: str):
28
+ """
29
+ Initialize Dataset Manager
30
+
31
+ Args:
32
+ repo_id: Hugging Face dataset repository ID (e.g., "username/dataset-name")
33
+ token: Hugging Face API token with write access
34
+ """
35
+ self.repo_id = repo_id
36
+ self.token = token
37
+ self.api = HfApi(token=token)
38
+
39
+ # Ensure repository exists
40
+ self._ensure_repo_exists()
41
+
42
+ logger.info(f"DatasetManager initialized for repository: {repo_id}")
43
+
44
+ def _ensure_repo_exists(self):
45
+ """Create dataset repository if it doesn't exist"""
46
+ try:
47
+ # Try to get repository info
48
+ self.api.repo_info(repo_id=self.repo_id, repo_type="dataset")
49
+ logger.info(f"Dataset repository {self.repo_id} already exists")
50
+ except Exception as e:
51
+ # Repository doesn't exist, create it
52
+ try:
53
+ self.api.create_repo(
54
+ repo_id=self.repo_id,
55
+ repo_type="dataset",
56
+ private=False,
57
+ exist_ok=True
58
+ )
59
+ logger.info(f"Created new dataset repository: {self.repo_id}")
60
+
61
+ # Create initial README
62
+ readme_content = f"""# NanoBanana Generated Images Dataset
63
+
64
+ This dataset contains images generated by the NanoBanana Gemini Image Generator.
65
+
66
+ ## Structure
67
+ - `images/` - Generated images organized by date or custom folders
68
+ - `YYYY_MM_DD/` - Images generated on specific dates
69
+ - Custom folders as specified during generation
70
+
71
+ ## Metadata
72
+ Each image is accompanied by a JSON metadata file containing:
73
+ - Generation prompt
74
+ - Model used
75
+ - Generation parameters
76
+ - Timestamp
77
+
78
+ ## Usage
79
+ You can load this dataset using the Hugging Face `datasets` library:
80
+
81
+ ```python
82
+ from datasets import load_dataset
83
+ dataset = load_dataset("{self.repo_id}")
84
+ ```
85
+
86
+ ---
87
+ Generated with ๐ŸŒ NanoBanana Image Generator
88
+ """
89
+ self._upload_readme(readme_content)
90
+ except Exception as create_error:
91
+ logger.warning(f"Could not create repository: {create_error}")
92
+
93
+ def _upload_readme(self, content: str):
94
+ """Upload README.md to repository"""
95
+ try:
96
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
97
+ f.write(content)
98
+ temp_path = f.name
99
+
100
+ self.api.upload_file(
101
+ path_or_fileobj=temp_path,
102
+ path_in_repo="README.md",
103
+ repo_id=self.repo_id,
104
+ repo_type="dataset"
105
+ )
106
+
107
+ # Clean up temp file
108
+ os.unlink(temp_path)
109
+ except Exception as e:
110
+ logger.error(f"Failed to upload README: {e}")
111
+
112
+ def save_image(
113
+ self,
114
+ image: Image.Image,
115
+ prompt: str,
116
+ folder_name: Optional[str] = None,
117
+ metadata: Optional[Dict[str, Any]] = None
118
+ ) -> Optional[str]:
119
+ """
120
+ Save image to dataset repository
121
+
122
+ Args:
123
+ image: PIL Image object to save
124
+ prompt: Generation prompt used
125
+ folder_name: Optional folder name. If None, uses YYYY_MM_DD format
126
+ metadata: Optional additional metadata to save
127
+
128
+ Returns:
129
+ URL to the uploaded image in the dataset repository, or None if failed
130
+ """
131
+ try:
132
+ # Determine folder name
133
+ if not folder_name:
134
+ folder_name = datetime.now().strftime("%Y_%m_%d")
135
+
136
+ # Generate unique filename
137
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # Milliseconds
138
+ image_filename = f"image_{timestamp}.png"
139
+ metadata_filename = f"image_{timestamp}.json"
140
+
141
+ # Paths in repository
142
+ image_path_in_repo = f"images/{folder_name}/{image_filename}"
143
+ metadata_path_in_repo = f"images/{folder_name}/{metadata_filename}"
144
+
145
+ # Create metadata
146
+ image_metadata = {
147
+ "prompt": prompt,
148
+ "timestamp": datetime.now().isoformat(),
149
+ "folder": folder_name,
150
+ "filename": image_filename,
151
+ "size": {
152
+ "width": image.width,
153
+ "height": image.height
154
+ }
155
+ }
156
+
157
+ # Add additional metadata if provided
158
+ if metadata:
159
+ image_metadata.update(metadata)
160
+
161
+ # Save image to temporary file
162
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as img_temp:
163
+ image.save(img_temp, format='PNG')
164
+ img_temp_path = img_temp.name
165
+
166
+ # Save metadata to temporary file
167
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as meta_temp:
168
+ json.dump(image_metadata, meta_temp, indent=2)
169
+ meta_temp_path = meta_temp.name
170
+
171
+ # Upload image
172
+ self.api.upload_file(
173
+ path_or_fileobj=img_temp_path,
174
+ path_in_repo=image_path_in_repo,
175
+ repo_id=self.repo_id,
176
+ repo_type="dataset",
177
+ commit_message=f"Add generated image: {image_filename}"
178
+ )
179
+
180
+ # Upload metadata
181
+ self.api.upload_file(
182
+ path_or_fileobj=meta_temp_path,
183
+ path_in_repo=metadata_path_in_repo,
184
+ repo_id=self.repo_id,
185
+ repo_type="dataset",
186
+ commit_message=f"Add metadata for: {image_filename}"
187
+ )
188
+
189
+ # Clean up temp files
190
+ os.unlink(img_temp_path)
191
+ os.unlink(meta_temp_path)
192
+
193
+ # Return URL to the image
194
+ dataset_url = f"https://huggingface.co/datasets/{self.repo_id}/blob/main/{image_path_in_repo}"
195
+
196
+ logger.info(f"Successfully saved image to dataset: {image_path_in_repo}")
197
+ return dataset_url
198
+
199
+ except Exception as e:
200
+ logger.error(f"Failed to save image to dataset: {e}")
201
+ return None
202
+
203
+ def get_folders(self) -> List[str]:
204
+ """
205
+ Get list of folders in the dataset repository
206
+
207
+ Returns:
208
+ List of folder names
209
+ """
210
+ try:
211
+ files = self.api.list_repo_files(
212
+ repo_id=self.repo_id,
213
+ repo_type="dataset"
214
+ )
215
+
216
+ # Extract folder names from paths
217
+ folders = set()
218
+ for file in files:
219
+ if file.startswith("images/"):
220
+ parts = file.split("/")
221
+ if len(parts) > 1:
222
+ folder = parts[1]
223
+ folders.add(folder)
224
+
225
+ return sorted(list(folders))
226
+
227
+ except Exception as e:
228
+ logger.error(f"Failed to get folders: {e}")
229
+ return []
230
+
231
+ def get_images_in_folder(self, folder_name: str) -> List[Dict[str, str]]:
232
+ """
233
+ Get list of images in a specific folder
234
+
235
+ Args:
236
+ folder_name: Name of the folder
237
+
238
+ Returns:
239
+ List of dictionaries containing image info
240
+ """
241
+ try:
242
+ files = self.api.list_repo_files(
243
+ repo_id=self.repo_id,
244
+ repo_type="dataset"
245
+ )
246
+
247
+ images = []
248
+ folder_prefix = f"images/{folder_name}/"
249
+
250
+ for file in files:
251
+ if file.startswith(folder_prefix) and file.endswith(".png"):
252
+ # Get corresponding metadata file
253
+ metadata_file = file.replace(".png", ".json")
254
+
255
+ image_info = {
256
+ "filename": os.path.basename(file),
257
+ "path": file,
258
+ "url": f"https://huggingface.co/datasets/{self.repo_id}/resolve/main/{file}",
259
+ "metadata_url": f"https://huggingface.co/datasets/{self.repo_id}/resolve/main/{metadata_file}"
260
+ }
261
+ images.append(image_info)
262
+
263
+ return sorted(images, key=lambda x: x["filename"], reverse=True)
264
+
265
+ except Exception as e:
266
+ logger.error(f"Failed to get images in folder {folder_name}: {e}")
267
+ return []
268
+
269
+ def batch_save_images(
270
+ self,
271
+ images_data: List[Dict[str, Any]],
272
+ folder_name: Optional[str] = None
273
+ ) -> List[Optional[str]]:
274
+ """
275
+ Save multiple images in batch
276
+
277
+ Args:
278
+ images_data: List of dicts with 'image', 'prompt', and optional 'metadata'
279
+ folder_name: Optional folder name for all images
280
+
281
+ Returns:
282
+ List of URLs for uploaded images
283
+ """
284
+ urls = []
285
+ for data in images_data:
286
+ url = self.save_image(
287
+ image=data['image'],
288
+ prompt=data['prompt'],
289
+ folder_name=folder_name,
290
+ metadata=data.get('metadata')
291
+ )
292
+ urls.append(url)
293
+
294
+ return urls
requirements.txt CHANGED
@@ -13,6 +13,8 @@ numpy>=1.24.0
13
  # Utilities
14
  python-dotenv>=1.0.0
15
  aiofiles>=23.2.1
 
16
 
17
- # For image generation via nanobanana MCP
18
- huggingface_hub>=0.20.0
 
 
13
  # Utilities
14
  python-dotenv>=1.0.0
15
  aiofiles>=23.2.1
16
+ requests>=2.28.0
17
 
18
+ # Hugging Face integration
19
+ huggingface_hub>=0.20.0
20
+ datasets>=2.14.0