mgbam commited on
Commit
3c0fb64
Β·
verified Β·
1 Parent(s): a7374a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -117
app.py CHANGED
@@ -32,7 +32,6 @@ if 'GEMINI_API_KEY' not in st.session_state:
32
  if 'gemini_handler' not in st.session_state:
33
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
34
  if 'visual_engine' not in st.session_state:
35
- # Ensure VisualEngine is initialized (it will print font loading status)
36
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
37
 
38
  # Story and generated content
@@ -59,11 +58,13 @@ def initialize_new_story():
59
  st.session_state.scene_image_prompts = []
60
  st.session_state.generated_images_paths = []
61
  st.session_state.video_path = None
62
- # Clear old media files if any (optional, good for space management on persistent storage)
63
- # For Hugging Face Spaces, temp files are usually cleared on restart/rebuild anyway.
64
  # if os.path.exists(st.session_state.visual_engine.output_dir):
65
- # for f in os.listdir(st.session_state.visual_engine.output_dir):
66
- # os.remove(os.path.join(st.session_state.visual_engine.output_dir, f))
 
 
 
67
 
68
 
69
  def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
@@ -71,61 +72,49 @@ def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=F
71
  Wrapper to generate the textual image prompt and then the actual visual.
72
  Updates session state for prompts and image paths.
73
  """
74
- st.info(f"Generating visual concept for Scene {scene_data.get('scene_number', scene_index + 1)}...")
 
75
 
76
- # 1. Get/Regenerate the detailed image prompt TEXT
77
  if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
78
- # For regeneration, we assume the prompt text is already updated in session state
79
- # (e.g., by a call to create_visual_regeneration_prompt and Gemini)
80
  textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
 
81
  else:
82
  textual_image_prompt = create_image_prompt_from_scene_data(
83
  scene_data,
84
  st.session_state.character_definitions,
85
  st.session_state.style_reference_description
86
  )
87
- # Optional: Call Gemini here to refine this initial prompt if desired
88
- # textual_image_prompt = st.session_state.gemini_handler.generate_image_prompt(textual_image_prompt)
89
 
90
  if not textual_image_prompt:
91
- st.error(f"Failed to create textual image prompt for Scene {scene_data.get('scene_number', scene_index + 1)}.")
92
  return False
93
 
94
- # Update session state for the textual prompt
95
- if scene_index < len(st.session_state.scene_image_prompts):
96
- st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
97
- else:
98
- # Ensure list is long enough before appending (shouldn't happen if initialized correctly)
99
  while len(st.session_state.scene_image_prompts) <= scene_index:
100
  st.session_state.scene_image_prompts.append("")
101
- st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
102
-
103
 
104
- # 2. Generate the actual visual (AI or placeholder) using the textual prompt
105
- image_filename = f"scene_{scene_data.get('scene_number', scene_index + 1)}_visual_v{version_count}.png"
106
 
107
  generated_image_path = st.session_state.visual_engine.generate_image_visual(
108
  textual_image_prompt,
109
  image_filename
110
  )
111
 
 
 
 
 
112
  if generated_image_path and os.path.exists(generated_image_path):
113
- st.success(f"Visual concept for Scene {scene_data.get('scene_number', scene_index + 1)} generated: {os.path.basename(generated_image_path)}")
114
- if scene_index < len(st.session_state.generated_images_paths):
115
- st.session_state.generated_images_paths[scene_index] = generated_image_path
116
- else:
117
- while len(st.session_state.generated_images_paths) <= scene_index:
118
- st.session_state.generated_images_paths.append(None)
119
- st.session_state.generated_images_paths[scene_index] = generated_image_path
120
  return True
121
  else:
122
- st.error(f"Visual generation failed for Scene {scene_data.get('scene_number', scene_index + 1)} (path: {generated_image_path}).")
123
- if scene_index < len(st.session_state.generated_images_paths):
124
- st.session_state.generated_images_paths[scene_index] = None
125
- else:
126
- while len(st.session_state.generated_images_paths) <= scene_index:
127
- st.session_state.generated_images_paths.append(None)
128
- st.session_state.generated_images_paths[scene_index] = None
129
  return False
130
 
131
  # --- UI Sidebar ---
@@ -135,7 +124,7 @@ with st.sidebar:
135
  user_idea = st.text_area("Enter your core story idea:", "A detective in a cyberpunk city investigates a rogue AI that believes it's the next step in evolution.", height=100, key="user_idea_input")
136
  genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror"], index=2, key="genre_select")
137
  mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
138
- num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider") # Max 5 for now
139
 
140
  if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
141
  initialize_new_story()
@@ -148,7 +137,6 @@ with st.sidebar:
148
  st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
149
  st.toast("Script breakdown complete!", icon="βœ…")
150
 
151
- # Initialize placeholders for prompts and images based on number of scenes
152
  num_actual_scenes = len(st.session_state.story_scenes)
153
  st.session_state.scene_image_prompts = [""] * num_actual_scenes
154
  st.session_state.generated_images_paths = [None] * num_actual_scenes
@@ -160,8 +148,8 @@ with st.sidebar:
160
  if st.session_state.story_scenes:
161
  with st.spinner("Phase 2: Generating initial visual concepts... πŸ–ΌοΈ"):
162
  success_count = 0
163
- for i, scene_data_loop_var in enumerate(st.session_state.story_scenes):
164
- if generate_visual_for_scene_wrapper(i, scene_data_loop_var, version_count=1):
165
  success_count +=1
166
  if success_count == len(st.session_state.story_scenes):
167
  st.toast("Initial visual concepts generated!", icon="πŸ–ΌοΈ")
@@ -170,24 +158,24 @@ with st.sidebar:
170
 
171
 
172
  st.markdown("---")
173
- st.markdown("### Advanced Options (Conceptual)")
174
  with st.expander("Character Consistency", expanded=False):
175
- char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name")
176
- char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc", height=80)
177
- if st.button("Add/Update Character", key="add_char_btn"):
178
  if char_name_input and char_desc_input:
179
  st.session_state.character_definitions[char_name_input] = char_desc_input
180
  st.success(f"Character '{char_name_input}' defined.")
181
  else:
182
  st.warning("Please provide both name and description.")
183
  if st.session_state.character_definitions:
184
- st.write("Defined Characters:")
185
  for char, desc in st.session_state.character_definitions.items():
186
- st.caption(f"**{char}:** {desc}")
187
 
188
  with st.expander("Style Transfer", expanded=False):
189
- style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_input", height=80)
190
- if st.button("Apply Textual Style", key="apply_style_btn"):
191
  st.session_state.style_reference_description = style_ref_text
192
  st.success("Style reference applied. Re-generate visuals or full story to see changes.")
193
 
@@ -199,14 +187,16 @@ if not st.session_state.story_scenes:
199
  else:
200
  for i, scene_data_display in enumerate(st.session_state.story_scenes):
201
  scene_num_display = scene_data_display.get('scene_number', i + 1)
202
- # Use a unique key part for expanders if scene numbers can repeat or change
203
- expander_key_part = scene_data_display.get('key_action', f"scene{i}")[:20].replace(" ", "")
 
 
204
 
205
  st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
206
 
207
- col1, col2 = st.columns([2, 3])
208
 
209
- with col1: # Scene Details
210
  with st.expander("Scene Details", expanded=True):
211
  st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
212
  st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
@@ -215,28 +205,25 @@ else:
215
  st.markdown(f"**Visual Style:** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
216
  st.markdown(f"**Camera:** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
217
 
218
- if i < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[i]:
219
- with st.popover("View Image Prompt Text"):
 
220
  st.markdown(f"**Textual Prompt for Image Generation:**")
221
- st.code(st.session_state.scene_image_prompts[i], language='text')
222
 
223
- with col2: # Image and Interactive Editing
224
- if i < len(st.session_state.generated_images_paths) and \
225
- st.session_state.generated_images_paths[i] and \
226
- os.path.exists(st.session_state.generated_images_paths[i]):
227
- st.image(st.session_state.generated_images_paths[i], caption=f"Visual Concept for Scene {scene_num_display}")
228
  else:
229
- if st.session_state.story_scenes: # Only show if story generation was attempted
230
- st.warning("Visual concept not generated or path is invalid for this scene.")
231
-
232
- # Interactive Storyboarding UI using unique keys for each scene's widgets
233
- popover_script_key = f"pop_script_{expander_key_part}_{i}"
234
- popover_visual_key = f"pop_visual_{expander_key_part}_{i}"
235
-
236
- with st.popover(f"✏️ Edit Scene {scene_num_display} Script", key=popover_script_key):
237
  feedback_script = st.text_area("Describe changes to script details:",
238
- key=f"script_feedback_{expander_key_part}_{i}", height=100)
239
- if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{expander_key_part}_{i}"):
240
  if feedback_script:
241
  with st.spinner(f"Gemini is rewriting Scene {scene_num_display}..."):
242
  regen_prompt = create_scene_regeneration_prompt(
@@ -244,66 +231,58 @@ else:
244
  )
245
  try:
246
  updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
247
- st.session_state.story_scenes[i] = updated_scene_data
248
  st.toast(f"Scene {scene_num_display} script updated!", icon="✍️")
249
- # Regenerate visuals for this updated scene with new script details
250
- # Increment version for the image filename
251
- current_version = 1
252
- if st.session_state.generated_images_paths[i]:
253
- try: # Try to extract version from existing filename
254
- base, ext = os.path.splitext(os.path.basename(st.session_state.generated_images_paths[i]))
255
- if '_v' in base:
256
- current_version = int(base.split('_v')[-1]) + 1
257
- except ValueError: pass # Keep current_version as 1 if parsing fails
258
 
259
- generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=current_version)
 
 
 
 
 
 
 
260
  st.rerun()
261
  except Exception as e:
262
  st.error(f"Error regenerating scene script: {e}")
263
  else:
264
  st.warning("Please provide feedback for script regeneration.")
265
 
266
- with st.popover(f"🎨 Edit Scene {scene_num_display} Visuals", key=popover_visual_key):
267
- current_prompt_for_edit = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt yet."
268
  st.caption("Current Image Prompt Text:")
269
- st.code(current_prompt_for_edit, language='text')
270
 
271
  feedback_visual = st.text_area("Describe visual changes to apply to the prompt:",
272
- key=f"visual_feedback_{expander_key_part}_{i}", height=100)
273
- if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{expander_key_part}_{i}"):
274
  if feedback_visual:
275
  with st.spinner(f"Refining image prompt for Scene {scene_num_display}..."):
276
- # This creates a prompt FOR GEMINI to refine the image prompt
277
  prompt_refinement_request = create_visual_regeneration_prompt(
278
- current_prompt_for_edit,
279
- feedback_visual,
280
- scene_data_display
281
  )
282
  try:
283
- # Gemini refines the textual image prompt
284
  refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
285
- st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
286
  st.toast(f"Image prompt for Scene {scene_num_display} refined!", icon="πŸ’‘")
287
 
288
- # Now generate the new visual using the refined prompt
289
- current_version = 1
290
- if st.session_state.generated_images_paths[i]:
291
  try:
292
- base, ext = os.path.splitext(os.path.basename(st.session_state.generated_images_paths[i]))
293
- if '_v' in base:
294
- current_version = int(base.split('_v')[-1]) + 1
295
- except ValueError: pass
296
 
297
- generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=current_version)
298
  st.rerun()
299
  except Exception as e:
300
  st.error(f"Error refining image prompt or regenerating visual: {e}")
301
  else:
302
  st.warning("Please provide feedback for visual regeneration.")
303
  st.markdown("---")
304
-
305
  # Video Generation Button
306
- if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths): # Check if any image exists
307
  if st.button("🎬 Assemble Animatic Video", key="assemble_video_btn", type="primary"):
308
  with st.spinner("Assembling video... This might take a moment."):
309
  valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
@@ -317,32 +296,29 @@ else:
317
  st.toast("Animatic video assembled!", icon="🎞️")
318
  st.balloons()
319
  else:
320
- st.error("Video assembly failed. Check logs for details.")
321
  else:
322
- st.error("No valid images found to assemble video. Please generate visuals for scenes first.")
323
- elif st.session_state.story_scenes: # Story exists but no images yet
324
- st.warning("Generate visuals for scenes before assembling the video.")
325
-
326
 
327
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
328
  st.header("🎬 Generated Animatic Video")
329
  try:
330
- with open(st.session_state.video_path, 'rb') as video_file:
331
- video_bytes = video_file.read()
332
- st.video(video_bytes)
333
- st.markdown(f"Video saved at: `{st.session_state.video_path}` (within the Space's file system)")
334
- # Provide a download button for the video
335
- with open(st.session_state.video_path, "rb") as fp_download:
336
  st.download_button(
337
  label="Download Animatic Video",
338
- data=fp_download,
339
- file_name=os.path.basename(st.session_state.video_path),
340
  mime="video/mp4"
341
  )
342
  except Exception as e:
343
  st.error(f"Error displaying or preparing video for download: {e}")
344
 
345
-
346
- # --- Footer or additional info ---
347
  st.sidebar.markdown("---")
348
- st.sidebar.info("CineGen AI by [Your Name/Company]. Powered by Gemini & Streamlit.")
 
32
  if 'gemini_handler' not in st.session_state:
33
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
34
  if 'visual_engine' not in st.session_state:
 
35
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
36
 
37
  # Story and generated content
 
58
  st.session_state.scene_image_prompts = []
59
  st.session_state.generated_images_paths = []
60
  st.session_state.video_path = None
61
+ # Optional: Clear old media files
 
62
  # if os.path.exists(st.session_state.visual_engine.output_dir):
63
+ # for f_name in os.listdir(st.session_state.visual_engine.output_dir):
64
+ # try:
65
+ # os.remove(os.path.join(st.session_state.visual_engine.output_dir, f_name))
66
+ # except OSError: # File might be in use by video player, handle gracefully
67
+ # print(f"Could not remove {f_name}, it might be in use.")
68
 
69
 
70
  def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
 
72
  Wrapper to generate the textual image prompt and then the actual visual.
73
  Updates session state for prompts and image paths.
74
  """
75
+ scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
76
+ st.info(f"Processing visual concept for Scene {scene_num_for_log} (v{version_count})...")
77
 
78
+ textual_image_prompt = ""
79
  if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
 
 
80
  textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
81
+ st.caption(f"Using (potentially refined) prompt for Scene {scene_num_for_log}.")
82
  else:
83
  textual_image_prompt = create_image_prompt_from_scene_data(
84
  scene_data,
85
  st.session_state.character_definitions,
86
  st.session_state.style_reference_description
87
  )
88
+ st.caption(f"Generated initial prompt for Scene {scene_num_for_log}.")
 
89
 
90
  if not textual_image_prompt:
91
+ st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.")
92
  return False
93
 
94
+ # Update session state for the textual prompt if it's new or changed
95
+ if scene_index >= len(st.session_state.scene_image_prompts):
 
 
 
96
  while len(st.session_state.scene_image_prompts) <= scene_index:
97
  st.session_state.scene_image_prompts.append("")
98
+ st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
 
99
 
100
+ image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
 
101
 
102
  generated_image_path = st.session_state.visual_engine.generate_image_visual(
103
  textual_image_prompt,
104
  image_filename
105
  )
106
 
107
+ # Ensure generated_images_paths is long enough
108
+ while len(st.session_state.generated_images_paths) <= scene_index:
109
+ st.session_state.generated_images_paths.append(None)
110
+
111
  if generated_image_path and os.path.exists(generated_image_path):
112
+ st.success(f"Visual concept for Scene {scene_num_for_log} (v{version_count}) available: {os.path.basename(generated_image_path)}")
113
+ st.session_state.generated_images_paths[scene_index] = generated_image_path
 
 
 
 
 
114
  return True
115
  else:
116
+ st.error(f"Visual generation failed for Scene {scene_num_for_log} (v{version_count}). Path: {generated_image_path}")
117
+ st.session_state.generated_images_paths[scene_index] = None
 
 
 
 
 
118
  return False
119
 
120
  # --- UI Sidebar ---
 
124
  user_idea = st.text_area("Enter your core story idea:", "A detective in a cyberpunk city investigates a rogue AI that believes it's the next step in evolution.", height=100, key="user_idea_input")
125
  genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror"], index=2, key="genre_select")
126
  mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
127
+ num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider")
128
 
129
  if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
130
  initialize_new_story()
 
137
  st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
138
  st.toast("Script breakdown complete!", icon="βœ…")
139
 
 
140
  num_actual_scenes = len(st.session_state.story_scenes)
141
  st.session_state.scene_image_prompts = [""] * num_actual_scenes
142
  st.session_state.generated_images_paths = [None] * num_actual_scenes
 
148
  if st.session_state.story_scenes:
149
  with st.spinner("Phase 2: Generating initial visual concepts... πŸ–ΌοΈ"):
150
  success_count = 0
151
+ for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
152
+ if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
153
  success_count +=1
154
  if success_count == len(st.session_state.story_scenes):
155
  st.toast("Initial visual concepts generated!", icon="πŸ–ΌοΈ")
 
158
 
159
 
160
  st.markdown("---")
161
+ st.markdown("### Advanced Options")
162
  with st.expander("Character Consistency", expanded=False):
163
+ char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input") # Unique key
164
+ char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc_adv_input", height=80) # Unique key
165
+ if st.button("Add/Update Character", key="add_char_adv_btn"): # Unique key
166
  if char_name_input and char_desc_input:
167
  st.session_state.character_definitions[char_name_input] = char_desc_input
168
  st.success(f"Character '{char_name_input}' defined.")
169
  else:
170
  st.warning("Please provide both name and description.")
171
  if st.session_state.character_definitions:
172
+ st.caption("Defined Characters:")
173
  for char, desc in st.session_state.character_definitions.items():
174
+ st.markdown(f"**{char}:** _{desc}_")
175
 
176
  with st.expander("Style Transfer", expanded=False):
177
+ style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_adv_input", height=80) # Unique key
178
+ if st.button("Apply Textual Style", key="apply_style_adv_btn"): # Unique key
179
  st.session_state.style_reference_description = style_ref_text
180
  st.success("Style reference applied. Re-generate visuals or full story to see changes.")
181
 
 
187
  else:
188
  for i, scene_data_display in enumerate(st.session_state.story_scenes):
189
  scene_num_display = scene_data_display.get('scene_number', i + 1)
190
+ # Create a more robust unique part for keys, sanitizing special characters
191
+ action_summary = scene_data_display.get('key_action', f"scene{i}")
192
+ key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
193
+ unique_key_part = key_part_raw.replace(" ", "_")[:20] # Take first 20 alphanumeric/space chars
194
 
195
  st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
196
 
197
+ col1, col2 = st.columns([2, 3]) # Ratio for text vs image
198
 
199
+ with col1:
200
  with st.expander("Scene Details", expanded=True):
201
  st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
202
  st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
 
205
  st.markdown(f"**Visual Style:** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
206
  st.markdown(f"**Camera:** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
207
 
208
+ current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
209
+ if current_textual_prompt:
210
+ with st.popover("View Image Prompt Text"):
211
  st.markdown(f"**Textual Prompt for Image Generation:**")
212
+ st.code(current_textual_prompt, language='text')
213
 
214
+ with col2:
215
+ current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
216
+ if current_image_path and os.path.exists(current_image_path):
217
+ st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
 
218
  else:
219
+ if st.session_state.story_scenes:
220
+ st.info("Visual concept for this scene is pending or failed.")
221
+
222
+ # Removed 'key' argument from st.popover calls
223
+ with st.popover(f"✏️ Edit Scene {scene_num_display} Script"):
 
 
 
224
  feedback_script = st.text_area("Describe changes to script details:",
225
+ key=f"script_feedback_{unique_key_part}_{i}", height=100)
226
+ if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
227
  if feedback_script:
228
  with st.spinner(f"Gemini is rewriting Scene {scene_num_display}..."):
229
  regen_prompt = create_scene_regeneration_prompt(
 
231
  )
232
  try:
233
  updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
234
+ st.session_state.story_scenes[i] = updated_scene_data
235
  st.toast(f"Scene {scene_num_display} script updated!", icon="✍️")
 
 
 
 
 
 
 
 
 
236
 
237
+ version = 1
238
+ if current_image_path:
239
+ try:
240
+ base, _ = os.path.splitext(os.path.basename(current_image_path))
241
+ if '_v' in base: version = int(base.split('_v')[-1]) + 1
242
+ except (ValueError, AttributeError, TypeError): pass
243
+
244
+ generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version)
245
  st.rerun()
246
  except Exception as e:
247
  st.error(f"Error regenerating scene script: {e}")
248
  else:
249
  st.warning("Please provide feedback for script regeneration.")
250
 
251
+ with st.popover(f"🎨 Edit Scene {scene_num_display} Visuals"):
252
+ prompt_for_edit_visuals = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt text available."
253
  st.caption("Current Image Prompt Text:")
254
+ st.code(prompt_for_edit_visuals, language='text')
255
 
256
  feedback_visual = st.text_area("Describe visual changes to apply to the prompt:",
257
+ key=f"visual_feedback_{unique_key_part}_{i}", height=100)
258
+ if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
259
  if feedback_visual:
260
  with st.spinner(f"Refining image prompt for Scene {scene_num_display}..."):
 
261
  prompt_refinement_request = create_visual_regeneration_prompt(
262
+ prompt_for_edit_visuals, feedback_visual, scene_data_display
 
 
263
  )
264
  try:
 
265
  refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
266
+ st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
267
  st.toast(f"Image prompt for Scene {scene_num_display} refined!", icon="πŸ’‘")
268
 
269
+ version = 1
270
+ if current_image_path:
 
271
  try:
272
+ base, _ = os.path.splitext(os.path.basename(current_image_path))
273
+ if '_v' in base: version = int(base.split('_v')[-1]) + 1
274
+ except (ValueError, AttributeError, TypeError): pass
 
275
 
276
+ generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version)
277
  st.rerun()
278
  except Exception as e:
279
  st.error(f"Error refining image prompt or regenerating visual: {e}")
280
  else:
281
  st.warning("Please provide feedback for visual regeneration.")
282
  st.markdown("---")
283
+
284
  # Video Generation Button
285
+ if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
286
  if st.button("🎬 Assemble Animatic Video", key="assemble_video_btn", type="primary"):
287
  with st.spinner("Assembling video... This might take a moment."):
288
  valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
 
296
  st.toast("Animatic video assembled!", icon="🎞️")
297
  st.balloons()
298
  else:
299
+ st.error("Video assembly failed. Check application logs for details from VisualEngine.")
300
  else:
301
+ st.error("No valid images found to assemble video. Please ensure visuals were generated successfully.")
302
+ elif st.session_state.story_scenes:
303
+ st.info("Generate visuals for scenes before assembling the video.")
 
304
 
305
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
306
  st.header("🎬 Generated Animatic Video")
307
  try:
308
+ with open(st.session_state.video_path, 'rb') as video_file_obj:
309
+ video_bytes_content = video_file_obj.read()
310
+ st.video(video_bytes_content)
311
+ # Download button
312
+ with open(st.session_state.video_path, "rb") as fp_download_video:
 
313
  st.download_button(
314
  label="Download Animatic Video",
315
+ data=fp_download_video,
316
+ file_name=os.path.basename(st.session_state.video_path), # e.g., "cinegen_pro_animatic.mp4"
317
  mime="video/mp4"
318
  )
319
  except Exception as e:
320
  st.error(f"Error displaying or preparing video for download: {e}")
321
 
322
+ # --- Footer ---
 
323
  st.sidebar.markdown("---")
324
+ st.sidebar.caption("CineGen AI Pro | Powered by Gemini & Streamlit")