mgbam commited on
Commit
870979c
Β·
verified Β·
1 Parent(s): f02ab98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -61
app.py CHANGED
@@ -3,7 +3,7 @@ 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,
7
  construct_dalle_prompt,
8
  create_narration_script_prompt_enhanced,
9
  create_scene_regeneration_prompt,
@@ -16,36 +16,51 @@ st.set_page_config(page_title="CineGen AI Ultra+", layout="wide", initial_sideba
16
 
17
  # --- Global State Variables & API Key Setup ---
18
  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
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")
29
  st.session_state.ELEVENLABS_API_KEY = load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY")
30
  st.session_state.PEXELS_API_KEY = load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY")
31
 
32
- if not st.session_state.GEMINI_API_KEY: st.error("Gemini API Key is essential and missing!"); st.stop()
 
 
33
 
34
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
35
- st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
 
 
 
 
36
  st.session_state.visual_engine.set_openai_api_key(st.session_state.OPENAI_API_KEY)
37
  st.session_state.visual_engine.set_elevenlabs_api_key(st.session_state.ELEVENLABS_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', ""),
44
  ('overall_narration_audio_path', None), ('narration_script_display', "")
45
  ]:
46
  if key not in st.session_state: st.session_state[key] = default_val
 
 
47
 
48
- # --- Helper Functions ---
49
  def initialize_new_project():
50
  st.session_state.story_treatment_scenes = []
51
  st.session_state.scene_dalle_prompts = []
@@ -53,39 +68,56 @@ def initialize_new_project():
53
  st.session_state.video_path = None
54
  st.session_state.overall_narration_audio_path = None
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,
61
  st.session_state.global_style_additions
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
 
75
  else:
76
- st.session_state.generated_visual_paths[scene_index] = None; return False
 
 
77
 
78
  # --- UI Sidebar ---
79
  with st.sidebar:
80
  st.title("🎬 CineGen AI Ultra+")
81
  st.markdown("### Creative Seed")
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")
89
 
90
  if st.button("🌌 Generate Cinematic Treatment", type="primary", key="generate_treatment_btn", use_container_width=True):
91
  initialize_new_project()
@@ -93,41 +125,55 @@ with st.sidebar:
93
  else:
94
  with st.status("AI Director is envisioning your masterpiece...", expanded=True) as status:
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
102
- status.update(label="Treatment complete! Generating visuals...", state="running")
103
 
104
  visual_successes = 0
105
  for i_scene, scene_content in enumerate(st.session_state.story_treatment_scenes):
106
- status.write(f" Creating visual for Scene {scene_content.get('scene_number', i_scene+1)}: {scene_content.get('scene_title','')}...")
107
  if generate_visual_for_scene_core(i_scene, scene_content, version=1): visual_successes += 1
108
 
109
  if visual_successes == 0 and num_gen_scenes > 0:
110
- status.update(label="Visual generation failed for all scenes. Check API keys/quota.", state="error", expanded=False); st.stop()
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
 
118
  st.session_state.overall_narration_audio_path = st.session_state.visual_engine.generate_narration_audio(narr_script)
119
  if st.session_state.overall_narration_audio_path: status.update(label="Voiceover ready! ✨", state="running")
120
- else: status.update(label="Voiceover failed. Video will be silent.", state="warning")
121
 
122
- status.update(label="All components ready! View storyboard. πŸš€", state="complete", expanded=False)
123
 
124
- except Exception as e: status.update(label=f"Error during generation: {e}", state="error", expanded=True); st.stop()
 
 
 
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"):
132
  if char_name_input and char_desc_input: st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip(); st.success(f"Character '{char_name_input.strip()}' saved.")
133
  else: st.warning("Provide name and description.")
@@ -136,25 +182,43 @@ with st.sidebar:
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'")
142
  current_style_desc = st.session_state.global_style_additions
143
  if st.button("Apply Global Styles", key="apply_styles_adv_btn_ultra"):
144
  final_desc = predefined_styles[selected_preset];
145
  if custom_keywords.strip(): final_desc = f"{final_desc}, {custom_keywords.strip()}" if final_desc else custom_keywords.strip()
146
  st.session_state.global_style_additions = final_desc.strip(); current_style_desc = final_desc.strip()
147
  if current_style_desc: st.success("Global styles applied!")
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")
155
- if st.button("Set Narrator Voice", key="set_voice_btn_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")
@@ -171,11 +235,11 @@ else:
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):
@@ -186,23 +250,25 @@ else:
186
  st.markdown(f"**Key Plot Beat:** {scene_content_display.get('key_plot_beat', 'N/A')}")
187
  st.markdown(f"**Dialogue Hook:** `\"{scene_content_display.get('suggested_dialogue_hook', '...')}\"`")
188
  st.markdown("---")
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)
@@ -213,16 +279,11 @@ else:
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)
@@ -247,13 +308,11 @@ else:
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()
 
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, # Using the new "Ultra" prompt
7
  construct_dalle_prompt,
8
  create_narration_script_prompt_enhanced,
9
  create_scene_regeneration_prompt,
 
16
 
17
  # --- Global State Variables & API Key Setup ---
18
  def load_api_key(key_name_streamlit, key_name_env):
19
+ key = None
20
+ secrets_available = hasattr(st, 'secrets') # Check if st.secrets exists
21
  try:
22
+ if secrets_available and key_name_streamlit in st.secrets:
23
+ key = st.secrets[key_name_streamlit]
24
+ except Exception as e: # Catch any error from accessing st.secrets
25
+ print(f"Note: Could not access st.secrets for {key_name_streamlit} (may be local dev): {e}")
26
+
27
+ if not key and key_name_env in os.environ: # Fallback to environment variable
28
+ key = os.environ[key_name_env]
29
  return key
30
 
31
+ # Initialize API Keys and handlers once using session state
32
  if 'keys_loaded' not in st.session_state:
33
  st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY")
34
  st.session_state.OPENAI_API_KEY = load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY")
35
  st.session_state.ELEVENLABS_API_KEY = load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY")
36
  st.session_state.PEXELS_API_KEY = load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY")
37
 
38
+ if not st.session_state.GEMINI_API_KEY:
39
+ st.error("Gemini API Key is essential and missing! Please set it in secrets or environment variables.")
40
+ st.stop()
41
 
42
  st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
43
+
44
+ # Initialize VisualEngine and set API keys
45
+ if 'visual_engine' not in st.session_state: # Ensure VE is also session-scoped if needed elsewhere before full init
46
+ st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
47
+
48
  st.session_state.visual_engine.set_openai_api_key(st.session_state.OPENAI_API_KEY)
49
  st.session_state.visual_engine.set_elevenlabs_api_key(st.session_state.ELEVENLABS_API_KEY)
50
  st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
51
+
52
+ st.session_state.keys_loaded = True # Mark keys as loaded
53
 
54
+ # Initialize other session state variables
55
  for key, default_val in [
56
  ('story_treatment_scenes', []), ('scene_dalle_prompts', []), ('generated_visual_paths', []),
57
  ('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
58
  ('overall_narration_audio_path', None), ('narration_script_display', "")
59
  ]:
60
  if key not in st.session_state: st.session_state[key] = default_val
61
+ # --- End State & API Key Setup ---
62
+
63
 
 
64
  def initialize_new_project():
65
  st.session_state.story_treatment_scenes = []
66
  st.session_state.scene_dalle_prompts = []
 
68
  st.session_state.video_path = None
69
  st.session_state.overall_narration_audio_path = None
70
  st.session_state.narration_script_display = ""
71
+ # Clean up old media files (optional, good for development)
72
+ # output_dir = st.session_state.visual_engine.output_dir
73
+ # if os.path.exists(output_dir):
74
+ # for f_name in os.listdir(output_dir):
75
+ # try: os.remove(os.path.join(output_dir, f_name))
76
+ # except Exception as e: print(f"Could not remove old file {f_name}: {e}")
77
+
78
 
79
  def generate_visual_for_scene_core(scene_index, scene_data, version=1):
80
+ # scene_data here is one scene from story_treatment_scenes
81
+ dalle_prompt = construct_dalle_prompt( # Use the new prompt constructor
82
  scene_data,
83
  st.session_state.character_definitions,
84
  st.session_state.global_style_additions
85
  )
86
+ if not dalle_prompt:
87
+ print(f"ERROR: DALL-E prompt construction failed for scene {scene_data.get('scene_number', scene_index+1)}")
88
+ return False
89
 
90
+ # Ensure lists are long enough (should be pre-initialized)
91
  while len(st.session_state.scene_dalle_prompts) <= scene_index: st.session_state.scene_dalle_prompts.append("")
92
  while len(st.session_state.generated_visual_paths) <= scene_index: st.session_state.generated_visual_paths.append(None)
93
 
94
+ st.session_state.scene_dalle_prompts[scene_index] = dalle_prompt # Store the generated DALL-E prompt
95
 
96
  filename = f"scene_{scene_data.get('scene_number', scene_index+1)}_visual_v{version}.png"
97
+ # Pass the full scene_data to visual_engine for Pexels query construction if DALL-E fails
98
  img_path = st.session_state.visual_engine.generate_image_visual(dalle_prompt, scene_data, filename)
99
 
100
  if img_path and os.path.exists(img_path):
101
+ st.session_state.generated_visual_paths[scene_index] = img_path
102
+ return True
103
  else:
104
+ st.session_state.generated_visual_paths[scene_index] = None
105
+ print(f"WARNING: Visual generation ultimately failed for scene {scene_data.get('scene_number', scene_index+1)}")
106
+ return False
107
 
108
  # --- UI Sidebar ---
109
  with st.sidebar:
110
  st.title("🎬 CineGen AI Ultra+")
111
  st.markdown("### Creative Seed")
112
  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")
113
+ genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Western", "Post-Apocalyptic", "Historical Drama", "Surreal"], index=6, key="genre_main")
114
+ mood = st.selectbox("Overall Mood:", ["Hopeful yet Desperate", "Mysterious & Eerie", "Gritty & Tense", "Epic & Awe-Inspiring", "Melancholy & Reflective", "Whimsical & Lighthearted"], index=0, key="mood_main")
115
+ num_scenes = st.slider("Number of Key Scenes:", 1, 3, 2, key="num_scenes_main") # Max 3 for API cost/time
116
+
117
+ creative_guidance_options = {"Standard Director": "standard", "Artistic Visionary": "more_artistic", "Experimental Storyteller": "experimental_narrative"}
118
+ selected_creative_guidance_key = st.selectbox("AI Creative Director Style:", options=list(creative_guidance_options.keys()), key="creative_guidance_select")
119
+ actual_creative_guidance = creative_guidance_options[selected_creative_guidance_key]
120
 
 
 
121
 
122
  if st.button("🌌 Generate Cinematic Treatment", type="primary", key="generate_treatment_btn", use_container_width=True):
123
  initialize_new_project()
 
125
  else:
126
  with st.status("AI Director is envisioning your masterpiece...", expanded=True) as status:
127
  status.write("Phase 1: Gemini crafting cinematic treatment (scenes, style, camera, sound)... πŸ“œ")
128
+ treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, actual_creative_guidance)
129
  try:
130
+ treatment_result_json = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt) # Re-use for JSON list
131
+ if not isinstance(treatment_result_json, list): # Basic validation
132
+ raise ValueError("Gemini did not return a valid list of scenes for the treatment.")
133
+ st.session_state.story_treatment_scenes = treatment_result_json
134
+
135
  num_gen_scenes = len(st.session_state.story_treatment_scenes)
136
+ if num_gen_scenes == 0: raise ValueError("Gemini returned an empty scene list.")
137
+
138
  st.session_state.scene_dalle_prompts = [""] * num_gen_scenes
139
  st.session_state.generated_visual_paths = [None] * num_gen_scenes
140
+ status.update(label="Treatment complete! βœ… Generating visuals...", state="running", expanded=True)
141
 
142
  visual_successes = 0
143
  for i_scene, scene_content in enumerate(st.session_state.story_treatment_scenes):
144
+ status.write(f" Creating visual for Scene {scene_content.get('scene_number', i_scene+1)}: {scene_content.get('scene_title','Untitled')}...")
145
  if generate_visual_for_scene_core(i_scene, scene_content, version=1): visual_successes += 1
146
 
147
  if visual_successes == 0 and num_gen_scenes > 0:
148
+ status.update(label="Visual generation failed for all scenes. Check DALL-E/Pexels API keys, quota, or try different prompts.", state="error", expanded=True); st.stop()
149
+ elif visual_successes < num_gen_scenes:
150
+ status.update(label=f"Visuals ready ({visual_successes}/{num_gen_scenes} succeeded). Generating narration script...", state="running", expanded=True)
151
+ else:
152
+ status.update(label="Visuals ready! Generating narration script...", state="running", expanded=True)
153
+
154
+ # Narration Generation
155
+ selected_voice_style = st.session_state.get("selected_voice_style_for_generation", "cinematic_trailer") # Get from session state if set by UI
156
+ narration_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, selected_voice_style)
157
  narr_script = st.session_state.gemini_handler.generate_image_prompt(narration_prompt)
158
  st.session_state.narration_script_display = narr_script
159
  status.update(label="Narration script ready! Synthesizing voice...", state="running")
160
 
161
  st.session_state.overall_narration_audio_path = st.session_state.visual_engine.generate_narration_audio(narr_script)
162
  if st.session_state.overall_narration_audio_path: status.update(label="Voiceover ready! ✨", state="running")
163
+ else: status.update(label="Voiceover failed or skipped. Video will be silent.", state="warning")
164
 
165
+ status.update(label="All components ready! View storyboard below. πŸš€", state="complete", expanded=False)
166
 
167
+ except ValueError as ve: # Catch our own validation errors
168
+ status.update(label=f"Treatment generation error: {ve}", state="error", expanded=True); st.stop()
169
+ except Exception as e:
170
+ status.update(label=f"Error during generation: {e}", state="error", expanded=True); st.stop()
171
 
172
  st.markdown("---")
173
  st.markdown("### Fine-Tuning Options")
174
  with st.expander("Define Characters", expanded=False):
175
  char_name_input = st.text_input("Character Name", key="char_name_adv_input_ultra")
176
+ 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...")
177
  if st.button("Save Character", key="add_char_adv_btn_ultra"):
178
  if char_name_input and char_desc_input: st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip(); st.success(f"Character '{char_name_input.strip()}' saved.")
179
  else: st.warning("Provide name and description.")
 
182
  for k,v in st.session_state.character_definitions.items(): st.markdown(f"**{k.title()}:** _{v}_")
183
 
184
  with st.expander("Global Style Overrides", expanded=False):
185
+ predefined_styles = { "Default (Director's Choice)": "", "Hyper-Realistic Gritty Noir": "hyper-realistic gritty neo-noir...", "Surreal Dreamscape Fantasy": "surreal dreamscape, epic fantasy elements...", "Vintage Analog Sci-Fi": "70s/80s analog sci-fi film aesthetic..."} # Shortened for brevity
186
  selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv_ultra")
187
+ 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'")
188
  current_style_desc = st.session_state.global_style_additions
189
  if st.button("Apply Global Styles", key="apply_styles_adv_btn_ultra"):
190
  final_desc = predefined_styles[selected_preset];
191
  if custom_keywords.strip(): final_desc = f"{final_desc}, {custom_keywords.strip()}" if final_desc else custom_keywords.strip()
192
  st.session_state.global_style_additions = final_desc.strip(); current_style_desc = final_desc.strip()
193
  if current_style_desc: st.success("Global styles applied!")
194
+ else: st.info("Global style additions cleared.")
195
  if current_style_desc: st.caption(f"Active global style additions: \"{current_style_desc}\"")
196
 
197
  with st.expander("Voice Customization (ElevenLabs)", expanded=False):
198
+ elevenlabs_voices_conceptual = ["Rachel", "Adam", "Bella", "Antoni", "Elli", "Josh", "Arnold", "Domi", "Fin", "Sarah", "Charlie", "Clyde", "Dorothy", "George"] # More options
199
+ # Get current voice from visual_engine, default if not set
200
+ engine_voice_id = "Rachel"
201
+ if hasattr(st.session_state, 'visual_engine') and st.session_state.visual_engine:
202
+ engine_voice_id = st.session_state.visual_engine.elevenlabs_voice_id
203
+
204
+ try:
205
+ current_voice_index = elevenlabs_voices_conceptual.index(engine_voice_id)
206
+ except ValueError:
207
+ current_voice_index = 0 # Default to Rachel if current ID not in list
208
+
209
+ selected_el_voice = st.selectbox("Narrator Voice:", elevenlabs_voices_conceptual,
210
+ index=current_voice_index,
211
+ key="el_voice_select_ultra")
212
+ # Store voice style for narration prompt
213
+ voice_styles_for_prompt = {"Cinematic Trailer": "cinematic_trailer", "Neutral Documentary": "documentary_neutral", "Character Introspection": "introspective_character"}
214
+ selected_prompt_voice_style_key = st.selectbox("Narration Script Style:", list(voice_styles_for_prompt.keys()), key="narration_style_select")
215
+ st.session_state.selected_voice_style_for_generation = voice_styles_for_prompt[selected_prompt_voice_style_key]
216
+
217
+ if st.button("Set Narrator Voice & Style", key="set_voice_btn_ultra"):
218
+ if hasattr(st.session_state, 'visual_engine'):
219
+ st.session_state.visual_engine.elevenlabs_voice_id = selected_el_voice
220
+ st.success(f"Narrator voice set to: {selected_el_voice}. Script style: {selected_prompt_voice_style_key}")
221
+
222
 
223
  # --- Main Content Area ---
224
  st.header("🎬 Cinematic Storyboard & Treatment")
 
235
  scene_title = scene_content_display.get('scene_title', 'Untitled Scene')
236
  unique_key_base = f"scene_{scene_num}_{''.join(filter(str.isalnum, scene_title[:10]))}"
237
 
238
+ if "director_note" in scene_content_display and scene_content_display['director_note']:
239
  st.info(f"🎬 Director's Note for Scene {scene_num}: {scene_content_display['director_note']}")
240
 
241
  st.subheader(f"SCENE {scene_num}: {scene_title.upper()}")
242
+ col_details, col_visual = st.columns([0.45, 0.55])
243
 
244
  with col_details:
245
  with st.expander("πŸ“ Scene Treatment Details", expanded=True):
 
250
  st.markdown(f"**Key Plot Beat:** {scene_content_display.get('key_plot_beat', 'N/A')}")
251
  st.markdown(f"**Dialogue Hook:** `\"{scene_content_display.get('suggested_dialogue_hook', '...')}\"`")
252
  st.markdown("---")
253
+ st.markdown(f"**🎬 Director's Visual Style:** _{scene_content_display.get('PROACTIVE_visual_style_감독', 'N/A')}_")
254
+ st.markdown(f"**πŸŽ₯ Director's Camera Work:** _{scene_content_display.get('PROACTIVE_camera_work_감독', 'N/A')}_")
255
+ st.markdown(f"**πŸ”Š Director's Sound Design:** _{scene_content_display.get('PROACTIVE_sound_design_감독', 'N/A')}_")
256
+
257
  current_dalle_prompt = st.session_state.scene_dalle_prompts[i_main] if i_main < len(st.session_state.scene_dalle_prompts) else None
258
  if current_dalle_prompt:
259
  with st.popover("πŸ‘οΈ View DALL-E Prompt"):
260
  st.markdown(f"**Full DALL-E Prompt:**"); st.code(current_dalle_prompt, language='text')
261
+
262
  pexels_query_display = scene_content_display.get('pexels_search_query_감독', None)
263
  if pexels_query_display:
264
+ st.caption(f"Suggested Pexels Query for fallback: `{pexels_query_display}`")
265
 
266
  with col_visual:
267
  current_img_path = st.session_state.generated_visual_paths[i_main] if i_main < len(st.session_state.generated_visual_paths) else None
268
  if current_img_path and os.path.exists(current_img_path):
269
  st.image(current_img_path, caption=f"Visual Concept for Scene {scene_num}: {scene_title}", use_column_width='always')
270
  else:
271
+ if st.session_state.story_treatment_scenes: st.caption("Visual concept for this scene is pending or failed.")
272
 
273
  with st.popover(f"✏️ Edit Scene {scene_num} Treatment"):
274
  feedback_script_edit = st.text_area("Describe changes to treatment details:", key=f"treat_feed_{unique_key_base}", height=150)
 
279
  try:
280
  updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt_text)
281
  st.session_state.story_treatment_scenes[i_main] = updated_scene_data
282
+ status_treat_regen.update(label="Treatment updated! Regenerating visual...", state="running")
 
283
  version_num = 1
284
+ if current_img_path:
285
+ 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
286
+ except: version_num=2
 
 
 
 
287
  if generate_visual_for_scene_core(i_main, updated_scene_data, version=version_num):
288
  status_treat_regen.update(label="Scene Treatment & Visual Updated! πŸŽ‰", state="complete", expanded=False)
289
  else: status_treat_regen.update(label="Treatment updated, visual failed.", state="warning", expanded=False)
 
308
  status_visual_edit_regen.update(label="DALL-E prompt refined! Regenerating visual...", state="running")
309
  version_num = 1
310
  if current_img_path:
311
+ 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
 
 
 
312
  except: version_num=2
313
 
314
+ # Pass current scene_content_display for Pexels fallback context
315
+ if generate_visual_for_scene_core(i_main, scene_content_display, version=version_num):
316
  status_visual_edit_regen.update(label="Visual Updated! πŸŽ‰", state="complete", expanded=False)
317
  else: status_visual_edit_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
318
  st.rerun()