import gc import os from typing import Optional import gradio as gr import torch from diffusers import AutoPipelineForText2Image, EulerAncestralDiscreteScheduler MODEL_ID = os.getenv("MODEL_ID", "stablediffusionapi/counterfeit-v30") DEFAULT_NEGATIVE = os.getenv( "DEFAULT_NEGATIVE", "blurry, lowres, worst quality, low quality, bad anatomy, watermark, text, extra fingers, realistic, photo, 3d, child, loli, young-looking", ) PRESETS = { "🎀 动漫头像": { "prompt": "masterpiece, best quality, 1girl, solo, anime style, upper body portrait, detailed eyes, clean lineart, soft cel shading", "negative": DEFAULT_NEGATIVE, "steps": 12, "cfg": 7.0, "width": 384, "height": 576, "seed": -1, }, "💼 成熟御姐": { "prompt": "masterpiece, best quality, 1woman, mature female, solo, anime style, office lady, detailed eyes, upper body portrait, clean lineart", "negative": DEFAULT_NEGATIVE, "steps": 12, "cfg": 7.0, "width": 384, "height": 576, "seed": -1, }, "🌸 樱花和服": { "prompt": "masterpiece, best quality, 1girl, anime style, kimono, cherry blossoms, elegant pose, detailed face, clean lineart", "negative": DEFAULT_NEGATIVE, "steps": 14, "cfg": 7.0, "width": 448, "height": 640, "seed": -1, }, "🏙 赛博少女": { "prompt": "masterpiece, best quality, 1girl, anime style, cyberpunk city, neon lights, upper body, detailed eyes, clean lineart", "negative": DEFAULT_NEGATIVE, "steps": 14, "cfg": 7.0, "width": 448, "height": 640, "seed": -1, }, } _pipe = None _device = "cuda" if torch.cuda.is_available() else "cpu" _dtype = torch.float16 if _device == "cuda" else torch.float32 def load_pipe(): global _pipe if _pipe is None: pipe = AutoPipelineForText2Image.from_pretrained( MODEL_ID, torch_dtype=_dtype, safety_checker=None, ) if hasattr(pipe, "safety_checker"): pipe.safety_checker = None if hasattr(pipe, "requires_safety_checker"): pipe.requires_safety_checker = False if hasattr(pipe, "scheduler") and pipe.scheduler is not None: pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config) if hasattr(pipe, "enable_attention_slicing"): pipe.enable_attention_slicing() if hasattr(pipe, "enable_vae_slicing"): pipe.enable_vae_slicing() if hasattr(pipe, "vae") and hasattr(pipe.vae, "enable_tiling"): pipe.vae.enable_tiling() pipe = pipe.to(_device) _pipe = pipe return _pipe def unload_pipe(): global _pipe if _pipe is not None: _pipe = None gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() def apply_preset(name: str): p = PRESETS[name] return ( p["prompt"], p["negative"], p["steps"], p["cfg"], p["width"], p["height"], p["seed"], ) def generate( prompt: str, negative_prompt: str, steps: int, guidance_scale: float, width: int, height: int, seed: int, ): if not prompt or not prompt.strip(): raise gr.Error("提示词不能为空") pipe = load_pipe() generator: Optional[torch.Generator] = None if seed >= 0: generator = torch.Generator(device=_device).manual_seed(int(seed)) with torch.inference_mode(): image = pipe( prompt=prompt, negative_prompt=negative_prompt, num_inference_steps=int(steps), guidance_scale=float(guidance_scale), width=int(width), height=int(height), generator=generator, ).images[0] return image with gr.Blocks(title="wode") as demo: gr.Markdown( f"# wode\n\n免费档二次元生图(Counterfeit 路线)\n\n当前模型:`{MODEL_ID}`\n\n推荐先用:`384x576 / 12 steps / CFG 7`。\n\n想更像动漫角色:正向词尽量写 `1girl/1woman + anime style + detailed eyes + clean lineart`。\n想偏成年风格:加 `mature female` 或 `adult woman`。默认反向词已压制 `realistic/photo` 和 `child-like`。\n\n⚠️ 免费 CPU 仍然会慢,通常要等 `1~5 分钟`。" ) with gr.Row(): with gr.Column(): prompt = gr.Textbox( label="正向提示词", lines=4, placeholder="例如:masterpiece, best quality, 1girl, anime style, detailed eyes, clean lineart", ) negative_prompt = gr.Textbox(label="反向提示词", lines=3, value=DEFAULT_NEGATIVE) with gr.Row(): steps = gr.Slider(6, 24, value=12, step=1, label="步数 Steps") guidance_scale = gr.Slider(1, 12, value=7.0, step=0.5, label="引导强度 CFG") with gr.Row(): width = gr.Slider(256, 640, value=384, step=64, label="宽度") height = gr.Slider(256, 896, value=576, step=64, label="高度") seed = gr.Number(label="随机种子(-1 为随机)", value=-1, precision=0) with gr.Row(): run = gr.Button("开始生成", variant="primary") unload = gr.Button("释放模型内存") with gr.Column(): out = gr.Image(label="生成结果", type="pil") gr.Markdown("## 二次元预设按钮") with gr.Row(): preset1 = gr.Button("🎀 动漫头像") preset2 = gr.Button("💼 成熟御姐") preset3 = gr.Button("🌸 樱花和服") preset4 = gr.Button("🏙 赛博少女") for btn, name in [ (preset1, "🎀 动漫头像"), (preset2, "💼 成熟御姐"), (preset3, "🌸 樱花和服"), (preset4, "🏙 赛博少女"), ]: btn.click( fn=lambda n=name: apply_preset(n), inputs=[], outputs=[prompt, negative_prompt, steps, guidance_scale, width, height, seed], ) gr.Examples( label="也可以直接点下面示例", examples=[ [ PRESETS["🎀 动漫头像"]["prompt"], DEFAULT_NEGATIVE, 12, 7.0, 384, 576, -1, ], [ PRESETS["💼 成熟御姐"]["prompt"], DEFAULT_NEGATIVE, 12, 7.0, 384, 576, -1, ], ], inputs=[prompt, negative_prompt, steps, guidance_scale, width, height, seed], ) run.click( fn=generate, inputs=[prompt, negative_prompt, steps, guidance_scale, width, height, seed], outputs=out, ) unload.click(fn=unload_pipe, outputs=[]) if __name__ == "__main__": demo.queue(max_size=4).launch(show_error=True)