rickveloper commited on
Commit
ec03850
·
1 Parent(s): e05dd5c

Initial VibeForge app

Browse files
Files changed (2) hide show
  1. app.py +147 -0
  2. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, random, re, torch
2
+ from typing import List, Tuple
3
+ from PIL import Image, ImageDraw, ImageFont
4
+ import gradio as gr
5
+ from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
6
+
7
+ # --------------------
8
+ # Config
9
+ # --------------------
10
+ MODEL_ID = os.getenv("MODEL_ID", "runwayml/stable-diffusion-v1-5")
11
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
12
+ DTYPE = torch.float16 if torch.cuda.is_available() else torch.float32
13
+
14
+ # Simple prompt guardrail (blocks obvious NSFW attempts)
15
+ NSFW_TERMS = [
16
+ r"\bnsfw\b", r"\bnude\b", r"\bnudity\b", r"\bsex\b", r"\bexplicit\b", r"\bporn\b",
17
+ r"\bboobs\b", r"\bbutt\b", r"\bass\b", r"\bnsfw\b", r"\bnaked\b", r"\btits\b",
18
+ r"\b18\+\b", r"\berotic\b", r"\bfetish\b"
19
+ ]
20
+ NSFW_REGEX = re.compile("|".join(NSFW_TERMS), flags=re.IGNORECASE)
21
+
22
+ # --------------------
23
+ # Load pipeline
24
+ # --------------------
25
+ pipe = StableDiffusionPipeline.from_pretrained(
26
+ MODEL_ID,
27
+ torch_dtype=DTYPE
28
+ )
29
+ pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
30
+
31
+ if torch.cuda.is_available():
32
+ pipe = pipe.to("cuda")
33
+ pipe.enable_attention_slicing()
34
+ pipe.enable_vae_slicing()
35
+ else:
36
+ pipe = pipe.to("cpu")
37
+
38
+ # --------------------
39
+ # Helpers
40
+ # --------------------
41
+ def blocked_tile(reason: str, width=512, height=512) -> Image.Image:
42
+ img = Image.new("RGB", (width, height), (20, 20, 24))
43
+ draw = ImageDraw.Draw(img)
44
+ text = f"BLOCKED\n{reason}"
45
+ try:
46
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", 28)
47
+ except:
48
+ font = ImageFont.load_default()
49
+ tw, th = draw.multiline_textbbox((0,0), text, font=font)[2:]
50
+ draw.multiline_text(((width - tw)//2, (height - th)//2), text, fill=(255,255,255), font=font, align="center")
51
+ return img
52
+
53
+ def is_prompt_nsfw(prompt: str) -> bool:
54
+ return bool(NSFW_REGEX.search(prompt or ""))
55
+
56
+ def generate(
57
+ prompt: str,
58
+ negative_prompt: str,
59
+ steps: int,
60
+ guidance: float,
61
+ width: int,
62
+ height: int,
63
+ seed: int,
64
+ batch_size: int
65
+ ) -> Tuple[List[Image.Image], str]:
66
+ if not prompt.strip():
67
+ return [], "Add a prompt to get rolling."
68
+
69
+ # Hard block obvious NSFW prompts before hitting the model
70
+ if is_prompt_nsfw(prompt) or is_prompt_nsfw(negative_prompt or ""):
71
+ img = blocked_tile("NSFW prompt detected")
72
+ return [img], "Blocked: NSFW prompt."
73
+
74
+ # Seed
75
+ if seed < 0:
76
+ seed = random.randint(0, 2**31 - 1)
77
+ generator = torch.Generator(device=DEVICE).manual_seed(seed)
78
+
79
+ out = pipe(
80
+ prompt=prompt,
81
+ negative_prompt=negative_prompt or None,
82
+ num_inference_steps=steps,
83
+ guidance_scale=guidance,
84
+ width=width,
85
+ height=height,
86
+ num_images_per_prompt=batch_size,
87
+ generator=generator
88
+ )
89
+
90
+ images = out.images
91
+ flags = getattr(out, "nsfw_content_detected", None)
92
+
93
+ # If the underlying safety checker flags NSFW, block it (no blur)
94
+ if flags:
95
+ for i, flagged in enumerate(flags):
96
+ if flagged:
97
+ images[i] = blocked_tile("NSFW content flagged")
98
+
99
+ msg = f"Seed: {seed} • Images: {len(images)}"
100
+ if flags is not None:
101
+ msg += f" • Flagged: {sum(1 for f in flags if f)}"
102
+ return images, msg
103
+
104
+ # --------------------
105
+ # UI
106
+ # --------------------
107
+ with gr.Blocks(title="VibeForge — Clean Image Generator") as demo:
108
+ gr.Markdown(
109
+ """
110
+ # VibeForge ⚒️
111
+ **Clean, creative image generation.**
112
+ NSFW inputs are blocked. Keep it classy and go wild on style, lighting, composition, mood.
113
+ """
114
+ )
115
+
116
+ with gr.Row():
117
+ with gr.Column(scale=3):
118
+ prompt = gr.Textbox(
119
+ label="Prompt",
120
+ placeholder="a cinematic photo of a vintage motorcycle by the ocean at sunset, golden hour, soft rim light, 50mm"
121
+ )
122
+ negative = gr.Textbox(label="Negative Prompt", placeholder="low quality, blurry, watermark, jpeg artifacts")
123
+ with gr.Row():
124
+ steps = gr.Slider(10, 50, value=28, step=1, label="Steps")
125
+ guidance = gr.Slider(1.0, 12.0, value=7.5, step=0.1, label="CFG")
126
+ with gr.Row():
127
+ width = gr.Dropdown(choices=[384, 448, 512, 640, 768], value=512, label="Width")
128
+ height = gr.Dropdown(choices=[384, 448, 512, 640, 768], value=512, label="Height")
129
+ with gr.Row():
130
+ seed = gr.Number(value=-1, label="Seed (-1 = random)", precision=0)
131
+ batch = gr.Slider(1, 4, value=1, step=1, label="Batch")
132
+
133
+ go = gr.Button("Generate", variant="primary")
134
+
135
+ with gr.Column(scale=5):
136
+ gallery = gr.Gallery(label="Output", columns=2, height=512)
137
+ info = gr.Markdown()
138
+
139
+ go.click(
140
+ fn=generate,
141
+ inputs=[prompt, negative, steps, guidance, width, height, seed, batch],
142
+ outputs=[gallery, info]
143
+ )
144
+
145
+ if __name__ == "__main__":
146
+ demo.launch()
147
+
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0
2
+ diffusers>=0.26.0
3
+ transformers>=4.41.0
4
+ accelerate>=0.30.0
5
+ safetensors>=0.4.0
6
+ torch
7
+ Pillow
8
+