File size: 7,133 Bytes
1d9be8e
 
ca9155f
12fcbf3
bc0e444
 
 
2fa6a20
845ef22
c7d1a06
12fcbf3
80d18f3
bc0e444
ca9155f
 
 
 
 
 
 
 
 
 
 
80d18f3
 
 
 
b162f8e
bc0e444
 
80d18f3
30175c5
bc0e444
 
 
 
 
 
 
 
 
 
 
 
 
 
80d18f3
bc0e444
 
 
 
80d18f3
bc0e444
 
 
 
 
 
 
 
 
 
 
 
80d18f3
bc0e444
 
 
 
80d18f3
bc0e444
 
80d18f3
 
 
 
12fcbf3
 
 
 
 
 
bc0e444
12fcbf3
 
80d18f3
12fcbf3
80d18f3
 
 
 
 
 
12fcbf3
 
 
 
bc0e444
12fcbf3
ca9155f
 
 
 
 
 
 
 
 
 
 
9c3af51
61da7bb
 
 
9c3af51
 
 
 
2fa6a20
 
 
 
80d18f3
12fcbf3
bc0e444
12fcbf3
9c3af51
12fcbf3
bc0e444
80d18f3
bc0e444
 
 
 
 
 
 
 
12fcbf3
 
bc0e444
12fcbf3
 
bc0e444
 
12fcbf3
bc0e444
 
12fcbf3
bc0e444
12fcbf3
bc0e444
 
 
 
 
 
 
30175c5
12fcbf3
8075177
2fa6a20
 
 
12fcbf3
ca9155f
 
 
 
 
 
 
 
 
 
 
12fcbf3
ca9155f
9c3af51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca9155f
9c3af51
 
ca9155f
 
9c3af51
 
 
 
 
 
 
ca9155f
9c3af51
 
bc0e444
9c3af51
 
 
 
 
 
 
 
 
 
 
ca9155f
 
 
 
 
 
 
 
 
9c3af51
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import gradio as gr
import random
import os
import requests
import base64
from PIL import Image
from io import BytesIO

HF_TOKEN = os.environ.get("girlToken")

API_BASE = "https://prithivmlmods-qwen-image-edit-2511-loras-fast.hf.space"
INFER_URL = f"{API_BASE}/gradio/infer"
NAMED_API_URL = f"{API_BASE}/gradio_api/call/v2/infer"

LORA_STYLES = [
    'Multiple-Angles', 'Photo-to-Anime', 'Anime-V2', 'Light-Migration',
    'Upscaler', 'Style-Transfer', 'Manga-Tone', 'Anything2Real',
    'Fal-Multiple-Angles', 'Polaroid-Photo', 'Unblur-Anything',
    'Midnight-Noir-Eyes-Spotlight', 'Hyper-Realistic-Portrait',
    'Ultra-Realistic-Portrait', 'Pixar-Inspired-3D', 'Noir-Comic-Book',
    'Any-light', 'Studio-DeLight', 'Cinematic-FlatLog',
]
MAX_SEED = 2**31 - 1

def encode_image_file_to_b64_payload(image_path):
    """
    返回符合API要求的图片Payload对象(list,不是json字符串)。
    """
    try:
        with open(image_path, "rb") as f:
            image_bytes = f.read()
        # 如非jpeg,转码
        try:
            img = Image.open(BytesIO(image_bytes))
            buffered = BytesIO()
            img.save(buffered, format="JPEG")
            image_bytes = buffered.getvalue()
        except Exception:
            pass
        im_b64 = base64.b64encode(image_bytes).decode("utf-8")
        payload = [
            {
                "data": im_b64,
                "mime_type": "image/jpeg",
                "orig_name": os.path.basename(image_path),
            }
        ]
        return payload
    except Exception as e:
        raise RuntimeError(f"图片编码失败: {e}")

def call_named_infer(
    images_b64_payload,
    prompt,
    lora_adapter,
    seed,
    randomize_seed,
    guidance_scale,
    steps
):
    headers = {
        'Authorization': f'Bearer {HF_TOKEN}',
        'Content-Type': 'application/json'
    }
    payload = {
        "images_b64_json": images_b64_payload, # 注意此处改为直接传 list
        "prompt": prompt,
        "lora_adapter": lora_adapter,
        "seed": int(seed),
        "randomize_seed": bool(randomize_seed),
        "guidance_scale": float(guidance_scale),
        "steps": int(steps),
    }
    print("准备调用/infer:", {**payload, "images_b64_json": "[payload omitted for brevity]"})
    import json
    # 注意: 直接以json.dumps(payload)传body
    resp = requests.post(NAMED_API_URL, data=json.dumps(payload), headers=headers)
    resp.raise_for_status()
    job = resp.json()
    event_id = job.get("event_id")
    return event_id

def poll_infer(event_id):
    url = f"{API_BASE}/gradio_api/call/infer/{event_id}"
    headers = {'Authorization': f'Bearer {HF_TOKEN}'}
    import time
    for i in range(60):
        resp = requests.get(url, headers=headers)
        try:
            result = resp.json()
        except Exception:
            print(f"[轮询第{i+1}次] 响应无法decode,返回内容:{resp.text[:200]}")
            time.sleep(2)
            continue
        if result.get("status") == "complete":
            return result.get("data"), result.get("outputs")
        elif result.get("status") == "error":
            raise Exception(result.get("error"))
        time.sleep(2)
    raise TimeoutError("等候API返回超时")

def infer(
    image,
    prompt,
    lora_adapter,
    seed,
    randomize_seed,
    guidance_scale,
    steps,
    progress=gr.Progress(track_tqdm=True),
):
    if image is None:
        print("未上传图片")
        return None, seed

    if not os.path.exists(image):
        print(f"图片路径不存在: {image}")
        return None, seed

    if randomize_seed:
        seed = random.randint(0, MAX_SEED)

    try:
        images_b64_payload = encode_image_file_to_b64_payload(image)
    except Exception as e:
        print(f"[图片 base64编码失败] {e}")
        return None, seed

    try:
        event_id = call_named_infer(
            images_b64_payload,
            prompt,
            lora_adapter,
            seed,
            randomize_seed,
            guidance_scale,
            steps
        )
        print("API返回event_id:", event_id)
        data, outputs = poll_infer(event_id)
        print("[API 完成] data:", data, "outputs:", outputs)
        img_out = None
        seed_used = seed
        if outputs:
            if isinstance(outputs, dict):
                img_out = outputs.get("url") or outputs.get("path")
                seed_used = outputs.get("seed", seed)
            elif isinstance(outputs, str) and outputs.startswith("/"):
                img_out = API_BASE + outputs
            else:
                img_out = outputs
        elif data:
            if isinstance(data, dict):
                img_out = data.get("url") or data.get("path")
                seed_used = data.get("seed", seed)
            elif isinstance(data, str) and data.startswith("/"):
                img_out = API_BASE + data
            else:
                img_out = data
        if img_out and isinstance(img_out, str) and not img_out.startswith("http"):
            img_out = API_BASE + img_out
        return img_out, int(seed_used)
    except Exception as e:
        import traceback
        traceback.print_exc()
        print(f"[API 调用异常] {e}")
        return None, seed

css = """
#col-container {
    margin: 0 auto;
    max-width: 640px;
}
"""

with gr.Blocks(css=css) as demo:
    with gr.Column(elem_id="col-container"):
        gr.Markdown("# 图像编辑 Demo\n基于 prithivMLmods/Qwen-Image-Edit-2511-LoRAs-Fast (新版API)")

        image = gr.Image(
            label="上传图片",
            sources=["upload"],
            type="filepath",
        )

        prompt = gr.Text(
            label="编辑描述(Prompt)",
            placeholder="请输入图片编辑描述...",
        )

        lora_adapter = gr.Dropdown(
            label="编辑风格(Style)",
            choices=LORA_STYLES,
            value="Photo-to-Anime"
        )

        run_button = gr.Button("执行编辑", variant="primary")

        result = gr.Image(label="结果图片", show_label=True)

        with gr.Accordion("高级设置", open=False):
            seed = gr.Slider(
                label="随机种子",
                minimum=0,
                maximum=MAX_SEED,
                step=1,
                value=0,
            )
            randomize_seed = gr.Checkbox(label="随机化种子", value=True)
            guidance_scale = gr.Slider(
                label="引导强度 (Guidance Scale)",
                minimum=1.0,
                maximum=10.0,
                step=0.1,
                value=1.0,
            )
            steps = gr.Slider(
                label="推理步数 (Steps)",
                minimum=1,
                maximum=50,
                step=1,
                value=4,
            )

    gr.on(
        triggers=[run_button.click, prompt.submit],
        fn=infer,
        inputs=[image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps],
        outputs=[result, seed],
    )

if __name__ == "__main__":
    demo.launch(ssr_mode=False, share=True)