maria355 commited on
Commit
82bcdd5
·
verified ·
1 Parent(s): b37f161

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +315 -357
app.py CHANGED
@@ -6,20 +6,10 @@ from PIL import Image
6
  import io
7
  import json
8
  import time
9
- import cv2
10
- import numpy as np
11
  import tempfile
12
  import os
13
  from gtts import gTTS
14
- import base64
15
- import zipfile
16
-
17
- # Check for MoviePy availability
18
- try:
19
- from moviepy.editor import ImageSequenceClip, concatenate_videoclips
20
- MOVIEPY_AVAILABLE = True
21
- except ImportError:
22
- MOVIEPY_AVAILABLE = False
23
 
24
  # Configure page
25
  st.set_page_config(
@@ -33,32 +23,38 @@ if 'generated_script' not in st.session_state:
33
  st.session_state.generated_script = None
34
  if 'storyboard_images' not in st.session_state:
35
  st.session_state.storyboard_images = []
36
- if 'video_preview' not in st.session_state:
37
- st.session_state.video_preview = None
38
 
39
- # API Configuration from secrets
40
- try:
41
- gemini_api_key = st.secrets["GEMINI_API_KEY"]
42
- hf_token = st.secrets["HF_TOKEN"]
43
-
44
- # Configure Gemini API
45
- genai.configure(api_key=gemini_api_key)
46
-
47
- # Show success message in sidebar
48
- st.sidebar.success("✅ API Keys loaded successfully!")
49
-
50
- except Exception as e:
51
- st.error("❌ API Keys not found in secrets. Please configure them in your deployment settings.")
52
- st.info("Required secrets: GEMINI_API_KEY, HF_TOKEN")
53
- st.stop()
 
 
 
 
 
 
 
 
 
54
 
55
  # Main title
56
  st.title("🎬 AI Video Script & Storyboard Generator")
57
  st.markdown("Create professional video scripts and visual storyboards with AI assistance")
58
 
59
- # Add information about the app
60
- st.info("🔑 **API Keys are pre-configured** - Just enter your video details and generate!")
61
-
62
  # Input section
63
  st.header("📝 Video Specifications")
64
 
@@ -105,7 +101,6 @@ with col2:
105
  def generate_script_with_gemini(topic, length, style, tone, platform):
106
  """Generate video script using Gemini API"""
107
  try:
108
- # Use the current Gemini model
109
  model = genai.GenerativeModel('gemini-1.5-flash')
110
 
111
  prompt = f"""
@@ -135,13 +130,13 @@ def generate_script_with_gemini(topic, length, style, tone, platform):
135
 
136
  Make sure the scenes add up to the total duration and are engaging for {platform}.
137
  Include specific visual descriptions that can be used to generate storyboard images.
138
- Make sure to return valid JSON only, no additional text or formatting.
139
  """
140
 
141
  response = model.generate_content(prompt)
142
-
143
- # Clean the response text to extract JSON
144
  response_text = response.text.strip()
 
 
145
  if response_text.startswith("```json"):
146
  response_text = response_text[7:-3]
147
  elif response_text.startswith("```"):
@@ -150,139 +145,149 @@ def generate_script_with_gemini(topic, length, style, tone, platform):
150
  script_data = json.loads(response_text)
151
  return script_data
152
 
153
- except json.JSONDecodeError as e:
154
- st.error(f"Error parsing JSON response: {str(e)}")
155
- st.write("Raw response:", response.text if 'response' in locals() else "No response")
156
- return None
157
  except Exception as e:
158
  st.error(f"Error generating script: {str(e)}")
159
- # Try alternative model if the first one fails
160
- try:
161
- st.info("Trying alternative model...")
162
- model = genai.GenerativeModel('gemini-1.5-pro')
163
- response = model.generate_content(prompt)
164
- response_text = response.text.strip()
165
- if response_text.startswith("```json"):
166
- response_text = response_text[7:-3]
167
- elif response_text.startswith("```"):
168
- response_text = response_text[3:-3]
169
- script_data = json.loads(response_text)
170
- return script_data
171
- except:
172
- return None
173
 
174
- def generate_storyboard_image(scene_description, art_style):
175
- """Generate storyboard image using free Hugging Face models"""
176
  try:
177
- # Initialize Hugging Face client
178
- client = InferenceClient(token=hf_token)
179
-
180
- # Style-specific enhancements
181
- style_prompts = {
182
- "Realistic": "photorealistic, high quality, detailed, professional photography",
183
- "Cartoon": "cartoon style, animated, colorful, Disney-like, illustration",
184
- "Cinematic": "cinematic lighting, dramatic, film still, high contrast, movie scene",
185
- "Minimalistic": "minimalist, clean, simple, geometric, modern design",
186
- "Sketch": "pencil sketch, hand-drawn, artistic, black and white line art",
187
- "Digital Art": "digital art, concept art, detailed, vibrant colors, fantasy art"
188
- }
189
-
190
- enhanced_prompt = f"{scene_description}, {style_prompts.get(art_style, '')}, storyboard frame, professional, high quality"
191
 
192
- # Try multiple free models in case one fails
193
- models_to_try = [
194
- "black-forest-labs/FLUX.1-schnell",
195
- "stabilityai/stable-diffusion-2-1",
196
- "runwayml/stable-diffusion-v1-5",
197
- "CompVis/stable-diffusion-v1-4"
198
- ]
199
 
200
- for model in models_to_try:
201
- try:
202
- image = client.text_to_image(
203
- enhanced_prompt,
204
- model=model
205
- )
206
- return image
207
- except Exception as model_error:
208
- st.warning(f"Model {model} failed, trying next...")
209
- continue
210
 
211
- # If all models fail, return None
212
- st.error("All image generation models failed")
213
- return None
 
 
 
 
 
 
 
 
214
 
 
 
 
 
 
215
  except Exception as e:
216
- st.error(f"Error generating image: {str(e)}")
217
  return None
218
 
219
- def create_video_preview(images, script_data):
220
- """Create a basic video preview with Ken Burns effect"""
221
- if not images or not script_data or not MOVIEPY_AVAILABLE:
222
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
 
 
 
 
224
  try:
225
- # Create temporary directory
226
- temp_dir = tempfile.mkdtemp()
227
 
228
- clips = []
229
- for i, (image, scene) in enumerate(zip(images, script_data['scenes'])):
230
- if image:
231
- # Save image temporarily
232
- img_path = os.path.join(temp_dir, f"scene_{i}.jpg")
233
- image.save(img_path)
234
-
235
- # Parse duration (extract number from string like "10 seconds")
236
- duration_str = scene.get('duration', '5 seconds')
237
- try:
238
- duration = float(duration_str.split()[0])
239
- except:
240
- duration = 5.0 # Default duration
241
-
242
- # Create image clip
243
- clip = ImageSequenceClip([img_path], durations=[duration])
244
- clip = clip.resize(height=480) # Standardize height
245
- clips.append(clip)
246
 
247
- if clips:
248
- # Concatenate all clips
249
- final_video = concatenate_videoclips(clips, method="compose")
250
-
251
- # Save video
252
- video_path = os.path.join(temp_dir, "preview.mp4")
253
- final_video.write_videofile(
254
- video_path,
255
- fps=24,
256
- codec='libx264',
257
- audio_codec='aac',
258
- verbose=False,
259
- logger=None
260
- )
261
-
262
- return video_path
263
-
264
- except Exception as e:
265
- st.error(f"Error creating video preview: {str(e)}")
 
266
  return None
267
 
268
  def create_gif_preview(images, script_data):
269
- """Create a GIF preview as an alternative to video"""
270
- if not images or not script_data:
271
- return None
272
-
273
  try:
274
- # Filter out None images
275
  valid_images = [img for img in images if img is not None]
276
  if not valid_images:
277
  return None
278
 
279
- # Resize images to consistent size
 
280
  resized_images = []
281
- target_size = (512, 384)
282
 
283
  for image in valid_images:
284
- resized_img = image.resize(target_size, Image.Resampling.LANCZOS)
285
- resized_images.append(resized_img)
 
 
 
 
 
 
286
 
287
  # Create GIF
288
  gif_buffer = io.BytesIO()
@@ -290,21 +295,24 @@ def create_gif_preview(images, script_data):
290
  gif_buffer,
291
  format='GIF',
292
  save_all=True,
293
- append_images=resized_images[1:],
294
- duration=2000, # 2 seconds per frame
295
  loop=0
296
  )
297
  gif_buffer.seek(0)
298
-
299
  return gif_buffer
300
 
301
  except Exception as e:
302
- st.error(f"Error creating GIF preview: {str(e)}")
303
  return None
304
 
305
  def text_to_speech(text, language='en'):
306
  """Convert text to speech using gTTS"""
307
  try:
 
 
 
 
308
  tts = gTTS(text=text, lang=language, slow=False)
309
  audio_buffer = io.BytesIO()
310
  tts.write_to_fp(audio_buffer)
@@ -314,81 +322,103 @@ def text_to_speech(text, language='en'):
314
  st.error(f"Error generating speech: {str(e)}")
315
  return None
316
 
317
- def generate_fallback_script(topic, length, style, tone, platform):
318
- """Generate a fallback script if Gemini fails"""
319
  try:
320
- # Parse length to get number of seconds
321
- if "second" in length:
322
- total_seconds = int(length.split()[0])
323
- elif "minute" in length:
324
- minutes = int(length.split()[0])
325
- total_seconds = minutes * 60
326
- else:
327
- total_seconds = 60
328
-
329
- # Calculate number of scenes (roughly 10-15 seconds per scene)
330
- num_scenes = max(2, total_seconds // 12)
331
- scene_duration = total_seconds // num_scenes
332
 
333
- scenes = []
334
- for i in range(num_scenes):
335
- scene = {
336
- "scene_number": i + 1,
337
- "duration": f"{scene_duration} seconds",
338
- "description": f"Scene {i+1} showing {topic} in {style.lower()} style",
339
- "dialogue": f"Narration for scene {i+1} about {topic}",
340
- "camera_angle": "Medium shot" if i % 2 == 0 else "Close-up",
341
- "visual_elements": f"Key visuals related to {topic}"
342
- }
343
- scenes.append(scene)
344
-
345
- return {
346
- "title": f"{topic} - {style} Video",
347
- "total_duration": length,
348
- "scenes": scenes
349
- }
350
- except:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  return None
352
 
353
  # Main generation button
354
  if st.button("🚀 Generate Video Script & Storyboard", type="primary"):
355
- if not video_topic:
356
  st.error("Please enter a video topic")
357
  else:
 
358
  with st.spinner("🤖 Generating script with AI..."):
359
  script_data = generate_script_with_gemini(video_topic, video_length, style, tone, platform)
360
 
361
- # If Gemini fails, use fallback
362
- if not script_data:
363
- st.warning("Primary AI model failed, using fallback script generation...")
364
- script_data = generate_fallback_script(video_topic, video_length, style, tone, platform)
365
-
366
  if script_data:
367
  st.session_state.generated_script = script_data
368
  st.success("✅ Script generated successfully!")
369
 
370
  # Generate storyboard images
371
- with st.spinner("🎨 Creating storyboard images..."):
372
- images = []
 
 
 
 
373
  progress_bar = st.progress(0)
 
 
 
374
 
375
  for i, scene in enumerate(script_data['scenes']):
376
- with st.spinner(f"Generating image {i+1}/{len(script_data['scenes'])}..."):
377
- image = generate_storyboard_image(
 
 
378
  scene['description'],
379
  art_style
380
  )
381
  images.append(image)
382
- progress_bar.progress((i + 1) / len(script_data['scenes']))
383
 
384
- # Add a small delay to avoid rate limiting
385
- time.sleep(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
- st.session_state.storyboard_images = images
 
388
 
389
- st.success("✅ Storyboard images generated!")
390
  else:
391
- st.error("Failed to generate script. Please try again or modify your request.")
392
 
393
  # Display results
394
  if st.session_state.generated_script:
@@ -399,11 +429,11 @@ if st.session_state.generated_script:
399
  st.write(f"**Duration:** {script_data.get('total_duration', 'N/A')}")
400
 
401
  # Display script in tabs
402
- tab1, tab2, tab3 = st.tabs(["📝 Script Details", "🖼️ Storyboard", "🎥 Preview"])
403
 
404
  with tab1:
405
  for i, scene in enumerate(script_data.get('scenes', []), 1):
406
- with st.expander(f"Scene {i} - {scene.get('duration', 'N/A')}"):
407
  col1, col2 = st.columns(2)
408
 
409
  with col1:
@@ -418,202 +448,130 @@ if st.session_state.generated_script:
418
  st.write("**Visual Elements:**")
419
  st.write(scene.get('visual_elements', 'N/A'))
420
 
421
- # Add text-to-speech for dialogue
422
- if scene.get('dialogue'):
423
- if st.button(f"🔊 Play Audio - Scene {i}", key=f"audio_{i}"):
424
- with st.spinner("Generating audio..."):
425
- audio_buffer = text_to_speech(scene['dialogue'])
426
- if audio_buffer:
427
- st.audio(audio_buffer.getvalue(), format='audio/mp3')
428
 
429
  with tab2:
430
  if st.session_state.storyboard_images:
431
  st.subheader("🎨 Storyboard Images")
432
 
433
- for i, (scene, image) in enumerate(zip(script_data['scenes'], st.session_state.storyboard_images)):
434
- col1, col2 = st.columns([1, 2])
 
 
435
 
436
- with col1:
437
- if image:
438
- st.image(image, caption=f"Scene {i+1}", use_column_width=True)
 
 
439
 
440
- # Refinement option
441
- if st.button(f"🔄 Regenerate Scene {i+1}", key=f"regen_{i}"):
442
- with st.spinner(f"Regenerating scene {i+1}..."):
443
- new_image = generate_storyboard_image(
444
- scene['description'],
445
- art_style
446
- )
447
- if new_image:
448
- st.session_state.storyboard_images[i] = new_image
449
- st.rerun()
450
- else:
451
- st.write("❌ Image generation failed for this scene")
452
- if st.button(f"🔄 Try Again - Scene {i+1}", key=f"retry_{i}"):
453
- with st.spinner(f"Trying to generate scene {i+1}..."):
454
- new_image = generate_storyboard_image(
455
- scene['description'],
456
- art_style
457
- )
458
- if new_image:
459
- st.session_state.storyboard_images[i] = new_image
460
- st.rerun()
461
-
462
- with col2:
463
- st.write(f"**Scene {i+1}: {scene.get('duration', 'N/A')}**")
464
- st.write(f"**Description:** {scene.get('description', 'N/A')}")
465
- st.write(f"**Dialogue:** {scene.get('dialogue', 'N/A')}")
466
- else:
467
- st.info("No storyboard images generated yet. Click the generate button above.")
468
-
469
- with tab3:
470
- st.subheader("🎥 Video Preview")
471
-
472
- col1, col2 = st.columns(2)
473
-
474
- with col1:
475
- if MOVIEPY_AVAILABLE:
476
- if st.button("🎬 Create Video Preview"):
477
- if st.session_state.storyboard_images:
478
- with st.spinner("Creating video preview..."):
479
- video_path = create_video_preview(
480
- st.session_state.storyboard_images,
481
- script_data
482
- )
483
 
484
- if video_path:
485
- st.session_state.video_preview = video_path
486
- st.success("Video preview created!")
487
- else:
488
- st.info("Video preview requires MoviePy. Feature not available in this deployment.")
489
-
490
- with col2:
491
- if st.button("📱 Create GIF Preview"):
492
- if st.session_state.storyboard_images:
493
- with st.spinner("Creating GIF preview..."):
494
  gif_buffer = create_gif_preview(
495
  st.session_state.storyboard_images,
496
  script_data
497
  )
498
-
499
  if gif_buffer:
500
- st.session_state.video_preview = gif_buffer
501
  st.success("GIF preview created!")
502
-
503
- # Display preview
504
- if st.session_state.video_preview:
505
- if isinstance(st.session_state.video_preview, str) and os.path.exists(st.session_state.video_preview):
506
- # Video file
507
- st.video(st.session_state.video_preview)
508
- elif hasattr(st.session_state.video_preview, 'getvalue'):
509
- # GIF buffer
510
- st.image(st.session_state.video_preview.getvalue())
511
-
512
- # Export options
513
- st.subheader("📥 Export Options")
514
 
515
  col1, col2, col3 = st.columns(3)
516
 
517
  with col1:
518
- if st.button("📄 Download Script (JSON)"):
519
- script_json = json.dumps(script_data, indent=2)
520
- st.download_button(
521
- label="Download JSON",
522
- data=script_json,
523
- file_name="video_script.json",
524
- mime="application/json"
525
- )
526
 
527
  with col2:
528
- if st.button("🖼️ Download Storyboard Images"):
529
- if st.session_state.storyboard_images:
530
- # Create a zip file with all images
531
- zip_buffer = io.BytesIO()
532
-
533
- with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
534
- for i, image in enumerate(st.session_state.storyboard_images):
535
- if image:
536
- img_buffer = io.BytesIO()
537
- image.save(img_buffer, format='PNG')
538
- zip_file.writestr(f"scene_{i+1}.png", img_buffer.getvalue())
539
-
540
  st.download_button(
541
- label="Download ZIP",
542
- data=zip_buffer.getvalue(),
543
- file_name="storyboard_images.zip",
544
  mime="application/zip"
545
  )
546
 
547
  with col3:
548
- if st.session_state.video_preview:
549
- if isinstance(st.session_state.video_preview, str) and st.session_state.video_preview.endswith('.mp4'):
550
- # MP4 video file
551
- if os.path.exists(st.session_state.video_preview):
552
- with open(st.session_state.video_preview, 'rb') as f:
553
- st.download_button(
554
- label="🎥 Download Video",
555
- data=f.read(),
556
- file_name="video_preview.mp4",
557
- mime="video/mp4"
558
- )
559
- elif hasattr(st.session_state.video_preview, 'getvalue'):
560
- # GIF buffer
561
- st.download_button(
562
- label="📱 Download GIF",
563
- data=st.session_state.video_preview.getvalue(),
564
- file_name="storyboard_preview.gif",
565
- mime="image/gif"
566
- )
567
-
568
- # Footer
569
- st.markdown("---")
570
- st.markdown("🤖 **Powered by**: Gemini AI • Free Hugging Face Models • gTTS")
571
- st.markdown("💡 **Tips**: Use detailed topic descriptions for better results. Experiment with different art styles!")
572
 
573
- # Sidebar info
574
  with st.sidebar:
575
- st.markdown("---")
576
  st.markdown("### 📚 How to Use")
577
  st.markdown("""
578
- 1. **Define Video**: Enter topic, length, and style
579
- 2. **Generate**: Click the generate button
580
- 3. **Refine**: Regenerate individual scenes if needed
581
- 4. **Export**: Download script, images, or video
582
  """)
583
 
584
- st.markdown("---")
585
  st.markdown("### 🔧 Features")
586
  st.markdown("""
587
- - ✅ **AI Script Generation** with Gemini
588
- - ✅ **Visual Storyboards** with Free HF Models
589
- - ✅ **Text-to-Speech** for narration
590
- - ✅ **Multiple Export Formats**
591
- - ✅ **Scene Regeneration**
592
- - ✅ **GIF Preview Creation**
593
  """)
594
 
595
- st.markdown("---")
596
- st.markdown("### 🆓 Free Models Used")
597
  st.markdown("""
598
- - **Script**: Gemini 1.5 Flash/Pro
599
- - **Images**: FLUX.1, Stable Diffusion
600
- - **Speech**: Google TTS
601
- """)
602
 
603
- if not MOVIEPY_AVAILABLE:
604
- st.markdown("---")
605
- st.markdown("### ℹ️ Note")
606
- st.markdown("Video preview feature disabled for faster deployment. GIF preview available!")
607
-
608
- # Additional configuration section
609
- st.sidebar.markdown("---")
610
- st.sidebar.markdown("### ⚙️ Configuration")
611
- st.sidebar.markdown("""
612
- **Required Environment Variables:**
613
- - `GEMINI_API_KEY`: Your Gemini API key
614
- - `HF_TOKEN`: Your Hugging Face token
615
 
616
- **Free API Limits:**
617
- - Gemini: 15 RPM, 32K TPM
618
- - Hugging Face: Rate limited per model
619
- """)
 
6
  import io
7
  import json
8
  import time
9
+ import zipfile
 
10
  import tempfile
11
  import os
12
  from gtts import gTTS
 
 
 
 
 
 
 
 
 
13
 
14
  # Configure page
15
  st.set_page_config(
 
23
  st.session_state.generated_script = None
24
  if 'storyboard_images' not in st.session_state:
25
  st.session_state.storyboard_images = []
26
+ if 'gif_preview' not in st.session_state:
27
+ st.session_state.gif_preview = None
28
 
29
+ # API Configuration
30
+ def load_api_keys():
31
+ """Load API keys from secrets or environment"""
32
+ try:
33
+ gemini_api_key = st.secrets.get("GEMINI_API_KEY") or os.getenv("GEMINI_API_KEY")
34
+ hf_token = st.secrets.get("HF_TOKEN") or os.getenv("HF_TOKEN")
35
+
36
+ if not gemini_api_key or not hf_token:
37
+ st.error("❌ API Keys not found. Please configure GEMINI_API_KEY and HF_TOKEN")
38
+ st.stop()
39
+
40
+ return gemini_api_key, hf_token
41
+ except Exception as e:
42
+ st.error(f" Error loading API keys: {str(e)}")
43
+ st.stop()
44
+
45
+ # Load API keys
46
+ gemini_api_key, hf_token = load_api_keys()
47
+
48
+ # Configure Gemini API
49
+ genai.configure(api_key=gemini_api_key)
50
+
51
+ # Initialize Hugging Face client
52
+ client = InferenceClient(token=hf_token)
53
 
54
  # Main title
55
  st.title("🎬 AI Video Script & Storyboard Generator")
56
  st.markdown("Create professional video scripts and visual storyboards with AI assistance")
57
 
 
 
 
58
  # Input section
59
  st.header("📝 Video Specifications")
60
 
 
101
  def generate_script_with_gemini(topic, length, style, tone, platform):
102
  """Generate video script using Gemini API"""
103
  try:
 
104
  model = genai.GenerativeModel('gemini-1.5-flash')
105
 
106
  prompt = f"""
 
130
 
131
  Make sure the scenes add up to the total duration and are engaging for {platform}.
132
  Include specific visual descriptions that can be used to generate storyboard images.
133
+ Return only valid JSON, no additional text.
134
  """
135
 
136
  response = model.generate_content(prompt)
 
 
137
  response_text = response.text.strip()
138
+
139
+ # Clean JSON response
140
  if response_text.startswith("```json"):
141
  response_text = response_text[7:-3]
142
  elif response_text.startswith("```"):
 
145
  script_data = json.loads(response_text)
146
  return script_data
147
 
 
 
 
 
148
  except Exception as e:
149
  st.error(f"Error generating script: {str(e)}")
150
+ return generate_fallback_script(topic, length, style, tone, platform)
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ def generate_fallback_script(topic, length, style, tone, platform):
153
+ """Generate a simple fallback script"""
154
  try:
155
+ # Parse length
156
+ if "second" in length.lower():
157
+ total_seconds = int(length.split()[0])
158
+ elif "minute" in length.lower():
159
+ minutes = int(length.split()[0])
160
+ total_seconds = minutes * 60
161
+ else:
162
+ total_seconds = 60
 
 
 
 
 
 
163
 
164
+ # Create scenes
165
+ num_scenes = max(3, min(8, total_seconds // 10)) # 3-8 scenes
166
+ scene_duration = total_seconds // num_scenes
 
 
 
 
167
 
168
+ scenes = []
169
+ scene_types = ["opening", "main content", "detail", "conclusion"]
 
 
 
 
 
 
 
 
170
 
171
+ for i in range(num_scenes):
172
+ scene_type = scene_types[min(i, len(scene_types)-1)]
173
+ scene = {
174
+ "scene_number": i + 1,
175
+ "duration": f"{scene_duration} seconds",
176
+ "description": f"A {style.lower()} {scene_type} scene about {topic}, showing professional visuals in {art_style.lower()} style",
177
+ "dialogue": f"Engaging {tone.lower()} narration about {topic} for scene {i+1}",
178
+ "camera_angle": ["Wide shot", "Medium shot", "Close-up", "Over shoulder"][i % 4],
179
+ "visual_elements": f"Professional visuals related to {topic}, {style.lower()} cinematography"
180
+ }
181
+ scenes.append(scene)
182
 
183
+ return {
184
+ "title": f"{topic} - {style} Video",
185
+ "total_duration": length,
186
+ "scenes": scenes
187
+ }
188
  except Exception as e:
189
+ st.error(f"Error creating fallback script: {str(e)}")
190
  return None
191
 
192
+ def generate_storyboard_image_stable(scene_description, art_style, max_retries=3):
193
+ """Generate storyboard image with better error handling"""
194
+
195
+ style_prompts = {
196
+ "Realistic": "photorealistic, professional, high quality, detailed",
197
+ "Cartoon": "cartoon style, animated, colorful, illustration, Disney-like",
198
+ "Cinematic": "cinematic, dramatic lighting, film still, movie scene",
199
+ "Minimalistic": "minimalist, clean, simple, modern design",
200
+ "Sketch": "pencil sketch, hand-drawn, artistic, line art",
201
+ "Digital Art": "digital art, concept art, vibrant colors, detailed"
202
+ }
203
+
204
+ # Create enhanced prompt
205
+ base_prompt = f"{scene_description}"
206
+ style_enhancement = style_prompts.get(art_style, "professional, high quality")
207
+ enhanced_prompt = f"{base_prompt}, {style_enhancement}, storyboard frame"
208
+
209
+ # Try different approaches
210
+ approaches = [
211
+ enhanced_prompt,
212
+ f"storyboard illustration: {base_prompt}, {style_enhancement}",
213
+ f"{base_prompt}, simple illustration, clean design"
214
+ ]
215
+
216
+ for attempt, prompt in enumerate(approaches):
217
+ try:
218
+ # Use a more reliable model
219
+ image = client.text_to_image(
220
+ prompt,
221
+ model="runwayml/stable-diffusion-v1-5" # More reliable model
222
+ )
223
+
224
+ if image and hasattr(image, 'size'):
225
+ return image
226
+ else:
227
+ raise Exception("Invalid image returned")
228
+
229
+ except Exception as e:
230
+ if attempt < len(approaches) - 1:
231
+ time.sleep(2) # Wait before retry
232
+ continue
233
+ else:
234
+ # Create a placeholder image as last resort
235
+ return create_placeholder_image(f"Scene: {scene_description[:50]}...")
236
 
237
+ return None
238
+
239
+ def create_placeholder_image(text):
240
+ """Create a placeholder image with text"""
241
  try:
242
+ from PIL import Image, ImageDraw, ImageFont
 
243
 
244
+ # Create a simple placeholder
245
+ img = Image.new('RGB', (512, 384), color=(200, 200, 200))
246
+ draw = ImageDraw.Draw(img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
+ # Try to use default font
249
+ try:
250
+ font = ImageFont.load_default()
251
+ except:
252
+ font = None
253
+
254
+ # Add text
255
+ text_lines = text.split(' ')
256
+ line_height = 30
257
+ y_pos = 150
258
+
259
+ for i in range(0, len(text_lines), 4): # 4 words per line
260
+ line = ' '.join(text_lines[i:i+4])
261
+ draw.text((50, y_pos), line, fill=(50, 50, 50), font=font)
262
+ y_pos += line_height
263
+ if y_pos > 300: # Don't overflow
264
+ break
265
+
266
+ return img
267
+ except Exception:
268
  return None
269
 
270
  def create_gif_preview(images, script_data):
271
+ """Create a GIF preview"""
 
 
 
272
  try:
273
+ # Filter valid images
274
  valid_images = [img for img in images if img is not None]
275
  if not valid_images:
276
  return None
277
 
278
+ # Resize images
279
+ target_size = (400, 300)
280
  resized_images = []
 
281
 
282
  for image in valid_images:
283
+ try:
284
+ resized_img = image.resize(target_size, Image.Resampling.LANCZOS)
285
+ resized_images.append(resized_img)
286
+ except Exception:
287
+ continue
288
+
289
+ if not resized_images:
290
+ return None
291
 
292
  # Create GIF
293
  gif_buffer = io.BytesIO()
 
295
  gif_buffer,
296
  format='GIF',
297
  save_all=True,
298
+ append_images=resized_images[1:] if len(resized_images) > 1 else [],
299
+ duration=2500, # 2.5 seconds per frame
300
  loop=0
301
  )
302
  gif_buffer.seek(0)
 
303
  return gif_buffer
304
 
305
  except Exception as e:
306
+ st.error(f"Error creating GIF: {str(e)}")
307
  return None
308
 
309
  def text_to_speech(text, language='en'):
310
  """Convert text to speech using gTTS"""
311
  try:
312
+ # Limit text length to avoid issues
313
+ if len(text) > 500:
314
+ text = text[:500] + "..."
315
+
316
  tts = gTTS(text=text, lang=language, slow=False)
317
  audio_buffer = io.BytesIO()
318
  tts.write_to_fp(audio_buffer)
 
322
  st.error(f"Error generating speech: {str(e)}")
323
  return None
324
 
325
+ def create_download_zip(images, script_data):
326
+ """Create a ZIP file with all content"""
327
  try:
328
+ zip_buffer = io.BytesIO()
 
 
 
 
 
 
 
 
 
 
 
329
 
330
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
331
+ # Add script as JSON
332
+ script_json = json.dumps(script_data, indent=2)
333
+ zip_file.writestr("script.json", script_json)
334
+
335
+ # Add script as readable text
336
+ script_text = f"Title: {script_data.get('title', '')}\n"
337
+ script_text += f"Duration: {script_data.get('total_duration', '')}\n\n"
338
+
339
+ for i, scene in enumerate(script_data.get('scenes', []), 1):
340
+ script_text += f"=== SCENE {i} ===\n"
341
+ script_text += f"Duration: {scene.get('duration', '')}\n"
342
+ script_text += f"Camera: {scene.get('camera_angle', '')}\n"
343
+ script_text += f"Description: {scene.get('description', '')}\n"
344
+ script_text += f"Dialogue: {scene.get('dialogue', '')}\n"
345
+ script_text += f"Visual Elements: {scene.get('visual_elements', '')}\n\n"
346
+
347
+ zip_file.writestr("script.txt", script_text)
348
+
349
+ # Add images
350
+ for i, image in enumerate(images):
351
+ if image:
352
+ img_buffer = io.BytesIO()
353
+ try:
354
+ image.save(img_buffer, format='PNG')
355
+ zip_file.writestr(f"scene_{i+1:02d}.png", img_buffer.getvalue())
356
+ except Exception:
357
+ continue
358
+
359
+ zip_buffer.seek(0)
360
+ return zip_buffer
361
+ except Exception as e:
362
+ st.error(f"Error creating ZIP file: {str(e)}")
363
  return None
364
 
365
  # Main generation button
366
  if st.button("🚀 Generate Video Script & Storyboard", type="primary"):
367
+ if not video_topic.strip():
368
  st.error("Please enter a video topic")
369
  else:
370
+ # Generate script
371
  with st.spinner("🤖 Generating script with AI..."):
372
  script_data = generate_script_with_gemini(video_topic, video_length, style, tone, platform)
373
 
 
 
 
 
 
374
  if script_data:
375
  st.session_state.generated_script = script_data
376
  st.success("✅ Script generated successfully!")
377
 
378
  # Generate storyboard images
379
+ st.info("🎨 Generating storyboard images (this may take a few minutes)...")
380
+ images = []
381
+
382
+ # Create progress tracking
383
+ progress_container = st.container()
384
+ with progress_container:
385
  progress_bar = st.progress(0)
386
+ status_text = st.empty()
387
+
388
+ total_scenes = len(script_data['scenes'])
389
 
390
  for i, scene in enumerate(script_data['scenes']):
391
+ status_text.text(f"Generating image {i+1}/{total_scenes}: Scene {i+1}")
392
+
393
+ try:
394
+ image = generate_storyboard_image_stable(
395
  scene['description'],
396
  art_style
397
  )
398
  images.append(image)
 
399
 
400
+ if image:
401
+ st.success(f"✅ Scene {i+1} generated successfully")
402
+ else:
403
+ st.warning(f"⚠️ Scene {i+1} failed, using placeholder")
404
+
405
+ except Exception as e:
406
+ st.error(f"❌ Error generating scene {i+1}: {str(e)}")
407
+ images.append(None)
408
+
409
+ progress_bar.progress((i + 1) / total_scenes)
410
+
411
+ # Rate limiting
412
+ if i < total_scenes - 1: # Don't wait after last image
413
+ time.sleep(3) # Wait 3 seconds between requests
414
+
415
+ status_text.text("✅ Storyboard generation complete!")
416
 
417
+ st.session_state.storyboard_images = images
418
+ st.success(f"✅ Generated {len([img for img in images if img is not None])} out of {len(images)} storyboard images!")
419
 
 
420
  else:
421
+ st.error("Failed to generate script. Please try again.")
422
 
423
  # Display results
424
  if st.session_state.generated_script:
 
429
  st.write(f"**Duration:** {script_data.get('total_duration', 'N/A')}")
430
 
431
  # Display script in tabs
432
+ tab1, tab2, tab3 = st.tabs(["📝 Script Details", "🖼️ Storyboard", "📥 Export"])
433
 
434
  with tab1:
435
  for i, scene in enumerate(script_data.get('scenes', []), 1):
436
+ with st.expander(f"Scene {i} - {scene.get('duration', 'N/A')}", expanded=False):
437
  col1, col2 = st.columns(2)
438
 
439
  with col1:
 
448
  st.write("**Visual Elements:**")
449
  st.write(scene.get('visual_elements', 'N/A'))
450
 
451
+ # Text-to-speech
452
+ dialogue = scene.get('dialogue', '')
453
+ if dialogue and st.button(f"🔊 Play Audio - Scene {i}", key=f"audio_{i}"):
454
+ with st.spinner("Generating audio..."):
455
+ audio_buffer = text_to_speech(dialogue)
456
+ if audio_buffer:
457
+ st.audio(audio_buffer.getvalue(), format='audio/mp3')
458
 
459
  with tab2:
460
  if st.session_state.storyboard_images:
461
  st.subheader("🎨 Storyboard Images")
462
 
463
+ # Show images in a grid
464
+ cols_per_row = 2
465
+ for i in range(0, len(st.session_state.storyboard_images), cols_per_row):
466
+ cols = st.columns(cols_per_row)
467
 
468
+ for j, col in enumerate(cols):
469
+ idx = i + j
470
+ if idx < len(st.session_state.storyboard_images):
471
+ image = st.session_state.storyboard_images[idx]
472
+ scene = script_data['scenes'][idx] if idx < len(script_data['scenes']) else {}
473
 
474
+ with col:
475
+ if image:
476
+ st.image(image, caption=f"Scene {idx+1}", use_column_width=True)
477
+ else:
478
+ st.write(f"❌ Scene {idx+1} - Image failed to generate")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
 
480
+ st.write(f"**Duration:** {scene.get('duration', 'N/A')}")
481
+ st.write(f"**Description:** {scene.get('description', 'N/A')[:100]}...")
482
+
483
+ # GIF Preview section
484
+ st.subheader("📱 Animated Preview")
485
+ col1, col2 = st.columns([1, 2])
486
+
487
+ with col1:
488
+ if st.button("🎬 Create GIF Preview"):
489
+ with st.spinner("Creating animated preview..."):
490
  gif_buffer = create_gif_preview(
491
  st.session_state.storyboard_images,
492
  script_data
493
  )
 
494
  if gif_buffer:
495
+ st.session_state.gif_preview = gif_buffer
496
  st.success("GIF preview created!")
497
+ else:
498
+ st.error("Failed to create GIF preview")
499
+
500
+ with col2:
501
+ if st.session_state.gif_preview:
502
+ st.image(st.session_state.gif_preview.getvalue(), caption="Storyboard Preview")
503
+ else:
504
+ st.info("Generate storyboard images first using the button above.")
505
+
506
+ with tab3:
507
+ st.subheader("📥 Download Options")
 
508
 
509
  col1, col2, col3 = st.columns(3)
510
 
511
  with col1:
512
+ # Script download
513
+ script_json = json.dumps(script_data, indent=2)
514
+ st.download_button(
515
+ label="📄 Download Script (JSON)",
516
+ data=script_json,
517
+ file_name=f"script_{int(time.time())}.json",
518
+ mime="application/json"
519
+ )
520
 
521
  with col2:
522
+ # ZIP download
523
+ if st.session_state.storyboard_images:
524
+ zip_data = create_download_zip(st.session_state.storyboard_images, script_data)
525
+ if zip_data:
 
 
 
 
 
 
 
 
526
  st.download_button(
527
+ label="📦 Download Complete Package",
528
+ data=zip_data.getvalue(),
529
+ file_name=f"storyboard_package_{int(time.time())}.zip",
530
  mime="application/zip"
531
  )
532
 
533
  with col3:
534
+ # GIF download
535
+ if st.session_state.gif_preview:
536
+ st.download_button(
537
+ label="🎬 Download GIF Preview",
538
+ data=st.session_state.gif_preview.getvalue(),
539
+ file_name=f"storyboard_preview_{int(time.time())}.gif",
540
+ mime="image/gif"
541
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
 
543
+ # Sidebar
544
  with st.sidebar:
 
545
  st.markdown("### 📚 How to Use")
546
  st.markdown("""
547
+ 1. **Enter Details**: Describe your video topic and preferences
548
+ 2. **Generate**: Click the generate button and wait
549
+ 3. **Review**: Check the script and storyboard images
550
+ 4. **Export**: Download your files
551
  """)
552
 
 
553
  st.markdown("### 🔧 Features")
554
  st.markdown("""
555
+ - ✅ AI-powered script generation
556
+ - ✅ Visual storyboard creation
557
+ - ✅ Text-to-speech narration
558
+ - ✅ GIF preview generation
559
+ - ✅ Complete package download
 
560
  """)
561
 
562
+ st.markdown("### ⚙️ Settings")
 
563
  st.markdown("""
564
+ **Required API Keys:**
565
+ - GEMINI_API_KEY
566
+ - HF_TOKEN (Hugging Face)
 
567
 
568
+ **Models Used:**
569
+ - Script: Gemini 1.5 Flash
570
+ - Images: Stable Diffusion v1.5
571
+ - Speech: Google TTS
572
+ """)
 
 
 
 
 
 
 
573
 
574
+ # Footer
575
+ st.markdown("---")
576
+ st.markdown("🤖 **Powered by**: Gemini AI • Hugging Face Google TTS")
577
+ st.markdown("💡 **Tips**: Be specific in your descriptions for better results!")