mgbam commited on
Commit
e20b484
Β·
verified Β·
1 Parent(s): 92cb699

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -59
app.py CHANGED
@@ -18,7 +18,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(level
18
  logger = logging.getLogger(__name__)
19
 
20
  # --- Global State Variables & API Key Setup ---
21
- def load_api_key(key_name_streamlit, key_name_env, service_name): # Same as previous
22
  key = None; secrets_available = hasattr(st, 'secrets')
23
  try:
24
  if secrets_available and key_name_streamlit in st.secrets:
@@ -31,22 +31,31 @@ def load_api_key(key_name_streamlit, key_name_env, service_name): # Same as prev
31
  if not key: logger.warning(f"{service_name} API Key NOT FOUND. Related features may be disabled or use fallbacks.")
32
  return key
33
 
34
- if 'services_initialized' not in st.session_state: # Same as previous
35
  logger.info("Initializing services and API keys for the first time this session...")
36
  st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY", "Gemini")
37
  st.session_state.OPENAI_API_KEY = load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY", "OpenAI/DALL-E")
38
  st.session_state.ELEVENLABS_API_KEY = load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY", "ElevenLabs")
39
  st.session_state.PEXELS_API_KEY = load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY", "Pexels")
40
  st.session_state.ELEVENLABS_VOICE_ID_CONFIG = load_api_key("ELEVENLABS_VOICE_ID", "ELEVENLABS_VOICE_ID", "ElevenLabs Voice ID")
41
- if not st.session_state.GEMINI_API_KEY: st.error("CRITICAL: Gemini API Key is essential and missing!"); logger.critical("Gemini API Key missing. Halting."); st.stop()
 
 
 
42
  try:
43
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
44
  logger.info("GeminiHandler initialized successfully.")
45
  except Exception as e: st.error(f"Failed to init GeminiHandler: {e}"); logger.critical(f"GeminiHandler init failed: {e}", exc_info=True); st.stop()
 
46
  try:
47
- default_voice = "Rachel"
48
- st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media", default_elevenlabs_voice_id=st.session_state.ELEVENLABS_VOICE_ID_CONFIG or default_voice)
 
 
 
 
49
  st.session_state.visual_engine.set_openai_api_key(st.session_state.OPENAI_API_KEY)
 
50
  st.session_state.visual_engine.set_elevenlabs_api_key(st.session_state.ELEVENLABS_API_KEY, voice_id_from_secret=st.session_state.ELEVENLABS_VOICE_ID_CONFIG)
51
  st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
52
  logger.info("VisualEngine initialized and API keys set (or attempted).")
@@ -55,14 +64,14 @@ if 'services_initialized' not in st.session_state: # Same as previous
55
  st.warning("VisualEngine critical setup issue. Some features will be disabled.")
56
  st.session_state.services_initialized = True; logger.info("Service initialization sequence complete.")
57
 
58
- for key, default_val in [ # Same
 
59
  ('story_treatment_scenes', []), ('scene_dalle_prompts', []), ('generated_visual_paths', []),
60
  ('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
61
  ('overall_narration_audio_path', None), ('narration_script_display', "")
62
  ]:
63
  if key not in st.session_state: st.session_state[key] = default_val
64
 
65
- # --- Helper Functions --- (initialize_new_project, generate_visual_for_scene_core - same)
66
  def initialize_new_project(): # Same
67
  st.session_state.story_treatment_scenes, st.session_state.scene_dalle_prompts, st.session_state.generated_visual_paths = [], [], []
68
  st.session_state.video_path, st.session_state.overall_narration_audio_path, st.session_state.narration_script_display = None, None, ""
@@ -81,15 +90,16 @@ def generate_visual_for_scene_core(scene_index, scene_data, version=1): # Same
81
  else:
82
  st.session_state.generated_visual_paths[scene_index] = None; logger.warning(f"Visual generation FAILED for Scene {scene_data.get('scene_number', scene_index+1)}. img_path was: {img_path}"); return False
83
 
84
- # --- UI Sidebar --- (Same as previous, with unique keys for this pass if needed)
85
  with st.sidebar:
86
- st.title("🎬 CineGen AI Ultra+"); st.markdown("### Creative Seed")
87
- user_idea = st.text_area("Core Story Idea / Theme:", "A lone wanderer searches for a mythical oasis...", height=120, key="user_idea_main_v5") # Key updated
88
- genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Post-Apocalyptic"], index=2, key="genre_main_v5")
89
- mood = st.selectbox("Overall Mood:", ["Hopeful yet Desperate", "Mysterious & Eerie"], index=0, key="mood_main_v5")
 
90
  num_scenes = st.slider("Number of Key Scenes:", 1, 3, 1, key="num_scenes_main_v5")
91
  creative_guidance_options = {"Standard Director": "standard", "Artistic Visionary": "more_artistic", "Experimental Storyteller": "experimental_narrative"}
92
- selected_creative_guidance_key = st.selectbox("AI Creative Director Style:", list(creative_guidance_options.keys()), key="creative_guidance_select_v5")
93
  actual_creative_guidance = creative_guidance_options[selected_creative_guidance_key]
94
 
95
  if st.button("🌌 Generate Cinematic Treatment", type="primary", key="generate_treatment_btn_v5", use_container_width=True):
@@ -97,53 +107,62 @@ with st.sidebar:
97
  if not user_idea.strip(): st.warning("Please provide a story idea.")
98
  else:
99
  with st.status("AI Director is envisioning your masterpiece...", expanded=True) as status:
100
- try: # Main generation try-except block
101
- status.write("Phase 1: Gemini crafting cinematic treatment..."); logger.info("Phase 1: Treatment Gen.")
102
  treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, actual_creative_guidance)
103
  treatment_result_json = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt)
104
  if not isinstance(treatment_result_json, list) or not treatment_result_json: raise ValueError("Gemini returned invalid scene list.")
105
  st.session_state.story_treatment_scenes = treatment_result_json; num_gen_scenes = len(st.session_state.story_treatment_scenes)
106
  st.session_state.scene_dalle_prompts = [""]*num_gen_scenes; st.session_state.generated_visual_paths = [None]*num_gen_scenes
107
  logger.info(f"Phase 1 complete. {num_gen_scenes} scenes."); status.update(label="Treatment complete! βœ… Generating visuals...", state="running")
108
- status.write("Phase 2: Creating visuals (DALL-E/Pexels)..."); logger.info("Phase 2: Visual Gen.")
 
109
  visual_successes = 0
110
  for i, sc_data in enumerate(st.session_state.story_treatment_scenes):
111
  sc_num_log = sc_data.get('scene_number', i+1)
112
  status.write(f" Visual for Scene {sc_num_log}..."); logger.info(f" Processing visual for Scene {sc_num_log}.")
113
  if generate_visual_for_scene_core(i, sc_data, version=1): visual_successes += 1
114
- current_status_label_ph2 = "Visuals ready! "; next_step_state = "running"
 
 
115
  if visual_successes == 0 and num_gen_scenes > 0:
116
  logger.error("Visual gen failed all scenes."); current_status_label_ph2 = "Visual gen FAILED for all scenes."; next_step_state="error";
117
- status.update(label=current_status_label_ph2, state=next_step_state, expanded=True); st.stop()
118
  elif visual_successes < num_gen_scenes:
119
  logger.warning(f"Visuals partial ({visual_successes}/{num_gen_scenes})."); current_status_label_ph2 = f"Visuals partially generated ({visual_successes}/{num_gen_scenes}). "
120
- status.update(label=f"{current_status_label_ph2}Generating narration script...", state=next_step_state if next_step_state=="error" else "running")
121
- if next_step_state == "error": st.stop()
 
122
  status.write("Phase 3: Generating narration script..."); logger.info("Phase 3: Narration Script Gen.")
123
- voice_style = st.session_state.get("selected_voice_style_for_generation", "cinematic_trailer")
124
- narr_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, voice_style)
125
  st.session_state.narration_script_display = st.session_state.gemini_handler.generate_image_prompt(narr_prompt)
126
  logger.info("Narration script generated."); status.update(label="Narration script ready! Synthesizing voice...", state="running")
127
- status.write("Phase 4: Synthesizing voice (ElevenLabs)..."); logger.info("Phase 4: Voice Synthesis.")
 
128
  st.session_state.overall_narration_audio_path = st.session_state.visual_engine.generate_narration_audio(st.session_state.narration_script_display)
129
- final_label = "All components ready! Storyboard below. πŸš€"; final_state_val = "complete"
 
 
130
  if not st.session_state.overall_narration_audio_path:
131
- final_label = f"{current_status_label_ph2}Storyboard ready (Voiceover skipped/failed)."
132
  logger.warning("Voiceover was skipped or failed.")
133
  else: logger.info("Voiceover generated successfully.")
134
- status.update(label=final_label, state=final_state_val, expanded=False)
 
135
  except ValueError as ve: logger.error(f"ValueError: {ve}", exc_info=True); status.update(label=f"Input or Gemini response error: {ve}", state="error", expanded=True);
136
  except Exception as e: logger.error(f"Unhandled Exception: {e}", exc_info=True); status.update(label=f"An unexpected error occurred: {e}", state="error", expanded=True);
137
 
138
  st.markdown("---"); st.markdown("### Fine-Tuning Options")
139
- with st.expander("Define Characters", expanded=False): # Same
140
  char_name = st.text_input("Character Name", key="char_name_adv_ultra_v5"); char_desc = st.text_area("Visual Description", key="char_desc_adv_ultra_v5", height=100, placeholder="e.g., Jax: rugged male astronaut...")
141
  if st.button("Save Character", key="add_char_adv_ultra_v5"):
142
  if char_name and char_desc: st.session_state.character_definitions[char_name.strip().lower()] = char_desc.strip(); st.success(f"Char '{char_name.strip()}' saved.")
143
  else: st.warning("Name and description needed.")
144
  if st.session_state.character_definitions: st.caption("Current Characters:"); [st.markdown(f"**{k.title()}:** _{v}_") for k,v in st.session_state.character_definitions.items()]
145
- with st.expander("Global Style Overrides", expanded=False): # Same
146
- presets = { "Default (Director's Choice)": "", "Hyper-Realistic Gritty Noir": "hyper-realistic gritty neo-noir...", "Surreal Dreamscape Fantasy": "surreal dreamscape...", "Vintage Analog Sci-Fi": "70s analog sci-fi..."}
 
147
  sel_preset = st.selectbox("Base Style Preset:", options=list(presets.keys()), key="style_preset_adv_ultra_v5")
148
  custom_kw = st.text_area("Additional Custom Style Keywords:", key="custom_style_adv_ultra_v5", height=80, placeholder="e.g., 'Dutch angle'")
149
  cur_style = st.session_state.global_style_additions
@@ -154,16 +173,31 @@ with st.sidebar:
154
  if cur_style: st.success("Global styles applied!")
155
  else: st.info("Global style additions cleared.")
156
  if cur_style: st.caption(f"Active global styles: \"{cur_style}\"")
157
- with st.expander("Voice & Narration Style", expanded=False): # Same
158
- default_voice_from_secret = st.session_state.get("ELEVENLABS_VOICE_ID_CONFIG", "Rachel")
159
- user_voice_id_override = st.text_input("ElevenLabs Voice ID (optional):", value=default_voice_from_secret, key="el_voice_id_override_v5",help="Overrides secret/default.")
 
 
 
 
 
 
 
 
 
 
160
  prompt_v_styles = {"Cinematic Trailer": "cinematic_trailer", "Neutral Documentary": "documentary_neutral", "Character Introspection": "introspective_character"}
161
  sel_prompt_v_style_key = st.selectbox("Narration Script Style:", list(prompt_v_styles.keys()), key="narr_style_sel_v5", index=0)
 
162
  if st.button("Set Narrator Voice & Style", key="set_voice_btn_ultra_v5"):
163
- final_voice_id_to_use = user_voice_id_override.strip() or default_voice_from_secret
164
- if hasattr(st.session_state, 'visual_engine'): st.session_state.visual_engine.elevenlabs_voice_id = final_voice_id_to_use
 
 
 
 
165
  st.session_state.selected_voice_style_for_generation = prompt_v_styles[sel_prompt_v_style_key]
166
- st.success(f"Narrator Voice ID: {final_voice_id_to_use}. Script Style: {sel_prompt_v_style_key}")
167
  logger.info(f"User updated ElevenLabs Voice ID to: {final_voice_id_to_use}, Script Style: {sel_prompt_v_style_key}")
168
 
169
  # --- Main Content Area ---
@@ -175,10 +209,10 @@ if not st.session_state.story_treatment_scenes: st.info("Use the sidebar to gene
175
  else:
176
  for i_main, scene_content_display in enumerate(st.session_state.story_treatment_scenes):
177
  scene_n = scene_content_display.get('scene_number', i_main + 1); scene_t = scene_content_display.get('scene_title', 'Untitled')
178
- key_base = f"s{scene_n}_{''.join(filter(str.isalnum, scene_t[:10]))}_v5" # Updated key for uniqueness
179
  if "director_note" in scene_content_display and scene_content_display['director_note']: st.info(f"🎬 Director Note S{scene_n}: {scene_content_display['director_note']}")
180
  st.subheader(f"SCENE {scene_n}: {scene_t.upper()}"); col_d, col_v = st.columns([0.45, 0.55])
181
- with col_d: # Scene Details (same display)
182
  with st.expander("πŸ“ Scene Treatment", expanded=True):
183
  st.markdown(f"**Beat:** {scene_content_display.get('emotional_beat', 'N/A')}"); st.markdown(f"**Setting:** {scene_content_display.get('setting_description', 'N/A')}"); st.markdown(f"**Chars:** {', '.join(scene_content_display.get('characters_involved', ['N/A']))}"); st.markdown(f"**Focus Moment:** _{scene_content_display.get('character_focus_moment', 'N/A')}_"); st.markdown(f"**Plot Beat:** {scene_content_display.get('key_plot_beat', 'N/A')}"); st.markdown(f"**Dialogue Hook:** `\"{scene_content_display.get('suggested_dialogue_hook', '...')}\"`"); st.markdown("---"); st.markdown(f"**Dir. Visual Style:** _{scene_content_display.get('PROACTIVE_visual_style_감독', 'N/A')}_"); st.markdown(f"**Dir. Camera:** _{scene_content_display.get('PROACTIVE_camera_work_감독', 'N/A')}_"); st.markdown(f"**Dir. Sound:** _{scene_content_display.get('PROACTIVE_sound_design_감독', 'N/A')}_")
184
  cur_d_prompt = st.session_state.scene_dalle_prompts[i_main] if i_main < len(st.session_state.scene_dalle_prompts) else None
@@ -186,7 +220,7 @@ else:
186
  with st.popover("πŸ‘οΈ DALL-E Prompt"): st.markdown(f"**DALL-E Prompt:**"); st.code(cur_d_prompt, language='text')
187
  pexels_q = scene_content_display.get('pexels_search_query_감독', None)
188
  if pexels_q: st.caption(f"Pexels Fallback Query: `{pexels_q}`")
189
- with col_v: # Image Display and Edit Popovers
190
  cur_img_p = st.session_state.generated_visual_paths[i_main] if i_main < len(st.session_state.generated_visual_paths) else None
191
  if cur_img_p and os.path.exists(cur_img_p): st.image(cur_img_p, caption=f"Scene {scene_n}: {scene_t}")
192
  else:
@@ -202,17 +236,11 @@ else:
202
  updated_sc_data = st.session_state.gemini_handler.regenerate_scene_script_details(prompt_text)
203
  st.session_state.story_treatment_scenes[i_main] = updated_sc_data
204
  s_treat_regen.update(label="Treatment updated! Regenerating visual...", state="running")
205
-
206
- # CORRECTED VERSIONING LOGIC
207
- v_num = 1
208
- if cur_img_p and os.path.exists(cur_img_p):
209
- try:
210
- base,_=os.path.splitext(os.path.basename(cur_img_p))
211
- if '_v' in base: v_num = int(base.split('_v')[-1])+1
212
- else: v_num = 2
213
- except (ValueError, IndexError, TypeError): v_num = 2
214
- else: v_num = 1
215
-
216
  if generate_visual_for_scene_core(i_main, updated_sc_data, version=v_num): s_treat_regen.update(label="Treatment & Visual Updated! πŸŽ‰", state="complete", expanded=False)
217
  else: s_treat_regen.update(label="Treatment updated, visual failed.", state="complete", expanded=False)
218
  st.rerun()
@@ -232,17 +260,11 @@ else:
232
  refined_d_prompt = st.session_state.gemini_handler.generate_image_prompt(ref_req_prompt)
233
  st.session_state.scene_dalle_prompts[i_main] = refined_d_prompt
234
  s_visual_regen.update(label="DALL-E prompt refined! Regenerating visual...", state="running")
235
-
236
- # CORRECTED VERSIONING LOGIC
237
  v_num = 1
238
  if cur_img_p and os.path.exists(cur_img_p):
239
- try:
240
- base,_=os.path.splitext(os.path.basename(cur_img_p))
241
- if '_v' in base: v_num = int(base.split('_v')[-1])+1
242
- else: v_num = 2
243
  except (ValueError, IndexError, TypeError): v_num=2
244
  else: v_num = 1
245
-
246
  if generate_visual_for_scene_core(i_main, scene_content_display, version=v_num): s_visual_regen.update(label="Visual Updated! πŸŽ‰", state="complete", expanded=False)
247
  else: s_visual_regen.update(label="Prompt refined, visual failed.", state="complete", expanded=False)
248
  st.rerun()
@@ -250,9 +272,8 @@ else:
250
  else: st.warning("Please provide feedback.")
251
  st.markdown("---")
252
 
253
- # Video Generation Button & Display (same logic)
254
  if st.session_state.story_treatment_scenes and any(p for p in st.session_state.generated_visual_paths if p is not None):
255
- if st.button("🎬 Assemble Narrated Cinematic Animatic", key="assemble_ultra_video_btn_v5", type="primary", use_container_width=True): # Key updated
256
  with st.status("Assembling Ultra Animatic...", expanded=True) as status_vid:
257
  img_data_vid = []
258
  for i_v, sc_c in enumerate(st.session_state.story_treatment_scenes):
@@ -274,7 +295,7 @@ else:
274
  with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read()
275
  st.video(video_bytes, format="video/mp4")
276
  with open(st.session_state.video_path, "rb") as fp_dl:
277
- st.download_button(label="Download Ultra Animatic", data=fp_dl, file_name=os.path.basename(st.session_state.video_path), mime="video/mp4", use_container_width=True, key="download_ultra_video_btn_v5" ) # Key updated
278
  except Exception as e: st.error(f"Error displaying video: {e}"); logger.error(f"Error displaying video: {e}", exc_info=True)
279
 
280
  # --- Footer ---
 
18
  logger = logging.getLogger(__name__)
19
 
20
  # --- Global State Variables & API Key Setup ---
21
+ def load_api_key(key_name_streamlit, key_name_env, service_name):
22
  key = None; secrets_available = hasattr(st, 'secrets')
23
  try:
24
  if secrets_available and key_name_streamlit in st.secrets:
 
31
  if not key: logger.warning(f"{service_name} API Key NOT FOUND. Related features may be disabled or use fallbacks.")
32
  return key
33
 
34
+ if 'services_initialized' not in st.session_state:
35
  logger.info("Initializing services and API keys for the first time this session...")
36
  st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY", "Gemini")
37
  st.session_state.OPENAI_API_KEY = load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY", "OpenAI/DALL-E")
38
  st.session_state.ELEVENLABS_API_KEY = load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY", "ElevenLabs")
39
  st.session_state.PEXELS_API_KEY = load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY", "Pexels")
40
  st.session_state.ELEVENLABS_VOICE_ID_CONFIG = load_api_key("ELEVENLABS_VOICE_ID", "ELEVENLABS_VOICE_ID", "ElevenLabs Voice ID")
41
+
42
+ if not st.session_state.GEMINI_API_KEY:
43
+ st.error("CRITICAL: Gemini API Key is essential and missing!"); logger.critical("Gemini API Key missing. Halting."); st.stop()
44
+
45
  try:
46
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
47
  logger.info("GeminiHandler initialized successfully.")
48
  except Exception as e: st.error(f"Failed to init GeminiHandler: {e}"); logger.critical(f"GeminiHandler init failed: {e}", exc_info=True); st.stop()
49
+
50
  try:
51
+ default_voice_id = "Rachel" # A common fallback if no secret is set
52
+ configured_voice_id = st.session_state.ELEVENLABS_VOICE_ID_CONFIG or default_voice_id
53
+ st.session_state.visual_engine = VisualEngine(
54
+ output_dir="temp_cinegen_media",
55
+ default_elevenlabs_voice_id=configured_voice_id # Pass it to __init__
56
+ )
57
  st.session_state.visual_engine.set_openai_api_key(st.session_state.OPENAI_API_KEY)
58
+ # set_elevenlabs_api_key will use the voice_id passed to __init__ or overridden by its own param
59
  st.session_state.visual_engine.set_elevenlabs_api_key(st.session_state.ELEVENLABS_API_KEY, voice_id_from_secret=st.session_state.ELEVENLABS_VOICE_ID_CONFIG)
60
  st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
61
  logger.info("VisualEngine initialized and API keys set (or attempted).")
 
64
  st.warning("VisualEngine critical setup issue. Some features will be disabled.")
65
  st.session_state.services_initialized = True; logger.info("Service initialization sequence complete.")
66
 
67
+ # Initialize other session state variables
68
+ for key, default_val in [
69
  ('story_treatment_scenes', []), ('scene_dalle_prompts', []), ('generated_visual_paths', []),
70
  ('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
71
  ('overall_narration_audio_path', None), ('narration_script_display', "")
72
  ]:
73
  if key not in st.session_state: st.session_state[key] = default_val
74
 
 
75
  def initialize_new_project(): # Same
76
  st.session_state.story_treatment_scenes, st.session_state.scene_dalle_prompts, st.session_state.generated_visual_paths = [], [], []
77
  st.session_state.video_path, st.session_state.overall_narration_audio_path, st.session_state.narration_script_display = None, None, ""
 
90
  else:
91
  st.session_state.generated_visual_paths[scene_index] = None; logger.warning(f"Visual generation FAILED for Scene {scene_data.get('scene_number', scene_index+1)}. img_path was: {img_path}"); return False
92
 
93
+ # --- UI Sidebar ---
94
  with st.sidebar:
95
+ st.title("🎬 CineGen AI Ultra+")
96
+ st.markdown("### Creative Seed")
97
+ 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_v5")
98
+ genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Western", "Post-Apocalyptic", "Historical Drama", "Surreal"], index=6, key="genre_main_v5")
99
+ mood = st.selectbox("Overall Mood:", ["Hopeful yet Desperate", "Mysterious & Eerie", "Gritty & Tense", "Epic & Awe-Inspiring", "Melancholy & Reflective", "Whimsical & Lighthearted"], index=0, key="mood_main_v5")
100
  num_scenes = st.slider("Number of Key Scenes:", 1, 3, 1, key="num_scenes_main_v5")
101
  creative_guidance_options = {"Standard Director": "standard", "Artistic Visionary": "more_artistic", "Experimental Storyteller": "experimental_narrative"}
102
+ selected_creative_guidance_key = st.selectbox("AI Creative Director Style:", options=list(creative_guidance_options.keys()), key="creative_guidance_select_v5")
103
  actual_creative_guidance = creative_guidance_options[selected_creative_guidance_key]
104
 
105
  if st.button("🌌 Generate Cinematic Treatment", type="primary", key="generate_treatment_btn_v5", use_container_width=True):
 
107
  if not user_idea.strip(): st.warning("Please provide a story idea.")
108
  else:
109
  with st.status("AI Director is envisioning your masterpiece...", expanded=True) as status:
110
+ try:
111
+ status.write("Phase 1: Gemini crafting cinematic treatment... πŸ“œ"); logger.info("Phase 1: Cinematic Treatment Gen.")
112
  treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, actual_creative_guidance)
113
  treatment_result_json = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt)
114
  if not isinstance(treatment_result_json, list) or not treatment_result_json: raise ValueError("Gemini returned invalid scene list.")
115
  st.session_state.story_treatment_scenes = treatment_result_json; num_gen_scenes = len(st.session_state.story_treatment_scenes)
116
  st.session_state.scene_dalle_prompts = [""]*num_gen_scenes; st.session_state.generated_visual_paths = [None]*num_gen_scenes
117
  logger.info(f"Phase 1 complete. {num_gen_scenes} scenes."); status.update(label="Treatment complete! βœ… Generating visuals...", state="running")
118
+
119
+ status.write("Phase 2: Creating visuals (DALL-E/Pexels)... πŸ–ΌοΈ"); logger.info("Phase 2: Visual Gen.")
120
  visual_successes = 0
121
  for i, sc_data in enumerate(st.session_state.story_treatment_scenes):
122
  sc_num_log = sc_data.get('scene_number', i+1)
123
  status.write(f" Visual for Scene {sc_num_log}..."); logger.info(f" Processing visual for Scene {sc_num_log}.")
124
  if generate_visual_for_scene_core(i, sc_data, version=1): visual_successes += 1
125
+
126
+ current_status_label_ph2 = "Visuals ready! "
127
+ next_step_state = "running" # Assume success unless visuals totally fail
128
  if visual_successes == 0 and num_gen_scenes > 0:
129
  logger.error("Visual gen failed all scenes."); current_status_label_ph2 = "Visual gen FAILED for all scenes."; next_step_state="error";
130
+ status.update(label=current_status_label_ph2, state=next_step_state, expanded=True); st.stop() # Stop if no visuals
131
  elif visual_successes < num_gen_scenes:
132
  logger.warning(f"Visuals partial ({visual_successes}/{num_gen_scenes})."); current_status_label_ph2 = f"Visuals partially generated ({visual_successes}/{num_gen_scenes}). "
133
+ status.update(label=f"{current_status_label_ph2}Generating narration script...", state=next_step_state) # Propagate error state if needed
134
+ if next_step_state == "error": st.stop() # Should have already stopped
135
+
136
  status.write("Phase 3: Generating narration script..."); logger.info("Phase 3: Narration Script Gen.")
137
+ voice_style_for_prompt = st.session_state.get("selected_voice_style_for_generation", "cinematic_trailer")
138
+ narr_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, voice_style_for_prompt)
139
  st.session_state.narration_script_display = st.session_state.gemini_handler.generate_image_prompt(narr_prompt)
140
  logger.info("Narration script generated."); status.update(label="Narration script ready! Synthesizing voice...", state="running")
141
+
142
+ status.write("Phase 4: Synthesizing voice (ElevenLabs)... πŸ”Š"); logger.info("Phase 4: Voice Synthesis.")
143
  st.session_state.overall_narration_audio_path = st.session_state.visual_engine.generate_narration_audio(st.session_state.narration_script_display)
144
+
145
+ final_label = "All components ready! Storyboard below. πŸš€"
146
+ final_state_val = "complete"
147
  if not st.session_state.overall_narration_audio_path:
148
+ final_label = f"{current_status_label_ph2}Storyboard ready (Voiceover skipped or failed)."
149
  logger.warning("Voiceover was skipped or failed.")
150
  else: logger.info("Voiceover generated successfully.")
151
+ status.update(label=final_label, state=final_state_val, expanded=False) # Always complete if this far
152
+
153
  except ValueError as ve: logger.error(f"ValueError: {ve}", exc_info=True); status.update(label=f"Input or Gemini response error: {ve}", state="error", expanded=True);
154
  except Exception as e: logger.error(f"Unhandled Exception: {e}", exc_info=True); status.update(label=f"An unexpected error occurred: {e}", state="error", expanded=True);
155
 
156
  st.markdown("---"); st.markdown("### Fine-Tuning Options")
157
+ with st.expander("Define Characters", expanded=False):
158
  char_name = st.text_input("Character Name", key="char_name_adv_ultra_v5"); char_desc = st.text_area("Visual Description", key="char_desc_adv_ultra_v5", height=100, placeholder="e.g., Jax: rugged male astronaut...")
159
  if st.button("Save Character", key="add_char_adv_ultra_v5"):
160
  if char_name and char_desc: st.session_state.character_definitions[char_name.strip().lower()] = char_desc.strip(); st.success(f"Char '{char_name.strip()}' saved.")
161
  else: st.warning("Name and description needed.")
162
  if st.session_state.character_definitions: st.caption("Current Characters:"); [st.markdown(f"**{k.title()}:** _{v}_") for k,v in st.session_state.character_definitions.items()]
163
+
164
+ with st.expander("Global Style Overrides", expanded=False):
165
+ presets = { "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."}
166
  sel_preset = st.selectbox("Base Style Preset:", options=list(presets.keys()), key="style_preset_adv_ultra_v5")
167
  custom_kw = st.text_area("Additional Custom Style Keywords:", key="custom_style_adv_ultra_v5", height=80, placeholder="e.g., 'Dutch angle'")
168
  cur_style = st.session_state.global_style_additions
 
173
  if cur_style: st.success("Global styles applied!")
174
  else: st.info("Global style additions cleared.")
175
  if cur_style: st.caption(f"Active global styles: \"{cur_style}\"")
176
+
177
+ with st.expander("Voice & Narration Style", expanded=False):
178
+ # User can override the voice ID from secrets if they want to experiment
179
+ default_voice_from_engine = "Rachel" # Fallback if engine not init or no secret
180
+ if hasattr(st.session_state, 'visual_engine') and st.session_state.visual_engine:
181
+ default_voice_from_engine = st.session_state.visual_engine.elevenlabs_voice_id
182
+
183
+ user_voice_id_override = st.text_input(
184
+ "ElevenLabs Voice ID (optional override):",
185
+ value=default_voice_from_engine,
186
+ key="el_voice_id_override_v5",
187
+ help=f"Defaulting to '{default_voice_from_engine}' from secrets/config. Enter a specific Voice ID from your ElevenLabs account to override."
188
+ )
189
  prompt_v_styles = {"Cinematic Trailer": "cinematic_trailer", "Neutral Documentary": "documentary_neutral", "Character Introspection": "introspective_character"}
190
  sel_prompt_v_style_key = st.selectbox("Narration Script Style:", list(prompt_v_styles.keys()), key="narr_style_sel_v5", index=0)
191
+
192
  if st.button("Set Narrator Voice & Style", key="set_voice_btn_ultra_v5"):
193
+ final_voice_id_to_use = user_voice_id_override.strip()
194
+ if not final_voice_id_to_use: # If user cleared the input, revert to the one from secrets/default
195
+ final_voice_id_to_use = st.session_state.get("ELEVENLABS_VOICE_ID_CONFIG", "Rachel")
196
+
197
+ if hasattr(st.session_state, 'visual_engine'):
198
+ st.session_state.visual_engine.elevenlabs_voice_id = final_voice_id_to_use
199
  st.session_state.selected_voice_style_for_generation = prompt_v_styles[sel_prompt_v_style_key]
200
+ st.success(f"Narrator Voice ID set to: {final_voice_id_to_use}. Script Style: {sel_prompt_v_style_key}")
201
  logger.info(f"User updated ElevenLabs Voice ID to: {final_voice_id_to_use}, Script Style: {sel_prompt_v_style_key}")
202
 
203
  # --- Main Content Area ---
 
209
  else:
210
  for i_main, scene_content_display in enumerate(st.session_state.story_treatment_scenes):
211
  scene_n = scene_content_display.get('scene_number', i_main + 1); scene_t = scene_content_display.get('scene_title', 'Untitled')
212
+ key_base = f"s{scene_n}_{''.join(filter(str.isalnum, scene_t[:10]))}_v5"
213
  if "director_note" in scene_content_display and scene_content_display['director_note']: st.info(f"🎬 Director Note S{scene_n}: {scene_content_display['director_note']}")
214
  st.subheader(f"SCENE {scene_n}: {scene_t.upper()}"); col_d, col_v = st.columns([0.45, 0.55])
215
+ with col_d:
216
  with st.expander("πŸ“ Scene Treatment", expanded=True):
217
  st.markdown(f"**Beat:** {scene_content_display.get('emotional_beat', 'N/A')}"); st.markdown(f"**Setting:** {scene_content_display.get('setting_description', 'N/A')}"); st.markdown(f"**Chars:** {', '.join(scene_content_display.get('characters_involved', ['N/A']))}"); st.markdown(f"**Focus Moment:** _{scene_content_display.get('character_focus_moment', 'N/A')}_"); st.markdown(f"**Plot Beat:** {scene_content_display.get('key_plot_beat', 'N/A')}"); st.markdown(f"**Dialogue Hook:** `\"{scene_content_display.get('suggested_dialogue_hook', '...')}\"`"); st.markdown("---"); st.markdown(f"**Dir. Visual Style:** _{scene_content_display.get('PROACTIVE_visual_style_감독', 'N/A')}_"); st.markdown(f"**Dir. Camera:** _{scene_content_display.get('PROACTIVE_camera_work_감독', 'N/A')}_"); st.markdown(f"**Dir. Sound:** _{scene_content_display.get('PROACTIVE_sound_design_감독', 'N/A')}_")
218
  cur_d_prompt = st.session_state.scene_dalle_prompts[i_main] if i_main < len(st.session_state.scene_dalle_prompts) else None
 
220
  with st.popover("πŸ‘οΈ DALL-E Prompt"): st.markdown(f"**DALL-E Prompt:**"); st.code(cur_d_prompt, language='text')
221
  pexels_q = scene_content_display.get('pexels_search_query_감독', None)
222
  if pexels_q: st.caption(f"Pexels Fallback Query: `{pexels_q}`")
223
+ with col_v:
224
  cur_img_p = st.session_state.generated_visual_paths[i_main] if i_main < len(st.session_state.generated_visual_paths) else None
225
  if cur_img_p and os.path.exists(cur_img_p): st.image(cur_img_p, caption=f"Scene {scene_n}: {scene_t}")
226
  else:
 
236
  updated_sc_data = st.session_state.gemini_handler.regenerate_scene_script_details(prompt_text)
237
  st.session_state.story_treatment_scenes[i_main] = updated_sc_data
238
  s_treat_regen.update(label="Treatment updated! Regenerating visual...", state="running")
239
+ v_num = 1
240
+ if cur_img_p and os.path.exists(cur_img_p):
241
+ try: b,_=os.path.splitext(os.path.basename(cur_img_p)); v_num = int(b.split('_v')[-1])+1 if '_v' in b else 2
242
+ except (ValueError, IndexError, TypeError): v_num = 2
243
+ else: v_num = 1
 
 
 
 
 
 
244
  if generate_visual_for_scene_core(i_main, updated_sc_data, version=v_num): s_treat_regen.update(label="Treatment & Visual Updated! πŸŽ‰", state="complete", expanded=False)
245
  else: s_treat_regen.update(label="Treatment updated, visual failed.", state="complete", expanded=False)
246
  st.rerun()
 
260
  refined_d_prompt = st.session_state.gemini_handler.generate_image_prompt(ref_req_prompt)
261
  st.session_state.scene_dalle_prompts[i_main] = refined_d_prompt
262
  s_visual_regen.update(label="DALL-E prompt refined! Regenerating visual...", state="running")
 
 
263
  v_num = 1
264
  if cur_img_p and os.path.exists(cur_img_p):
265
+ try: b,_=os.path.splitext(os.path.basename(cur_img_p)); v_num = int(b.split('_v')[-1])+1 if '_v' in b else 2
 
 
 
266
  except (ValueError, IndexError, TypeError): v_num=2
267
  else: v_num = 1
 
268
  if generate_visual_for_scene_core(i_main, scene_content_display, version=v_num): s_visual_regen.update(label="Visual Updated! πŸŽ‰", state="complete", expanded=False)
269
  else: s_visual_regen.update(label="Prompt refined, visual failed.", state="complete", expanded=False)
270
  st.rerun()
 
272
  else: st.warning("Please provide feedback.")
273
  st.markdown("---")
274
 
 
275
  if st.session_state.story_treatment_scenes and any(p for p in st.session_state.generated_visual_paths if p is not None):
276
+ if st.button("🎬 Assemble Narrated Cinematic Animatic", key="assemble_ultra_video_btn_v5", type="primary", use_container_width=True):
277
  with st.status("Assembling Ultra Animatic...", expanded=True) as status_vid:
278
  img_data_vid = []
279
  for i_v, sc_c in enumerate(st.session_state.story_treatment_scenes):
 
295
  with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read()
296
  st.video(video_bytes, format="video/mp4")
297
  with open(st.session_state.video_path, "rb") as fp_dl:
298
+ st.download_button(label="Download Ultra Animatic", data=fp_dl, file_name=os.path.basename(st.session_state.video_path), mime="video/mp4", use_container_width=True, key="download_ultra_video_btn_v5" )
299
  except Exception as e: st.error(f"Error displaying video: {e}"); logger.error(f"Error displaying video: {e}", exc_info=True)
300
 
301
  # --- Footer ---