maldons77 commited on
Commit
3195f8b
·
verified ·
1 Parent(s): 2eff3db

Upload 6 files

Browse files
Files changed (6) hide show
  1. LICENSE +22 -0
  2. README.md +34 -0
  3. ai-storyboard-banner.png +0 -0
  4. app.py +200 -0
  5. requirements.txt +8 -0
  6. runtime.txt +1 -0
LICENSE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2025 Eric Maldon
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ---
3
+ title: AI Storyboard Creator
4
+ emoji: 🎬
5
+ colorFrom: blue
6
+ colorTo: purple
7
+ sdk: gradio
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ <p align="center">
13
+ <img src="ai-storyboard-banner.png" alt="AI Storyboard Creator banner" width="100%">
14
+ </p>
15
+
16
+ # AI Storyboard Creator
17
+
18
+ ## Overview
19
+ Turn a single text prompt into a **mini storyboard**: the app generates 3–6 images, adds a short caption to each panel, renders a preview grid, and lets you **download a PDF**. Works on **CPU basic** and supports **ZeroGPU** (on-demand GPU).
20
+
21
+ ## Features
22
+ - 3–6 panels with **style presets** (Realistic, Anime, Watercolor, Comic, Sketch).
23
+ - **Caption strip** automatically added under each panel.
24
+ - **PDF export** (one page with all panels).
25
+ - CPU-friendly; faster with GPU/ZeroGPU.
26
+
27
+ ## How to Run Locally
28
+ ```bash
29
+ pip install -r requirements.txt
30
+ python app.py
31
+ ```
32
+
33
+ ## Acceptable Use
34
+ This project is intended for educational and creative purposes. Do **not** use it to generate harmful, illegal, or offensive content. Please respect model licenses and the Hugging Face Acceptable Use Policy.
ai-storyboard-banner.png ADDED
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch, os, io, math
3
+ from diffusers import StableDiffusionPipeline
4
+ from transformers import pipeline, set_seed
5
+ from PIL import Image, ImageDraw, ImageFont
6
+ from spaces import GPU # ZeroGPU
7
+
8
+ # ------------------ Globals (CPU-safe) ------------------
9
+ _txtgen = None
10
+ _t2i_cpu = None
11
+
12
+ STYLE_PRESETS = {
13
+ "Realistic photo": "realistic photography, 35mm, finely detailed, natural lighting, high dynamic range",
14
+ "Anime": "anime, vibrant colors, cel shading, highly detailed, clean lineart",
15
+ "Watercolor": "watercolor painting, soft edges, light wash, textured paper, gentle colors",
16
+ "Comic": "comic book style, halftone, bold lines, dramatic shading, vibrant colors",
17
+ "Sketch": "pencil sketch, cross-hatching, grayscale, rough paper texture",
18
+ }
19
+ NEGATIVE = "nsfw, nudity, gore, deformed, extra limbs, low quality, blurry, worst quality, lowres, text artifacts, watermark, logo"
20
+
21
+ # ------------------ Loaders ------------------
22
+ def get_txtgen_cpu():
23
+ """Text generator stays on CPU to avoid CUDA init in main process (required by ZeroGPU)."""
24
+ global _txtgen
25
+ if _txtgen is None:
26
+ _txtgen = pipeline("text-generation", model="distilgpt2", device=-1)
27
+ return _txtgen
28
+
29
+ def get_t2i_cpu():
30
+ """CPU pipeline; heavy but safe for CPU basic hardware."""
31
+ global _t2i_cpu
32
+ if _t2i_cpu is None:
33
+ _t2i_cpu = StableDiffusionPipeline.from_pretrained(
34
+ "stabilityai/sd-turbo",
35
+ torch_dtype=torch.float32,
36
+ safety_checker=None
37
+ )
38
+ _t2i_cpu.enable_attention_slicing()
39
+ return _t2i_cpu
40
+
41
+ # ------------------ GPU batch (ZeroGPU) ------------------
42
+ @GPU(duration=120)
43
+ def t2i_generate_batch_gpu(prompts, width, height, steps, guidance, negative_prompt):
44
+ """Runs entirely inside a GPU-allocated context (ZeroGPU)."""
45
+ pipe = StableDiffusionPipeline.from_pretrained(
46
+ "stabilityai/sd-turbo",
47
+ torch_dtype=torch.float16,
48
+ safety_checker=None
49
+ ).to("cuda")
50
+ images = []
51
+ for p in prompts:
52
+ img = pipe(
53
+ prompt=p,
54
+ negative_prompt=negative_prompt,
55
+ num_inference_steps=steps,
56
+ guidance_scale=guidance,
57
+ width=width,
58
+ height=height,
59
+ ).images[0]
60
+ images.append(img)
61
+ return images
62
+
63
+ # ------------------ Helpers ------------------
64
+ def build_prompt(user_prompt: str, style: str, panel_idx: int, num_panels: int) -> str:
65
+ style_desc = STYLE_PRESETS.get(style, "")
66
+ beat = ["opening shot", "rising action", "key moment", "twist", "resolution"]
67
+ beat_text = beat[min(panel_idx, len(beat)-1)]
68
+ return f"{user_prompt}, {style_desc}, storyboard panel {panel_idx+1} of {num_panels}, {beat_text}, cinematic composition, wide shot"
69
+
70
+ def generate_captions(user_prompt: str, n: int = 4):
71
+ gen = get_txtgen_cpu()
72
+ base = f"Write {n} very short scene captions (max 10 words each) for a storyboard about: {user_prompt}. Number them 1..{n}. Keep it safe and family-friendly."
73
+ out = gen(base, max_new_tokens=80, temperature=0.9, top_p=0.95, do_sample=True)[0]["generated_text"]
74
+ lines = [l.strip() for l in out.split("\n") if l.strip()]
75
+ caps = []
76
+ for l in lines:
77
+ # Accept "1. xxx", "1) xxx", "1 - xxx"
78
+ if l[0:1].isdigit():
79
+ parts = l.split(maxsplit=1)
80
+ if len(parts) > 1:
81
+ caps.append(parts[1].strip(" .)"))
82
+ while len(caps) < n:
83
+ caps.append(f"Scene {len(caps)+1}")
84
+ return caps[:n]
85
+
86
+ def grid(images, cols=2, bg=(20,20,20), pad=8):
87
+ if not images:
88
+ return None
89
+ w, h = images[0].size
90
+ rows = math.ceil(len(images)/cols)
91
+ grid_w = cols*w + (cols+1)*pad
92
+ grid_h = rows*h + (rows+1)*pad + 40
93
+ canvas = Image.new("RGB", (grid_w, grid_h), bg)
94
+ draw = ImageDraw.Draw(canvas)
95
+ title = "Storyboard"
96
+ try:
97
+ font = ImageFont.truetype("DejaVuSans.ttf", 22)
98
+ except Exception:
99
+ font = ImageFont.load_default()
100
+ tw, th = draw.textsize(title, font=font)
101
+ draw.text(((grid_w-tw)//2, pad//2), title, fill=(230,230,230), font=font)
102
+ for i, img in enumerate(images):
103
+ r = i // cols
104
+ c = i % cols
105
+ x = pad + c*(w+pad)
106
+ y = pad + th + pad + r*(h+pad)
107
+ canvas.paste(img, (x,y))
108
+ return canvas
109
+
110
+ def images_to_pdf(images):
111
+ if not images:
112
+ return None
113
+ pil_images = [im.convert("RGB") for im in images]
114
+ buf = io.BytesIO()
115
+ pil_images[0].save(buf, format="PDF", save_all=True, append_images=pil_images[1:])
116
+ buf.seek(0)
117
+ return buf
118
+
119
+ # ------------------ Main ------------------
120
+ def create_storyboard(user_prompt, style, num_panels, width, height, seed):
121
+ if not user_prompt or not user_prompt.strip():
122
+ return None, None, "Please enter a prompt."
123
+ if seed and str(seed).strip().isdigit():
124
+ set_seed(int(seed))
125
+
126
+ # Build prompts and captions
127
+ captions = generate_captions(user_prompt, n=num_panels)
128
+ prompts = [build_prompt(user_prompt, style, i, num_panels) for i in range(num_panels)]
129
+
130
+ # Try GPU batch (ZeroGPU). If fails (no GPU), fallback to CPU.
131
+ images = None
132
+ try:
133
+ images = t2i_generate_batch_gpu(prompts, width, height, steps=2, guidance=0.0, negative_prompt=NEGATIVE)
134
+ except Exception as e:
135
+ # GPU not available or failed → CPU path
136
+ pipe = get_t2i_cpu()
137
+ images = []
138
+ for p in prompts:
139
+ img = pipe(
140
+ prompt=p,
141
+ negative_prompt=NEGATIVE,
142
+ num_inference_steps=4,
143
+ guidance_scale=0.0,
144
+ width=width,
145
+ height=height,
146
+ ).images[0]
147
+ images.append(img)
148
+
149
+ # Add caption strips
150
+ out_panels = []
151
+ for img, cap in zip(images, captions):
152
+ draw = ImageDraw.Draw(img)
153
+ try:
154
+ font = ImageFont.truetype("DejaVuSans.ttf", size=max(16, width//28))
155
+ except Exception:
156
+ font = ImageFont.load_default()
157
+ text = cap[:80]
158
+ text_w, text_h = draw.textsize(text, font=font)
159
+ strip_h = text_h + 14
160
+ strip = Image.new("RGB", (img.width, strip_h), (0,0,0))
161
+ d2 = ImageDraw.Draw(strip)
162
+ d2.text(((img.width - text_w)//2, 7), text, font=font, fill=(255,255,255))
163
+ combined = Image.new("RGB", (img.width, img.height + strip_h), (0,0,0))
164
+ combined.paste(img, (0,0))
165
+ combined.paste(strip, (0,img.height))
166
+ out_panels.append(combined)
167
+
168
+ preview = grid(out_panels, cols=2)
169
+ pdf_buf = images_to_pdf(out_panels)
170
+ pdf_file = ("storyboard.pdf", pdf_buf) if pdf_buf else None
171
+ return preview, pdf_file, "Done."
172
+
173
+ # ------------------ UI ------------------
174
+ with gr.Blocks(title="AI Storyboard Creator") as demo:
175
+ gr.Markdown(
176
+ """
177
+ # AI Storyboard Creator
178
+ Turn a single prompt into a tiny storyboard: 3–6 panels, captions, and a downloadable PDF.
179
+ Works on **CPU basic** and supports **ZeroGPU** (GPU on-demand).
180
+ """
181
+ )
182
+ with gr.Row():
183
+ prompt = gr.Textbox(label="Story prompt", lines=2, placeholder="A cyberpunk cat exploring a neon-lit Tokyo at night")
184
+ with gr.Row():
185
+ style = gr.Dropdown(label="Style", choices=list(STYLE_PRESETS.keys()), value="Comic")
186
+ num_panels = gr.Slider(3, 6, value=4, step=1, label="Number of panels")
187
+ with gr.Row():
188
+ width = gr.Slider(384, 768, value=448, step=64, label="Panel width (px)")
189
+ height = gr.Slider(384, 768, value=448, step=64, label="Panel height (px)")
190
+ seed = gr.Textbox(label="Seed (optional)", placeholder="e.g., 42")
191
+ btn = gr.Button("Create Storyboard")
192
+
193
+ info = gr.Markdown("> First run may download models (approx. 1–2 minutes).")
194
+ preview = gr.Image(label="Preview (grid)", show_download_button=False)
195
+ pdf = gr.File(label="Download PDF")
196
+ msg = gr.Markdown()
197
+ btn.click(create_storyboard, inputs=[prompt, style, num_panels, width, height, seed], outputs=[preview, pdf, msg])
198
+
199
+ if __name__ == "__main__":
200
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.36.1
2
+ diffusers>=0.29.0
3
+ transformers>=4.41.0
4
+ accelerate>=0.30.0
5
+ torch
6
+ safetensors
7
+ pillow
8
+ spaces>=0.19.0
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.10