Update app.py
Browse files
app.py
CHANGED
@@ -5,6 +5,7 @@ from core.visual_engine import VisualEngine
|
|
5 |
from core.prompt_engineering import (
|
6 |
create_cinematic_treatment_prompt,
|
7 |
construct_dalle_prompt,
|
|
|
8 |
create_narration_script_prompt_enhanced,
|
9 |
create_scene_regeneration_prompt,
|
10 |
create_visual_regeneration_prompt
|
@@ -17,7 +18,6 @@ st.set_page_config(page_title="CineGen AI Ultra+", layout="wide", initial_sideba
|
|
17 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
18 |
logger = logging.getLogger(__name__)
|
19 |
|
20 |
-
# <<< ADDED/MODIFIED START >>>
|
21 |
# --- Global Definitions for New Features ---
|
22 |
SHOT_TYPES_OPTIONS = [
|
23 |
"Director's Choice", "Establishing Shot", "Long Shot", "Full Shot",
|
@@ -26,9 +26,9 @@ SHOT_TYPES_OPTIONS = [
|
|
26 |
"Over the Shoulder", "Tracking Shot", "Dolly Zoom", "Crane Shot",
|
27 |
"Aerial Shot", "Static Shot", "Dutch Angle", "Whip Pan"
|
28 |
]
|
29 |
-
DEFAULT_SCENE_DURATION_SECS = 5
|
30 |
DEFAULT_SHOT_TYPE = "Director's Choice"
|
31 |
-
#
|
32 |
|
33 |
|
34 |
# --- Global State Variables & API Key Setup ---
|
@@ -52,6 +52,7 @@ if 'services_initialized' not in st.session_state:
|
|
52 |
st.session_state.ELEVENLABS_API_KEY = load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY", "ElevenLabs")
|
53 |
st.session_state.PEXELS_API_KEY = load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY", "Pexels")
|
54 |
st.session_state.ELEVENLABS_VOICE_ID_CONFIG = load_api_key("ELEVENLABS_VOICE_ID", "ELEVENLABS_VOICE_ID", "ElevenLabs Voice ID")
|
|
|
55 |
|
56 |
if not st.session_state.GEMINI_API_KEY:
|
57 |
st.error("CRITICAL: Gemini API Key is essential and missing!"); logger.critical("Gemini API Key missing. Halting."); st.stop()
|
@@ -62,15 +63,16 @@ if 'services_initialized' not in st.session_state:
|
|
62 |
except Exception as e: st.error(f"Failed to init GeminiHandler: {e}"); logger.critical(f"GeminiHandler init failed: {e}", exc_info=True); st.stop()
|
63 |
|
64 |
try:
|
65 |
-
default_voice_id = "Rachel"
|
66 |
configured_voice_id = st.session_state.ELEVENLABS_VOICE_ID_CONFIG or default_voice_id
|
67 |
st.session_state.visual_engine = VisualEngine(
|
68 |
output_dir="temp_cinegen_media",
|
69 |
-
default_elevenlabs_voice_id=configured_voice_id
|
70 |
)
|
71 |
st.session_state.visual_engine.set_openai_api_key(st.session_state.OPENAI_API_KEY)
|
72 |
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)
|
73 |
st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
|
|
|
74 |
logger.info("VisualEngine initialized and API keys set (or attempted).")
|
75 |
except Exception as e:
|
76 |
st.error(f"Failed to init VisualEngine or set its API keys: {e}"); logger.critical(f"VisualEngine init/key setting failed: {e}", exc_info=True)
|
@@ -78,30 +80,86 @@ if 'services_initialized' not in st.session_state:
|
|
78 |
st.session_state.services_initialized = True; logger.info("Service initialization sequence complete.")
|
79 |
|
80 |
# Initialize other session state variables
|
|
|
81 |
for key, default_val in [
|
82 |
-
('story_treatment_scenes', []), ('
|
83 |
('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
|
84 |
('overall_narration_audio_path', None), ('narration_script_display', "")
|
85 |
]:
|
86 |
if key not in st.session_state: st.session_state[key] = default_val
|
87 |
|
88 |
def initialize_new_project():
|
89 |
-
st.session_state.story_treatment_scenes
|
|
|
|
|
90 |
st.session_state.video_path, st.session_state.overall_narration_audio_path, st.session_state.narration_script_display = None, None, ""
|
91 |
logger.info("New project initialized.")
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
else:
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
# --- UI Sidebar ---
|
107 |
with st.sidebar:
|
@@ -110,7 +168,7 @@ with st.sidebar:
|
|
110 |
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")
|
111 |
genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Western", "Post-Apocalyptic", "Historical Drama", "Surreal"], index=6, key="genre_main_v5")
|
112 |
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")
|
113 |
-
num_scenes = st.slider("Number of Key Scenes:", 1, 10,
|
114 |
creative_guidance_options = {"Standard Director": "standard", "Artistic Visionary": "more_artistic", "Experimental Storyteller": "experimental_narrative"}
|
115 |
selected_creative_guidance_key = st.selectbox("AI Creative Director Style:", options=list(creative_guidance_options.keys()), key="creative_guidance_select_v5")
|
116 |
actual_creative_guidance = creative_guidance_options[selected_creative_guidance_key]
|
@@ -122,47 +180,50 @@ with st.sidebar:
|
|
122 |
with st.status("AI Director is envisioning your masterpiece...", expanded=True) as status:
|
123 |
try:
|
124 |
status.write("Phase 1: Gemini crafting cinematic treatment... π"); logger.info("Phase 1: Cinematic Treatment Gen.")
|
125 |
-
# Note: Consider updating create_cinematic_treatment_prompt to also ask Gemini
|
126 |
-
# for 'suggested_shot_type' and 'estimated_duration_secs' for each scene.
|
127 |
treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, actual_creative_guidance)
|
128 |
-
treatment_result_json_raw = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt)
|
129 |
-
if not isinstance(treatment_result_json_raw, list) or not treatment_result_json_raw: raise ValueError("Gemini returned invalid scene list.")
|
130 |
|
131 |
-
# <<< ADDED/MODIFIED START >>>
|
132 |
-
# Process raw scenes and add default shot type and duration
|
133 |
processed_scenes = []
|
134 |
for scene_data_from_gemini in treatment_result_json_raw:
|
135 |
-
scene_data_from_gemini['
|
136 |
-
scene_data_from_gemini['
|
|
|
|
|
137 |
processed_scenes.append(scene_data_from_gemini)
|
138 |
st.session_state.story_treatment_scenes = processed_scenes
|
139 |
-
# <<< ADDED/MODIFIED END >>>
|
140 |
|
141 |
num_gen_scenes = len(st.session_state.story_treatment_scenes)
|
142 |
-
|
|
|
|
|
|
|
143 |
logger.info(f"Phase 1 complete. {num_gen_scenes} scenes."); status.update(label="Treatment complete! β
Generating visuals...", state="running")
|
144 |
|
145 |
-
status.write("Phase 2: Creating
|
146 |
visual_successes = 0
|
147 |
for i, sc_data in enumerate(st.session_state.story_treatment_scenes):
|
148 |
sc_num_log = sc_data.get('scene_number', i+1)
|
149 |
-
status.write(f"
|
150 |
-
|
|
|
|
|
|
|
151 |
|
152 |
-
current_status_label_ph2 = "
|
153 |
next_step_state = "running"
|
154 |
if visual_successes == 0 and num_gen_scenes > 0:
|
155 |
-
logger.error("Visual gen failed all scenes."); current_status_label_ph2 = "
|
156 |
status.update(label=current_status_label_ph2, state=next_step_state, expanded=True); st.stop()
|
157 |
elif visual_successes < num_gen_scenes:
|
158 |
-
logger.warning(f"
|
159 |
status.update(label=f"{current_status_label_ph2}Generating narration script...", state=next_step_state)
|
160 |
if next_step_state == "error": st.stop()
|
161 |
|
162 |
status.write("Phase 3: Generating narration script..."); logger.info("Phase 3: Narration Script Gen.")
|
163 |
voice_style_for_prompt = st.session_state.get("selected_voice_style_for_generation", "cinematic_trailer")
|
164 |
narr_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, voice_style_for_prompt)
|
165 |
-
st.session_state.narration_script_display = st.session_state.gemini_handler.generate_image_prompt(narr_prompt) #
|
166 |
logger.info("Narration script generated."); status.update(label="Narration script ready! Synthesizing voice...", state="running")
|
167 |
|
168 |
status.write("Phase 4: Synthesizing voice (ElevenLabs)... π"); logger.info("Phase 4: Voice Synthesis.")
|
@@ -180,6 +241,7 @@ with st.sidebar:
|
|
180 |
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);
|
181 |
|
182 |
st.markdown("---"); st.markdown("### Fine-Tuning Options")
|
|
|
183 |
with st.expander("Define Characters", expanded=False):
|
184 |
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...")
|
185 |
if st.button("Save Character", key="add_char_adv_ultra_v5"):
|
@@ -225,6 +287,7 @@ with st.sidebar:
|
|
225 |
st.success(f"Narrator Voice ID set to: {final_voice_id_to_use}. Script Style: {sel_prompt_v_style_key}")
|
226 |
logger.info(f"User updated ElevenLabs Voice ID to: {final_voice_id_to_use}, Script Style: {sel_prompt_v_style_key}")
|
227 |
|
|
|
228 |
# --- Main Content Area ---
|
229 |
st.header("π¬ Cinematic Storyboard & Treatment")
|
230 |
if st.session_state.narration_script_display:
|
@@ -234,169 +297,221 @@ if not st.session_state.story_treatment_scenes: st.info("Use the sidebar to gene
|
|
234 |
else:
|
235 |
for i_main, scene_content_display in enumerate(st.session_state.story_treatment_scenes):
|
236 |
scene_n = scene_content_display.get('scene_number', i_main + 1); scene_t = scene_content_display.get('scene_title', 'Untitled')
|
237 |
-
key_base = f"s{scene_n}_{''.join(filter(str.isalnum, scene_t[:10]))}_v5_{i_main}"
|
238 |
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']}")
|
239 |
st.subheader(f"SCENE {scene_n}: {scene_t.upper()}"); col_d, col_v = st.columns([0.45, 0.55])
|
240 |
-
with col_d:
|
241 |
-
with st.expander("π Scene Treatment & Controls", expanded=True): # MODIFIED Expander Title
|
242 |
-
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')}_")
|
243 |
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
st.markdown("---")
|
246 |
-
st.markdown("##### Shot &
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
|
248 |
-
# Shot Type Selection
|
249 |
-
current_shot_type = st.session_state.story_treatment_scenes[i_main].get('shot_type', DEFAULT_SHOT_TYPE)
|
250 |
-
try:
|
251 |
-
shot_type_index = SHOT_TYPES_OPTIONS.index(current_shot_type)
|
252 |
-
except ValueError:
|
253 |
-
shot_type_index = SHOT_TYPES_OPTIONS.index(DEFAULT_SHOT_TYPE) # Fallback if value is somehow invalid
|
254 |
-
|
255 |
-
new_shot_type = st.selectbox(
|
256 |
-
"Dominant Shot Type:",
|
257 |
-
options=SHOT_TYPES_OPTIONS,
|
258 |
-
index=shot_type_index,
|
259 |
-
key=f"shot_type_widget_{key_base}",
|
260 |
-
help="Suggests the primary camera shot for this scene. Influences visual generation style if DALL-E prompt is ever made aware of it."
|
261 |
-
)
|
262 |
-
if new_shot_type != st.session_state.story_treatment_scenes[i_main]['shot_type']:
|
263 |
-
st.session_state.story_treatment_scenes[i_main]['shot_type'] = new_shot_type
|
264 |
-
# No st.rerun() needed unless other UI elements immediately depend on this change
|
265 |
-
|
266 |
-
# Scene Duration Control
|
267 |
-
current_duration = st.session_state.story_treatment_scenes[i_main].get('scene_duration_secs', DEFAULT_SCENE_DURATION_SECS)
|
268 |
-
new_duration = st.number_input(
|
269 |
-
"Scene Duration (seconds):",
|
270 |
-
min_value=1,
|
271 |
-
max_value=300, # Max 5 minutes per scene image
|
272 |
-
value=current_duration,
|
273 |
-
step=1,
|
274 |
-
key=f"duration_widget_{key_base}",
|
275 |
-
help="Approximate duration this scene's visual will be shown in the final animatic."
|
276 |
-
)
|
277 |
-
if new_duration != st.session_state.story_treatment_scenes[i_main]['scene_duration_secs']:
|
278 |
-
st.session_state.story_treatment_scenes[i_main]['scene_duration_secs'] = new_duration
|
279 |
-
# No st.rerun() needed unless other UI elements immediately depend on this change
|
280 |
st.markdown("---")
|
281 |
-
#
|
|
|
|
|
|
|
|
|
282 |
|
283 |
-
cur_d_prompt = st.session_state.scene_dalle_prompts[i_main] if i_main < len(st.session_state.scene_dalle_prompts) else None
|
284 |
-
if cur_d_prompt:
|
285 |
-
with st.popover("ποΈ DALL-E Prompt"): st.markdown(f"**DALL-E Prompt:**"); st.code(cur_d_prompt, language='text')
|
286 |
pexels_q = scene_content_display.get('pexels_search_query_κ°λ
', None)
|
287 |
if pexels_q: st.caption(f"Pexels Fallback Query: `{pexels_q}`")
|
288 |
|
289 |
-
with col_v:
|
290 |
-
|
291 |
-
if
|
292 |
-
|
293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
|
295 |
with st.popover(f"βοΈ Edit Scene {scene_n} Treatment"):
|
296 |
fb_script = st.text_area("Changes to treatment:", key=f"treat_fb_{key_base}", height=150)
|
297 |
if st.button(f"π Update Scene {scene_n} Treatment", key=f"regen_treat_btn_{key_base}"):
|
298 |
if fb_script:
|
299 |
-
with st.status(f"Updating Scene {scene_n}...", expanded=True) as s_treat_regen:
|
|
|
|
|
|
|
|
|
|
|
300 |
prompt_text = create_scene_regeneration_prompt(scene_content_display, fb_script, st.session_state.story_treatment_scenes)
|
301 |
try:
|
302 |
-
|
303 |
-
|
304 |
-
#
|
305 |
-
|
306 |
-
|
307 |
-
|
|
|
|
|
|
|
|
|
308 |
|
309 |
-
# Merge Gemini's script updates with existing scene data, preserving our custom fields
|
310 |
-
updated_sc_data = {**st.session_state.story_treatment_scenes[i_main], **updated_sc_data_raw}
|
311 |
-
updated_sc_data['shot_type'] = updated_sc_data_raw.get('suggested_shot_type', original_shot_type) # If Gemini suggests it, use it, else keep old
|
312 |
-
updated_sc_data['scene_duration_secs'] = updated_sc_data_raw.get('estimated_duration_secs', original_duration) # Same for duration
|
313 |
|
314 |
st.session_state.story_treatment_scenes[i_main] = updated_sc_data
|
315 |
-
|
316 |
|
317 |
-
s_treat_regen.update(label="Treatment updated! Regenerating visual...", state="running")
|
318 |
v_num = 1
|
319 |
-
if
|
320 |
-
try: b,_=os.path.splitext(os.path.basename(
|
321 |
-
except
|
322 |
else: v_num = 1
|
323 |
-
|
324 |
-
|
|
|
|
|
|
|
325 |
st.rerun()
|
326 |
except Exception as e_regen: s_treat_regen.update(label=f"Error: {e_regen}", state="error"); logger.error(f"Scene treatment regen error: {e_regen}", exc_info=True)
|
327 |
else: st.warning("Please provide feedback.")
|
328 |
|
329 |
with st.popover(f"π¨ Edit Scene {scene_n} Visual Prompt"):
|
330 |
-
|
331 |
-
st.caption("Current
|
332 |
-
fb_visual = st.text_area("Changes for
|
333 |
-
if st.button(f"π Update Scene {scene_n}
|
334 |
if fb_visual:
|
335 |
-
with st.status(f"Refining prompt &
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
else: st.warning("Please provide feedback.")
|
353 |
st.markdown("---")
|
354 |
|
355 |
-
|
|
|
|
|
356 |
if st.button("π¬ Assemble Narrated Cinematic Animatic", key="assemble_ultra_video_btn_v5", type="primary", use_container_width=True):
|
357 |
with st.status("Assembling Ultra Animatic...", expanded=True) as status_vid:
|
358 |
-
|
359 |
for i_v, sc_c in enumerate(st.session_state.story_treatment_scenes):
|
360 |
-
|
361 |
-
if
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
'path': img_p_v,
|
366 |
'scene_num': sc_c.get('scene_number', i_v + 1),
|
367 |
'key_action': sc_c.get('key_plot_beat', ''),
|
368 |
-
'duration':
|
369 |
})
|
370 |
-
status_vid.write(f"Adding Scene {sc_c.get('scene_number', i_v + 1)} (
|
371 |
-
# <<< ADDED/MODIFIED END >>>
|
372 |
else:
|
373 |
-
logger.warning(f"Skipping Scene {sc_c.get('scene_number', i_v
|
374 |
-
|
375 |
|
376 |
-
if
|
377 |
status_vid.write("Calling video engine...");
|
378 |
-
#
|
379 |
-
|
380 |
-
# images_data (list of dicts with 'path' and 'duration') and no longer needs duration_per_image.
|
381 |
-
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
|
382 |
-
images_data=img_data_vid, # Pass the list of dicts
|
383 |
overall_narration_path=st.session_state.overall_narration_audio_path,
|
384 |
output_filename="cinegen_ultra_animatic.mp4",
|
385 |
-
fps=24
|
386 |
)
|
387 |
-
|
388 |
-
|
389 |
-
else:
|
390 |
-
|
391 |
-
|
|
|
|
|
|
|
392 |
|
393 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
394 |
st.header("π¬ Generated Cinematic Animatic");
|
395 |
try:
|
396 |
with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read()
|
397 |
st.video(video_bytes, format="video/mp4")
|
398 |
-
|
399 |
-
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" )
|
400 |
except Exception as e: st.error(f"Error displaying video: {e}"); logger.error(f"Error displaying video: {e}", exc_info=True)
|
401 |
|
402 |
# --- Footer ---
|
|
|
5 |
from core.prompt_engineering import (
|
6 |
create_cinematic_treatment_prompt,
|
7 |
construct_dalle_prompt,
|
8 |
+
construct_text_to_video_prompt, # Import new function
|
9 |
create_narration_script_prompt_enhanced,
|
10 |
create_scene_regeneration_prompt,
|
11 |
create_visual_regeneration_prompt
|
|
|
18 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
19 |
logger = logging.getLogger(__name__)
|
20 |
|
|
|
21 |
# --- Global Definitions for New Features ---
|
22 |
SHOT_TYPES_OPTIONS = [
|
23 |
"Director's Choice", "Establishing Shot", "Long Shot", "Full Shot",
|
|
|
26 |
"Over the Shoulder", "Tracking Shot", "Dolly Zoom", "Crane Shot",
|
27 |
"Aerial Shot", "Static Shot", "Dutch Angle", "Whip Pan"
|
28 |
]
|
29 |
+
DEFAULT_SCENE_DURATION_SECS = 5
|
30 |
DEFAULT_SHOT_TYPE = "Director's Choice"
|
31 |
+
ASSET_TYPE_OPTIONS = ["Auto (Director's Choice)", "Image", "Video Clip"] # For user selection
|
32 |
|
33 |
|
34 |
# --- Global State Variables & API Key Setup ---
|
|
|
52 |
st.session_state.ELEVENLABS_API_KEY = load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY", "ElevenLabs")
|
53 |
st.session_state.PEXELS_API_KEY = load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY", "Pexels")
|
54 |
st.session_state.ELEVENLABS_VOICE_ID_CONFIG = load_api_key("ELEVENLABS_VOICE_ID", "ELEVENLABS_VOICE_ID", "ElevenLabs Voice ID")
|
55 |
+
st.session_state.RUNWAY_API_KEY = load_api_key("RUNWAY_API_KEY", "RUNWAY_API_KEY", "RunwayML") # Load Runway Key
|
56 |
|
57 |
if not st.session_state.GEMINI_API_KEY:
|
58 |
st.error("CRITICAL: Gemini API Key is essential and missing!"); logger.critical("Gemini API Key missing. Halting."); st.stop()
|
|
|
63 |
except Exception as e: st.error(f"Failed to init GeminiHandler: {e}"); logger.critical(f"GeminiHandler init failed: {e}", exc_info=True); st.stop()
|
64 |
|
65 |
try:
|
66 |
+
default_voice_id = "Rachel"
|
67 |
configured_voice_id = st.session_state.ELEVENLABS_VOICE_ID_CONFIG or default_voice_id
|
68 |
st.session_state.visual_engine = VisualEngine(
|
69 |
output_dir="temp_cinegen_media",
|
70 |
+
default_elevenlabs_voice_id=configured_voice_id
|
71 |
)
|
72 |
st.session_state.visual_engine.set_openai_api_key(st.session_state.OPENAI_API_KEY)
|
73 |
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)
|
74 |
st.session_state.visual_engine.set_pexels_api_key(st.session_state.PEXELS_API_KEY)
|
75 |
+
st.session_state.visual_engine.set_runway_api_key(st.session_state.RUNWAY_API_KEY) # Set Runway Key
|
76 |
logger.info("VisualEngine initialized and API keys set (or attempted).")
|
77 |
except Exception as e:
|
78 |
st.error(f"Failed to init VisualEngine or set its API keys: {e}"); logger.critical(f"VisualEngine init/key setting failed: {e}", exc_info=True)
|
|
|
80 |
st.session_state.services_initialized = True; logger.info("Service initialization sequence complete.")
|
81 |
|
82 |
# Initialize other session state variables
|
83 |
+
# <<< MODIFIED START >>> : Renamed generated_visual_paths to generated_scene_assets
|
84 |
for key, default_val in [
|
85 |
+
('story_treatment_scenes', []), ('scene_prompts', []), ('generated_scene_assets', []), # Stores dicts: {'path':..., 'type':...}
|
86 |
('video_path', None), ('character_definitions', {}), ('global_style_additions', ""),
|
87 |
('overall_narration_audio_path', None), ('narration_script_display', "")
|
88 |
]:
|
89 |
if key not in st.session_state: st.session_state[key] = default_val
|
90 |
|
91 |
def initialize_new_project():
|
92 |
+
st.session_state.story_treatment_scenes = []
|
93 |
+
st.session_state.scene_prompts = [] # Stores DALL-E or Text-to-Video prompts
|
94 |
+
st.session_state.generated_scene_assets = [] # Stores dicts {'path': ..., 'type': ..., 'error': ...}
|
95 |
st.session_state.video_path, st.session_state.overall_narration_audio_path, st.session_state.narration_script_display = None, None, ""
|
96 |
logger.info("New project initialized.")
|
97 |
+
# <<< MODIFIED END >>>
|
98 |
+
|
99 |
+
# <<< MODIFIED START >>> : Updated function to use generate_scene_asset
|
100 |
+
def generate_asset_for_scene_core(scene_index, scene_data, version=1, user_selected_asset_type="Auto (Director's Choice)"):
|
101 |
+
"""
|
102 |
+
Generates a visual asset (image or video clip) for a scene.
|
103 |
+
Returns True on success, False on failure.
|
104 |
+
"""
|
105 |
+
# Determine asset type: user override > Gemini suggestion > default to image
|
106 |
+
final_asset_type_decision = "image" # Default
|
107 |
+
gemini_suggested_type = scene_data.get('suggested_asset_type_κ°λ
', 'image').lower()
|
108 |
+
|
109 |
+
if user_selected_asset_type == "Image":
|
110 |
+
final_asset_type_decision = "image"
|
111 |
+
elif user_selected_asset_type == "Video Clip":
|
112 |
+
final_asset_type_decision = "video_clip"
|
113 |
+
elif user_selected_asset_type == "Auto (Director's Choice)":
|
114 |
+
final_asset_type_decision = gemini_suggested_type if gemini_suggested_type == "video_clip" else "image"
|
115 |
+
|
116 |
+
generate_as_video = (final_asset_type_decision == "video_clip")
|
117 |
+
prompt_text_for_visual = ""
|
118 |
+
|
119 |
+
if generate_as_video:
|
120 |
+
# Construct prompt for text-to-video (e.g., RunwayML)
|
121 |
+
prompt_text_for_visual = construct_text_to_video_prompt(scene_data, st.session_state.character_definitions, st.session_state.global_style_additions)
|
122 |
+
# Note: seed_image_path could be an enhancement if DALL-E image is generated first
|
123 |
+
else:
|
124 |
+
# Construct prompt for DALL-E (image)
|
125 |
+
prompt_text_for_visual = construct_dalle_prompt(scene_data, st.session_state.character_definitions, st.session_state.global_style_additions)
|
126 |
+
|
127 |
+
if not prompt_text_for_visual:
|
128 |
+
logger.error(f"Visual prompt construction failed for scene {scene_data.get('scene_number', scene_index+1)} (Type: {final_asset_type_decision})")
|
129 |
+
return False
|
130 |
+
|
131 |
+
# Ensure session state lists are long enough
|
132 |
+
while len(st.session_state.scene_prompts) <= scene_index: st.session_state.scene_prompts.append("")
|
133 |
+
while len(st.session_state.generated_scene_assets) <= scene_index: st.session_state.generated_scene_assets.append(None)
|
134 |
+
|
135 |
+
st.session_state.scene_prompts[scene_index] = prompt_text_for_visual
|
136 |
+
|
137 |
+
# Filename base (extension will be added by visual_engine)
|
138 |
+
filename_base = f"scene_{scene_data.get('scene_number', scene_index+1)}_asset_v{version}"
|
139 |
+
runway_duration = scene_data.get('video_clip_duration_estimate_secs_κ°λ
', DEFAULT_SCENE_DURATION_SECS)
|
140 |
+
if runway_duration <= 0 : runway_duration = DEFAULT_SCENE_DURATION_SECS # Ensure positive duration
|
141 |
+
|
142 |
+
asset_result = st.session_state.visual_engine.generate_scene_asset(
|
143 |
+
image_prompt_text=prompt_text_for_visual, # This is generic, used for DALL-E or T2V
|
144 |
+
scene_data=scene_data,
|
145 |
+
scene_identifier_filename_base=filename_base,
|
146 |
+
generate_as_video_clip=generate_as_video,
|
147 |
+
runway_target_duration=runway_duration
|
148 |
+
# input_image_for_runway=None # TODO: Could be an enhancement
|
149 |
+
)
|
150 |
+
|
151 |
+
st.session_state.generated_scene_assets[scene_index] = asset_result # Store the whole dict
|
152 |
+
|
153 |
+
if asset_result and not asset_result['error'] and asset_result.get('path') and os.path.exists(asset_result['path']):
|
154 |
+
logger.info(f"Asset ({asset_result.get('type')}) generated for Scene {scene_data.get('scene_number', scene_index+1)}: {os.path.basename(asset_result['path'])}")
|
155 |
+
return True
|
156 |
else:
|
157 |
+
err_msg = asset_result.get('error_message', 'Unknown error') if asset_result else 'Asset result is None'
|
158 |
+
logger.warning(f"Asset generation FAILED for Scene {scene_data.get('scene_number', scene_index+1)}. Type attempted: {final_asset_type_decision}. Path was: {asset_result.get('path') if asset_result else 'N/A'}. Error: {err_msg}")
|
159 |
+
# Store a failure state
|
160 |
+
st.session_state.generated_scene_assets[scene_index] = {'path': None, 'type': 'none', 'error': True, 'error_message': err_msg, 'prompt_used': prompt_text_for_visual}
|
161 |
+
return False
|
162 |
+
# <<< MODIFIED END >>>
|
163 |
|
164 |
# --- UI Sidebar ---
|
165 |
with st.sidebar:
|
|
|
168 |
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")
|
169 |
genre = st.selectbox("Primary Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller", "Western", "Post-Apocalyptic", "Historical Drama", "Surreal"], index=6, key="genre_main_v5")
|
170 |
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")
|
171 |
+
num_scenes = st.slider("Number of Key Scenes:", 1, 10, 2, key="num_scenes_main_v5")
|
172 |
creative_guidance_options = {"Standard Director": "standard", "Artistic Visionary": "more_artistic", "Experimental Storyteller": "experimental_narrative"}
|
173 |
selected_creative_guidance_key = st.selectbox("AI Creative Director Style:", options=list(creative_guidance_options.keys()), key="creative_guidance_select_v5")
|
174 |
actual_creative_guidance = creative_guidance_options[selected_creative_guidance_key]
|
|
|
180 |
with st.status("AI Director is envisioning your masterpiece...", expanded=True) as status:
|
181 |
try:
|
182 |
status.write("Phase 1: Gemini crafting cinematic treatment... π"); logger.info("Phase 1: Cinematic Treatment Gen.")
|
|
|
|
|
183 |
treatment_prompt = create_cinematic_treatment_prompt(user_idea, genre, mood, num_scenes, actual_creative_guidance)
|
184 |
+
treatment_result_json_raw = st.session_state.gemini_handler.generate_story_breakdown(treatment_prompt) # Expect list of dicts
|
185 |
+
if not isinstance(treatment_result_json_raw, list) or not treatment_result_json_raw: raise ValueError("Gemini returned invalid scene list format.")
|
186 |
|
|
|
|
|
187 |
processed_scenes = []
|
188 |
for scene_data_from_gemini in treatment_result_json_raw:
|
189 |
+
scene_data_from_gemini['user_shot_type'] = scene_data_from_gemini.get('PROACTIVE_camera_work_κ°λ
', DEFAULT_SHOT_TYPE) # Default from Gemini's suggestion
|
190 |
+
scene_data_from_gemini['user_scene_duration_secs'] = scene_data_from_gemini.get('video_clip_duration_estimate_secs_κ°λ
', DEFAULT_SCENE_DURATION_SECS)
|
191 |
+
if scene_data_from_gemini['user_scene_duration_secs'] <=0: scene_data_from_gemini['user_scene_duration_secs'] = DEFAULT_SCENE_DURATION_SECS
|
192 |
+
scene_data_from_gemini['user_selected_asset_type'] = "Auto (Director's Choice)" # Default for UI
|
193 |
processed_scenes.append(scene_data_from_gemini)
|
194 |
st.session_state.story_treatment_scenes = processed_scenes
|
|
|
195 |
|
196 |
num_gen_scenes = len(st.session_state.story_treatment_scenes)
|
197 |
+
# <<< MODIFIED START >>>
|
198 |
+
st.session_state.scene_prompts = [""]*num_gen_scenes
|
199 |
+
st.session_state.generated_scene_assets = [None]*num_gen_scenes # Initialize list for asset dicts
|
200 |
+
# <<< MODIFIED END >>>
|
201 |
logger.info(f"Phase 1 complete. {num_gen_scenes} scenes."); status.update(label="Treatment complete! β
Generating visuals...", state="running")
|
202 |
|
203 |
+
status.write("Phase 2: Creating visual assets (Image/Video)... πΌοΈπ¬"); logger.info("Phase 2: Visual Asset Gen.")
|
204 |
visual_successes = 0
|
205 |
for i, sc_data in enumerate(st.session_state.story_treatment_scenes):
|
206 |
sc_num_log = sc_data.get('scene_number', i+1)
|
207 |
+
status.write(f" Asset for Scene {sc_num_log}..."); logger.info(f" Processing asset for Scene {sc_num_log}.")
|
208 |
+
# <<< MODIFIED START >>> : Calling new function
|
209 |
+
if generate_asset_for_scene_core(i, sc_data, version=1): # Default to 'Auto' asset type for initial gen
|
210 |
+
visual_successes += 1
|
211 |
+
# <<< MODIFIED END >>>
|
212 |
|
213 |
+
current_status_label_ph2 = "Visual assets ready! "
|
214 |
next_step_state = "running"
|
215 |
if visual_successes == 0 and num_gen_scenes > 0:
|
216 |
+
logger.error("Visual asset gen failed for all scenes."); current_status_label_ph2 = "Asset gen FAILED for all scenes."; next_step_state="error";
|
217 |
status.update(label=current_status_label_ph2, state=next_step_state, expanded=True); st.stop()
|
218 |
elif visual_successes < num_gen_scenes:
|
219 |
+
logger.warning(f"Assets partially generated ({visual_successes}/{num_gen_scenes})."); current_status_label_ph2 = f"Assets partially generated ({visual_successes}/{num_gen_scenes}). "
|
220 |
status.update(label=f"{current_status_label_ph2}Generating narration script...", state=next_step_state)
|
221 |
if next_step_state == "error": st.stop()
|
222 |
|
223 |
status.write("Phase 3: Generating narration script..."); logger.info("Phase 3: Narration Script Gen.")
|
224 |
voice_style_for_prompt = st.session_state.get("selected_voice_style_for_generation", "cinematic_trailer")
|
225 |
narr_prompt = create_narration_script_prompt_enhanced(st.session_state.story_treatment_scenes, mood, genre, voice_style_for_prompt)
|
226 |
+
st.session_state.narration_script_display = st.session_state.gemini_handler.generate_image_prompt(narr_prompt) # This generates a string
|
227 |
logger.info("Narration script generated."); status.update(label="Narration script ready! Synthesizing voice...", state="running")
|
228 |
|
229 |
status.write("Phase 4: Synthesizing voice (ElevenLabs)... π"); logger.info("Phase 4: Voice Synthesis.")
|
|
|
241 |
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);
|
242 |
|
243 |
st.markdown("---"); st.markdown("### Fine-Tuning Options")
|
244 |
+
# ... (Character, Global Style, Voice expanders - no changes needed here for this fix) ...
|
245 |
with st.expander("Define Characters", expanded=False):
|
246 |
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...")
|
247 |
if st.button("Save Character", key="add_char_adv_ultra_v5"):
|
|
|
287 |
st.success(f"Narrator Voice ID set to: {final_voice_id_to_use}. Script Style: {sel_prompt_v_style_key}")
|
288 |
logger.info(f"User updated ElevenLabs Voice ID to: {final_voice_id_to_use}, Script Style: {sel_prompt_v_style_key}")
|
289 |
|
290 |
+
|
291 |
# --- Main Content Area ---
|
292 |
st.header("π¬ Cinematic Storyboard & Treatment")
|
293 |
if st.session_state.narration_script_display:
|
|
|
297 |
else:
|
298 |
for i_main, scene_content_display in enumerate(st.session_state.story_treatment_scenes):
|
299 |
scene_n = scene_content_display.get('scene_number', i_main + 1); scene_t = scene_content_display.get('scene_title', 'Untitled')
|
300 |
+
key_base = f"s{scene_n}_{''.join(filter(str.isalnum, scene_t[:10]))}_v5_{i_main}"
|
301 |
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']}")
|
302 |
st.subheader(f"SCENE {scene_n}: {scene_t.upper()}"); col_d, col_v = st.columns([0.45, 0.55])
|
|
|
|
|
|
|
303 |
|
304 |
+
with col_d: # Treatment and Controls Column
|
305 |
+
with st.expander("π Scene Treatment & Controls", expanded=True):
|
306 |
+
# Display scene textual details (emotional_beat, setting, etc.)
|
307 |
+
st.markdown(f"**Beat:** {scene_content_display.get('emotional_beat', 'N/A')}")
|
308 |
+
st.markdown(f"**Setting:** {scene_content_display.get('setting_description', 'N/A')}")
|
309 |
+
st.markdown(f"**Chars:** {', '.join(scene_content_display.get('characters_involved', ['N/A']))}")
|
310 |
+
st.markdown(f"**Focus Moment:** _{scene_content_display.get('character_focus_moment', 'N/A')}_")
|
311 |
+
st.markdown(f"**Plot Beat:** {scene_content_display.get('key_plot_beat', 'N/A')}")
|
312 |
+
st.markdown(f"**Dialogue Hook:** `\"{scene_content_display.get('suggested_dialogue_hook', '...')}\"`")
|
313 |
+
st.markdown("---")
|
314 |
+
st.markdown(f"**Dir. Visual Style:** _{scene_content_display.get('PROACTIVE_visual_style_κ°λ
', 'N/A')}_")
|
315 |
+
st.markdown(f"**Dir. Camera:** _{scene_content_display.get('PROACTIVE_camera_work_κ°λ
', 'N/A')}_")
|
316 |
+
st.markdown(f"**Dir. Sound:** _{scene_content_display.get('PROACTIVE_sound_design_κ°λ
', 'N/A')}_")
|
317 |
st.markdown("---")
|
318 |
+
st.markdown("##### Shot, Pacing & Asset Controls")
|
319 |
+
|
320 |
+
# User Shot Type (Camera Angle)
|
321 |
+
current_shot_type = st.session_state.story_treatment_scenes[i_main].get('user_shot_type', DEFAULT_SHOT_TYPE)
|
322 |
+
try: shot_type_index = SHOT_TYPES_OPTIONS.index(current_shot_type)
|
323 |
+
except ValueError: shot_type_index = SHOT_TYPES_OPTIONS.index(DEFAULT_SHOT_TYPE)
|
324 |
+
new_shot_type = st.selectbox("Dominant Shot Type:", options=SHOT_TYPES_OPTIONS, index=shot_type_index, key=f"shot_type_widget_{key_base}")
|
325 |
+
if new_shot_type != current_shot_type:
|
326 |
+
st.session_state.story_treatment_scenes[i_main]['user_shot_type'] = new_shot_type
|
327 |
+
# Consider if a re-run is needed or if DALL-E prompt should be updated based on this
|
328 |
+
|
329 |
+
# User Scene Duration
|
330 |
+
current_duration = st.session_state.story_treatment_scenes[i_main].get('user_scene_duration_secs', DEFAULT_SCENE_DURATION_SECS)
|
331 |
+
new_duration = st.number_input("Scene Duration (seconds):", min_value=1, max_value=300, value=current_duration, step=1, key=f"duration_widget_{key_base}")
|
332 |
+
if new_duration != current_duration:
|
333 |
+
st.session_state.story_treatment_scenes[i_main]['user_scene_duration_secs'] = new_duration
|
334 |
+
|
335 |
+
# <<< MODIFIED START >>> : User Asset Type Selection
|
336 |
+
current_user_asset_type = st.session_state.story_treatment_scenes[i_main].get('user_selected_asset_type', "Auto (Director's Choice)")
|
337 |
+
try: asset_type_idx = ASSET_TYPE_OPTIONS.index(current_user_asset_type)
|
338 |
+
except ValueError: asset_type_idx = 0 # Default to Auto
|
339 |
+
new_user_asset_type = st.selectbox("Asset Type Override:", ASSET_TYPE_OPTIONS, index=asset_type_idx, key=f"asset_type_sel_{key_base}",
|
340 |
+
help="Choose 'Image' or 'Video Clip'. 'Auto' uses Gemini's suggestion.")
|
341 |
+
if new_user_asset_type != current_user_asset_type:
|
342 |
+
st.session_state.story_treatment_scenes[i_main]['user_selected_asset_type'] = new_user_asset_type
|
343 |
+
# This change will be picked up by regeneration buttons
|
344 |
+
# <<< MODIFIED END >>>
|
345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
346 |
st.markdown("---")
|
347 |
+
# Display generated prompt for the asset
|
348 |
+
current_prompt_for_asset = st.session_state.scene_prompts[i_main] if i_main < len(st.session_state.scene_prompts) else None
|
349 |
+
if current_prompt_for_asset:
|
350 |
+
with st.popover("ποΈ View Asset Generation Prompt"):
|
351 |
+
st.markdown(f"**Prompt used for current asset:**"); st.code(current_prompt_for_asset, language='text')
|
352 |
|
|
|
|
|
|
|
353 |
pexels_q = scene_content_display.get('pexels_search_query_κ°λ
', None)
|
354 |
if pexels_q: st.caption(f"Pexels Fallback Query: `{pexels_q}`")
|
355 |
|
356 |
+
with col_v: # Visuals Column
|
357 |
+
# <<< MODIFIED START >>> : Display logic for different asset types
|
358 |
+
current_asset_data = st.session_state.generated_scene_assets[i_main] if i_main < len(st.session_state.generated_scene_assets) else None
|
359 |
+
if current_asset_data and not current_asset_data.get('error') and current_asset_data.get('path') and os.path.exists(current_asset_data['path']):
|
360 |
+
asset_path = current_asset_data['path']
|
361 |
+
asset_type = current_asset_data.get('type', 'image') # Default to image if type missing
|
362 |
+
if asset_type == 'image':
|
363 |
+
st.image(asset_path, caption=f"Scene {scene_n} ({asset_type}): {scene_t}")
|
364 |
+
elif asset_type == 'video':
|
365 |
+
try:
|
366 |
+
with open(asset_path, 'rb') as vf: video_bytes = vf.read()
|
367 |
+
st.video(video_bytes, format="video/mp4", start_time=0)
|
368 |
+
st.caption(f"Scene {scene_n} ({asset_type}): {scene_t}")
|
369 |
+
except Exception as e_vid:
|
370 |
+
st.error(f"Error displaying video {asset_path}: {e_vid}")
|
371 |
+
logger.error(f"Error displaying video {asset_path}: {e_vid}", exc_info=True)
|
372 |
+
else:
|
373 |
+
st.warning(f"Unknown asset type '{asset_type}' for Scene {scene_n}.")
|
374 |
+
else: # No asset, or error during generation
|
375 |
+
if st.session_state.story_treatment_scenes: # Check if treatment exists
|
376 |
+
error_msg = current_asset_data.get('error_message', 'Visual pending or failed.') if current_asset_data else 'Visual pending or failed.'
|
377 |
+
st.caption(error_msg)
|
378 |
+
# <<< MODIFIED END >>>
|
379 |
|
380 |
with st.popover(f"βοΈ Edit Scene {scene_n} Treatment"):
|
381 |
fb_script = st.text_area("Changes to treatment:", key=f"treat_fb_{key_base}", height=150)
|
382 |
if st.button(f"π Update Scene {scene_n} Treatment", key=f"regen_treat_btn_{key_base}"):
|
383 |
if fb_script:
|
384 |
+
with st.status(f"Updating Scene {scene_n} Treatment & Asset...", expanded=True) as s_treat_regen:
|
385 |
+
# Preserve user's shot type, duration, and asset type choices
|
386 |
+
user_shot_type = st.session_state.story_treatment_scenes[i_main]['user_shot_type']
|
387 |
+
user_duration = st.session_state.story_treatment_scenes[i_main]['user_scene_duration_secs']
|
388 |
+
user_asset_type_choice = st.session_state.story_treatment_scenes[i_main]['user_selected_asset_type']
|
389 |
+
|
390 |
prompt_text = create_scene_regeneration_prompt(scene_content_display, fb_script, st.session_state.story_treatment_scenes)
|
391 |
try:
|
392 |
+
updated_sc_data_from_gemini = st.session_state.gemini_handler.regenerate_scene_script_details(prompt_text)
|
393 |
+
# Merge, but prioritize user's UI choices for duration/shot/asset type
|
394 |
+
updated_sc_data = {**updated_sc_data_from_gemini} # Start with Gemini's new script
|
395 |
+
updated_sc_data['user_shot_type'] = user_shot_type
|
396 |
+
updated_sc_data['user_scene_duration_secs'] = user_duration
|
397 |
+
updated_sc_data['user_selected_asset_type'] = user_asset_type_choice
|
398 |
+
# Gemini might re-suggest asset type/duration, but user's direct settings take precedence for next gen
|
399 |
+
# We can log if Gemini's suggestion differs from user's explicit choice.
|
400 |
+
if updated_sc_data.get('suggested_asset_type_κ°λ
') != user_asset_type_choice and user_asset_type_choice != "Auto (Director's Choice)":
|
401 |
+
logger.info(f"Scene {scene_n}: User asset choice '{user_asset_type_choice}' overrides Gemini suggestion '{updated_sc_data.get('suggested_asset_type_κ°λ
')}'.")
|
402 |
|
|
|
|
|
|
|
|
|
403 |
|
404 |
st.session_state.story_treatment_scenes[i_main] = updated_sc_data
|
405 |
+
s_treat_regen.update(label="Treatment updated! Regenerating asset...", state="running")
|
406 |
|
|
|
407 |
v_num = 1
|
408 |
+
if current_asset_data and current_asset_data.get('path') and os.path.exists(current_asset_data['path']):
|
409 |
+
try: b,_=os.path.splitext(os.path.basename(current_asset_data['path'])); v_num = int(b.split('_v')[-1])+1 if '_v' in b else 2
|
410 |
+
except: v_num = 2
|
411 |
else: v_num = 1
|
412 |
+
# <<< MODIFIED START >>> : Call new function, pass user_selected_asset_type
|
413 |
+
if generate_asset_for_scene_core(i_main, updated_sc_data, version=v_num, user_selected_asset_type=user_asset_type_choice):
|
414 |
+
s_treat_regen.update(label="Treatment & Asset Updated! π", state="complete", expanded=False)
|
415 |
+
else: s_treat_regen.update(label="Treatment updated, asset failed.", state="complete", expanded=False)
|
416 |
+
# <<< MODIFIED END >>>
|
417 |
st.rerun()
|
418 |
except Exception as e_regen: s_treat_regen.update(label=f"Error: {e_regen}", state="error"); logger.error(f"Scene treatment regen error: {e_regen}", exc_info=True)
|
419 |
else: st.warning("Please provide feedback.")
|
420 |
|
421 |
with st.popover(f"π¨ Edit Scene {scene_n} Visual Prompt"):
|
422 |
+
prompt_to_edit = st.session_state.scene_prompts[i_main] if i_main < len(st.session_state.scene_prompts) else "No prompt generated yet."
|
423 |
+
st.caption("Current Asset Generation Prompt:"); st.code(prompt_to_edit, language='text')
|
424 |
+
fb_visual = st.text_area("Changes for asset generation prompt:", key=f"visual_fb_{key_base}", height=150)
|
425 |
+
if st.button(f"π Update Scene {scene_n} Asset", key=f"regen_visual_btn_{key_base}"):
|
426 |
if fb_visual:
|
427 |
+
with st.status(f"Refining prompt & asset for Scene {scene_n}...", expanded=True) as s_visual_regen:
|
428 |
+
user_asset_type_choice = st.session_state.story_treatment_scenes[i_main]['user_selected_asset_type']
|
429 |
+
is_video_prompt = (user_asset_type_choice == "Video Clip") or \
|
430 |
+
(user_asset_type_choice == "Auto (Director's Choice)" and \
|
431 |
+
scene_content_display.get('suggested_asset_type_κ°λ
') == 'video_clip')
|
432 |
+
|
433 |
+
# Note: Visual regeneration prompt is primarily for DALL-E (images).
|
434 |
+
# For video, we might need a different refinement strategy or just regenerate with the same prompt construction.
|
435 |
+
# For simplicity here, if it's a video, we'll regenerate the prompt using standard construction.
|
436 |
+
# If it's an image, we use Gemini to refine the DALL-E prompt.
|
437 |
+
new_asset_gen_prompt = ""
|
438 |
+
if not is_video_prompt : # Refining an image prompt
|
439 |
+
ref_req_prompt_for_gemini = create_visual_regeneration_prompt(prompt_to_edit, fb_visual, scene_content_display,
|
440 |
+
st.session_state.character_definitions, st.session_state.global_style_additions)
|
441 |
+
try:
|
442 |
+
new_asset_gen_prompt = st.session_state.gemini_handler.refine_image_prompt_from_feedback(ref_req_prompt_for_gemini)
|
443 |
+
st.session_state.scene_prompts[i_main] = new_asset_gen_prompt
|
444 |
+
s_visual_regen.update(label="Image prompt refined by Gemini! Regenerating asset...", state="running")
|
445 |
+
except Exception as e_gemini_refine:
|
446 |
+
s_visual_regen.update(label=f"Error refining prompt: {e_gemini_refine}", state="error");
|
447 |
+
logger.error(f"Visual prompt refinement error: {e_gemini_refine}", exc_info=True)
|
448 |
+
continue # Skip asset generation if prompt refinement failed
|
449 |
+
else: # For video, or auto choosing video, reconstruct the prompt
|
450 |
+
new_asset_gen_prompt = construct_text_to_video_prompt(scene_content_display, st.session_state.character_definitions, st.session_state.global_style_additions)
|
451 |
+
st.session_state.scene_prompts[i_main] = new_asset_gen_prompt
|
452 |
+
s_visual_regen.update(label="Video prompt reconstructed! Regenerating asset...", state="running")
|
453 |
+
|
454 |
+
|
455 |
+
v_num = 1
|
456 |
+
if current_asset_data and current_asset_data.get('path') and os.path.exists(current_asset_data['path']):
|
457 |
+
try: b,_=os.path.splitext(os.path.basename(current_asset_data['path'])); v_num = int(b.split('_v')[-1])+1 if '_v' in b else 2
|
458 |
+
except: v_num=2
|
459 |
+
else: v_num = 1
|
460 |
+
|
461 |
+
# <<< MODIFIED START >>> : Call new function
|
462 |
+
# Pass the current scene_content_display as its prompt might have changed.
|
463 |
+
# User asset type choice from the scene data for consistency
|
464 |
+
if generate_asset_for_scene_core(i_main, st.session_state.story_treatment_scenes[i_main], version=v_num, user_selected_asset_type=user_asset_type_choice):
|
465 |
+
s_visual_regen.update(label="Asset Updated! π", state="complete", expanded=False)
|
466 |
+
else: s_visual_regen.update(label="Prompt updated, asset regeneration failed.", state="complete", expanded=False)
|
467 |
+
# <<< MODIFIED END >>>
|
468 |
+
st.rerun()
|
469 |
else: st.warning("Please provide feedback.")
|
470 |
st.markdown("---")
|
471 |
|
472 |
+
# Video Assembly Button
|
473 |
+
# <<< MODIFIED START >>> : Check generated_scene_assets and use its data
|
474 |
+
if st.session_state.story_treatment_scenes and any(asset_info and not asset_info.get('error') and asset_info.get('path') for asset_info in st.session_state.generated_scene_assets if asset_info is not None):
|
475 |
if st.button("π¬ Assemble Narrated Cinematic Animatic", key="assemble_ultra_video_btn_v5", type="primary", use_container_width=True):
|
476 |
with st.status("Assembling Ultra Animatic...", expanded=True) as status_vid:
|
477 |
+
assets_for_video_assembly = []
|
478 |
for i_v, sc_c in enumerate(st.session_state.story_treatment_scenes):
|
479 |
+
asset_info = st.session_state.generated_scene_assets[i_v] if i_v < len(st.session_state.generated_scene_assets) else None
|
480 |
+
if asset_info and not asset_info.get('error') and asset_info.get('path') and os.path.exists(asset_info['path']):
|
481 |
+
assets_for_video_assembly.append({
|
482 |
+
'path': asset_info['path'],
|
483 |
+
'type': asset_info.get('type', 'image'), # Default to image if type missing
|
|
|
484 |
'scene_num': sc_c.get('scene_number', i_v + 1),
|
485 |
'key_action': sc_c.get('key_plot_beat', ''),
|
486 |
+
'duration': sc_c.get('user_scene_duration_secs', DEFAULT_SCENE_DURATION_SECS) # Use user-set duration
|
487 |
})
|
488 |
+
status_vid.write(f"Adding Scene {sc_c.get('scene_number', i_v + 1)} ({asset_info.get('type')}).")
|
|
|
489 |
else:
|
490 |
+
logger.warning(f"Skipping Scene {sc_c.get('scene_number', i_v+1)} for video: No valid asset.")
|
|
|
491 |
|
492 |
+
if assets_for_video_assembly:
|
493 |
status_vid.write("Calling video engine...");
|
494 |
+
st.session_state.video_path = st.session_state.visual_engine.assemble_animatic_from_assets( # Changed method name
|
495 |
+
asset_data_list=assets_for_video_assembly, # Pass the list of asset dicts
|
|
|
|
|
|
|
496 |
overall_narration_path=st.session_state.overall_narration_audio_path,
|
497 |
output_filename="cinegen_ultra_animatic.mp4",
|
498 |
+
fps=24
|
499 |
)
|
500 |
+
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
501 |
+
status_vid.update(label="Ultra animatic assembled! π", state="complete", expanded=False); st.balloons()
|
502 |
+
else:
|
503 |
+
status_vid.update(label="Video assembly failed. Check logs.", state="error", expanded=False); logger.error("Video assembly returned None or file does not exist.")
|
504 |
+
else:
|
505 |
+
status_vid.update(label="No valid assets for video assembly.", state="error", expanded=False); logger.warning("No valid assets found for video assembly.")
|
506 |
+
elif st.session_state.story_treatment_scenes: st.info("Generate visual assets before assembling video.")
|
507 |
+
# <<< MODIFIED END >>>
|
508 |
|
509 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
510 |
st.header("π¬ Generated Cinematic Animatic");
|
511 |
try:
|
512 |
with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read()
|
513 |
st.video(video_bytes, format="video/mp4")
|
514 |
+
st.download_button(label="Download Ultra Animatic", data=video_bytes, file_name=os.path.basename(st.session_state.video_path), mime="video/mp4", use_container_width=True, key="download_ultra_video_btn_v5" )
|
|
|
515 |
except Exception as e: st.error(f"Error displaying video: {e}"); logger.error(f"Error displaying video: {e}", exc_info=True)
|
516 |
|
517 |
# --- Footer ---
|