mgbam commited on
Commit
2ffd5d9
Β·
verified Β·
1 Parent(s): 9d84ba9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -178
app.py CHANGED
@@ -13,62 +13,42 @@ import os
13
  # --- Configuration & Initialization ---
14
  st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
15
 
16
- # --- Global State Variables (Using session state for persistence) ---
17
- # Load Gemini API Key
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:
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:
28
- st.error(f"Error loading Gemini API Key from secrets: {e}")
29
- st.stop()
30
 
31
- # Initialize Gemini Handler
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
 
35
- # Initialize Visual Engine and set OpenAI API Key
36
  if 'visual_engine' not in st.session_state:
37
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
38
- openai_key_from_secrets = None
39
- try:
40
- if "OPENAI_API_KEY" in st.secrets:
41
- openai_key_from_secrets = st.secrets["OPENAI_API_KEY"]
42
- except AttributeError:
43
- print("st.secrets not available (likely local dev without secrets.toml). Checking environment variables.")
44
- except Exception as e:
45
- print(f"Could not access st.secrets for OPENAI_API_KEY: {e}")
 
 
 
 
 
46
 
47
- if not openai_key_from_secrets and "OPENAI_API_KEY" in os.environ:
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
- else:
53
- st.session_state.visual_engine.set_openai_api_key(None)
54
- st.sidebar.caption("OpenAI API Key for DALL-E not found. Visuals will be placeholders.")
55
 
56
- # Story and generated content state
57
- if 'story_scenes' not in st.session_state:
58
- st.session_state.story_scenes = []
59
- if 'scene_image_prompts' not in st.session_state:
60
- st.session_state.scene_image_prompts = []
61
- if 'generated_images_paths' not in st.session_state:
62
- st.session_state.generated_images_paths = []
63
- if 'video_path' not in st.session_state:
64
- st.session_state.video_path = None
65
- if 'character_definitions' not in st.session_state:
66
- st.session_state.character_definitions = {}
67
- if 'style_reference_description' not in st.session_state:
68
- st.session_state.style_reference_description = None
69
 
70
  # --- Helper Functions ---
71
- def initialize_new_story():
72
  st.session_state.story_scenes = []
73
  st.session_state.scene_image_prompts = []
74
  st.session_state.generated_images_paths = []
@@ -77,26 +57,26 @@ def initialize_new_story():
77
  def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
78
  scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
79
  textual_image_prompt = ""
 
80
  if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
81
- textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
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
- if not textual_image_prompt:
89
- return False
 
90
  if scene_index >= len(st.session_state.scene_image_prompts):
91
- while len(st.session_state.scene_image_prompts) <= scene_index:
92
- st.session_state.scene_image_prompts.append("")
93
  st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
 
94
  image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
95
- generated_image_path = st.session_state.visual_engine.generate_image_visual(
96
- textual_image_prompt, image_filename
97
- )
98
- while len(st.session_state.generated_images_paths) <= scene_index:
99
- st.session_state.generated_images_paths.append(None)
100
  if generated_image_path and os.path.exists(generated_image_path):
101
  st.session_state.generated_images_paths[scene_index] = generated_image_path
102
  return True
@@ -109,211 +89,191 @@ with st.sidebar:
109
  st.title("🎬 CineGen AI Pro")
110
  st.markdown("### Creative Controls")
111
  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")
112
- genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror", "Cyberpunk"], index=6, key="genre_select")
113
- mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful", "Wonder"], index=6, key="mood_select")
114
- num_scenes_val = st.slider("Number of Scenes:", 1, 5, 1, key="num_scenes_slider") # Default to 1 for faster testing
115
 
116
  if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
117
  initialize_new_story()
118
- if not user_idea.strip():
119
- st.warning("Please enter a story idea.")
120
  else:
121
- with st.status("Generating story...", expanded=True) as status_main:
122
- st.write("Phase 1: Gemini is drafting the script & scene breakdown... πŸ“œ")
123
  story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
124
  try:
125
  st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
126
- status_main.update(label="Script breakdown complete! βœ…", state="running", expanded=True)
127
  num_actual_scenes = len(st.session_state.story_scenes)
128
  st.session_state.scene_image_prompts = [""] * num_actual_scenes
129
  st.session_state.generated_images_paths = [None] * num_actual_scenes
130
  except Exception as e:
131
- status_main.update(label=f"Failed to generate story breakdown: {e}", state="error", expanded=True)
132
- st.session_state.story_scenes = []
133
- st.stop()
134
  if st.session_state.story_scenes:
135
- st.write("Phase 2: Generating initial visual concepts... πŸ–ΌοΈ (This may take time with DALL-E)")
136
  success_count = 0
137
- for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
138
- scene_num_log = scene_data_loop_var.get('scene_number', i_loop + 1)
139
- st.write(f"Generating visual for Scene {scene_num_log}...")
140
- if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
141
- success_count +=1
142
- st.write(f"Visual for Scene {scene_num_log} done.")
143
- else:
144
- st.write(f"Visual for Scene {scene_num_log} failed.")
145
- if success_count == len(st.session_state.story_scenes):
146
- status_main.update(label="All concepts generated successfully! πŸŽ‰", state="complete", expanded=False)
147
- elif success_count > 0:
148
- status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals generated.", state="warning", expanded=False)
149
- else:
150
- status_main.update(label="Visual concept generation failed for all scenes.", state="error", expanded=False)
151
 
152
  st.markdown("---")
153
  st.markdown("### Advanced Options")
154
  with st.expander("Character Consistency", expanded=False):
155
- char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input")
156
- char_desc_input = st.text_area("Character Description (e.g., 'female, short red hair, green eyes, wearing a blue jumpsuit')", key="char_desc_adv_input", height=100)
157
  if st.button("Add/Update Character", key="add_char_adv_btn"):
158
  if char_name_input and char_desc_input:
159
- st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip()
160
  st.success(f"Character '{char_name_input.strip()}' defined.")
161
- else:
162
- st.warning("Please provide both name and description.")
163
  if st.session_state.character_definitions:
164
  st.caption("Defined Characters:")
165
- for char_key, desc_val in st.session_state.character_definitions.items():
166
- st.markdown(f"**{char_key.title()}:** _{desc_val}_")
167
 
168
- with st.expander("Style Transfer", expanded=False):
169
- style_ref_text = st.text_area("Describe Visual Style (e.g., 'impressionistic oil painting, vibrant colors')", key="style_text_adv_input", height=100)
170
- if st.button("Apply Textual Style", key="apply_style_adv_btn"):
171
- st.session_state.style_reference_description = style_ref_text.strip()
172
- st.success("Style reference applied. Re-generate visuals or full story to see changes.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- # --- Main Content Area ---
175
- st.header("πŸ“ Cinematic Storyboard")
176
 
 
 
177
  if not st.session_state.story_scenes:
178
- st.info("Enter your idea in the sidebar and click 'Generate Full Story Concept' to begin.")
179
  else:
180
  for i, scene_data_display in enumerate(st.session_state.story_scenes):
 
181
  scene_num_display = scene_data_display.get('scene_number', i + 1)
182
  action_summary = scene_data_display.get('key_action', f"scene{i}")
183
  key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
184
  unique_key_part = key_part_raw.replace(" ", "_")[:20]
185
-
186
  st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
187
  col1, col2 = st.columns([2, 3])
188
 
189
- with col1:
190
  with st.expander("Scene Details", expanded=True):
191
  st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
192
  st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
193
  st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}")
194
  st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`")
195
- st.markdown(f"**Visual Style:** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
196
- st.markdown(f"**Camera:** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
197
-
198
  current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
199
  if current_textual_prompt:
200
- with st.popover("View Image Prompt Text"):
201
- st.markdown(f"**Textual Prompt for Image Generation:**")
202
- 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):
206
- # MODIFICATION HERE: Removed use_column_width=True
207
  st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
208
  else:
209
- if st.session_state.story_scenes:
210
- st.caption("Visual for this scene is pending or failed to generate.")
211
 
 
212
  with st.popover(f"✏️ Edit Scene {scene_num_display} Script"):
213
- feedback_script = st.text_area("Describe changes to script details:",
214
- key=f"script_feedback_{unique_key_part}_{i}", height=100)
215
- if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
216
  if feedback_script:
217
- with st.status(f"Rewriting Scene {scene_num_display} script...", expanded=True) as status_script_regen:
218
- regen_prompt = create_scene_regeneration_prompt(
219
- scene_data_display, feedback_script, st.session_state.story_scenes
220
- )
221
  try:
222
  updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
223
  st.session_state.story_scenes[i] = updated_scene_data
224
  status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
225
  version = 1
226
  if current_image_path:
227
- try:
228
- base, _ = os.path.splitext(os.path.basename(current_image_path))
229
- if '_v' in base: version = int(base.split('_v')[-1]) + 1
230
- except : pass
231
- if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version):
232
  status_script_regen.update(label="Scene script & visual updated! πŸŽ‰", state="complete", expanded=False)
233
- else:
234
- status_script_regen.update(label="Script updated, but visual failed.", state="warning", expanded=False)
235
  st.rerun()
236
- except Exception as e:
237
- status_script_regen.update(label=f"Error regenerating scene script: {e}", state="error")
238
- else:
239
- st.warning("Please provide feedback for script regeneration.")
240
 
241
  with st.popover(f"🎨 Edit Scene {scene_num_display} Visuals"):
242
- 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."
243
- st.caption("Current Image Prompt Text:")
244
- st.code(prompt_for_edit_visuals, language='text')
245
- feedback_visual = st.text_area("Describe visual changes to apply to the prompt:",
246
- key=f"visual_feedback_{unique_key_part}_{i}", height=100)
247
- if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
248
  if feedback_visual:
249
- with st.status(f"Refining prompt & regenerating visual for Scene {scene_num_display}...", expanded=True) as status_visual_regen:
250
  prompt_refinement_request = create_visual_regeneration_prompt(
251
- prompt_for_edit_visuals, feedback_visual, scene_data_display
 
252
  )
253
  try:
254
  refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
255
- st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
256
- status_visual_regen.update(label="Image prompt refined! Regenerating visual...", state="running")
257
  version = 1
258
  if current_image_path:
259
- try:
260
- base, _ = os.path.splitext(os.path.basename(current_image_path))
261
- if '_v' in base: version = int(base.split('_v')[-1]) + 1
262
- except : pass
263
- if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version):
264
- status_visual_regen.update(label="Visual for Scene updated! πŸŽ‰", state="complete", expanded=False)
265
- else:
266
- status_visual_regen.update(label="Prompt refined, but visual failed.", state="warning", expanded=False)
267
  st.rerun()
268
- except Exception as e:
269
- status_visual_regen.update(label=f"Error refining prompt or regenerating visual: {e}", state="error")
270
- else:
271
- st.warning("Please provide feedback for visual regeneration.")
272
  st.markdown("---")
273
 
 
274
  if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
275
  if st.button("🎬 Assemble Enhanced Animatic", key="assemble_enhanced_video_btn", type="primary", use_container_width=True):
276
- with st.status("Assembling enhanced animatic video...", expanded=False) as status_video:
 
277
  image_data_for_video = []
278
  for idx, scene_info in enumerate(st.session_state.story_scenes):
279
  img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
280
  if img_path and os.path.exists(img_path):
281
  image_data_for_video.append({
282
- 'path': img_path,
283
- 'scene_num': scene_info.get('scene_number', idx + 1),
284
  'key_action': scene_info.get('key_action', '')
285
- })
286
  if image_data_for_video:
287
- st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
288
- image_data_for_video,
289
- output_filename="cinegen_pro_animatic_enhanced.mp4",
290
- duration_per_image=4,
291
- fps=24
292
- )
293
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
294
- status_video.update(label="Enhanced animatic assembled! πŸŽ‰", state="complete")
295
- st.balloons()
296
- else:
297
- status_video.update(label="Enhanced video assembly failed. Check logs.", state="error")
298
- else:
299
- status_video.update(label="No valid images to assemble video.", state="error")
300
- elif st.session_state.story_scenes:
301
- st.info("Generate visuals for scenes before assembling the video.")
302
-
303
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
304
  st.header("🎬 Generated Animatic Video")
305
  try:
306
- with open(st.session_state.video_path, 'rb') as video_file_obj:
307
- video_bytes_content = video_file_obj.read()
308
  st.video(video_bytes_content, format="video/mp4")
309
  with open(st.session_state.video_path, "rb") as fp_download_video:
310
- st.download_button(
311
- label="Download Enhanced Animatic", data=fp_download_video,
312
  file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
313
- use_container_width=True, key="download_video_btn"
314
- )
315
- except Exception as e:
316
- st.error(f"Error displaying or preparing video for download: {e}")
317
 
318
  # --- Footer ---
319
  st.sidebar.markdown("---")
 
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 = []
 
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
 
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)
105
+ status_main.update(label="Script complete! βœ… Generating visuals...", state="running")
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)
223
+ if st.button(f"πŸ”„ Update Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
 
 
224
  if feedback_visual:
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("---")