Update app.py
Browse files
app.py
CHANGED
@@ -1,54 +1,71 @@
|
|
1 |
# app.py
|
2 |
import streamlit as st
|
3 |
from core.gemini_handler import GeminiHandler
|
4 |
-
from core.visual_engine import VisualEngine # Your updated VisualEngine
|
5 |
from core.prompt_engineering import (
|
6 |
create_story_breakdown_prompt,
|
7 |
-
create_image_prompt_from_scene_data,
|
8 |
create_scene_regeneration_prompt,
|
9 |
-
create_visual_regeneration_prompt
|
10 |
)
|
11 |
import os
|
12 |
-
# import json # Only if you need to st.json for debugging
|
13 |
|
14 |
# --- Configuration & Initialization ---
|
15 |
st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
|
16 |
|
17 |
# --- Global State Variables (Using session state for persistence) ---
|
|
|
18 |
if 'GEMINI_API_KEY' not in st.session_state:
|
19 |
try:
|
20 |
st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
|
21 |
-
except KeyError:
|
22 |
if "GEMINI_API_KEY" in os.environ:
|
23 |
st.session_state.GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
|
24 |
else:
|
25 |
st.error("GEMINI_API_KEY not found in secrets or environment variables. Please add it.")
|
26 |
st.stop()
|
27 |
-
except Exception as e:
|
28 |
-
st.error(f"Error loading secrets: {e}")
|
29 |
st.stop()
|
30 |
|
31 |
-
|
32 |
if 'gemini_handler' not in st.session_state:
|
33 |
st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
|
|
|
|
|
34 |
if 'visual_engine' not in st.session_state:
|
35 |
st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
-
# Story and generated content
|
38 |
-
if 'story_scenes' not in st.session_state:
|
39 |
st.session_state.story_scenes = []
|
40 |
-
if 'scene_image_prompts' not in st.session_state:
|
41 |
st.session_state.scene_image_prompts = []
|
42 |
-
if 'generated_images_paths' not in st.session_state:
|
43 |
st.session_state.generated_images_paths = []
|
44 |
if 'video_path' not in st.session_state:
|
45 |
st.session_state.video_path = None
|
46 |
-
|
47 |
-
# For Character Consistency (placeholders)
|
48 |
if 'character_definitions' not in st.session_state:
|
49 |
st.session_state.character_definitions = {}
|
50 |
-
|
51 |
-
# For Style Transfer (placeholders)
|
52 |
if 'style_reference_description' not in st.session_state:
|
53 |
st.session_state.style_reference_description = None
|
54 |
|
@@ -58,40 +75,27 @@ def initialize_new_story():
|
|
58 |
st.session_state.scene_image_prompts = []
|
59 |
st.session_state.generated_images_paths = []
|
60 |
st.session_state.video_path = None
|
61 |
-
# Optional: Clear old media files
|
62 |
-
# if os.path.exists(st.session_state.visual_engine.output_dir):
|
63 |
-
# for f_name in os.listdir(st.session_state.visual_engine.output_dir):
|
64 |
-
# try:
|
65 |
-
# os.remove(os.path.join(st.session_state.visual_engine.output_dir, f_name))
|
66 |
-
# except OSError: # File might be in use by video player, handle gracefully
|
67 |
-
# print(f"Could not remove {f_name}, it might be in use.")
|
68 |
-
|
69 |
|
70 |
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
|
71 |
-
"""
|
72 |
-
Wrapper to generate the textual image prompt and then the actual visual.
|
73 |
-
Updates session state for prompts and image paths.
|
74 |
-
"""
|
75 |
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
|
76 |
-
st.info
|
|
|
|
|
77 |
|
78 |
textual_image_prompt = ""
|
79 |
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
|
80 |
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
|
81 |
-
st.caption(f"Using (potentially refined) prompt for Scene {scene_num_for_log}.")
|
82 |
else:
|
83 |
textual_image_prompt = create_image_prompt_from_scene_data(
|
84 |
scene_data,
|
85 |
st.session_state.character_definitions,
|
86 |
st.session_state.style_reference_description
|
87 |
)
|
88 |
-
st.caption(f"Generated initial prompt for Scene {scene_num_for_log}.")
|
89 |
|
90 |
if not textual_image_prompt:
|
91 |
st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.")
|
92 |
return False
|
93 |
|
94 |
-
# Update session state for the textual prompt if it's new or changed
|
95 |
if scene_index >= len(st.session_state.scene_image_prompts):
|
96 |
while len(st.session_state.scene_image_prompts) <= scene_index:
|
97 |
st.session_state.scene_image_prompts.append("")
|
@@ -99,21 +103,21 @@ def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=F
|
|
99 |
|
100 |
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
|
101 |
|
|
|
102 |
generated_image_path = st.session_state.visual_engine.generate_image_visual(
|
103 |
textual_image_prompt,
|
104 |
image_filename
|
105 |
)
|
106 |
|
107 |
-
# Ensure generated_images_paths is long enough
|
108 |
while len(st.session_state.generated_images_paths) <= scene_index:
|
109 |
st.session_state.generated_images_paths.append(None)
|
110 |
|
111 |
if generated_image_path and os.path.exists(generated_image_path):
|
112 |
-
st.success(f"Visual
|
113 |
st.session_state.generated_images_paths[scene_index] = generated_image_path
|
114 |
return True
|
115 |
else:
|
116 |
-
st.
|
117 |
st.session_state.generated_images_paths[scene_index] = None
|
118 |
return False
|
119 |
|
@@ -126,46 +130,52 @@ with st.sidebar:
|
|
126 |
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
|
127 |
num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider")
|
128 |
|
129 |
-
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
|
130 |
initialize_new_story()
|
131 |
if not user_idea.strip():
|
132 |
st.warning("Please enter a story idea.")
|
133 |
else:
|
134 |
-
with st.
|
|
|
135 |
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
|
136 |
try:
|
137 |
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
|
138 |
-
|
139 |
|
140 |
num_actual_scenes = len(st.session_state.story_scenes)
|
141 |
st.session_state.scene_image_prompts = [""] * num_actual_scenes
|
142 |
st.session_state.generated_images_paths = [None] * num_actual_scenes
|
143 |
|
144 |
except Exception as e:
|
145 |
-
|
146 |
st.session_state.story_scenes = []
|
|
|
147 |
|
148 |
-
|
149 |
-
|
150 |
success_count = 0
|
151 |
for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
|
|
|
152 |
if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
|
153 |
success_count +=1
|
|
|
154 |
if success_count == len(st.session_state.story_scenes):
|
155 |
-
|
|
|
|
|
156 |
else:
|
157 |
-
|
158 |
|
159 |
|
160 |
st.markdown("---")
|
161 |
st.markdown("### Advanced Options")
|
162 |
with st.expander("Character Consistency", expanded=False):
|
163 |
-
char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input")
|
164 |
-
char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc_adv_input", height=80)
|
165 |
-
if st.button("Add/Update Character", key="add_char_adv_btn"):
|
166 |
if char_name_input and char_desc_input:
|
167 |
-
st.session_state.character_definitions[char_name_input] = char_desc_input
|
168 |
-
st.success(f"Character '{char_name_input}' defined.")
|
169 |
else:
|
170 |
st.warning("Please provide both name and description.")
|
171 |
if st.session_state.character_definitions:
|
@@ -174,9 +184,9 @@ with st.sidebar:
|
|
174 |
st.markdown(f"**{char}:** _{desc}_")
|
175 |
|
176 |
with st.expander("Style Transfer", expanded=False):
|
177 |
-
style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_adv_input", height=80)
|
178 |
-
if st.button("Apply Textual Style", key="apply_style_adv_btn"):
|
179 |
-
st.session_state.style_reference_description = style_ref_text
|
180 |
st.success("Style reference applied. Re-generate visuals or full story to see changes.")
|
181 |
|
182 |
# --- Main Content Area ---
|
@@ -187,14 +197,12 @@ if not st.session_state.story_scenes:
|
|
187 |
else:
|
188 |
for i, scene_data_display in enumerate(st.session_state.story_scenes):
|
189 |
scene_num_display = scene_data_display.get('scene_number', i + 1)
|
190 |
-
# Create a more robust unique part for keys, sanitizing special characters
|
191 |
action_summary = scene_data_display.get('key_action', f"scene{i}")
|
192 |
key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
|
193 |
-
unique_key_part = key_part_raw.replace(" ", "_")[:20]
|
194 |
|
195 |
st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
|
196 |
-
|
197 |
-
col1, col2 = st.columns([2, 3]) # Ratio for text vs image
|
198 |
|
199 |
with col1:
|
200 |
with st.expander("Scene Details", expanded=True):
|
@@ -210,41 +218,42 @@ else:
|
|
210 |
with st.popover("View Image Prompt Text"):
|
211 |
st.markdown(f"**Textual Prompt for Image Generation:**")
|
212 |
st.code(current_textual_prompt, language='text')
|
213 |
-
|
214 |
with col2:
|
215 |
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
|
216 |
if current_image_path and os.path.exists(current_image_path):
|
217 |
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
|
218 |
else:
|
219 |
if st.session_state.story_scenes:
|
220 |
-
st.
|
221 |
|
222 |
-
# Removed 'key' argument from st.popover calls
|
223 |
with st.popover(f"βοΈ Edit Scene {scene_num_display} Script"):
|
224 |
feedback_script = st.text_area("Describe changes to script details:",
|
225 |
key=f"script_feedback_{unique_key_part}_{i}", height=100)
|
226 |
if st.button(f"π Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
|
227 |
if feedback_script:
|
228 |
-
with st.
|
229 |
regen_prompt = create_scene_regeneration_prompt(
|
230 |
scene_data_display, feedback_script, st.session_state.story_scenes
|
231 |
)
|
232 |
try:
|
233 |
updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
|
234 |
st.session_state.story_scenes[i] = updated_scene_data
|
235 |
-
|
236 |
|
237 |
version = 1
|
238 |
if current_image_path:
|
239 |
try:
|
240 |
base, _ = os.path.splitext(os.path.basename(current_image_path))
|
241 |
if '_v' in base: version = int(base.split('_v')[-1]) + 1
|
242 |
-
except
|
243 |
|
244 |
-
generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version)
|
|
|
|
|
|
|
245 |
st.rerun()
|
246 |
except Exception as e:
|
247 |
-
|
248 |
else:
|
249 |
st.warning("Please provide feedback for script regeneration.")
|
250 |
|
@@ -257,48 +266,50 @@ else:
|
|
257 |
key=f"visual_feedback_{unique_key_part}_{i}", height=100)
|
258 |
if st.button(f"π Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
|
259 |
if feedback_visual:
|
260 |
-
with st.
|
261 |
prompt_refinement_request = create_visual_regeneration_prompt(
|
262 |
prompt_for_edit_visuals, feedback_visual, scene_data_display
|
263 |
)
|
264 |
try:
|
265 |
refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
|
266 |
st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
|
267 |
-
|
268 |
|
269 |
version = 1
|
270 |
if current_image_path:
|
271 |
try:
|
272 |
base, _ = os.path.splitext(os.path.basename(current_image_path))
|
273 |
if '_v' in base: version = int(base.split('_v')[-1]) + 1
|
274 |
-
except
|
275 |
|
276 |
-
generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version)
|
|
|
|
|
|
|
277 |
st.rerun()
|
278 |
except Exception as e:
|
279 |
-
|
280 |
else:
|
281 |
st.warning("Please provide feedback for visual regeneration.")
|
282 |
st.markdown("---")
|
283 |
|
284 |
-
# Video Generation Button
|
285 |
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
|
286 |
-
if st.button("π¬ Assemble Animatic Video", key="assemble_video_btn", type="primary"):
|
287 |
-
with st.
|
288 |
valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
|
289 |
if valid_image_paths_for_video:
|
290 |
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
|
291 |
valid_image_paths_for_video,
|
292 |
output_filename="cinegen_pro_animatic.mp4",
|
293 |
-
duration_per_image=3
|
294 |
)
|
295 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
296 |
-
|
297 |
st.balloons()
|
298 |
else:
|
299 |
-
|
300 |
else:
|
301 |
-
|
302 |
elif st.session_state.story_scenes:
|
303 |
st.info("Generate visuals for scenes before assembling the video.")
|
304 |
|
@@ -308,17 +319,15 @@ else:
|
|
308 |
with open(st.session_state.video_path, 'rb') as video_file_obj:
|
309 |
video_bytes_content = video_file_obj.read()
|
310 |
st.video(video_bytes_content)
|
311 |
-
# Download button
|
312 |
with open(st.session_state.video_path, "rb") as fp_download_video:
|
313 |
st.download_button(
|
314 |
-
label="Download Animatic Video",
|
315 |
-
|
316 |
-
|
317 |
-
mime="video/mp4"
|
318 |
)
|
319 |
except Exception as e:
|
320 |
st.error(f"Error displaying or preparing video for download: {e}")
|
321 |
|
322 |
# --- Footer ---
|
323 |
st.sidebar.markdown("---")
|
324 |
-
st.sidebar.caption("CineGen AI Pro | Powered by Gemini & Streamlit")
|
|
|
1 |
# app.py
|
2 |
import streamlit as st
|
3 |
from core.gemini_handler import GeminiHandler
|
4 |
+
from core.visual_engine import VisualEngine # Your updated VisualEngine with DALL-E prep
|
5 |
from core.prompt_engineering import (
|
6 |
create_story_breakdown_prompt,
|
7 |
+
create_image_prompt_from_scene_data,
|
8 |
create_scene_regeneration_prompt,
|
9 |
+
create_visual_regeneration_prompt
|
10 |
)
|
11 |
import os
|
12 |
+
# import json # Only if you need to st.json for debugging
|
13 |
|
14 |
# --- Configuration & Initialization ---
|
15 |
st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
|
16 |
|
17 |
# --- Global State Variables (Using session state for persistence) ---
|
18 |
+
# Load Gemini API Key
|
19 |
if 'GEMINI_API_KEY' not in st.session_state:
|
20 |
try:
|
21 |
st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
|
22 |
+
except KeyError:
|
23 |
if "GEMINI_API_KEY" in os.environ:
|
24 |
st.session_state.GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
|
25 |
else:
|
26 |
st.error("GEMINI_API_KEY not found in secrets or environment variables. Please add it.")
|
27 |
st.stop()
|
28 |
+
except Exception as e:
|
29 |
+
st.error(f"Error loading Gemini API Key from secrets: {e}")
|
30 |
st.stop()
|
31 |
|
32 |
+
# Initialize Gemini Handler
|
33 |
if 'gemini_handler' not in st.session_state:
|
34 |
st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
|
35 |
+
|
36 |
+
# Initialize Visual Engine and set OpenAI API Key
|
37 |
if 'visual_engine' not in st.session_state:
|
38 |
st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
|
39 |
+
# --- TRY TO SET OPENAI API KEY FOR VISUAL ENGINE ---
|
40 |
+
openai_key_from_secrets = None
|
41 |
+
try:
|
42 |
+
if "OPENAI_API_KEY" in st.secrets:
|
43 |
+
openai_key_from_secrets = st.secrets["OPENAI_API_KEY"]
|
44 |
+
except Exception as e:
|
45 |
+
print(f"Could not access st.secrets for OPENAI_API_KEY: {e}") # Log, don't stop app
|
46 |
+
|
47 |
+
if not openai_key_from_secrets and "OPENAI_API_KEY" in os.environ: # Fallback to env var
|
48 |
+
openai_key_from_secrets = os.environ["OPENAI_API_KEY"]
|
49 |
+
|
50 |
+
if openai_key_from_secrets:
|
51 |
+
st.session_state.visual_engine.set_openai_api_key(openai_key_from_secrets)
|
52 |
+
# st.sidebar.info("OpenAI API Key loaded. DALL-E generation enabled.") # Optional UI feedback
|
53 |
+
else:
|
54 |
+
st.session_state.visual_engine.set_openai_api_key(None)
|
55 |
+
st.sidebar.warning("OpenAI API Key for DALL-E not found. Visuals will be placeholders.")
|
56 |
+
# --- END OF OPENAI API KEY SETUP ---
|
57 |
|
58 |
+
# Story and generated content state
|
59 |
+
if 'story_scenes' not in st.session_state:
|
60 |
st.session_state.story_scenes = []
|
61 |
+
if 'scene_image_prompts' not in st.session_state:
|
62 |
st.session_state.scene_image_prompts = []
|
63 |
+
if 'generated_images_paths' not in st.session_state:
|
64 |
st.session_state.generated_images_paths = []
|
65 |
if 'video_path' not in st.session_state:
|
66 |
st.session_state.video_path = None
|
|
|
|
|
67 |
if 'character_definitions' not in st.session_state:
|
68 |
st.session_state.character_definitions = {}
|
|
|
|
|
69 |
if 'style_reference_description' not in st.session_state:
|
70 |
st.session_state.style_reference_description = None
|
71 |
|
|
|
75 |
st.session_state.scene_image_prompts = []
|
76 |
st.session_state.generated_images_paths = []
|
77 |
st.session_state.video_path = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
|
79 |
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
|
|
|
|
|
|
|
|
|
80 |
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
|
81 |
+
# This st.info will appear briefly in the main app area below the "Generate" button.
|
82 |
+
# Consider moving detailed progress to sidebar or using st.status.
|
83 |
+
# st.info(f"Processing visual concept for Scene {scene_num_for_log} (v{version_count})...")
|
84 |
|
85 |
textual_image_prompt = ""
|
86 |
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
|
87 |
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
|
|
|
88 |
else:
|
89 |
textual_image_prompt = create_image_prompt_from_scene_data(
|
90 |
scene_data,
|
91 |
st.session_state.character_definitions,
|
92 |
st.session_state.style_reference_description
|
93 |
)
|
|
|
94 |
|
95 |
if not textual_image_prompt:
|
96 |
st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.")
|
97 |
return False
|
98 |
|
|
|
99 |
if scene_index >= len(st.session_state.scene_image_prompts):
|
100 |
while len(st.session_state.scene_image_prompts) <= scene_index:
|
101 |
st.session_state.scene_image_prompts.append("")
|
|
|
103 |
|
104 |
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
|
105 |
|
106 |
+
# The actual generation (AI or placeholder) happens here
|
107 |
generated_image_path = st.session_state.visual_engine.generate_image_visual(
|
108 |
textual_image_prompt,
|
109 |
image_filename
|
110 |
)
|
111 |
|
|
|
112 |
while len(st.session_state.generated_images_paths) <= scene_index:
|
113 |
st.session_state.generated_images_paths.append(None)
|
114 |
|
115 |
if generated_image_path and os.path.exists(generated_image_path):
|
116 |
+
# st.success(f"Visual for Scene {scene_num_for_log} (v{version_count}) ready.") # Can be noisy
|
117 |
st.session_state.generated_images_paths[scene_index] = generated_image_path
|
118 |
return True
|
119 |
else:
|
120 |
+
st.warning(f"Visual generation failed or path invalid for Scene {scene_num_for_log} (v{version_count}).")
|
121 |
st.session_state.generated_images_paths[scene_index] = None
|
122 |
return False
|
123 |
|
|
|
130 |
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
|
131 |
num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider")
|
132 |
|
133 |
+
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
|
134 |
initialize_new_story()
|
135 |
if not user_idea.strip():
|
136 |
st.warning("Please enter a story idea.")
|
137 |
else:
|
138 |
+
with st.status("Generating story...", expanded=True) as status_main:
|
139 |
+
st.write("Phase 1: Gemini is drafting the script & scene breakdown... π")
|
140 |
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
|
141 |
try:
|
142 |
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
|
143 |
+
status_main.update(label="Script breakdown complete! β
", state="running")
|
144 |
|
145 |
num_actual_scenes = len(st.session_state.story_scenes)
|
146 |
st.session_state.scene_image_prompts = [""] * num_actual_scenes
|
147 |
st.session_state.generated_images_paths = [None] * num_actual_scenes
|
148 |
|
149 |
except Exception as e:
|
150 |
+
status_main.update(label=f"Failed to generate story breakdown: {e}", state="error")
|
151 |
st.session_state.story_scenes = []
|
152 |
+
st.stop() # Stop further execution if script fails
|
153 |
|
154 |
+
if st.session_state.story_scenes:
|
155 |
+
st.write("Phase 2: Generating initial visual concepts... πΌοΈ")
|
156 |
success_count = 0
|
157 |
for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
|
158 |
+
st.write(f"Generating visual for Scene {scene_data_loop_var.get('scene_number', i_loop + 1)}...")
|
159 |
if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
|
160 |
success_count +=1
|
161 |
+
|
162 |
if success_count == len(st.session_state.story_scenes):
|
163 |
+
status_main.update(label="All concepts generated successfully! π", state="complete", expanded=False)
|
164 |
+
elif success_count > 0:
|
165 |
+
status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals generated. Some failed.", state="warning", expanded=False)
|
166 |
else:
|
167 |
+
status_main.update(label="Visual concept generation failed for all scenes.", state="error", expanded=False)
|
168 |
|
169 |
|
170 |
st.markdown("---")
|
171 |
st.markdown("### Advanced Options")
|
172 |
with st.expander("Character Consistency", expanded=False):
|
173 |
+
char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input")
|
174 |
+
char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc_adv_input", height=80)
|
175 |
+
if st.button("Add/Update Character", key="add_char_adv_btn"):
|
176 |
if char_name_input and char_desc_input:
|
177 |
+
st.session_state.character_definitions[char_name_input.strip()] = char_desc_input.strip()
|
178 |
+
st.success(f"Character '{char_name_input.strip()}' defined.")
|
179 |
else:
|
180 |
st.warning("Please provide both name and description.")
|
181 |
if st.session_state.character_definitions:
|
|
|
184 |
st.markdown(f"**{char}:** _{desc}_")
|
185 |
|
186 |
with st.expander("Style Transfer", expanded=False):
|
187 |
+
style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_adv_input", height=80)
|
188 |
+
if st.button("Apply Textual Style", key="apply_style_adv_btn"):
|
189 |
+
st.session_state.style_reference_description = style_ref_text.strip()
|
190 |
st.success("Style reference applied. Re-generate visuals or full story to see changes.")
|
191 |
|
192 |
# --- Main Content Area ---
|
|
|
197 |
else:
|
198 |
for i, scene_data_display in enumerate(st.session_state.story_scenes):
|
199 |
scene_num_display = scene_data_display.get('scene_number', i + 1)
|
|
|
200 |
action_summary = scene_data_display.get('key_action', f"scene{i}")
|
201 |
key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
|
202 |
+
unique_key_part = key_part_raw.replace(" ", "_")[:20]
|
203 |
|
204 |
st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
|
205 |
+
col1, col2 = st.columns([2, 3])
|
|
|
206 |
|
207 |
with col1:
|
208 |
with st.expander("Scene Details", expanded=True):
|
|
|
218 |
with st.popover("View Image Prompt Text"):
|
219 |
st.markdown(f"**Textual Prompt for Image Generation:**")
|
220 |
st.code(current_textual_prompt, language='text')
|
|
|
221 |
with col2:
|
222 |
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
|
223 |
if current_image_path and os.path.exists(current_image_path):
|
224 |
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
|
225 |
else:
|
226 |
if st.session_state.story_scenes:
|
227 |
+
st.caption("Visual for this scene is pending or failed to generate.")
|
228 |
|
|
|
229 |
with st.popover(f"βοΈ Edit Scene {scene_num_display} Script"):
|
230 |
feedback_script = st.text_area("Describe changes to script details:",
|
231 |
key=f"script_feedback_{unique_key_part}_{i}", height=100)
|
232 |
if st.button(f"π Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
|
233 |
if feedback_script:
|
234 |
+
with st.status(f"Rewriting Scene {scene_num_display} script...", expanded=True) as status_script_regen:
|
235 |
regen_prompt = create_scene_regeneration_prompt(
|
236 |
scene_data_display, feedback_script, st.session_state.story_scenes
|
237 |
)
|
238 |
try:
|
239 |
updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
|
240 |
st.session_state.story_scenes[i] = updated_scene_data
|
241 |
+
status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
|
242 |
|
243 |
version = 1
|
244 |
if current_image_path:
|
245 |
try:
|
246 |
base, _ = os.path.splitext(os.path.basename(current_image_path))
|
247 |
if '_v' in base: version = int(base.split('_v')[-1]) + 1
|
248 |
+
except : pass
|
249 |
|
250 |
+
if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version):
|
251 |
+
status_script_regen.update(label="Scene script & visual updated! π", state="complete", expanded=False)
|
252 |
+
else:
|
253 |
+
status_script_regen.update(label="Script updated, but visual failed.", state="warning", expanded=False)
|
254 |
st.rerun()
|
255 |
except Exception as e:
|
256 |
+
status_script_regen.update(label=f"Error regenerating scene script: {e}", state="error")
|
257 |
else:
|
258 |
st.warning("Please provide feedback for script regeneration.")
|
259 |
|
|
|
266 |
key=f"visual_feedback_{unique_key_part}_{i}", height=100)
|
267 |
if st.button(f"π Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
|
268 |
if feedback_visual:
|
269 |
+
with st.status(f"Refining prompt & regenerating visual for Scene {scene_num_display}...", expanded=True) as status_visual_regen:
|
270 |
prompt_refinement_request = create_visual_regeneration_prompt(
|
271 |
prompt_for_edit_visuals, feedback_visual, scene_data_display
|
272 |
)
|
273 |
try:
|
274 |
refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
|
275 |
st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
|
276 |
+
status_visual_regen.update(label="Image prompt refined! Regenerating visual...", state="running")
|
277 |
|
278 |
version = 1
|
279 |
if current_image_path:
|
280 |
try:
|
281 |
base, _ = os.path.splitext(os.path.basename(current_image_path))
|
282 |
if '_v' in base: version = int(base.split('_v')[-1]) + 1
|
283 |
+
except : pass
|
284 |
|
285 |
+
if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): # Pass current scene_data, as only prompt changed
|
286 |
+
status_visual_regen.update(label="Visual for Scene updated! π", state="complete", expanded=False)
|
287 |
+
else:
|
288 |
+
status_visual_regen.update(label="Prompt refined, but visual failed.", state="warning", expanded=False)
|
289 |
st.rerun()
|
290 |
except Exception as e:
|
291 |
+
status_visual_regen.update(label=f"Error refining prompt or regenerating visual: {e}", state="error")
|
292 |
else:
|
293 |
st.warning("Please provide feedback for visual regeneration.")
|
294 |
st.markdown("---")
|
295 |
|
|
|
296 |
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
|
297 |
+
if st.button("π¬ Assemble Animatic Video", key="assemble_video_btn", type="primary", use_container_width=True):
|
298 |
+
with st.status("Assembling video...", expanded=False) as status_video:
|
299 |
valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
|
300 |
if valid_image_paths_for_video:
|
301 |
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
|
302 |
valid_image_paths_for_video,
|
303 |
output_filename="cinegen_pro_animatic.mp4",
|
304 |
+
duration_per_image=3 # Adjust as needed
|
305 |
)
|
306 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
307 |
+
status_video.update(label="Animatic video assembled! π", state="complete")
|
308 |
st.balloons()
|
309 |
else:
|
310 |
+
status_video.update(label="Video assembly failed. Check logs.", state="error")
|
311 |
else:
|
312 |
+
status_video.update(label="No valid images to assemble video.", state="error")
|
313 |
elif st.session_state.story_scenes:
|
314 |
st.info("Generate visuals for scenes before assembling the video.")
|
315 |
|
|
|
319 |
with open(st.session_state.video_path, 'rb') as video_file_obj:
|
320 |
video_bytes_content = video_file_obj.read()
|
321 |
st.video(video_bytes_content)
|
|
|
322 |
with open(st.session_state.video_path, "rb") as fp_download_video:
|
323 |
st.download_button(
|
324 |
+
label="Download Animatic Video", data=fp_download_video,
|
325 |
+
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
|
326 |
+
use_container_width=True
|
|
|
327 |
)
|
328 |
except Exception as e:
|
329 |
st.error(f"Error displaying or preparing video for download: {e}")
|
330 |
|
331 |
# --- Footer ---
|
332 |
st.sidebar.markdown("---")
|
333 |
+
st.sidebar.caption("CineGen AI Pro | Powered by Gemini & Streamlit.")
|