| import asyncio |
| import base64 |
| import io |
| import logging |
| import os |
|
|
| import gradio as gr |
| import httpx |
| from PIL import Image |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(asctime)s [%(levelname)s] %(message)s", |
| ) |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| async def generate_async(images, prompt, variation, size): |
| logger.info("giving it to BytePlus...") |
| api_key = os.getenv("BYTEPLUS_API_KEY") |
| base_url = os.getenv("BYTEPLUS_URL", "").rstrip("/") |
| response_format = "b64_json" |
| watermark = False |
|
|
| images_input = [] |
| images_output = [] |
|
|
| |
| for img in images or []: |
| try: |
| with open(img.name, "rb") as f: |
| image_bytes = f.read() |
| encoded = base64.b64encode(image_bytes).decode("utf-8") |
| prefixed_b64 = f"data:image/png;base64,{encoded}" |
| images_input.append(prefixed_b64) |
| except Exception as e: |
| logger.error(f"⚠️ Failed to process image {img.name}: {e}") |
|
|
| try: |
| model_name = "seedream-4-0-250828" |
| request_data = { |
| "model": model_name, |
| "prompt": prompt, |
| "response_format": response_format, |
| "sequential_image_generation": "disabled", |
| "size": size, |
| "watermark": watermark, |
| } |
|
|
| if images_input: |
| request_data["image"] = images_input |
|
|
| headers = { |
| "Content-Type": "application/json", |
| "Authorization": f"Bearer {api_key}", |
| } |
| logger.info("Sending request to BytePlus...") |
| async with httpx.AsyncClient(timeout=120) as client: |
| for _ in range(int(variation)): |
| response = await client.post( |
| f"{base_url}/images/generations", |
| json=request_data, |
| headers=headers, |
| ) |
| response.raise_for_status() |
| result = response.json() |
|
|
| |
| for item in result.get("data", []): |
| b64_str = item.get("b64_json") |
| if b64_str: |
| try: |
| image_data = base64.b64decode(b64_str) |
| image = Image.open(io.BytesIO(image_data)) |
| images_output.append(image) |
| except Exception as e: |
| logger.warning(f"⚠️ Failed to decode base64 image: {e}") |
|
|
| return images_output |
|
|
| except Exception as e: |
| logger.error(f"⚠️ Failed to process everything: {e}") |
| return [] |
|
|
|
|
| |
| async def generate(images, prompt, variation, size): |
| return await generate_async(images, prompt, variation, size) |
|
|
|
|
| |
| with gr.Blocks(theme=gr.themes.Glass()) as demo: |
| gr.Markdown("## 🔥 Multi-API Image-to-Image Generator") |
|
|
| with gr.Row(): |
| |
| with gr.Column(scale=1): |
| |
| image_input = gr.File( |
| label="Upload your reference images (optional)", |
| file_count="multiple", |
| file_types=["image"], |
| ) |
|
|
| |
| input_preview = gr.Gallery(label="Preview", columns=3, height="auto") |
|
|
| |
| image_input.change( |
| lambda files: [f.name for f in files] if files else [], |
| inputs=image_input, |
| outputs=input_preview, |
| ) |
|
|
| prompt_input = gr.Textbox( |
| label="Prompt", |
| placeholder="Describe how you want to modify or generate the image...", |
| lines=2, |
| ) |
|
|
| variation_choice = gr.Dropdown( |
| choices=[1, 2, 3, 4, 5], |
| label="Number of Variations", |
| value=1, |
| ) |
|
|
| size_choice = gr.Dropdown( |
| choices=[ |
| "2048x2048", |
| "1728x2304", |
| "2304x1728", |
| "2560x1440", |
| "1440x2560", |
| "1664x2496", |
| "2496x1664", |
| "3024x1296", |
| ], |
| label="Output Size", |
| value="2048x2048", |
| ) |
|
|
| btn = gr.Button("🚀 Generate", variant="primary", size="lg") |
|
|
| |
| with gr.Column(scale=1): |
| gallery = gr.Gallery(label="Generated Results", columns=2, height="auto") |
|
|
| btn.click( |
| fn=generate, |
| inputs=[image_input, prompt_input, variation_choice, size_choice], |
| outputs=gallery, |
| ) |
|
|
| demo.launch() |
|
|