mgbam commited on
Commit
259f581
Β·
verified Β·
1 Parent(s): 09d5c67

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -81
app.py CHANGED
@@ -1,54 +1,71 @@
1
  # app.py
2
  import streamlit as st
3
  from core.gemini_handler import GeminiHandler
4
- from core.visual_engine import VisualEngine # Your updated VisualEngine
5
  from core.prompt_engineering import (
6
  create_story_breakdown_prompt,
7
- create_image_prompt_from_scene_data, # Generates the TEXT prompt for AI/placeholder
8
  create_scene_regeneration_prompt,
9
- create_visual_regeneration_prompt # Regenerates the TEXT prompt
10
  )
11
  import os
12
- # import json # Only if you need to st.json for debugging, not essential for core logic
13
 
14
  # --- Configuration & Initialization ---
15
  st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
16
 
17
  # --- Global State Variables (Using session state for persistence) ---
 
18
  if 'GEMINI_API_KEY' not in st.session_state:
19
  try:
20
  st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
21
- except KeyError: # For local dev if secrets aren't set yet
22
  if "GEMINI_API_KEY" in os.environ:
23
  st.session_state.GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
24
  else:
25
  st.error("GEMINI_API_KEY not found in secrets or environment variables. Please add it.")
26
  st.stop()
27
- except Exception as e: # Catch any other secrets-related errors
28
- st.error(f"Error loading secrets: {e}")
29
  st.stop()
30
 
31
-
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
38
- if 'story_scenes' not in st.session_state: # List of scene dicts
39
  st.session_state.story_scenes = []
40
- if 'scene_image_prompts' not in st.session_state: # List of textual image prompts
41
  st.session_state.scene_image_prompts = []
42
- if 'generated_images_paths' not in st.session_state: # List of file paths for generated visuals
43
  st.session_state.generated_images_paths = []
44
  if 'video_path' not in st.session_state:
45
  st.session_state.video_path = None
46
-
47
- # For Character Consistency (placeholders)
48
  if 'character_definitions' not in st.session_state:
49
  st.session_state.character_definitions = {}
50
-
51
- # For Style Transfer (placeholders)
52
  if 'style_reference_description' not in st.session_state:
53
  st.session_state.style_reference_description = None
54
 
@@ -58,40 +75,27 @@ def initialize_new_story():
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):
71
- """
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("")
@@ -99,21 +103,21 @@ def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=F
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
 
@@ -126,46 +130,52 @@ with st.sidebar:
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()
131
  if not user_idea.strip():
132
  st.warning("Please enter a story idea.")
133
  else:
134
- with st.spinner("Phase 1: Gemini is drafting the script & scene breakdown... πŸ“œ"):
 
135
  story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
136
  try:
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
143
 
144
  except Exception as e:
145
- st.error(f"Failed to generate story breakdown: {e}")
146
  st.session_state.story_scenes = []
 
147
 
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="πŸ–ΌοΈ")
 
 
156
  else:
157
- st.warning(f"{success_count}/{len(st.session_state.story_scenes)} visual concepts generated. Some may have failed.")
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:
@@ -174,9 +184,9 @@ with st.sidebar:
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
 
182
  # --- Main Content Area ---
@@ -187,14 +197,12 @@ if not st.session_state.story_scenes:
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):
@@ -210,41 +218,42 @@ else:
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(
230
  scene_data_display, feedback_script, st.session_state.story_scenes
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
 
@@ -257,48 +266,50 @@ else:
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)]
289
  if valid_image_paths_for_video:
290
  st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
291
  valid_image_paths_for_video,
292
  output_filename="cinegen_pro_animatic.mp4",
293
- duration_per_image=3
294
  )
295
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
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
 
@@ -308,17 +319,15 @@ else:
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")
 
1
  # app.py
2
  import streamlit as st
3
  from core.gemini_handler import GeminiHandler
4
+ from core.visual_engine import VisualEngine # Your updated VisualEngine with DALL-E prep
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
+ # import json # Only if you need to st.json for debugging
13
 
14
  # --- Configuration & Initialization ---
15
  st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
16
 
17
  # --- Global State Variables (Using session state for persistence) ---
18
+ # Load Gemini API Key
19
  if 'GEMINI_API_KEY' not in st.session_state:
20
  try:
21
  st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
22
+ except KeyError:
23
  if "GEMINI_API_KEY" in os.environ:
24
  st.session_state.GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
25
  else:
26
  st.error("GEMINI_API_KEY not found in secrets or environment variables. Please add it.")
27
  st.stop()
28
+ except Exception as e:
29
+ st.error(f"Error loading Gemini API Key from secrets: {e}")
30
  st.stop()
31
 
32
+ # Initialize Gemini Handler
33
  if 'gemini_handler' not in st.session_state:
34
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
35
+
36
+ # Initialize Visual Engine and set OpenAI API Key
37
  if 'visual_engine' not in st.session_state:
38
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
39
+ # --- TRY TO SET OPENAI API KEY FOR VISUAL ENGINE ---
40
+ openai_key_from_secrets = None
41
+ try:
42
+ if "OPENAI_API_KEY" in st.secrets:
43
+ openai_key_from_secrets = st.secrets["OPENAI_API_KEY"]
44
+ except Exception as e:
45
+ print(f"Could not access st.secrets for OPENAI_API_KEY: {e}") # Log, don't stop app
46
+
47
+ if not openai_key_from_secrets and "OPENAI_API_KEY" in os.environ: # Fallback to env var
48
+ openai_key_from_secrets = os.environ["OPENAI_API_KEY"]
49
+
50
+ if openai_key_from_secrets:
51
+ st.session_state.visual_engine.set_openai_api_key(openai_key_from_secrets)
52
+ # st.sidebar.info("OpenAI API Key loaded. DALL-E generation enabled.") # Optional UI feedback
53
+ else:
54
+ st.session_state.visual_engine.set_openai_api_key(None)
55
+ st.sidebar.warning("OpenAI API Key for DALL-E not found. Visuals will be placeholders.")
56
+ # --- END OF OPENAI API KEY SETUP ---
57
 
58
+ # Story and generated content state
59
+ if 'story_scenes' not in st.session_state:
60
  st.session_state.story_scenes = []
61
+ if 'scene_image_prompts' not in st.session_state:
62
  st.session_state.scene_image_prompts = []
63
+ if 'generated_images_paths' not in st.session_state:
64
  st.session_state.generated_images_paths = []
65
  if 'video_path' not in st.session_state:
66
  st.session_state.video_path = None
 
 
67
  if 'character_definitions' not in st.session_state:
68
  st.session_state.character_definitions = {}
 
 
69
  if 'style_reference_description' not in st.session_state:
70
  st.session_state.style_reference_description = None
71
 
 
75
  st.session_state.scene_image_prompts = []
76
  st.session_state.generated_images_paths = []
77
  st.session_state.video_path = None
 
 
 
 
 
 
 
 
78
 
79
  def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
 
 
 
 
80
  scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
81
+ # This st.info will appear briefly in the main app area below the "Generate" button.
82
+ # Consider moving detailed progress to sidebar or using st.status.
83
+ # st.info(f"Processing visual concept for Scene {scene_num_for_log} (v{version_count})...")
84
 
85
  textual_image_prompt = ""
86
  if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
87
  textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
 
88
  else:
89
  textual_image_prompt = create_image_prompt_from_scene_data(
90
  scene_data,
91
  st.session_state.character_definitions,
92
  st.session_state.style_reference_description
93
  )
 
94
 
95
  if not textual_image_prompt:
96
  st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.")
97
  return False
98
 
 
99
  if scene_index >= len(st.session_state.scene_image_prompts):
100
  while len(st.session_state.scene_image_prompts) <= scene_index:
101
  st.session_state.scene_image_prompts.append("")
 
103
 
104
  image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
105
 
106
+ # The actual generation (AI or placeholder) happens here
107
  generated_image_path = st.session_state.visual_engine.generate_image_visual(
108
  textual_image_prompt,
109
  image_filename
110
  )
111
 
 
112
  while len(st.session_state.generated_images_paths) <= scene_index:
113
  st.session_state.generated_images_paths.append(None)
114
 
115
  if generated_image_path and os.path.exists(generated_image_path):
116
+ # st.success(f"Visual for Scene {scene_num_for_log} (v{version_count}) ready.") # Can be noisy
117
  st.session_state.generated_images_paths[scene_index] = generated_image_path
118
  return True
119
  else:
120
+ st.warning(f"Visual generation failed or path invalid for Scene {scene_num_for_log} (v{version_count}).")
121
  st.session_state.generated_images_paths[scene_index] = None
122
  return False
123
 
 
130
  mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
131
  num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider")
132
 
133
+ if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
134
  initialize_new_story()
135
  if not user_idea.strip():
136
  st.warning("Please enter a story idea.")
137
  else:
138
+ with st.status("Generating story...", expanded=True) as status_main:
139
+ st.write("Phase 1: Gemini is drafting the script & scene breakdown... πŸ“œ")
140
  story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
141
  try:
142
  st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
143
+ status_main.update(label="Script breakdown complete! βœ…", state="running")
144
 
145
  num_actual_scenes = len(st.session_state.story_scenes)
146
  st.session_state.scene_image_prompts = [""] * num_actual_scenes
147
  st.session_state.generated_images_paths = [None] * num_actual_scenes
148
 
149
  except Exception as e:
150
+ status_main.update(label=f"Failed to generate story breakdown: {e}", state="error")
151
  st.session_state.story_scenes = []
152
+ st.stop() # Stop further execution if script fails
153
 
154
+ if st.session_state.story_scenes:
155
+ st.write("Phase 2: Generating initial visual concepts... πŸ–ΌοΈ")
156
  success_count = 0
157
  for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
158
+ st.write(f"Generating visual for Scene {scene_data_loop_var.get('scene_number', i_loop + 1)}...")
159
  if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
160
  success_count +=1
161
+
162
  if success_count == len(st.session_state.story_scenes):
163
+ status_main.update(label="All concepts generated successfully! πŸŽ‰", state="complete", expanded=False)
164
+ elif success_count > 0:
165
+ status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals generated. Some failed.", state="warning", expanded=False)
166
  else:
167
+ status_main.update(label="Visual concept generation failed for all scenes.", state="error", expanded=False)
168
 
169
 
170
  st.markdown("---")
171
  st.markdown("### Advanced Options")
172
  with st.expander("Character Consistency", expanded=False):
173
+ char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input")
174
+ char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc_adv_input", height=80)
175
+ if st.button("Add/Update Character", key="add_char_adv_btn"):
176
  if char_name_input and char_desc_input:
177
+ st.session_state.character_definitions[char_name_input.strip()] = char_desc_input.strip()
178
+ st.success(f"Character '{char_name_input.strip()}' defined.")
179
  else:
180
  st.warning("Please provide both name and description.")
181
  if st.session_state.character_definitions:
 
184
  st.markdown(f"**{char}:** _{desc}_")
185
 
186
  with st.expander("Style Transfer", expanded=False):
187
+ style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_adv_input", height=80)
188
+ if st.button("Apply Textual Style", key="apply_style_adv_btn"):
189
+ st.session_state.style_reference_description = style_ref_text.strip()
190
  st.success("Style reference applied. Re-generate visuals or full story to see changes.")
191
 
192
  # --- Main Content Area ---
 
197
  else:
198
  for i, scene_data_display in enumerate(st.session_state.story_scenes):
199
  scene_num_display = scene_data_display.get('scene_number', i + 1)
 
200
  action_summary = scene_data_display.get('key_action', f"scene{i}")
201
  key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
202
+ unique_key_part = key_part_raw.replace(" ", "_")[:20]
203
 
204
  st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
205
+ col1, col2 = st.columns([2, 3])
 
206
 
207
  with col1:
208
  with st.expander("Scene Details", expanded=True):
 
218
  with st.popover("View Image Prompt Text"):
219
  st.markdown(f"**Textual Prompt for Image Generation:**")
220
  st.code(current_textual_prompt, language='text')
 
221
  with col2:
222
  current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
223
  if current_image_path and os.path.exists(current_image_path):
224
  st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
225
  else:
226
  if st.session_state.story_scenes:
227
+ st.caption("Visual for this scene is pending or failed to generate.")
228
 
 
229
  with st.popover(f"✏️ Edit Scene {scene_num_display} Script"):
230
  feedback_script = st.text_area("Describe changes to script details:",
231
  key=f"script_feedback_{unique_key_part}_{i}", height=100)
232
  if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
233
  if feedback_script:
234
+ with st.status(f"Rewriting Scene {scene_num_display} script...", expanded=True) as status_script_regen:
235
  regen_prompt = create_scene_regeneration_prompt(
236
  scene_data_display, feedback_script, st.session_state.story_scenes
237
  )
238
  try:
239
  updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
240
  st.session_state.story_scenes[i] = updated_scene_data
241
+ status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
242
 
243
  version = 1
244
  if current_image_path:
245
  try:
246
  base, _ = os.path.splitext(os.path.basename(current_image_path))
247
  if '_v' in base: version = int(base.split('_v')[-1]) + 1
248
+ except : pass
249
 
250
+ if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version):
251
+ status_script_regen.update(label="Scene script & visual updated! πŸŽ‰", state="complete", expanded=False)
252
+ else:
253
+ status_script_regen.update(label="Script updated, but visual failed.", state="warning", expanded=False)
254
  st.rerun()
255
  except Exception as e:
256
+ status_script_regen.update(label=f"Error regenerating scene script: {e}", state="error")
257
  else:
258
  st.warning("Please provide feedback for script regeneration.")
259
 
 
266
  key=f"visual_feedback_{unique_key_part}_{i}", height=100)
267
  if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
268
  if feedback_visual:
269
+ with st.status(f"Refining prompt & regenerating visual for Scene {scene_num_display}...", expanded=True) as status_visual_regen:
270
  prompt_refinement_request = create_visual_regeneration_prompt(
271
  prompt_for_edit_visuals, feedback_visual, scene_data_display
272
  )
273
  try:
274
  refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
275
  st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
276
+ status_visual_regen.update(label="Image prompt refined! Regenerating visual...", state="running")
277
 
278
  version = 1
279
  if current_image_path:
280
  try:
281
  base, _ = os.path.splitext(os.path.basename(current_image_path))
282
  if '_v' in base: version = int(base.split('_v')[-1]) + 1
283
+ except : pass
284
 
285
+ if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): # Pass current scene_data, as only prompt changed
286
+ status_visual_regen.update(label="Visual for Scene updated! πŸŽ‰", state="complete", expanded=False)
287
+ else:
288
+ status_visual_regen.update(label="Prompt refined, but visual failed.", state="warning", expanded=False)
289
  st.rerun()
290
  except Exception as e:
291
+ status_visual_regen.update(label=f"Error refining prompt or regenerating visual: {e}", state="error")
292
  else:
293
  st.warning("Please provide feedback for visual regeneration.")
294
  st.markdown("---")
295
 
 
296
  if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
297
+ if st.button("🎬 Assemble Animatic Video", key="assemble_video_btn", type="primary", use_container_width=True):
298
+ with st.status("Assembling video...", expanded=False) as status_video:
299
  valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
300
  if valid_image_paths_for_video:
301
  st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
302
  valid_image_paths_for_video,
303
  output_filename="cinegen_pro_animatic.mp4",
304
+ duration_per_image=3 # Adjust as needed
305
  )
306
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
307
+ status_video.update(label="Animatic video assembled! πŸŽ‰", state="complete")
308
  st.balloons()
309
  else:
310
+ status_video.update(label="Video assembly failed. Check logs.", state="error")
311
  else:
312
+ status_video.update(label="No valid images to assemble video.", state="error")
313
  elif st.session_state.story_scenes:
314
  st.info("Generate visuals for scenes before assembling the video.")
315
 
 
319
  with open(st.session_state.video_path, 'rb') as video_file_obj:
320
  video_bytes_content = video_file_obj.read()
321
  st.video(video_bytes_content)
 
322
  with open(st.session_state.video_path, "rb") as fp_download_video:
323
  st.download_button(
324
+ label="Download Animatic Video", data=fp_download_video,
325
+ file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
326
+ use_container_width=True
 
327
  )
328
  except Exception as e:
329
  st.error(f"Error displaying or preparing video for download: {e}")
330
 
331
  # --- Footer ---
332
  st.sidebar.markdown("---")
333
+ st.sidebar.caption("CineGen AI Pro | Powered by Gemini & Streamlit.")