mgbam commited on
Commit
d8cdb3b
Β·
verified Β·
1 Parent(s): e555883

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -48
app.py CHANGED
@@ -3,11 +3,11 @@ import streamlit as st
3
  from core.gemini_handler import GeminiHandler
4
  from core.visual_engine import VisualEngine
5
  from core.prompt_engineering import (
6
- create_cinematic_treatment_prompt, # UPDATED
7
- construct_dalle_prompt, # UPDATED
8
- create_narration_script_prompt_enhanced, # UPDATED
9
- create_scene_regeneration_prompt, # UPDATED
10
- create_visual_regeneration_prompt # UPDATED
11
  )
12
  import os
13
 
@@ -19,11 +19,10 @@ def load_api_key(key_name_streamlit, key_name_env):
19
  key = None; secrets_available = hasattr(st, 'secrets')
20
  try:
21
  if secrets_available and key_name_streamlit in st.secrets: key = st.secrets[key_name_streamlit]
22
- except Exception: pass # Silently ignore if st.secrets access fails
23
  if not key and key_name_env in os.environ: key = os.environ[key_name_env]
24
  return key
25
 
26
- # Initialize API Keys and handlers once
27
  if 'keys_loaded' not in st.session_state:
28
  st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY")
29
  st.session_state.OPENAI_API_KEY = load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY")
@@ -39,7 +38,6 @@ if 'keys_loaded' not in st.session_state:
39
  st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
40
  st.session_state.keys_loaded = True
41
 
42
- # Initialize other session state variables
43
  for key, default_val in [
44
  ('story_treatment_scenes', []), ('scene_dalle_prompts', []), ('generated_visual_paths', []),
45
  ('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
@@ -57,7 +55,6 @@ def initialize_new_project():
57
  st.session_state.narration_script_display = ""
58
 
59
  def generate_visual_for_scene_core(scene_index, scene_data, version=1):
60
- # scene_data here is one scene from story_treatment_scenes
61
  dalle_prompt = construct_dalle_prompt(
62
  scene_data,
63
  st.session_state.character_definitions,
@@ -65,15 +62,13 @@ def generate_visual_for_scene_core(scene_index, scene_data, version=1):
65
  )
66
  if not dalle_prompt: return False
67
 
68
- # Ensure lists are long enough
69
  while len(st.session_state.scene_dalle_prompts) <= scene_index: st.session_state.scene_dalle_prompts.append("")
70
  while len(st.session_state.generated_visual_paths) <= scene_index: st.session_state.generated_visual_paths.append(None)
71
 
72
  st.session_state.scene_dalle_prompts[scene_index] = dalle_prompt
73
 
74
  filename = f"scene_{scene_data.get('scene_number', scene_index+1)}_visual_v{version}.png"
75
- # Pass the full scene_data to visual_engine for Pexels query construction if needed
76
- img_path = st.session_state.visual_engine.generate_image_visual(dalle_prompt, scene_data, filename)
77
 
78
  if img_path and os.path.exists(img_path):
79
  st.session_state.generated_visual_paths[scene_index] = img_path; return True
@@ -87,7 +82,7 @@ with st.sidebar:
87
  user_idea = st.text_area("Core Story Idea / Theme:", "A lone wanderer searches for a mythical oasis in a vast, post-apocalyptic desert, haunted by mirages and mechanical scavengers.", height=120, key="user_idea_main")
88
  genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Western", "Post-Apocalyptic"], index=6, key="genre_main")
89
  mood = st.selectbox("Overall Mood:", ["Hopeful yet Desperate", "Mysterious & Eerie", "Gritty & Tense", "Epic & Awe-Inspiring", "Melancholy & Reflective"], index=0, key="mood_main")
90
- num_scenes = st.slider("Number of Key Scenes:", 1, 4, 2, key="num_scenes_main") # Max 3-4 for API cost/time
91
 
92
  creative_guidance_options = {"Standard": "standard", "More Artistic": "more_artistic", "Experimental Narrative": "experimental_narrative"}
93
  selected_creative_guidance = st.selectbox("AI Creative Guidance Level:", options=list(creative_guidance_options.keys()), key="creative_guidance_select")
@@ -100,7 +95,7 @@ with st.sidebar:
100
  status.write("Phase 1: Gemini crafting cinematic treatment (scenes, style, camera, sound)... πŸ“œ")
101
  treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, creative_guidance_options[selected_creative_guidance])
102
  try:
103
- st.session_state.story_treatment_scenes = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt) # Re-use for JSON list
104
  num_gen_scenes = len(st.session_state.story_treatment_scenes)
105
  st.session_state.scene_dalle_prompts = [""] * num_gen_scenes
106
  st.session_state.generated_visual_paths = [None] * num_gen_scenes
@@ -116,7 +111,7 @@ with st.sidebar:
116
 
117
  status.update(label="Visuals ready! Generating narration script...", state="running")
118
  narration_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, st.session_state.get("selected_voice_style", "cinematic_trailer"))
119
- narr_script = st.session_state.gemini_handler.generate_image_prompt(narration_prompt) # generate_image_prompt is just text gen
120
  st.session_state.narration_script_display = narr_script
121
  status.update(label="Narration script ready! Synthesizing voice...", state="running")
122
 
@@ -130,9 +125,7 @@ with st.sidebar:
130
 
131
  st.markdown("---")
132
  st.markdown("### Fine-Tuning Options")
133
- # Character Definitions Expander (same as previous)
134
  with st.expander("Define Characters", expanded=False):
135
- # ... (UI for char_name_input, char_desc_input, add button - same as previous, ensure lowercase keys for definitions)
136
  char_name_input = st.text_input("Character Name", key="char_name_adv_input_ultra")
137
  char_desc_input = st.text_area("Detailed Visual Description", key="char_desc_adv_input_ultra", height=100, placeholder="e.g., Jax: rugged male astronaut, mid-40s, salt-and-pepper short hair, cybernetic left eye glowing faintly blue, wearing a patched-up crimson flight suit.")
138
  if st.button("Save Character", key="add_char_adv_btn_ultra"):
@@ -142,9 +135,7 @@ with st.sidebar:
142
  st.caption("Current Characters:")
143
  for k,v in st.session_state.character_definitions.items(): st.markdown(f"**{k.title()}:** _{v}_")
144
 
145
- # Style Controls Expander (same as previous)
146
  with st.expander("Global Style Overrides", expanded=False):
147
- # ... (UI for predefined_styles, custom_style_keywords, apply button - same as previous) ...
148
  predefined_styles = { "Default (Director's Choice)": "", "Hyper-Realistic Gritty Noir": "hyper-realistic gritty neo-noir, extreme detail, deep dynamic shadows, complex reflections on wet surfaces, cinematic film grain, desaturated palette with isolated vibrant neon accents (e.g. red, cyan), anamorphic lens distortion, atmospheric haze.", "Surreal Dreamscape Fantasy": "surreal dreamscape, epic fantasy elements, painterly with photorealistic details, impossible architecture, bioluminescent flora, otherworldly color palette (e.g., magenta skies, turquoise rivers), style of Roger Dean meets ZdzisΕ‚aw BeksiΕ„ski.", "Vintage Analog Sci-Fi": "70s/80s analog sci-fi film aesthetic, tangible practical effects look, subtle light leaks, lens flares, warm filmic tones mixed with cool blues, detailed retro-futuristic technology with chunky buttons and CRT screens."}
149
  selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv_ultra")
150
  custom_keywords = st.text_area("Additional Custom Style Keywords:", key="custom_style_keywords_adv_input_ultra", height=80, placeholder="e.g., 'shot with Arri Alexa, shallow depth of field, golden hour tones'")
@@ -157,10 +148,7 @@ with st.sidebar:
157
  else: st.info("Global style additions cleared (using Director's per-scene choice).")
158
  if current_style_desc: st.caption(f"Active global style additions: \"{current_style_desc}\"")
159
 
160
- # Voice Customization Expander (same as previous)
161
  with st.expander("Voice Customization (ElevenLabs)", expanded=False):
162
- # ... (UI for available_voices_conceptual, selected_voice, set button - same) ...
163
- # In a real app, fetch from ElevenLabs API if key is present. This is a placeholder list.
164
  elevenlabs_voices = ["Rachel", "Adam", "Bella", "Antoni", "Elli", "Josh", "Arnold", "Domi", "Fin", "Sarah"]
165
  current_el_voice = st.session_state.visual_engine.elevenlabs_voice_id if hasattr(st.session_state, 'visual_engine') else "Rachel"
166
  selected_el_voice = st.selectbox("Narrator Voice:", elevenlabs_voices, index=elevenlabs_voices.index(current_el_voice) if current_el_voice in elevenlabs_voices else 0, key="el_voice_select_ultra")
@@ -168,11 +156,9 @@ with st.sidebar:
168
  if hasattr(st.session_state, 'visual_engine'): st.session_state.visual_engine.elevenlabs_voice_id = selected_el_voice
169
  st.success(f"Narrator voice set to: {selected_el_voice}")
170
 
171
-
172
  # --- Main Content Area ---
173
  st.header("🎬 Cinematic Storyboard & Treatment")
174
 
175
- # Display Narration Script if available
176
  if st.session_state.narration_script_display:
177
  with st.expander("πŸ“œ View Full Narration Script", expanded=False):
178
  st.markdown(f"> _{st.session_state.narration_script_display}_")
@@ -183,15 +169,13 @@ else:
183
  for i_main, scene_content_display in enumerate(st.session_state.story_treatment_scenes):
184
  scene_num = scene_content_display.get('scene_number', i_main + 1)
185
  scene_title = scene_content_display.get('scene_title', 'Untitled Scene')
186
- unique_key_base = f"scene_{scene_num}_{''.join(filter(str.isalnum, scene_title[:10]))}" # For unique widget keys
187
 
188
- st.subheader(f"SCENE {scene_num}: {scene_title.upper()}")
189
-
190
- # Display Director's Notes if any (for experimental narrative)
191
  if "director_note" in scene_content_display:
192
  st.info(f"🎬 Director's Note for Scene {scene_num}: {scene_content_display['director_note']}")
193
 
194
- col_details, col_visual = st.columns([0.45, 0.55]) # Adjust ratio
 
195
 
196
  with col_details:
197
  with st.expander("πŸ“ Scene Treatment Details", expanded=True):
@@ -205,25 +189,21 @@ else:
205
  st.markdown(f"**🎬 Director's Visual Style:** {scene_content_display.get('PROACTIVE_visual_style_감독', 'N/A')}")
206
  st.markdown(f"**πŸŽ₯ Director's Camera Work:** {scene_content_display.get('PROACTIVE_camera_work_감독', 'N/A')}")
207
  st.markdown(f"**πŸ”Š Director's Sound Design:** {scene_content_display.get('PROACTIVE_sound_design_감독', 'N/A')}")
208
-
209
  current_dalle_prompt = st.session_state.scene_dalle_prompts[i_main] if i_main < len(st.session_state.scene_dalle_prompts) else None
210
  if current_dalle_prompt:
211
  with st.popover("πŸ‘οΈ View DALL-E Prompt"):
212
  st.markdown(f"**Full DALL-E Prompt:**"); st.code(current_dalle_prompt, language='text')
213
-
214
  pexels_query_display = scene_content_display.get('pexels_search_query_감독', None)
215
  if pexels_query_display:
216
  st.caption(f"Suggested Pexels Query: `{pexels_query_display}`")
217
-
218
-
219
  with col_visual:
220
  current_img_path = st.session_state.generated_visual_paths[i_main] if i_main < len(st.session_state.generated_visual_paths) else None
221
  if current_img_path and os.path.exists(current_img_path):
222
  st.image(current_img_path, caption=f"Visual Concept for Scene {scene_num}: {scene_title}", use_column_width='always')
223
  else:
224
- st.caption("Visual concept pending or failed.")
225
 
226
- # Edit Popovers - logic for calling regeneration needs to use the new prompt structures
227
  with st.popover(f"✏️ Edit Scene {scene_num} Treatment"):
228
  feedback_script_edit = st.text_area("Describe changes to treatment details:", key=f"treat_feed_{unique_key_base}", height=150)
229
  if st.button(f"πŸ”„ Update Scene {scene_num} Treatment", key=f"regen_treat_btn_{unique_key_base}"):
@@ -231,13 +211,18 @@ else:
231
  with st.status(f"Updating Scene {scene_num} Treatment...", expanded=True) as status_treat_regen:
232
  regen_prompt_text = create_scene_regeneration_prompt(scene_content_display, feedback_script_edit, st.session_state.story_treatment_scenes)
233
  try:
234
- updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt_text) # Assumes this returns one scene obj
235
  st.session_state.story_treatment_scenes[i_main] = updated_scene_data
236
  status_treat_regen.update(label="Treatment updated! Regenerating visual & DALL-E prompt...", state="running")
 
237
  version_num = 1
238
- if current_img_path: try: base,_=os.path.splitext(os.path.basename(current_img_path)); version_num = int(base.split('_v')[-1])+1 if '_v' in base else 2 except: version_num=2
 
 
 
 
 
239
 
240
- # Regenerate visual for the updated scene data
241
  if generate_visual_for_scene_core(i_main, updated_scene_data, version=version_num):
242
  status_treat_regen.update(label="Scene Treatment & Visual Updated! πŸŽ‰", state="complete", expanded=False)
243
  else: status_treat_regen.update(label="Treatment updated, visual failed.", state="warning", expanded=False)
@@ -252,20 +237,23 @@ else:
252
  if st.button(f"πŸ”„ Update Scene {scene_num} Visual Prompt & Image", key=f"regen_visual_btn_{unique_key_base}"):
253
  if feedback_visual_edit:
254
  with st.status(f"Refining DALL-E prompt & regenerating visual...", expanded=True) as status_visual_edit_regen:
255
- # Gemini refines the DALL-E prompt
256
  refinement_req_prompt = create_visual_regeneration_prompt(
257
  dalle_prompt_to_edit, feedback_visual_edit, scene_content_display,
258
  st.session_state.character_definitions, st.session_state.global_style_additions
259
  )
260
  try:
261
- refined_dalle_prompt = st.session_state.gemini_handler.generate_image_prompt(refinement_req_prompt) # generate_image_prompt is just text gen
262
- st.session_state.scene_dalle_prompts[i_main] = refined_dalle_prompt # Update stored DALL-E prompt
263
  status_visual_edit_regen.update(label="DALL-E prompt refined! Regenerating visual...", state="running")
264
  version_num = 1
265
- if current_img_path: try: base,_=os.path.splitext(os.path.basename(current_img_path)); version_num = int(base.split('_v')[-1])+1 if '_v' in base else 2 except: version_num=2
 
 
 
 
 
266
 
267
- # Generate visual with the NEW refined DALL-E prompt
268
- if generate_visual_for_scene_core(i_main, scene_content_display, version=version_num): # scene_content_display is still relevant context
269
  status_visual_edit_regen.update(label="Visual Updated! πŸŽ‰", state="complete", expanded=False)
270
  else: status_visual_edit_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
271
  st.rerun()
@@ -273,7 +261,6 @@ else:
273
  else: st.warning("Please provide feedback for visual prompt regeneration.")
274
  st.markdown("---")
275
 
276
- # Video Generation Button
277
  if st.session_state.story_treatment_scenes and any(p for p in st.session_state.generated_visual_paths if p is not None):
278
  if st.button("🎬 Assemble Narrated Cinematic Animatic", key="assemble_ultra_video_btn", type="primary", use_container_width=True):
279
  with st.status("Assembling Ultra Animatic...", expanded=True) as status_vid:
@@ -283,7 +270,7 @@ else:
283
  if img_p and os.path.exists(img_p):
284
  image_data_for_vid.append({
285
  'path':img_p, 'scene_num':scene_c.get('scene_number',i_vid+1),
286
- 'key_action':scene_c.get('key_plot_beat','') # Use key_plot_beat for overlay
287
  }); status_vid.write(f"Adding Scene {scene_c.get('scene_number', i_vid + 1)} to video.")
288
 
289
  if image_data_for_vid:
@@ -292,7 +279,7 @@ else:
292
  image_data_for_vid,
293
  overall_narration_path=st.session_state.overall_narration_audio_path,
294
  output_filename="cinegen_ultra_animatic.mp4",
295
- duration_per_image=st.session_state.visual_engine.video_overlay_font_size * 0.15, # Dynamic duration based on text overlay font size
296
  fps=24
297
  )
298
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
@@ -301,7 +288,6 @@ else:
301
  else: status_vid.update(label="No valid images for video.", state="error", expanded=False)
302
  elif st.session_state.story_treatment_scenes: st.info("Generate visuals before assembling video.")
303
 
304
- # Video display and download
305
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
306
  st.header("🎬 Generated Cinematic Animatic")
307
  try:
 
3
  from core.gemini_handler import GeminiHandler
4
  from core.visual_engine import VisualEngine
5
  from core.prompt_engineering import (
6
+ create_cinematic_treatment_prompt,
7
+ construct_dalle_prompt,
8
+ create_narration_script_prompt_enhanced,
9
+ create_scene_regeneration_prompt,
10
+ create_visual_regeneration_prompt
11
  )
12
  import os
13
 
 
19
  key = None; secrets_available = hasattr(st, 'secrets')
20
  try:
21
  if secrets_available and key_name_streamlit in st.secrets: key = st.secrets[key_name_streamlit]
22
+ except Exception: pass
23
  if not key and key_name_env in os.environ: key = os.environ[key_name_env]
24
  return key
25
 
 
26
  if 'keys_loaded' not in st.session_state:
27
  st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY")
28
  st.session_state.OPENAI_API_KEY = load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY")
 
38
  st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
39
  st.session_state.keys_loaded = True
40
 
 
41
  for key, default_val in [
42
  ('story_treatment_scenes', []), ('scene_dalle_prompts', []), ('generated_visual_paths', []),
43
  ('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
 
55
  st.session_state.narration_script_display = ""
56
 
57
  def generate_visual_for_scene_core(scene_index, scene_data, version=1):
 
58
  dalle_prompt = construct_dalle_prompt(
59
  scene_data,
60
  st.session_state.character_definitions,
 
62
  )
63
  if not dalle_prompt: return False
64
 
 
65
  while len(st.session_state.scene_dalle_prompts) <= scene_index: st.session_state.scene_dalle_prompts.append("")
66
  while len(st.session_state.generated_visual_paths) <= scene_index: st.session_state.generated_visual_paths.append(None)
67
 
68
  st.session_state.scene_dalle_prompts[scene_index] = dalle_prompt
69
 
70
  filename = f"scene_{scene_data.get('scene_number', scene_index+1)}_visual_v{version}.png"
71
+ img_path = st.session_state.visual_engine.generate_image_visual(dalle_prompt, scene_data, filename)
 
72
 
73
  if img_path and os.path.exists(img_path):
74
  st.session_state.generated_visual_paths[scene_index] = img_path; return True
 
82
  user_idea = st.text_area("Core Story Idea / Theme:", "A lone wanderer searches for a mythical oasis in a vast, post-apocalyptic desert, haunted by mirages and mechanical scavengers.", height=120, key="user_idea_main")
83
  genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Western", "Post-Apocalyptic"], index=6, key="genre_main")
84
  mood = st.selectbox("Overall Mood:", ["Hopeful yet Desperate", "Mysterious & Eerie", "Gritty & Tense", "Epic & Awe-Inspiring", "Melancholy & Reflective"], index=0, key="mood_main")
85
+ num_scenes = st.slider("Number of Key Scenes:", 1, 4, 2, key="num_scenes_main")
86
 
87
  creative_guidance_options = {"Standard": "standard", "More Artistic": "more_artistic", "Experimental Narrative": "experimental_narrative"}
88
  selected_creative_guidance = st.selectbox("AI Creative Guidance Level:", options=list(creative_guidance_options.keys()), key="creative_guidance_select")
 
95
  status.write("Phase 1: Gemini crafting cinematic treatment (scenes, style, camera, sound)... πŸ“œ")
96
  treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, creative_guidance_options[selected_creative_guidance])
97
  try:
98
+ st.session_state.story_treatment_scenes = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt)
99
  num_gen_scenes = len(st.session_state.story_treatment_scenes)
100
  st.session_state.scene_dalle_prompts = [""] * num_gen_scenes
101
  st.session_state.generated_visual_paths = [None] * num_gen_scenes
 
111
 
112
  status.update(label="Visuals ready! Generating narration script...", state="running")
113
  narration_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, st.session_state.get("selected_voice_style", "cinematic_trailer"))
114
+ narr_script = st.session_state.gemini_handler.generate_image_prompt(narration_prompt)
115
  st.session_state.narration_script_display = narr_script
116
  status.update(label="Narration script ready! Synthesizing voice...", state="running")
117
 
 
125
 
126
  st.markdown("---")
127
  st.markdown("### Fine-Tuning Options")
 
128
  with st.expander("Define Characters", expanded=False):
 
129
  char_name_input = st.text_input("Character Name", key="char_name_adv_input_ultra")
130
  char_desc_input = st.text_area("Detailed Visual Description", key="char_desc_adv_input_ultra", height=100, placeholder="e.g., Jax: rugged male astronaut, mid-40s, salt-and-pepper short hair, cybernetic left eye glowing faintly blue, wearing a patched-up crimson flight suit.")
131
  if st.button("Save Character", key="add_char_adv_btn_ultra"):
 
135
  st.caption("Current Characters:")
136
  for k,v in st.session_state.character_definitions.items(): st.markdown(f"**{k.title()}:** _{v}_")
137
 
 
138
  with st.expander("Global Style Overrides", expanded=False):
 
139
  predefined_styles = { "Default (Director's Choice)": "", "Hyper-Realistic Gritty Noir": "hyper-realistic gritty neo-noir, extreme detail, deep dynamic shadows, complex reflections on wet surfaces, cinematic film grain, desaturated palette with isolated vibrant neon accents (e.g. red, cyan), anamorphic lens distortion, atmospheric haze.", "Surreal Dreamscape Fantasy": "surreal dreamscape, epic fantasy elements, painterly with photorealistic details, impossible architecture, bioluminescent flora, otherworldly color palette (e.g., magenta skies, turquoise rivers), style of Roger Dean meets ZdzisΕ‚aw BeksiΕ„ski.", "Vintage Analog Sci-Fi": "70s/80s analog sci-fi film aesthetic, tangible practical effects look, subtle light leaks, lens flares, warm filmic tones mixed with cool blues, detailed retro-futuristic technology with chunky buttons and CRT screens."}
140
  selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv_ultra")
141
  custom_keywords = st.text_area("Additional Custom Style Keywords:", key="custom_style_keywords_adv_input_ultra", height=80, placeholder="e.g., 'shot with Arri Alexa, shallow depth of field, golden hour tones'")
 
148
  else: st.info("Global style additions cleared (using Director's per-scene choice).")
149
  if current_style_desc: st.caption(f"Active global style additions: \"{current_style_desc}\"")
150
 
 
151
  with st.expander("Voice Customization (ElevenLabs)", expanded=False):
 
 
152
  elevenlabs_voices = ["Rachel", "Adam", "Bella", "Antoni", "Elli", "Josh", "Arnold", "Domi", "Fin", "Sarah"]
153
  current_el_voice = st.session_state.visual_engine.elevenlabs_voice_id if hasattr(st.session_state, 'visual_engine') else "Rachel"
154
  selected_el_voice = st.selectbox("Narrator Voice:", elevenlabs_voices, index=elevenlabs_voices.index(current_el_voice) if current_el_voice in elevenlabs_voices else 0, key="el_voice_select_ultra")
 
156
  if hasattr(st.session_state, 'visual_engine'): st.session_state.visual_engine.elevenlabs_voice_id = selected_el_voice
157
  st.success(f"Narrator voice set to: {selected_el_voice}")
158
 
 
159
  # --- Main Content Area ---
160
  st.header("🎬 Cinematic Storyboard & Treatment")
161
 
 
162
  if st.session_state.narration_script_display:
163
  with st.expander("πŸ“œ View Full Narration Script", expanded=False):
164
  st.markdown(f"> _{st.session_state.narration_script_display}_")
 
169
  for i_main, scene_content_display in enumerate(st.session_state.story_treatment_scenes):
170
  scene_num = scene_content_display.get('scene_number', i_main + 1)
171
  scene_title = scene_content_display.get('scene_title', 'Untitled Scene')
172
+ unique_key_base = f"scene_{scene_num}_{''.join(filter(str.isalnum, scene_title[:10]))}"
173
 
 
 
 
174
  if "director_note" in scene_content_display:
175
  st.info(f"🎬 Director's Note for Scene {scene_num}: {scene_content_display['director_note']}")
176
 
177
+ st.subheader(f"SCENE {scene_num}: {scene_title.upper()}")
178
+ col_details, col_visual = st.columns([0.45, 0.55])
179
 
180
  with col_details:
181
  with st.expander("πŸ“ Scene Treatment Details", expanded=True):
 
189
  st.markdown(f"**🎬 Director's Visual Style:** {scene_content_display.get('PROACTIVE_visual_style_감독', 'N/A')}")
190
  st.markdown(f"**πŸŽ₯ Director's Camera Work:** {scene_content_display.get('PROACTIVE_camera_work_감독', 'N/A')}")
191
  st.markdown(f"**πŸ”Š Director's Sound Design:** {scene_content_display.get('PROACTIVE_sound_design_감독', 'N/A')}")
 
192
  current_dalle_prompt = st.session_state.scene_dalle_prompts[i_main] if i_main < len(st.session_state.scene_dalle_prompts) else None
193
  if current_dalle_prompt:
194
  with st.popover("πŸ‘οΈ View DALL-E Prompt"):
195
  st.markdown(f"**Full DALL-E Prompt:**"); st.code(current_dalle_prompt, language='text')
 
196
  pexels_query_display = scene_content_display.get('pexels_search_query_감독', None)
197
  if pexels_query_display:
198
  st.caption(f"Suggested Pexels Query: `{pexels_query_display}`")
199
+
 
200
  with col_visual:
201
  current_img_path = st.session_state.generated_visual_paths[i_main] if i_main < len(st.session_state.generated_visual_paths) else None
202
  if current_img_path and os.path.exists(current_img_path):
203
  st.image(current_img_path, caption=f"Visual Concept for Scene {scene_num}: {scene_title}", use_column_width='always')
204
  else:
205
+ if st.session_state.story_treatment_scenes: st.caption("Visual concept pending or failed.")
206
 
 
207
  with st.popover(f"✏️ Edit Scene {scene_num} Treatment"):
208
  feedback_script_edit = st.text_area("Describe changes to treatment details:", key=f"treat_feed_{unique_key_base}", height=150)
209
  if st.button(f"πŸ”„ Update Scene {scene_num} Treatment", key=f"regen_treat_btn_{unique_key_base}"):
 
211
  with st.status(f"Updating Scene {scene_num} Treatment...", expanded=True) as status_treat_regen:
212
  regen_prompt_text = create_scene_regeneration_prompt(scene_content_display, feedback_script_edit, st.session_state.story_treatment_scenes)
213
  try:
214
+ updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt_text)
215
  st.session_state.story_treatment_scenes[i_main] = updated_scene_data
216
  status_treat_regen.update(label="Treatment updated! Regenerating visual & DALL-E prompt...", state="running")
217
+
218
  version_num = 1
219
+ if current_img_path:
220
+ try:
221
+ base,_=os.path.splitext(os.path.basename(current_img_path))
222
+ if '_v' in base: version_num = int(base.split('_v')[-1])+1
223
+ else: version_num = 2 # If no _v, start with v2 for regenerated
224
+ except: version_num=2 # Fallback if parsing fails
225
 
 
226
  if generate_visual_for_scene_core(i_main, updated_scene_data, version=version_num):
227
  status_treat_regen.update(label="Scene Treatment & Visual Updated! πŸŽ‰", state="complete", expanded=False)
228
  else: status_treat_regen.update(label="Treatment updated, visual failed.", state="warning", expanded=False)
 
237
  if st.button(f"πŸ”„ Update Scene {scene_num} Visual Prompt & Image", key=f"regen_visual_btn_{unique_key_base}"):
238
  if feedback_visual_edit:
239
  with st.status(f"Refining DALL-E prompt & regenerating visual...", expanded=True) as status_visual_edit_regen:
 
240
  refinement_req_prompt = create_visual_regeneration_prompt(
241
  dalle_prompt_to_edit, feedback_visual_edit, scene_content_display,
242
  st.session_state.character_definitions, st.session_state.global_style_additions
243
  )
244
  try:
245
+ refined_dalle_prompt = st.session_state.gemini_handler.generate_image_prompt(refinement_req_prompt)
246
+ st.session_state.scene_dalle_prompts[i_main] = refined_dalle_prompt
247
  status_visual_edit_regen.update(label="DALL-E prompt refined! Regenerating visual...", state="running")
248
  version_num = 1
249
+ if current_img_path:
250
+ try:
251
+ base,_=os.path.splitext(os.path.basename(current_img_path))
252
+ if '_v' in base: version_num = int(base.split('_v')[-1])+1
253
+ else: version_num = 2
254
+ except: version_num=2
255
 
256
+ if generate_visual_for_scene_core(i_main, scene_content_display, version=version_num):
 
257
  status_visual_edit_regen.update(label="Visual Updated! πŸŽ‰", state="complete", expanded=False)
258
  else: status_visual_edit_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
259
  st.rerun()
 
261
  else: st.warning("Please provide feedback for visual prompt regeneration.")
262
  st.markdown("---")
263
 
 
264
  if st.session_state.story_treatment_scenes and any(p for p in st.session_state.generated_visual_paths if p is not None):
265
  if st.button("🎬 Assemble Narrated Cinematic Animatic", key="assemble_ultra_video_btn", type="primary", use_container_width=True):
266
  with st.status("Assembling Ultra Animatic...", expanded=True) as status_vid:
 
270
  if img_p and os.path.exists(img_p):
271
  image_data_for_vid.append({
272
  'path':img_p, 'scene_num':scene_c.get('scene_number',i_vid+1),
273
+ 'key_action':scene_c.get('key_plot_beat','')
274
  }); status_vid.write(f"Adding Scene {scene_c.get('scene_number', i_vid + 1)} to video.")
275
 
276
  if image_data_for_vid:
 
279
  image_data_for_vid,
280
  overall_narration_path=st.session_state.overall_narration_audio_path,
281
  output_filename="cinegen_ultra_animatic.mp4",
282
+ duration_per_image=5,
283
  fps=24
284
  )
285
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
 
288
  else: status_vid.update(label="No valid images for video.", state="error", expanded=False)
289
  elif st.session_state.story_treatment_scenes: st.info("Generate visuals before assembling video.")
290
 
 
291
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
292
  st.header("🎬 Generated Cinematic Animatic")
293
  try: