victordfmsc commited on
Commit
3ea2df4
Β·
verified Β·
1 Parent(s): 156f2e1

v9: Gemini 3 Pro Image primary + 3.1 Flash + 2.5 Flash fallback, improved prompts

Browse files
Files changed (1) hide show
  1. app.py +95 -36
app.py CHANGED
@@ -7,7 +7,9 @@ GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', '')
7
  HF_TOKEN = os.environ.get('HF_TOKEN', '')
8
 
9
  GEMINI_TEXT_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'
10
- GEMINI_IMAGE_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent'
 
 
11
 
12
  # ── FONTS ─────────────────────────────────────────────────────────────────────
13
  _B = ['/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
@@ -37,9 +39,8 @@ def gemini_text(prompt, api_key):
37
  return data['candidates'][0]['content']['parts'][0]['text']
38
 
39
  # ── GEMINI IMAGEN (page generation) ──────────────────────────────────────────
40
- def gemini_imagen(prompt, api_key, width=1024, height=1408):
41
- """Gemini 2.0 Flash Preview Image Generation - free tier compatible."""
42
- url = GEMINI_IMAGE_URL + '?key=' + api_key
43
  body = {
44
  'contents': [{'parts': [{'text': prompt}]}],
45
  'generationConfig': {
@@ -47,9 +48,10 @@ def gemini_imagen(prompt, api_key, width=1024, height=1408):
47
  'temperature': 1.0
48
  }
49
  }
50
- for attempt in range(3):
 
51
  try:
52
- r = requests.post(url, json=body, timeout=120)
53
  if r.status_code == 200:
54
  data = r.json()
55
  candidates = data.get('candidates', [])
@@ -60,17 +62,41 @@ def gemini_imagen(prompt, api_key, width=1024, height=1408):
60
  img_bytes = base64.b64decode(part['inlineData']['data'])
61
  img = Image.open(BytesIO(img_bytes)).convert('RGB')
62
  return img.resize((width, height), Image.LANCZOS)
 
 
63
  elif r.status_code == 429:
64
- print('Gemini image 429, waiting ' + str(15*(attempt+1)) + 's...')
65
- time.sleep(15 * (attempt + 1))
66
  else:
67
- print('Gemini image error ' + str(r.status_code) + ': ' + r.text[:300])
68
  return None
69
  except Exception as e:
70
  print('Gemini image exception: ' + str(e)[:100])
71
  return None
72
  return None
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  # ── POLLINATIONS FALLBACK ─────────────────────────────────────────────────────
75
  def pollinations(prompt, seed, retries=3):
76
  enc = urllib.parse.quote(prompt[:600])
@@ -182,33 +208,66 @@ def fallback_script(topic, num_pages, style, language):
182
 
183
  # ── PAGE IMAGE PROMPT BUILDER ─────────────────────────────────────────────────
184
  def build_page_prompt(page_data, script, style):
185
- chars = script.get('characters', [])
186
- char_ref = '. '.join(c['name'] + ': ' + c['description'] for c in chars[:2])
187
- panels = page_data.get('panels', [])[:4]
188
- base = page_data.get('page_image_prompt', '')
189
-
190
- if not base or len(base) < 50:
191
- # Build from panels
192
- parts = []
193
- for i, p in enumerate(panels):
194
- dlg_text = ' | '.join('"' + d['text'] + '" - ' + d['character']
195
- for d in p.get('dialogue', [])[:1])
196
- parts.append('Panel ' + str(i+1) + ': ' + str(p.get('scene','')) +
197
- ', ' + str(p.get('action','')) +
198
- ('. Speech bubble: ' + dlg_text if dlg_text else ''))
199
- base = '. '.join(parts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  prompt = (
202
- style + ' comic book FULL PAGE, professional comic art, high detail. '
203
- 'LAYOUT: 4 panels arranged with clear black borders and white gutters between them. '
204
- 'Panel arrangement: large panel top, two medium panels middle, one wide panel bottom. '
205
- + str(base) + '. '
206
- 'IMPORTANT: Each panel must have white oval speech bubbles with BLACK TEXT showing character dialogue. '
207
- 'Bubbles should be small (max 20% of panel area), positioned near speaking character. '
208
- 'Characters consistent: ' + char_ref + '. '
209
- 'Comic book style: bold outlines, flat vivid colors, dramatic shadows, NO watermarks.'
 
 
 
 
 
210
  )
211
- return prompt[:1000]
212
 
213
  # ── GENERATE ONE PAGE ─────────────────────────────────────────────────────────
214
  def generate_page(page_data, script, style, title, page_num, total, api_key):
@@ -217,7 +276,7 @@ def generate_page(page_data, script, style, title, page_num, total, api_key):
217
 
218
  print('Page ' + str(page_num) + '/' + str(total) + ' generating...')
219
 
220
- # Try Gemini 2.0 Flash Image first
221
  art = None
222
  if api_key:
223
  art = gemini_imagen(prompt, api_key)
@@ -275,7 +334,7 @@ def generate_comic_ui(topic, num_pages, style, language, gemini_key):
275
  script, pages = generate_comic(topic, num_pages, style, language, gemini_key)
276
  info = json.dumps({'title':script.get('title',''),'summary':script.get('story_summary',''),
277
  'characters':script.get('characters',[]),'total_pages':len(pages),
278
- 'engine':'Gemini 2.0 Flash Image β†’ Pollinations FLUX fallback'},
279
  ensure_ascii=False, indent=2)
280
  return script.get('title',topic), script.get('story_summary',''), pages, info
281
 
@@ -284,7 +343,7 @@ STYLES=['manga','superhero comic','watercolor comic','noir comic','cartoon',
284
 
285
  with gr.Blocks(title='Comic AI Generator') as demo:
286
  gr.Markdown('# Comic AI Generator')
287
- gr.Markdown('Powered by **Gemini 2.5 Flash Image** β€” each page is one AI-generated full comic page (1240Γ—1754 px) with panels, borders and speech bubbles drawn by AI.')
288
  with gr.Tabs():
289
  with gr.Tab('Generate Comic'):
290
  with gr.Row():
 
7
  HF_TOKEN = os.environ.get('HF_TOKEN', '')
8
 
9
  GEMINI_TEXT_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'
10
+ GEMINI_IMAGE_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:generateContent'
11
+ GEMINI_IMAGE_URL2 = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image-preview:generateContent'
12
+ GEMINI_IMAGE_URL3 = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent'
13
 
14
  # ── FONTS ─────────────────────────────────────────────────────────────────────
15
  _B = ['/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
 
39
  return data['candidates'][0]['content']['parts'][0]['text']
40
 
41
  # ── GEMINI IMAGEN (page generation) ──────────────────────────────────────────
42
+ def gemini_imagen_call(url, prompt, api_key, width, height):
43
+ """Single Gemini image model call with retry on 429."""
 
44
  body = {
45
  'contents': [{'parts': [{'text': prompt}]}],
46
  'generationConfig': {
 
48
  'temperature': 1.0
49
  }
50
  }
51
+ full_url = url + '?key=' + api_key
52
+ for attempt in range(2):
53
  try:
54
+ r = requests.post(full_url, json=body, timeout=120)
55
  if r.status_code == 200:
56
  data = r.json()
57
  candidates = data.get('candidates', [])
 
62
  img_bytes = base64.b64decode(part['inlineData']['data'])
63
  img = Image.open(BytesIO(img_bytes)).convert('RGB')
64
  return img.resize((width, height), Image.LANCZOS)
65
+ print('Gemini image: no image in response from ' + url.split('/models/')[1].split(':')[0])
66
+ return None
67
  elif r.status_code == 429:
68
+ print('Gemini image 429 (' + url.split('/models/')[1].split(':')[0] + '), waiting ' + str(20*(attempt+1)) + 's...')
69
+ time.sleep(20 * (attempt + 1))
70
  else:
71
+ print('Gemini image error ' + str(r.status_code) + ' (' + url.split('/models/')[1].split(':')[0] + '): ' + r.text[:200])
72
  return None
73
  except Exception as e:
74
  print('Gemini image exception: ' + str(e)[:100])
75
  return None
76
  return None
77
 
78
+ def gemini_imagen(prompt, api_key, width=1024, height=1408):
79
+ """Try Gemini image models from best to fastest: 3-pro β†’ 3.1-flash β†’ 2.5-flash."""
80
+ # Model 1: Gemini 3 Pro Image (best quality)
81
+ print(' Trying Gemini 3 Pro Image...')
82
+ img = gemini_imagen_call(GEMINI_IMAGE_URL, prompt, api_key, width, height)
83
+ if img:
84
+ print(' Gemini 3 Pro Image SUCCESS')
85
+ return img
86
+ # Model 2: Gemini 3.1 Flash Image (fast + good quality)
87
+ print(' Trying Gemini 3.1 Flash Image...')
88
+ img = gemini_imagen_call(GEMINI_IMAGE_URL2, prompt, api_key, width, height)
89
+ if img:
90
+ print(' Gemini 3.1 Flash Image SUCCESS')
91
+ return img
92
+ # Model 3: Gemini 2.5 Flash Image (fastest)
93
+ print(' Trying Gemini 2.5 Flash Image...')
94
+ img = gemini_imagen_call(GEMINI_IMAGE_URL3, prompt, api_key, width, height)
95
+ if img:
96
+ print(' Gemini 2.5 Flash Image SUCCESS')
97
+ return img
98
+ return None
99
+
100
  # ── POLLINATIONS FALLBACK ─────────────────────────────────────────────────────
101
  def pollinations(prompt, seed, retries=3):
102
  enc = urllib.parse.quote(prompt[:600])
 
208
 
209
  # ── PAGE IMAGE PROMPT BUILDER ─────────────────────────────────────────────────
210
  def build_page_prompt(page_data, script, style):
211
+ """Build a rich, detailed prompt for Gemini 3 Pro image generation."""
212
+ title = script.get('title', 'Comic')
213
+ genre = script.get('genre', 'adventure')
214
+ chars = script.get('characters', [])
215
+ panels = page_data.get('panels', [])[:4]
216
+ page_num = page_data.get('page_number', 1)
217
+ total_p = script.get('total_pages', 1)
218
+
219
+ # Character descriptions for consistency
220
+ char_descs = []
221
+ for c in chars[:3]:
222
+ char_descs.append(c['name'] + ': ' + c.get('description','') + (', wears ' + c.get('costume','') if c.get('costume') else ''))
223
+ char_ref = '. '.join(char_descs) if char_descs else ''
224
+
225
+ # Build per-panel descriptions with full dialogue
226
+ panel_parts = []
227
+ num_panels = len(panels)
228
+ for i, p in enumerate(panels):
229
+ scene = str(p.get('scene', ''))
230
+ action = str(p.get('action', ''))
231
+ caption = str(p.get('caption', ''))
232
+ dialogues = p.get('dialogue', [])
233
+ dlg_list = []
234
+ for d in dialogues:
235
+ dlg_list.append(d['character'] + ' says: "' + d['text'] + '"')
236
+ dlg_str = '; '.join(dlg_list) if dlg_list else 'no dialogue'
237
+ cap_str = (' Narrator caption box: "' + caption + '"') if caption else ''
238
+ panel_parts.append(
239
+ 'PANEL ' + str(i+1) + ': Scene=' + scene +
240
+ '. Action=' + action + '. ' + dlg_str + cap_str
241
+ )
242
+
243
+ # Choose layout based on panel count
244
+ if num_panels == 1:
245
+ layout_desc = 'ONE large full-page panel'
246
+ elif num_panels == 2:
247
+ layout_desc = 'TWO panels: large top panel, large bottom panel, separated by thick black gutter'
248
+ elif num_panels == 3:
249
+ layout_desc = 'THREE panels: wide panoramic top panel, two equal panels side-by-side at bottom, separated by thick black gutters'
250
+ else:
251
+ layout_desc = 'FOUR panels: large wide panel at top, two medium square panels in middle row, one wide panel at bottom, all separated by thick black gutters (8px)'
252
+
253
+ panels_text = ' | '.join(panel_parts)
254
 
255
  prompt = (
256
+ 'CREATE A PROFESSIONAL ' + style.upper() + ' COMIC BOOK PAGE. '
257
+ 'Comic: "' + title + '" (' + genre + '). Page ' + str(page_num) + ' of ' + str(total_p) + '. '
258
+ 'LAYOUT: ' + layout_desc + '. '
259
+ 'PANELS: ' + panels_text + '. '
260
+ 'SPEECH BUBBLES: White rounded oval bubbles with clear BLACK TEXT for every dialogue line. '
261
+ 'Bubbles sized to fit text (max 25% of panel area), tail pointing to speaker. '
262
+ 'Text must be legible. Do NOT overlap speech bubbles with main action. '
263
+ 'STYLE REQUIREMENTS: '
264
+ + style + ' art style. Bold black panel borders (8-10px). White gutter between panels. '
265
+ 'Dynamic compositions. Dramatic lighting and shadows. Vivid expressive colors. '
266
+ 'Professional comic book quality. NO watermarks, NO logos, NO page numbers in image. '
267
+ + (('CHARACTERS (keep consistent): ' + char_ref + '. ') if char_ref else '') +
268
+ 'Make every panel visually compelling and clearly readable as a comic book.'
269
  )
270
+ return prompt[:1800]
271
 
272
  # ── GENERATE ONE PAGE ─────────────────────────────────────────────────────────
273
  def generate_page(page_data, script, style, title, page_num, total, api_key):
 
276
 
277
  print('Page ' + str(page_num) + '/' + str(total) + ' generating...')
278
 
279
+ # Try Gemini image models (3-pro β†’ 3.1-flash β†’ 2.5-flash β†’ Pollinations)
280
  art = None
281
  if api_key:
282
  art = gemini_imagen(prompt, api_key)
 
334
  script, pages = generate_comic(topic, num_pages, style, language, gemini_key)
335
  info = json.dumps({'title':script.get('title',''),'summary':script.get('story_summary',''),
336
  'characters':script.get('characters',[]),'total_pages':len(pages),
337
+ 'engine':'Gemini 3 Pro Image β†’ 3.1 Flash β†’ 2.5 Flash β†’ Pollinations fallback'},
338
  ensure_ascii=False, indent=2)
339
  return script.get('title',topic), script.get('story_summary',''), pages, info
340
 
 
343
 
344
  with gr.Blocks(title='Comic AI Generator') as demo:
345
  gr.Markdown('# Comic AI Generator')
346
+ gr.Markdown('Powered by **Gemini 3 Pro Image** β€” each page is one AI-generated full comic page (1240Γ—1754 px) with panels, borders and speech bubbles drawn by AI.')
347
  with gr.Tabs():
348
  with gr.Tab('Generate Comic'):
349
  with gr.Row():