| from flask import Flask, request, jsonify, send_file, render_template_string |
| import requests |
| import io |
| import os |
| import random |
| from PIL import Image |
| from deep_translator import GoogleTranslator |
| from flask_cors import CORS |
|
|
| app = Flask(__name__) |
| CORS(app) |
|
|
| API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev" |
| API_TOKEN = os.getenv("HF_READ_TOKEN") |
| headers = {"Authorization": f"Bearer {API_TOKEN}"} |
| timeout = 300 |
|
|
| |
| def query(prompt, negative_prompt="", steps=35, cfg_scale=7, sampler="DPM++ 2M Karras", seed=-1, strength=0.7, width=1024, height=1024): |
| if not prompt: |
| return None, "Prompt is required" |
|
|
| key = random.randint(0, 999) |
| |
| |
| prompt = GoogleTranslator(source='ru', target='en').translate(prompt) |
| print(f'Generation {key} translation: {prompt}') |
|
|
| |
| prompt = f"{prompt} | ultra detail, ultra elaboration, ultra quality, perfect." |
| print(f'Generation {key}: {prompt}') |
| |
| payload = { |
| "inputs": prompt, |
| "is_negative": False, |
| "steps": steps, |
| "cfg_scale": cfg_scale, |
| "seed": seed if seed != -1 else random.randint(1, 1000000000), |
| "strength": strength, |
| "parameters": { |
| "width": width, |
| "height": height |
| } |
| } |
|
|
| for attempt in range(3): |
| try: |
| response = requests.post(API_URL, headers=headers, json=payload, timeout=timeout) |
| if response.status_code != 200: |
| return None, f"Error: Failed to get image. Status code: {response.status_code}, Details: {response.text}" |
|
|
| image_bytes = response.content |
| image = Image.open(io.BytesIO(image_bytes)) |
| return image, None |
| except requests.exceptions.Timeout: |
| if attempt < 2: |
| print("Timeout occurred, retrying...") |
| continue |
| return None, "Error: The request timed out. Please try again." |
| except requests.exceptions.RequestException as e: |
| return None, f"Request Exception: {str(e)}" |
| except Exception as e: |
| return None, f"Error when trying to open the image: {e}" |
|
|
| |
| index_html = """ |
| <!DOCTYPE html> |
| <html lang="ja"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>FLUX.1-Dev Image Generator</title> |
| <script> |
| async function generateImage() { |
| const prompt = document.getElementById("prompt").value; |
| const negative_prompt = document.getElementById("negative_prompt").value; |
| const width = document.getElementById("width").value; |
| const height = document.getElementById("height").value; |
| const steps = document.getElementById("steps").value; |
| const cfgs = document.getElementById("cfgs").value; |
| const sampler = document.getElementById("sampler").value; |
| const strength = document.getElementById("strength").value; |
| const seed = document.getElementById("seed").value; |
| const button = document.getElementById("generate-button"); |
| const errorMessage = document.getElementById("error-message"); |
| button.disabled = true; // ボタンを無効化 |
| errorMessage.textContent = ''; // エラーメッセージをクリア |
| |
| const params = new URLSearchParams({ |
| prompt, |
| negative_prompt, |
| width, |
| height, |
| steps, |
| cfgs, |
| sampler, |
| strength, |
| seed |
| }); |
| |
| try { |
| const response = await fetch(`https://soiz-flux-1-dev-serverless.hf.space/generate?${params.toString()}`); |
| if (!response.ok) { |
| throw new Error('画像生成に失敗しました'); |
| } |
| const imageBlob = await response.blob(); |
| const reader = new FileReader(); |
| reader.onloadend = function () { |
| const img = document.getElementById("generated-image"); |
| img.src = reader.result; // dataURLを設定 |
| img.style.display = 'block'; // 画像を表示 |
| }; |
| reader.readAsDataURL(imageBlob); |
| } catch (error) { |
| errorMessage.textContent = error.message; // エラーメッセージを表示 |
| console.error(error); // エラーをコンソールに出力 |
| } finally { |
| button.disabled = false; // ボタンを再有効化 |
| } |
| } |
| |
| function syncWidth(value) { |
| document.getElementById("width-slider").value = value; |
| } |
| |
| function syncHeight(value) { |
| document.getElementById("height-slider").value = value; |
| } |
| |
| function updateWidthInput() { |
| const widthSlider = document.getElementById("width-slider"); |
| const widthInput = document.getElementById("width"); |
| widthInput.value = widthSlider.value; |
| } |
| |
| function updateHeightInput() { |
| const heightSlider = document.getElementById("height-slider"); |
| const heightInput = document.getElementById("height"); |
| heightInput.value = heightSlider.value; |
| } |
| </script> |
| </head> |
| <body> |
| <h1>FLUX.1-Dev Image Generator</h1> |
| <form onsubmit="event.preventDefault(); generateImage();"> |
| <label for="prompt">Prompt:</label><br> |
| <textarea id="prompt" name="prompt" rows="4" cols="50" placeholder="Enter your prompt" required></textarea><br><br> |
| |
| <label for="negative_prompt">Negative Prompt:</label><br> |
| <textarea id="negative_prompt" name="negative_prompt" rows="4" cols="50" placeholder="Enter negative prompt (optional)"></textarea><br><br> |
| |
| <label for="width">Width:</label> |
| <input type="number" id="width" name="width" value="1024" min="64" max="2048" step="8" style="width:250px" oninput="syncWidth(this.value)"> |
| <input type="range" id="width-slider" name="width-slider" min="64" max="2048" value="1024" step="8" style="width:250px; display: inline-block;" oninput="updateWidthInput()"><br><br> |
| |
| <label for="height">Height:</label> |
| <input type="number" id="height" name="height" value="1024" min="64" max="2048" step="8" oninput="syncHeight(this.value)"> |
| <input type="range" id="height-slider" name="height-slider" min="64" max="2048" value="1024" step="8" style="width:250px; display: inline-block;" oninput="updateHeightInput()"><br><br> |
| |
| <label for="steps">Sampling Steps:</label> |
| <input type="number" id="steps" name="steps" value="35"><br><br> |
| |
| <label for="cfgs">CFG Scale:</label> |
| <input type="number" id="cfgs" name="cfgs" value="7"><br><br> |
| |
| <label for="sampler">Sampling Method:</label> |
| <select id="sampler" name="sampler"> |
| <option value="DPM++ 2M Karras">DPM++ 2M Karras</option> |
| <option value="DPM++ SDE Karras">DPM++ SDE Karras</option> |
| <option value="Euler">Euler</option> |
| <option value="Euler a">Euler a</option> |
| <option value="Heun">Heun</option> |
| <option value="DDIM">DDIM</option> |
| </select><br><br> |
| |
| <label for="strength">Strength:</label> |
| <input type="number" id="strength" name="strength" value="0.7" step="0.01" min="0" max="1"><br><br> |
| |
| <label for="seed">Seed:</label> |
| <input type="number" id="seed" name="seed" value="-1" step="1"><br><br> |
| |
| <button type="submit" id="generate-button">Generate Image</button> |
| </form> |
| |
| <div id="error-message" style="color: red;"></div> |
| |
| <h2>Generated Image:</h2> |
| <img id="generated-image" src="" alt="Generated Image" style="max-width: 100%; height: auto; display: none;"> |
| </body> |
| </html> |
| |
| """ |
|
|
| @app.route('/') |
| def index(): |
| return render_template_string(index_html) |
|
|
| @app.route('/generate', methods=['GET']) |
| def generate_image(): |
| |
| prompt = request.args.get("prompt", "") |
| negative_prompt = request.args.get("negative_prompt", "") |
| steps = int(request.args.get("steps", 35)) |
| cfg_scale = float(request.args.get("cfgs", 7)) |
| sampler = request.args.get("sampler", "DPM++ 2M Karras") |
| strength = float(request.args.get("strength", 0.7)) |
| seed = int(request.args.get("seed", -1)) |
| width = int(request.args.get("width", 1024)) |
| height = int(request.args.get("height", 1024)) |
|
|
| |
| image, error = query(prompt, negative_prompt, steps, cfg_scale, sampler, seed, strength, width, height) |
| |
| if error: |
| return jsonify({"error": error}), 400 |
|
|
| |
| img_bytes = io.BytesIO() |
| image.save(img_bytes, format='PNG') |
| img_bytes.seek(0) |
| return send_file(img_bytes, mimetype='image/png') |
|
|
| if __name__ == "__main__": |
| app.run(host='0.0.0.0', port=7860) |
|
|