mgbam commited on
Commit
cc8faa4
Β·
verified Β·
1 Parent(s): 2477130

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -142
app.py CHANGED
@@ -5,100 +5,102 @@ from core.visual_engine import VisualEngine
5
  from core.prompt_engineering import (
6
  create_story_breakdown_prompt,
7
  create_image_prompt_from_scene_data,
 
8
  create_scene_regeneration_prompt,
9
  create_visual_regeneration_prompt
10
  )
11
  import os
12
 
13
  # --- Configuration & Initialization ---
14
- st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
15
 
16
- # --- Global State Variables ---
17
- if 'GEMINI_API_KEY' not in st.session_state:
18
- try: st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
19
- except:
20
- if "GEMINI_API_KEY" in os.environ: st.session_state.GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
21
- else: st.error("GEMINI_API_KEY not found."); st.stop()
 
 
 
 
22
 
 
 
 
23
  if 'gemini_handler' not in st.session_state:
24
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
25
 
26
  if 'visual_engine' not in st.session_state:
27
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
28
- openai_key = None
29
- try:
30
- if "OPENAI_API_KEY" in st.secrets: openai_key = st.secrets["OPENAI_API_KEY"]
31
- except AttributeError: print("st.secrets not available. Checking env for OpenAI key.")
32
- except Exception as e: print(f"Error accessing st.secrets for OPENAI_API_KEY: {e}")
33
- if not openai_key and "OPENAI_API_KEY" in os.environ: openai_key = os.environ["OPENAI_API_KEY"]
34
- st.session_state.visual_engine.set_openai_api_key(openai_key)
35
- if not openai_key and 'openai_warning_shown' not in st.session_state:
36
- st.sidebar.warning("OpenAI Key missing. DALL-E disabled, using placeholders.")
37
- st.session_state.openai_warning_shown = True # Show only once per session
38
- elif openai_key and 'openai_success_shown' not in st.session_state:
39
- # st.sidebar.caption("OpenAI Key loaded. DALL-E ready.") # Can be a bit noisy
40
- st.session_state.openai_success_shown = True
41
 
 
 
 
 
 
 
 
 
42
 
43
- if 'story_scenes' not in st.session_state: st.session_state.story_scenes = []
44
- if 'scene_image_prompts' not in st.session_state: st.session_state.scene_image_prompts = []
45
- if 'generated_images_paths' not in st.session_state: st.session_state.generated_images_paths = []
46
- if 'video_path' not in st.session_state: st.session_state.video_path = None
47
- if 'character_definitions' not in st.session_state: st.session_state.character_definitions = {}
48
- if 'style_reference_description' not in st.session_state: st.session_state.style_reference_description = ""
49
 
50
- # --- Helper Functions ---
51
- def initialize_new_story(): # Remains the same
52
  st.session_state.story_scenes = []
53
  st.session_state.scene_image_prompts = []
54
  st.session_state.generated_images_paths = []
55
  st.session_state.video_path = None
 
 
 
56
 
57
  def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
 
 
58
  scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
59
  textual_image_prompt = ""
60
-
61
  if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
62
- textual_image_prompt = st.session_state.scene_image_prompts[scene_index] # Use the already refined prompt
63
- else: # Initial generation
64
  textual_image_prompt = create_image_prompt_from_scene_data(
65
- scene_data,
66
- st.session_state.character_definitions,
67
- st.session_state.style_reference_description
68
- )
69
  if not textual_image_prompt: return False
70
-
71
- # Update session state with the textual prompt used (might be initial or refined)
72
  if scene_index >= len(st.session_state.scene_image_prompts):
73
  while len(st.session_state.scene_image_prompts) <= scene_index: st.session_state.scene_image_prompts.append("")
74
  st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
75
-
76
- image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
77
- generated_image_path = st.session_state.visual_engine.generate_image_visual(textual_image_prompt, image_filename)
78
 
 
 
 
 
79
  while len(st.session_state.generated_images_paths) <= scene_index: st.session_state.generated_images_paths.append(None)
80
  if generated_image_path and os.path.exists(generated_image_path):
81
- st.session_state.generated_images_paths[scene_index] = generated_image_path
82
- return True
83
  else:
84
- st.session_state.generated_images_paths[scene_index] = None
85
- return False
86
 
87
  # --- UI Sidebar ---
88
  with st.sidebar:
89
- st.title("🎬 CineGen AI Pro")
90
  st.markdown("### Creative Controls")
91
- user_idea = st.text_area("Enter your core story idea:", "A lone astronaut discovers a glowing alien artifact on Mars, a sense of wonder and slight dread.", height=100, key="user_idea_input")
92
- genre = st.selectbox("Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror"], index=0, key="genre_select")
93
- mood = st.selectbox("Mood:", ["Wonder", "Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=0, key="mood_select")
94
- num_scenes_val = st.slider("Number of Scenes:", 1, 3, 1, key="num_scenes_slider") # Max 3 for DALL-E cost/time
 
95
 
96
- if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
 
97
  initialize_new_story()
98
  if not user_idea.strip(): st.warning("Please enter a story idea.")
99
  else:
100
- with st.status("Generating story and visuals...", expanded=True) as status_main:
101
- st.write("Phase 1: Drafting script with Gemini... πŸ“œ")
 
102
  story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
103
  try:
104
  st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
@@ -106,117 +108,122 @@ with st.sidebar:
106
  num_actual_scenes = len(st.session_state.story_scenes)
107
  st.session_state.scene_image_prompts = [""] * num_actual_scenes
108
  st.session_state.generated_images_paths = [None] * num_actual_scenes
109
- except Exception as e:
110
- status_main.update(label=f"Script generation failed: {e}", state="error"); st.stop()
111
 
112
  if st.session_state.story_scenes:
113
- st.write("Phase 2: Creating visuals with DALL-E (this can take time)... πŸ–ΌοΈ")
114
- success_count = 0
115
  for i, scene_data in enumerate(st.session_state.story_scenes):
116
- st.write(f" Processing Scene {scene_data.get('scene_number', i + 1)}...")
117
- if generate_visual_for_scene_wrapper(i, scene_data, version_count=1): success_count +=1
 
 
 
118
 
119
- if success_count == len(st.session_state.story_scenes): status_main.update(label="All done! Story and visuals ready. πŸŽ‰", state="complete", expanded=False)
120
- elif success_count > 0: status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals OK.", state="warning", expanded=False)
121
- else: status_main.update(label="Visual generation failed for all scenes.", state="error", expanded=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- st.markdown("---")
124
- st.markdown("### Advanced Options")
 
 
125
  with st.expander("Character Consistency", expanded=False):
126
  char_name_input = st.text_input("Character Name (e.g., Jax)", key="char_name_adv_input")
127
  char_desc_input = st.text_area("Character Description (e.g., 'male astronaut, rugged, dark short hair, blue eyes, wearing a worn white and orange spacesuit with a cracked visor helmet slung at his hip')", key="char_desc_adv_input", height=120)
128
  if st.button("Add/Update Character", key="add_char_adv_btn"):
129
- if char_name_input and char_desc_input:
130
- st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip() # Use lower for key
131
- st.success(f"Character '{char_name_input.strip()}' defined.")
132
  else: st.warning("Provide both name and description.")
133
  if st.session_state.character_definitions:
134
  st.caption("Defined Characters:")
135
  for char_key, desc_val in st.session_state.character_definitions.items(): st.markdown(f"**{char_key.title()}:** _{desc_val}_")
136
-
137
- with st.expander("Style Controls", expanded=True): # Expanded by default
138
- predefined_styles = {
139
- "Default (Cinematic Photorealism)": "",
140
- "Gritty Neo-Noir": "neo-noir aesthetic, gritty realism, deep shadows, rain-slicked streets, desaturated colors with vibrant neon highlights, anamorphic lens flares, film grain",
141
- "Epic Fantasy Matte Painting": "epic fantasy matte painting style, vast landscapes, dramatic skies, painterly textures, rich colors, Lord of the Rings inspired, highly detailed",
142
- "Impressionistic Dream": "impressionistic oil painting style, visible brushstrokes, soft focus, dreamlike atmosphere, pastel color palette, ethereal lighting",
143
- "Vintage Sci-Fi (70s/80s Film)": "retro sci-fi aesthetic, analog film look, subtle film grain, practical effects feel, slightly desaturated colors, chrome and plastic textures, classic 70s/80s anamorphic lens flares, detailed spaceship interiors or alien landscapes",
144
- "Modern Anime Key Visual": "high-detail modern anime key visual style, dynamic compositions, vibrant saturated colors, crisp cel-shaded look with intricate lighting and deep shadows, expressive character designs with detailed eyes"
145
- }
146
  selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv")
147
- custom_keywords = st.text_area("Add Custom Style Keywords:", key="custom_style_keywords_adv_input", height=80, placeholder="e.g., 'Dutch angle, fisheye lens, sepia tone'")
148
-
149
  current_style_desc = st.session_state.style_reference_description
150
  if st.button("Apply & Set Styles", key="apply_styles_adv_btn"):
151
- final_desc = predefined_styles[selected_preset]
152
- if custom_keywords.strip():
153
- final_desc = f"{final_desc}, {custom_keywords.strip()}" if final_desc else custom_keywords.strip()
154
- st.session_state.style_reference_description = final_desc.strip()
155
- current_style_desc = final_desc.strip() # Update for immediate display
156
- if current_style_desc: st.success("Styles applied! Regenerate story or visuals.")
157
  else: st.info("Default style (no specific additions).")
158
-
159
- if current_style_desc: st.caption(f"Active style prompt additions: \"{current_style_desc}\"")
160
  else: st.caption("No specific style prompt additions active.")
 
 
 
 
 
 
 
 
 
 
 
161
 
 
 
 
162
 
163
- # --- Main Content Area (largely same as previous full version, with minor tweaks) ---
164
  st.header("πŸ“ Cinematic Storyboard")
 
 
 
 
165
  if not st.session_state.story_scenes:
166
- st.info("Enter your idea in the sidebar and click '✨ Generate Full Story Concept' to begin.")
167
  else:
168
  for i, scene_data_display in enumerate(st.session_state.story_scenes):
169
- # ... (scene_num_display, unique_key_part, subheader, columns - same) ...
170
- scene_num_display = scene_data_display.get('scene_number', i + 1)
171
- action_summary = scene_data_display.get('key_action', f"scene{i}")
172
- key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
173
- unique_key_part = key_part_raw.replace(" ", "_")[:20]
174
  st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
175
  col1, col2 = st.columns([2, 3])
176
-
177
- with col1: # Scene Details
178
  with st.expander("Scene Details", expanded=True):
179
- st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
180
- st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
181
- st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}")
182
- st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`")
183
- st.markdown(f"**Visual Style Suggestion (from Gemini):** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
184
- st.markdown(f"**Camera Suggestion (from Gemini):** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
185
  current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
186
  if current_textual_prompt:
187
- with st.popover("View Full Image Prompt"):
188
- st.markdown(f"**Full Textual Prompt Sent to DALL-E:**"); st.code(current_textual_prompt, language='text')
189
-
190
- with col2: # Image and Edit Buttons
191
  current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
192
- if current_image_path and os.path.exists(current_image_path):
193
- st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
194
  else:
195
  if st.session_state.story_scenes: st.caption("Visual for this scene is pending or failed.")
196
-
197
- # Edit Popovers (logic remains largely the same, calls generate_visual_for_scene_wrapper)
198
  with st.popover(f"✏️ Edit Scene {scene_num_display} Script"):
 
199
  feedback_script = st.text_area("Describe script changes:", key=f"script_feedback_{unique_key_part}_{i}", height=100)
200
  if st.button(f"πŸ”„ Update Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
201
  if feedback_script:
202
  with st.status(f"Updating Scene {scene_num_display}...", expanded=True) as status_script_regen:
203
  regen_prompt = create_scene_regeneration_prompt(scene_data_display, feedback_script, st.session_state.story_scenes)
204
  try:
205
- updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
206
- st.session_state.story_scenes[i] = updated_scene_data
207
  status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
208
  version = 1
209
- if current_image_path:
210
- try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2
211
- except: version = 2 # Default to v2 if parsing fails
212
- if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=False, version_count=version): # is_regeneration=False to use new scene_data for prompt
213
- status_script_regen.update(label="Scene script & visual updated! πŸŽ‰", state="complete", expanded=False)
214
  else: status_script_regen.update(label="Script updated, visual failed.", state="warning", expanded=False)
215
  st.rerun()
216
  except Exception as e: status_script_regen.update(label=f"Error: {e}", state="error")
217
  else: st.warning("Please provide feedback.")
218
-
219
  with st.popover(f"🎨 Edit Scene {scene_num_display} Visuals"):
 
220
  prompt_for_edit_visuals = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt."
221
  st.caption("Current Full Image Prompt:"); st.code(prompt_for_edit_visuals, language='text')
222
  feedback_visual = st.text_area("Describe visual changes for the prompt:", key=f"visual_feedback_{unique_key_part}_{i}", height=100)
@@ -225,56 +232,60 @@ else:
225
  with st.status(f"Refining prompt & regenerating visual...", expanded=True) as status_visual_regen:
226
  prompt_refinement_request = create_visual_regeneration_prompt(
227
  prompt_for_edit_visuals, feedback_visual, scene_data_display,
228
- st.session_state.character_definitions, st.session_state.style_reference_description
229
  )
230
  try:
231
  refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
232
- st.session_state.scene_image_prompts[i] = refined_textual_image_prompt # Update the prompt in session state
233
  status_visual_regen.update(label="Prompt refined! Regenerating visual...", state="running")
234
  version = 1
235
- if current_image_path:
236
- try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2
237
- except: version = 2
238
- if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): # is_regeneration=True to use the refined prompt
239
- status_visual_regen.update(label="Visual updated! πŸŽ‰", state="complete", expanded=False)
240
  else: status_visual_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
241
  st.rerun()
242
  except Exception as e: status_visual_regen.update(label=f"Error: {e}", state="error")
243
  else: st.warning("Please provide feedback.")
244
  st.markdown("---")
245
 
246
- # Video Generation Button and Display (remains same as previous full app.py)
247
  if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
248
- if st.button("🎬 Assemble Enhanced Animatic", key="assemble_enhanced_video_btn", type="primary", use_container_width=True):
249
- # ... (video assembly logic - same as before) ...
250
- with st.status("Assembling enhanced animatic video...", expanded=True) as status_video:
251
  image_data_for_video = []
 
252
  for idx, scene_info in enumerate(st.session_state.story_scenes):
253
  img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
254
  if img_path and os.path.exists(img_path):
255
- image_data_for_video.append({
256
- 'path': img_path, 'scene_num': scene_info.get('scene_number', idx + 1),
257
- 'key_action': scene_info.get('key_action', '')
258
- }); st.write(f"Adding Scene {scene_info.get('scene_number', idx + 1)} to video.")
259
  if image_data_for_video:
260
- st.write("Calling video engine..."); st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
261
- image_data_for_video, output_filename="cinegen_pro_animatic_enhanced.mp4", duration_per_image=4, fps=24 )
 
 
 
 
 
 
262
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
263
- status_video.update(label="Enhanced animatic assembled! πŸŽ‰", state="complete", expanded=False); st.balloons()
264
  else: status_video.update(label="Video assembly failed. Check logs.", state="error", expanded=False)
265
  else: status_video.update(label="No valid images to assemble video.", state="error", expanded=False)
266
  elif st.session_state.story_scenes: st.info("Generate visuals for scenes before assembling the video.")
 
 
267
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
268
- st.header("🎬 Generated Animatic Video")
269
  try:
270
- with open(st.session_state.video_path, 'rb') as video_file_obj: video_bytes_content = video_file_obj.read()
271
- st.video(video_bytes_content, format="video/mp4")
272
- with open(st.session_state.video_path, "rb") as fp_download_video:
273
- st.download_button(label="Download Enhanced Animatic", data=fp_download_video,
274
  file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
275
- use_container_width=True, key="download_video_btn_enhanced" )
276
  except Exception as e: st.error(f"Error displaying video: {e}")
277
 
278
  # --- Footer ---
279
  st.sidebar.markdown("---")
280
- st.sidebar.caption("CineGen AI Pro | Powered by Gemini & Streamlit.")
 
5
  from core.prompt_engineering import (
6
  create_story_breakdown_prompt,
7
  create_image_prompt_from_scene_data,
8
+ create_narration_script_prompt, # New
9
  create_scene_regeneration_prompt,
10
  create_visual_regeneration_prompt
11
  )
12
  import os
13
 
14
  # --- Configuration & Initialization ---
15
+ st.set_page_config(page_title="CineGen AI Ultra", layout="wide", initial_sidebar_state="expanded")
16
 
17
+ # --- Global State Variables & API Key Setup ---
18
+ # Simplified API Key Loading
19
+ def load_api_key(key_name_streamlit, key_name_env):
20
+ key = None
21
+ try:
22
+ if key_name_streamlit in st.secrets: key = st.secrets[key_name_streamlit]
23
+ except AttributeError: print(f"st.secrets not available for {key_name_streamlit}.") # Local dev
24
+ except Exception as e: print(f"Error accessing st.secrets for {key_name_streamlit}: {e}")
25
+ if not key and key_name_env in os.environ: key = os.environ[key_name_env]
26
+ return key
27
 
28
+ if 'GEMINI_API_KEY' not in st.session_state:
29
+ st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY")
30
+ if not st.session_state.GEMINI_API_KEY: st.error("Gemini API Key missing!"); st.stop()
31
  if 'gemini_handler' not in st.session_state:
32
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
33
 
34
  if 'visual_engine' not in st.session_state:
35
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
36
+ st.session_state.visual_engine.set_openai_api_key(load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY"))
37
+ st.session_state.visual_engine.set_elevenlabs_api_key(load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY"))
38
+ st.session_state.visual_engine.set_pexels_api_key(load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY"))
 
 
 
 
 
 
 
 
 
 
39
 
40
+ # Initialize other session state variables (same as before)
41
+ for key, default_val in [
42
+ ('story_scenes', []), ('scene_image_prompts', []), ('generated_images_paths', []),
43
+ ('video_path', None), ('character_definitions', {}), ('style_reference_description', ""),
44
+ ('overall_narration_path', None), ('narration_script_for_display', "")
45
+ ]:
46
+ if key not in st.session_state: st.session_state[key] = default_val
47
+ # --- End State & API Key Setup ---
48
 
 
 
 
 
 
 
49
 
50
+ def initialize_new_story(): # Remains same
 
51
  st.session_state.story_scenes = []
52
  st.session_state.scene_image_prompts = []
53
  st.session_state.generated_images_paths = []
54
  st.session_state.video_path = None
55
+ st.session_state.overall_narration_path = None
56
+ st.session_state.narration_script_for_display = ""
57
+
58
 
59
  def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
60
+ # ... (This helper remains the same, but now `generate_image_visual` in engine has Pexels fallback) ...
61
+ # Pass scene_data for Pexels query generation
62
  scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
63
  textual_image_prompt = ""
 
64
  if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
65
+ textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
66
+ else:
67
  textual_image_prompt = create_image_prompt_from_scene_data(
68
+ scene_data, st.session_state.character_definitions, st.session_state.style_reference_description)
 
 
 
69
  if not textual_image_prompt: return False
 
 
70
  if scene_index >= len(st.session_state.scene_image_prompts):
71
  while len(st.session_state.scene_image_prompts) <= scene_index: st.session_state.scene_image_prompts.append("")
72
  st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
73
+ image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png" # DALL-E saves as PNG
 
 
74
 
75
+ # Pass scene_data to visual engine for Pexels query if DALL-E fails
76
+ generated_image_path = st.session_state.visual_engine.generate_image_visual(
77
+ textual_image_prompt, scene_data, image_filename
78
+ )
79
  while len(st.session_state.generated_images_paths) <= scene_index: st.session_state.generated_images_paths.append(None)
80
  if generated_image_path and os.path.exists(generated_image_path):
81
+ st.session_state.generated_images_paths[scene_index] = generated_image_path; return True
 
82
  else:
83
+ st.session_state.generated_images_paths[scene_index] = None; return False
84
+
85
 
86
  # --- UI Sidebar ---
87
  with st.sidebar:
88
+ st.title("🎬 CineGen AI Ultra")
89
  st.markdown("### Creative Controls")
90
+ # ... (user_idea, genre, mood, num_scenes - same) ...
91
+ user_idea = st.text_area("Core story idea:", "A lone astronaut discovers a glowing alien artifact on Mars, a sense of wonder and slight dread.", height=100, key="user_idea_input")
92
+ genre = st.selectbox("Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller"], index=0, key="genre_select")
93
+ mood = st.selectbox("Mood:", ["Wonder", "Suspenseful", "Mysterious", "Gritty", "Epic"], index=0, key="mood_select")
94
+ num_scenes_val = st.slider("Number of Scenes:", 1, 3, 1, key="num_scenes_slider")
95
 
96
+
97
+ if st.button("πŸš€ Generate Cinematic Masterpiece", type="primary", key="generate_masterpiece_btn", use_container_width=True):
98
  initialize_new_story()
99
  if not user_idea.strip(): st.warning("Please enter a story idea.")
100
  else:
101
+ with st.status("Crafting your cinematic vision...", expanded=True) as status_main:
102
+ st.write("Phase 1: Gemini is drafting the script... πŸ“œ")
103
+ # ... (Story generation - same) ...
104
  story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
105
  try:
106
  st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
 
108
  num_actual_scenes = len(st.session_state.story_scenes)
109
  st.session_state.scene_image_prompts = [""] * num_actual_scenes
110
  st.session_state.generated_images_paths = [None] * num_actual_scenes
111
+ except Exception as e: status_main.update(label=f"Script gen failed: {e}", state="error"); st.stop()
 
112
 
113
  if st.session_state.story_scenes:
114
+ st.write("Phase 2: Creating visuals (DALL-E/Pexels)... πŸ–ΌοΈ")
115
+ visual_success_count = 0
116
  for i, scene_data in enumerate(st.session_state.story_scenes):
117
+ st.write(f" Processing Scene {scene_data.get('scene_number', i + 1)} visuals...")
118
+ if generate_visual_for_scene_wrapper(i, scene_data, version_count=1): visual_success_count +=1
119
+
120
+ if visual_success_count == 0 :
121
+ status_main.update(label="Visual gen failed for all scenes.", state="error", expanded=False); st.stop()
122
 
123
+ st.write("Phase 3: Generating narration script with Gemini... 🎀")
124
+ narration_prompt = create_narration_script_prompt(st.session_state.story_scenes, mood, genre)
125
+ try:
126
+ narration_script = st.session_state.gemini_handler.generate_image_prompt(narration_prompt) # Re-use for text gen
127
+ st.session_state.narration_script_for_display = narration_script
128
+ status_main.update(label="Narration script ready! Generating voice...", state="running")
129
+
130
+ st.write("Phase 4: Synthesizing voice with ElevenLabs... πŸ”Š")
131
+ st.session_state.overall_narration_path = st.session_state.visual_engine.generate_narration_audio(
132
+ narration_script, "overall_narration.mp3"
133
+ )
134
+ if st.session_state.overall_narration_path:
135
+ status_main.update(label="Voiceover generated! ✨", state="running")
136
+ else:
137
+ status_main.update(label="Voiceover failed, proceeding without.", state="warning")
138
+ except Exception as e:
139
+ status_main.update(label=f"Narration/Voice gen failed: {e}", state="warning")
140
 
141
+ status_main.update(label="All major components generated! Storyboard ready. πŸŽ‰", state="complete", expanded=False)
142
+
143
+ st.markdown("---") # Advanced Options (Character, Style - same as previous full app.py)
144
+ # ... (Character Consistency expander - same)
145
  with st.expander("Character Consistency", expanded=False):
146
  char_name_input = st.text_input("Character Name (e.g., Jax)", key="char_name_adv_input")
147
  char_desc_input = st.text_area("Character Description (e.g., 'male astronaut, rugged, dark short hair, blue eyes, wearing a worn white and orange spacesuit with a cracked visor helmet slung at his hip')", key="char_desc_adv_input", height=120)
148
  if st.button("Add/Update Character", key="add_char_adv_btn"):
149
+ if char_name_input and char_desc_input: st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip(); st.success(f"Character '{char_name_input.strip()}' defined.")
 
 
150
  else: st.warning("Provide both name and description.")
151
  if st.session_state.character_definitions:
152
  st.caption("Defined Characters:")
153
  for char_key, desc_val in st.session_state.character_definitions.items(): st.markdown(f"**{char_key.title()}:** _{desc_val}_")
154
+ # ... (Style Controls expander - same)
155
+ with st.expander("Style Controls", expanded=True):
156
+ predefined_styles = { "Default (Cinematic Photorealism)": "", "Gritty Neo-Noir": "neo-noir aesthetic, gritty realism, deep shadows...", "Epic Fantasy Matte Painting": "epic fantasy matte painting style...", "Impressionistic Dream": "impressionistic oil painting style...", "Vintage Sci-Fi (70s/80s Film)": "retro sci-fi aesthetic, analog film look...", "Modern Anime Key Visual": "high-detail modern anime key visual style..." }
 
 
 
 
 
 
 
157
  selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv")
158
+ custom_keywords = st.text_area("Add Custom Style Keywords:", key="custom_style_keywords_adv_input", height=80, placeholder="e.g., 'Dutch angle, fisheye lens'")
 
159
  current_style_desc = st.session_state.style_reference_description
160
  if st.button("Apply & Set Styles", key="apply_styles_adv_btn"):
161
+ final_desc = predefined_styles[selected_preset];
162
+ if custom_keywords.strip(): final_desc = f"{final_desc}, {custom_keywords.strip()}" if final_desc else custom_keywords.strip()
163
+ st.session_state.style_reference_description = final_desc.strip(); current_style_desc = final_desc.strip()
164
+ if current_style_desc: st.success("Styles applied!")
 
 
165
  else: st.info("Default style (no specific additions).")
166
+ if current_style_desc: st.caption(f"Active style additions: \"{current_style_desc}\"")
 
167
  else: st.caption("No specific style prompt additions active.")
168
+ # Conceptual Voice selection for ElevenLabs
169
+ with st.expander("Voice Customization (ElevenLabs)", expanded=False):
170
+ # In a real app, you'd fetch voice list from ElevenLabs API if key is present
171
+ available_voices_conceptual = ["Rachel", "Adam", "Bella", "Antoni", "Elli", "Josh", "Arnold", "Callum"]
172
+ selected_voice = st.selectbox("Choose Narrator Voice:", available_voices_conceptual,
173
+ index=available_voices_conceptual.index(st.session_state.visual_engine.elevenlabs_voice_id) if st.session_state.visual_engine.elevenlabs_voice_id in available_voices_conceptual else 0,
174
+ key="elevenlabs_voice_select")
175
+ if st.button("Set Voice", key="set_voice_btn"):
176
+ st.session_state.visual_engine.elevenlabs_voice_id = selected_voice
177
+ st.success(f"Narrator voice set to: {selected_voice}")
178
+
179
 
180
+ # --- Main Content Area ---
181
+ # ... (Header, Scene Iteration, Details, Image Display, Edit Popovers - mostly the same as previous full app.py)
182
+ # Key change: Pass character_definitions and style_reference_description to create_visual_regeneration_prompt
183
 
 
184
  st.header("πŸ“ Cinematic Storyboard")
185
+ if st.session_state.narration_script_for_display: # Display narration script if generated
186
+ with st.expander("View Generated Narration Script", expanded=False):
187
+ st.markdown(st.session_state.narration_script_for_display)
188
+
189
  if not st.session_state.story_scenes:
190
+ st.info("Enter your idea in the sidebar and click 'πŸš€ Generate Cinematic Masterpiece' to begin.")
191
  else:
192
  for i, scene_data_display in enumerate(st.session_state.story_scenes):
193
+ # ... (scene_num_display, unique_key_part, subheader, columns, Scene Details, Image Display - same as previous)
194
+ scene_num_display = scene_data_display.get('scene_number', i + 1); action_summary = scene_data_display.get('key_action', f"scene{i}"); key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace()); unique_key_part = key_part_raw.replace(" ", "_")[:20]
 
 
 
195
  st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
196
  col1, col2 = st.columns([2, 3])
197
+ with col1:
 
198
  with st.expander("Scene Details", expanded=True):
199
+ st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}"); st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}"); st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}"); st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`"); st.markdown(f"**Visual Style Suggestion (Gemini):** {scene_data_display.get('visual_style_suggestion', 'N/A')}"); st.markdown(f"**Camera Suggestion (Gemini):** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
 
 
 
 
 
200
  current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
201
  if current_textual_prompt:
202
+ with st.popover("View Full Image Prompt"): st.markdown(f"**Full Textual Prompt Sent to DALL-E:**"); st.code(current_textual_prompt, language='text')
203
+ with col2:
 
 
204
  current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
205
+ if current_image_path and os.path.exists(current_image_path): st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
 
206
  else:
207
  if st.session_state.story_scenes: st.caption("Visual for this scene is pending or failed.")
 
 
208
  with st.popover(f"✏️ Edit Scene {scene_num_display} Script"):
209
+ # ... (Script edit logic - same, but uses updated generate_visual_for_scene_wrapper)
210
  feedback_script = st.text_area("Describe script changes:", key=f"script_feedback_{unique_key_part}_{i}", height=100)
211
  if st.button(f"πŸ”„ Update Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
212
  if feedback_script:
213
  with st.status(f"Updating Scene {scene_num_display}...", expanded=True) as status_script_regen:
214
  regen_prompt = create_scene_regeneration_prompt(scene_data_display, feedback_script, st.session_state.story_scenes)
215
  try:
216
+ updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt); st.session_state.story_scenes[i] = updated_scene_data
 
217
  status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
218
  version = 1
219
+ if current_image_path: try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2 except: version = 2
220
+ if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=False, version_count=version): status_script_regen.update(label="Scene script & visual updated! πŸŽ‰", state="complete", expanded=False)
 
 
 
221
  else: status_script_regen.update(label="Script updated, visual failed.", state="warning", expanded=False)
222
  st.rerun()
223
  except Exception as e: status_script_regen.update(label=f"Error: {e}", state="error")
224
  else: st.warning("Please provide feedback.")
 
225
  with st.popover(f"🎨 Edit Scene {scene_num_display} Visuals"):
226
+ # ... (Visual edit logic - same, but passes more context to create_visual_regeneration_prompt)
227
  prompt_for_edit_visuals = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt."
228
  st.caption("Current Full Image Prompt:"); st.code(prompt_for_edit_visuals, language='text')
229
  feedback_visual = st.text_area("Describe visual changes for the prompt:", key=f"visual_feedback_{unique_key_part}_{i}", height=100)
 
232
  with st.status(f"Refining prompt & regenerating visual...", expanded=True) as status_visual_regen:
233
  prompt_refinement_request = create_visual_regeneration_prompt(
234
  prompt_for_edit_visuals, feedback_visual, scene_data_display,
235
+ st.session_state.character_definitions, st.session_state.style_reference_description # Pass these
236
  )
237
  try:
238
  refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
239
+ st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
240
  status_visual_regen.update(label="Prompt refined! Regenerating visual...", state="running")
241
  version = 1
242
+ if current_image_path: try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2 except: version = 2
243
+ if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): status_visual_regen.update(label="Visual updated! πŸŽ‰", state="complete", expanded=False)
 
 
 
244
  else: status_visual_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
245
  st.rerun()
246
  except Exception as e: status_visual_regen.update(label=f"Error: {e}", state="error")
247
  else: st.warning("Please provide feedback.")
248
  st.markdown("---")
249
 
250
+ # Video Generation - Pass overall_narration_path
251
  if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
252
+ if st.button("🎬 Assemble Narrated Masterpiece", key="assemble_narrated_video_btn", type="primary", use_container_width=True):
253
+ with st.status("Assembling narrated animatic video...", expanded=True) as status_video:
 
254
  image_data_for_video = []
255
+ # ... (image_data_for_video assembly - same) ...
256
  for idx, scene_info in enumerate(st.session_state.story_scenes):
257
  img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
258
  if img_path and os.path.exists(img_path):
259
+ image_data_for_video.append({'path':img_path, 'scene_num':scene_info.get('scene_number',idx+1), 'key_action':scene_info.get('key_action','')})
260
+ st.write(f"Adding Scene {scene_info.get('scene_number', idx + 1)} to video.")
261
+
 
262
  if image_data_for_video:
263
+ st.write("Calling video engine (with narration if available)...")
264
+ st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
265
+ image_data_for_video,
266
+ overall_narration_path=st.session_state.overall_narration_path, # Pass narration
267
+ output_filename="cinegen_narrated_masterpiece.mp4",
268
+ duration_per_image=5, # Longer for narration
269
+ fps=24
270
+ )
271
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
272
+ status_video.update(label="Narrated masterpiece assembled! πŸŽ‰", state="complete", expanded=False); st.balloons()
273
  else: status_video.update(label="Video assembly failed. Check logs.", state="error", expanded=False)
274
  else: status_video.update(label="No valid images to assemble video.", state="error", expanded=False)
275
  elif st.session_state.story_scenes: st.info("Generate visuals for scenes before assembling the video.")
276
+
277
+ # Video display and download - (same)
278
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
279
+ st.header("🎬 Generated Narrated Masterpiece")
280
  try:
281
+ with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read()
282
+ st.video(video_bytes, format="video/mp4")
283
+ with open(st.session_state.video_path, "rb") as fp_dl:
284
+ st.download_button(label="Download Narrated Masterpiece", data=fp_dl,
285
  file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
286
+ use_container_width=True, key="download_narrated_video_btn" )
287
  except Exception as e: st.error(f"Error displaying video: {e}")
288
 
289
  # --- Footer ---
290
  st.sidebar.markdown("---")
291
+ st.sidebar.caption("CineGen AI Ultra | Powered by Gemini, DALL-E, ElevenLabs & Streamlit.")