Update app.py
Browse files
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):
|
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:
|
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 |
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 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 ---
|
85 |
with st.sidebar:
|
86 |
-
st.title("π¬ CineGen AI Ultra+")
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
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:
|
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 |
-
|
|
|
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 |
-
|
|
|
|
|
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
|
121 |
-
if next_step_state == "error": st.stop()
|
|
|
122 |
status.write("Phase 3: Generating narration script..."); logger.info("Phase 3: Narration Script Gen.")
|
123 |
-
|
124 |
-
narr_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre,
|
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 |
-
|
|
|
128 |
st.session_state.overall_narration_audio_path = st.session_state.visual_engine.generate_narration_audio(st.session_state.narration_script_display)
|
129 |
-
|
|
|
|
|
130 |
if not st.session_state.overall_narration_audio_path:
|
131 |
-
final_label = f"{current_status_label_ph2}Storyboard ready (Voiceover skipped
|
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):
|
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 |
-
|
146 |
-
|
|
|
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 |
-
|
158 |
-
|
159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
164 |
-
if
|
|
|
|
|
|
|
|
|
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"
|
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:
|
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:
|
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 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
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):
|
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" )
|
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 ---
|