Update app.py
Browse files
app.py
CHANGED
@@ -5,100 +5,102 @@ from core.visual_engine import 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 |
|
13 |
# --- Configuration & Initialization ---
|
14 |
-
st.set_page_config(page_title="CineGen AI
|
15 |
|
16 |
-
# --- Global State Variables ---
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
22 |
|
|
|
|
|
|
|
23 |
if 'gemini_handler' not in st.session_state:
|
24 |
st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
|
25 |
|
26 |
if 'visual_engine' not in st.session_state:
|
27 |
st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
except AttributeError: print("st.secrets not available. Checking env for OpenAI key.")
|
32 |
-
except Exception as e: print(f"Error accessing st.secrets for OPENAI_API_KEY: {e}")
|
33 |
-
if not openai_key and "OPENAI_API_KEY" in os.environ: openai_key = os.environ["OPENAI_API_KEY"]
|
34 |
-
st.session_state.visual_engine.set_openai_api_key(openai_key)
|
35 |
-
if not openai_key and 'openai_warning_shown' not in st.session_state:
|
36 |
-
st.sidebar.warning("OpenAI Key missing. DALL-E disabled, using placeholders.")
|
37 |
-
st.session_state.openai_warning_shown = True # Show only once per session
|
38 |
-
elif openai_key and 'openai_success_shown' not in st.session_state:
|
39 |
-
# st.sidebar.caption("OpenAI Key loaded. DALL-E ready.") # Can be a bit noisy
|
40 |
-
st.session_state.openai_success_shown = True
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
-
if 'story_scenes' not in st.session_state: st.session_state.story_scenes = []
|
44 |
-
if 'scene_image_prompts' not in st.session_state: st.session_state.scene_image_prompts = []
|
45 |
-
if 'generated_images_paths' not in st.session_state: st.session_state.generated_images_paths = []
|
46 |
-
if 'video_path' not in st.session_state: st.session_state.video_path = None
|
47 |
-
if 'character_definitions' not in st.session_state: st.session_state.character_definitions = {}
|
48 |
-
if 'style_reference_description' not in st.session_state: st.session_state.style_reference_description = ""
|
49 |
|
50 |
-
|
51 |
-
def initialize_new_story(): # Remains the same
|
52 |
st.session_state.story_scenes = []
|
53 |
st.session_state.scene_image_prompts = []
|
54 |
st.session_state.generated_images_paths = []
|
55 |
st.session_state.video_path = None
|
|
|
|
|
|
|
56 |
|
57 |
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
|
|
|
|
|
58 |
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
|
59 |
textual_image_prompt = ""
|
60 |
-
|
61 |
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
|
62 |
-
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
|
63 |
-
else:
|
64 |
textual_image_prompt = create_image_prompt_from_scene_data(
|
65 |
-
scene_data,
|
66 |
-
st.session_state.character_definitions,
|
67 |
-
st.session_state.style_reference_description
|
68 |
-
)
|
69 |
if not textual_image_prompt: return False
|
70 |
-
|
71 |
-
# Update session state with the textual prompt used (might be initial or refined)
|
72 |
if scene_index >= len(st.session_state.scene_image_prompts):
|
73 |
while len(st.session_state.scene_image_prompts) <= scene_index: st.session_state.scene_image_prompts.append("")
|
74 |
st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
|
75 |
-
|
76 |
-
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
|
77 |
-
generated_image_path = st.session_state.visual_engine.generate_image_visual(textual_image_prompt, image_filename)
|
78 |
|
|
|
|
|
|
|
|
|
79 |
while len(st.session_state.generated_images_paths) <= scene_index: st.session_state.generated_images_paths.append(None)
|
80 |
if generated_image_path and os.path.exists(generated_image_path):
|
81 |
-
st.session_state.generated_images_paths[scene_index] = generated_image_path
|
82 |
-
return True
|
83 |
else:
|
84 |
-
st.session_state.generated_images_paths[scene_index] = None
|
85 |
-
|
86 |
|
87 |
# --- UI Sidebar ---
|
88 |
with st.sidebar:
|
89 |
-
st.title("π¬ CineGen AI
|
90 |
st.markdown("### Creative Controls")
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
|
|
95 |
|
96 |
-
|
|
|
97 |
initialize_new_story()
|
98 |
if not user_idea.strip(): st.warning("Please enter a story idea.")
|
99 |
else:
|
100 |
-
with st.status("
|
101 |
-
st.write("Phase 1:
|
|
|
102 |
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
|
103 |
try:
|
104 |
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
|
@@ -106,117 +108,122 @@ with st.sidebar:
|
|
106 |
num_actual_scenes = len(st.session_state.story_scenes)
|
107 |
st.session_state.scene_image_prompts = [""] * num_actual_scenes
|
108 |
st.session_state.generated_images_paths = [None] * num_actual_scenes
|
109 |
-
except Exception as e:
|
110 |
-
status_main.update(label=f"Script generation failed: {e}", state="error"); st.stop()
|
111 |
|
112 |
if st.session_state.story_scenes:
|
113 |
-
st.write("Phase 2: Creating visuals
|
114 |
-
|
115 |
for i, scene_data in enumerate(st.session_state.story_scenes):
|
116 |
-
st.write(f" Processing Scene {scene_data.get('scene_number', i + 1)}...")
|
117 |
-
if generate_visual_for_scene_wrapper(i, scene_data, version_count=1):
|
|
|
|
|
|
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
|
123 |
-
|
124 |
-
|
|
|
|
|
125 |
with st.expander("Character Consistency", expanded=False):
|
126 |
char_name_input = st.text_input("Character Name (e.g., Jax)", key="char_name_adv_input")
|
127 |
char_desc_input = st.text_area("Character Description (e.g., 'male astronaut, rugged, dark short hair, blue eyes, wearing a worn white and orange spacesuit with a cracked visor helmet slung at his hip')", key="char_desc_adv_input", height=120)
|
128 |
if st.button("Add/Update Character", key="add_char_adv_btn"):
|
129 |
-
if char_name_input and char_desc_input:
|
130 |
-
st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip() # Use lower for key
|
131 |
-
st.success(f"Character '{char_name_input.strip()}' defined.")
|
132 |
else: st.warning("Provide both name and description.")
|
133 |
if st.session_state.character_definitions:
|
134 |
st.caption("Defined Characters:")
|
135 |
for char_key, desc_val in st.session_state.character_definitions.items(): st.markdown(f"**{char_key.title()}:** _{desc_val}_")
|
136 |
-
|
137 |
-
with st.expander("Style Controls", expanded=True):
|
138 |
-
predefined_styles = {
|
139 |
-
"Default (Cinematic Photorealism)": "",
|
140 |
-
"Gritty Neo-Noir": "neo-noir aesthetic, gritty realism, deep shadows, rain-slicked streets, desaturated colors with vibrant neon highlights, anamorphic lens flares, film grain",
|
141 |
-
"Epic Fantasy Matte Painting": "epic fantasy matte painting style, vast landscapes, dramatic skies, painterly textures, rich colors, Lord of the Rings inspired, highly detailed",
|
142 |
-
"Impressionistic Dream": "impressionistic oil painting style, visible brushstrokes, soft focus, dreamlike atmosphere, pastel color palette, ethereal lighting",
|
143 |
-
"Vintage Sci-Fi (70s/80s Film)": "retro sci-fi aesthetic, analog film look, subtle film grain, practical effects feel, slightly desaturated colors, chrome and plastic textures, classic 70s/80s anamorphic lens flares, detailed spaceship interiors or alien landscapes",
|
144 |
-
"Modern Anime Key Visual": "high-detail modern anime key visual style, dynamic compositions, vibrant saturated colors, crisp cel-shaded look with intricate lighting and deep shadows, expressive character designs with detailed eyes"
|
145 |
-
}
|
146 |
selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv")
|
147 |
-
custom_keywords = st.text_area("Add Custom Style Keywords:", key="custom_style_keywords_adv_input", height=80, placeholder="e.g., 'Dutch angle, fisheye lens
|
148 |
-
|
149 |
current_style_desc = st.session_state.style_reference_description
|
150 |
if st.button("Apply & Set Styles", key="apply_styles_adv_btn"):
|
151 |
-
final_desc = predefined_styles[selected_preset]
|
152 |
-
if custom_keywords.strip():
|
153 |
-
|
154 |
-
|
155 |
-
current_style_desc = final_desc.strip() # Update for immediate display
|
156 |
-
if current_style_desc: st.success("Styles applied! Regenerate story or visuals.")
|
157 |
else: st.info("Default style (no specific additions).")
|
158 |
-
|
159 |
-
if current_style_desc: st.caption(f"Active style prompt additions: \"{current_style_desc}\"")
|
160 |
else: st.caption("No specific style prompt additions active.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
|
|
|
|
|
|
|
162 |
|
163 |
-
# --- Main Content Area (largely same as previous full version, with minor tweaks) ---
|
164 |
st.header("π Cinematic Storyboard")
|
|
|
|
|
|
|
|
|
165 |
if not st.session_state.story_scenes:
|
166 |
-
st.info("Enter your idea in the sidebar and click '
|
167 |
else:
|
168 |
for i, scene_data_display in enumerate(st.session_state.story_scenes):
|
169 |
-
# ... (scene_num_display, unique_key_part, subheader, columns - same)
|
170 |
-
scene_num_display = scene_data_display.get('scene_number', i + 1)
|
171 |
-
action_summary = scene_data_display.get('key_action', f"scene{i}")
|
172 |
-
key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
|
173 |
-
unique_key_part = key_part_raw.replace(" ", "_")[:20]
|
174 |
st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
|
175 |
col1, col2 = st.columns([2, 3])
|
176 |
-
|
177 |
-
with col1: # Scene Details
|
178 |
with st.expander("Scene Details", expanded=True):
|
179 |
-
st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
|
180 |
-
st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
|
181 |
-
st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}")
|
182 |
-
st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`")
|
183 |
-
st.markdown(f"**Visual Style Suggestion (from Gemini):** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
|
184 |
-
st.markdown(f"**Camera Suggestion (from Gemini):** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
|
185 |
current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
|
186 |
if current_textual_prompt:
|
187 |
-
with st.popover("View Full Image Prompt"):
|
188 |
-
|
189 |
-
|
190 |
-
with col2: # Image and Edit Buttons
|
191 |
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
|
192 |
-
if current_image_path and os.path.exists(current_image_path):
|
193 |
-
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
|
194 |
else:
|
195 |
if st.session_state.story_scenes: st.caption("Visual for this scene is pending or failed.")
|
196 |
-
|
197 |
-
# Edit Popovers (logic remains largely the same, calls generate_visual_for_scene_wrapper)
|
198 |
with st.popover(f"βοΈ Edit Scene {scene_num_display} Script"):
|
|
|
199 |
feedback_script = st.text_area("Describe script changes:", key=f"script_feedback_{unique_key_part}_{i}", height=100)
|
200 |
if st.button(f"π Update Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
|
201 |
if feedback_script:
|
202 |
with st.status(f"Updating Scene {scene_num_display}...", expanded=True) as status_script_regen:
|
203 |
regen_prompt = create_scene_regeneration_prompt(scene_data_display, feedback_script, st.session_state.story_scenes)
|
204 |
try:
|
205 |
-
updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
|
206 |
-
st.session_state.story_scenes[i] = updated_scene_data
|
207 |
status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
|
208 |
version = 1
|
209 |
-
if current_image_path:
|
210 |
-
|
211 |
-
except: version = 2 # Default to v2 if parsing fails
|
212 |
-
if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=False, version_count=version): # is_regeneration=False to use new scene_data for prompt
|
213 |
-
status_script_regen.update(label="Scene script & visual updated! π", state="complete", expanded=False)
|
214 |
else: status_script_regen.update(label="Script updated, visual failed.", state="warning", expanded=False)
|
215 |
st.rerun()
|
216 |
except Exception as e: status_script_regen.update(label=f"Error: {e}", state="error")
|
217 |
else: st.warning("Please provide feedback.")
|
218 |
-
|
219 |
with st.popover(f"π¨ Edit Scene {scene_num_display} Visuals"):
|
|
|
220 |
prompt_for_edit_visuals = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt."
|
221 |
st.caption("Current Full Image Prompt:"); st.code(prompt_for_edit_visuals, language='text')
|
222 |
feedback_visual = st.text_area("Describe visual changes for the prompt:", key=f"visual_feedback_{unique_key_part}_{i}", height=100)
|
@@ -225,56 +232,60 @@ else:
|
|
225 |
with st.status(f"Refining prompt & regenerating visual...", expanded=True) as status_visual_regen:
|
226 |
prompt_refinement_request = create_visual_regeneration_prompt(
|
227 |
prompt_for_edit_visuals, feedback_visual, scene_data_display,
|
228 |
-
st.session_state.character_definitions, st.session_state.style_reference_description
|
229 |
)
|
230 |
try:
|
231 |
refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
|
232 |
-
st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
|
233 |
status_visual_regen.update(label="Prompt refined! Regenerating visual...", state="running")
|
234 |
version = 1
|
235 |
-
if current_image_path:
|
236 |
-
|
237 |
-
except: version = 2
|
238 |
-
if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): # is_regeneration=True to use the refined prompt
|
239 |
-
status_visual_regen.update(label="Visual updated! π", state="complete", expanded=False)
|
240 |
else: status_visual_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
|
241 |
st.rerun()
|
242 |
except Exception as e: status_visual_regen.update(label=f"Error: {e}", state="error")
|
243 |
else: st.warning("Please provide feedback.")
|
244 |
st.markdown("---")
|
245 |
|
246 |
-
# Video Generation
|
247 |
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
|
248 |
-
if st.button("π¬ Assemble
|
249 |
-
|
250 |
-
with st.status("Assembling enhanced animatic video...", expanded=True) as status_video:
|
251 |
image_data_for_video = []
|
|
|
252 |
for idx, scene_info in enumerate(st.session_state.story_scenes):
|
253 |
img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
|
254 |
if img_path and os.path.exists(img_path):
|
255 |
-
image_data_for_video.append({
|
256 |
-
|
257 |
-
|
258 |
-
}); st.write(f"Adding Scene {scene_info.get('scene_number', idx + 1)} to video.")
|
259 |
if image_data_for_video:
|
260 |
-
st.write("Calling video engine...")
|
261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
263 |
-
status_video.update(label="
|
264 |
else: status_video.update(label="Video assembly failed. Check logs.", state="error", expanded=False)
|
265 |
else: status_video.update(label="No valid images to assemble video.", state="error", expanded=False)
|
266 |
elif st.session_state.story_scenes: st.info("Generate visuals for scenes before assembling the video.")
|
|
|
|
|
267 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
268 |
-
st.header("π¬ Generated
|
269 |
try:
|
270 |
-
with open(st.session_state.video_path, 'rb') as
|
271 |
-
st.video(
|
272 |
-
with open(st.session_state.video_path, "rb") as
|
273 |
-
st.download_button(label="Download
|
274 |
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
|
275 |
-
use_container_width=True, key="
|
276 |
except Exception as e: st.error(f"Error displaying video: {e}")
|
277 |
|
278 |
# --- Footer ---
|
279 |
st.sidebar.markdown("---")
|
280 |
-
st.sidebar.caption("CineGen AI
|
|
|
5 |
from core.prompt_engineering import (
|
6 |
create_story_breakdown_prompt,
|
7 |
create_image_prompt_from_scene_data,
|
8 |
+
create_narration_script_prompt, # New
|
9 |
create_scene_regeneration_prompt,
|
10 |
create_visual_regeneration_prompt
|
11 |
)
|
12 |
import os
|
13 |
|
14 |
# --- Configuration & Initialization ---
|
15 |
+
st.set_page_config(page_title="CineGen AI Ultra", layout="wide", initial_sidebar_state="expanded")
|
16 |
|
17 |
+
# --- Global State Variables & API Key Setup ---
|
18 |
+
# Simplified API Key Loading
|
19 |
+
def load_api_key(key_name_streamlit, key_name_env):
|
20 |
+
key = None
|
21 |
+
try:
|
22 |
+
if key_name_streamlit in st.secrets: key = st.secrets[key_name_streamlit]
|
23 |
+
except AttributeError: print(f"st.secrets not available for {key_name_streamlit}.") # Local dev
|
24 |
+
except Exception as e: print(f"Error accessing st.secrets for {key_name_streamlit}: {e}")
|
25 |
+
if not key and key_name_env in os.environ: key = os.environ[key_name_env]
|
26 |
+
return key
|
27 |
|
28 |
+
if 'GEMINI_API_KEY' not in st.session_state:
|
29 |
+
st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY")
|
30 |
+
if not st.session_state.GEMINI_API_KEY: st.error("Gemini API Key missing!"); st.stop()
|
31 |
if 'gemini_handler' not in st.session_state:
|
32 |
st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
|
33 |
|
34 |
if 'visual_engine' not in st.session_state:
|
35 |
st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
|
36 |
+
st.session_state.visual_engine.set_openai_api_key(load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY"))
|
37 |
+
st.session_state.visual_engine.set_elevenlabs_api_key(load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY"))
|
38 |
+
st.session_state.visual_engine.set_pexels_api_key(load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
+
# Initialize other session state variables (same as before)
|
41 |
+
for key, default_val in [
|
42 |
+
('story_scenes', []), ('scene_image_prompts', []), ('generated_images_paths', []),
|
43 |
+
('video_path', None), ('character_definitions', {}), ('style_reference_description', ""),
|
44 |
+
('overall_narration_path', None), ('narration_script_for_display', "")
|
45 |
+
]:
|
46 |
+
if key not in st.session_state: st.session_state[key] = default_val
|
47 |
+
# --- End State & API Key Setup ---
|
48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
+
def initialize_new_story(): # Remains same
|
|
|
51 |
st.session_state.story_scenes = []
|
52 |
st.session_state.scene_image_prompts = []
|
53 |
st.session_state.generated_images_paths = []
|
54 |
st.session_state.video_path = None
|
55 |
+
st.session_state.overall_narration_path = None
|
56 |
+
st.session_state.narration_script_for_display = ""
|
57 |
+
|
58 |
|
59 |
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
|
60 |
+
# ... (This helper remains the same, but now `generate_image_visual` in engine has Pexels fallback) ...
|
61 |
+
# Pass scene_data for Pexels query generation
|
62 |
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
|
63 |
textual_image_prompt = ""
|
|
|
64 |
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
|
65 |
+
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
|
66 |
+
else:
|
67 |
textual_image_prompt = create_image_prompt_from_scene_data(
|
68 |
+
scene_data, st.session_state.character_definitions, st.session_state.style_reference_description)
|
|
|
|
|
|
|
69 |
if not textual_image_prompt: return False
|
|
|
|
|
70 |
if scene_index >= len(st.session_state.scene_image_prompts):
|
71 |
while len(st.session_state.scene_image_prompts) <= scene_index: st.session_state.scene_image_prompts.append("")
|
72 |
st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
|
73 |
+
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png" # DALL-E saves as PNG
|
|
|
|
|
74 |
|
75 |
+
# Pass scene_data to visual engine for Pexels query if DALL-E fails
|
76 |
+
generated_image_path = st.session_state.visual_engine.generate_image_visual(
|
77 |
+
textual_image_prompt, scene_data, image_filename
|
78 |
+
)
|
79 |
while len(st.session_state.generated_images_paths) <= scene_index: st.session_state.generated_images_paths.append(None)
|
80 |
if generated_image_path and os.path.exists(generated_image_path):
|
81 |
+
st.session_state.generated_images_paths[scene_index] = generated_image_path; return True
|
|
|
82 |
else:
|
83 |
+
st.session_state.generated_images_paths[scene_index] = None; return False
|
84 |
+
|
85 |
|
86 |
# --- UI Sidebar ---
|
87 |
with st.sidebar:
|
88 |
+
st.title("π¬ CineGen AI Ultra")
|
89 |
st.markdown("### Creative Controls")
|
90 |
+
# ... (user_idea, genre, mood, num_scenes - same) ...
|
91 |
+
user_idea = st.text_area("Core story idea:", "A lone astronaut discovers a glowing alien artifact on Mars, a sense of wonder and slight dread.", height=100, key="user_idea_input")
|
92 |
+
genre = st.selectbox("Genre:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller"], index=0, key="genre_select")
|
93 |
+
mood = st.selectbox("Mood:", ["Wonder", "Suspenseful", "Mysterious", "Gritty", "Epic"], index=0, key="mood_select")
|
94 |
+
num_scenes_val = st.slider("Number of Scenes:", 1, 3, 1, key="num_scenes_slider")
|
95 |
|
96 |
+
|
97 |
+
if st.button("π Generate Cinematic Masterpiece", type="primary", key="generate_masterpiece_btn", use_container_width=True):
|
98 |
initialize_new_story()
|
99 |
if not user_idea.strip(): st.warning("Please enter a story idea.")
|
100 |
else:
|
101 |
+
with st.status("Crafting your cinematic vision...", expanded=True) as status_main:
|
102 |
+
st.write("Phase 1: Gemini is drafting the script... π")
|
103 |
+
# ... (Story generation - same) ...
|
104 |
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
|
105 |
try:
|
106 |
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
|
|
|
108 |
num_actual_scenes = len(st.session_state.story_scenes)
|
109 |
st.session_state.scene_image_prompts = [""] * num_actual_scenes
|
110 |
st.session_state.generated_images_paths = [None] * num_actual_scenes
|
111 |
+
except Exception as e: status_main.update(label=f"Script gen failed: {e}", state="error"); st.stop()
|
|
|
112 |
|
113 |
if st.session_state.story_scenes:
|
114 |
+
st.write("Phase 2: Creating visuals (DALL-E/Pexels)... πΌοΈ")
|
115 |
+
visual_success_count = 0
|
116 |
for i, scene_data in enumerate(st.session_state.story_scenes):
|
117 |
+
st.write(f" Processing Scene {scene_data.get('scene_number', i + 1)} visuals...")
|
118 |
+
if generate_visual_for_scene_wrapper(i, scene_data, version_count=1): visual_success_count +=1
|
119 |
+
|
120 |
+
if visual_success_count == 0 :
|
121 |
+
status_main.update(label="Visual gen failed for all scenes.", state="error", expanded=False); st.stop()
|
122 |
|
123 |
+
st.write("Phase 3: Generating narration script with Gemini... π€")
|
124 |
+
narration_prompt = create_narration_script_prompt(st.session_state.story_scenes, mood, genre)
|
125 |
+
try:
|
126 |
+
narration_script = st.session_state.gemini_handler.generate_image_prompt(narration_prompt) # Re-use for text gen
|
127 |
+
st.session_state.narration_script_for_display = narration_script
|
128 |
+
status_main.update(label="Narration script ready! Generating voice...", state="running")
|
129 |
+
|
130 |
+
st.write("Phase 4: Synthesizing voice with ElevenLabs... π")
|
131 |
+
st.session_state.overall_narration_path = st.session_state.visual_engine.generate_narration_audio(
|
132 |
+
narration_script, "overall_narration.mp3"
|
133 |
+
)
|
134 |
+
if st.session_state.overall_narration_path:
|
135 |
+
status_main.update(label="Voiceover generated! β¨", state="running")
|
136 |
+
else:
|
137 |
+
status_main.update(label="Voiceover failed, proceeding without.", state="warning")
|
138 |
+
except Exception as e:
|
139 |
+
status_main.update(label=f"Narration/Voice gen failed: {e}", state="warning")
|
140 |
|
141 |
+
status_main.update(label="All major components generated! Storyboard ready. π", state="complete", expanded=False)
|
142 |
+
|
143 |
+
st.markdown("---") # Advanced Options (Character, Style - same as previous full app.py)
|
144 |
+
# ... (Character Consistency expander - same)
|
145 |
with st.expander("Character Consistency", expanded=False):
|
146 |
char_name_input = st.text_input("Character Name (e.g., Jax)", key="char_name_adv_input")
|
147 |
char_desc_input = st.text_area("Character Description (e.g., 'male astronaut, rugged, dark short hair, blue eyes, wearing a worn white and orange spacesuit with a cracked visor helmet slung at his hip')", key="char_desc_adv_input", height=120)
|
148 |
if st.button("Add/Update Character", key="add_char_adv_btn"):
|
149 |
+
if char_name_input and char_desc_input: st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip(); st.success(f"Character '{char_name_input.strip()}' defined.")
|
|
|
|
|
150 |
else: st.warning("Provide both name and description.")
|
151 |
if st.session_state.character_definitions:
|
152 |
st.caption("Defined Characters:")
|
153 |
for char_key, desc_val in st.session_state.character_definitions.items(): st.markdown(f"**{char_key.title()}:** _{desc_val}_")
|
154 |
+
# ... (Style Controls expander - same)
|
155 |
+
with st.expander("Style Controls", expanded=True):
|
156 |
+
predefined_styles = { "Default (Cinematic Photorealism)": "", "Gritty Neo-Noir": "neo-noir aesthetic, gritty realism, deep shadows...", "Epic Fantasy Matte Painting": "epic fantasy matte painting style...", "Impressionistic Dream": "impressionistic oil painting style...", "Vintage Sci-Fi (70s/80s Film)": "retro sci-fi aesthetic, analog film look...", "Modern Anime Key Visual": "high-detail modern anime key visual style..." }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv")
|
158 |
+
custom_keywords = st.text_area("Add Custom Style Keywords:", key="custom_style_keywords_adv_input", height=80, placeholder="e.g., 'Dutch angle, fisheye lens'")
|
|
|
159 |
current_style_desc = st.session_state.style_reference_description
|
160 |
if st.button("Apply & Set Styles", key="apply_styles_adv_btn"):
|
161 |
+
final_desc = predefined_styles[selected_preset];
|
162 |
+
if custom_keywords.strip(): final_desc = f"{final_desc}, {custom_keywords.strip()}" if final_desc else custom_keywords.strip()
|
163 |
+
st.session_state.style_reference_description = final_desc.strip(); current_style_desc = final_desc.strip()
|
164 |
+
if current_style_desc: st.success("Styles applied!")
|
|
|
|
|
165 |
else: st.info("Default style (no specific additions).")
|
166 |
+
if current_style_desc: st.caption(f"Active style additions: \"{current_style_desc}\"")
|
|
|
167 |
else: st.caption("No specific style prompt additions active.")
|
168 |
+
# Conceptual Voice selection for ElevenLabs
|
169 |
+
with st.expander("Voice Customization (ElevenLabs)", expanded=False):
|
170 |
+
# In a real app, you'd fetch voice list from ElevenLabs API if key is present
|
171 |
+
available_voices_conceptual = ["Rachel", "Adam", "Bella", "Antoni", "Elli", "Josh", "Arnold", "Callum"]
|
172 |
+
selected_voice = st.selectbox("Choose Narrator Voice:", available_voices_conceptual,
|
173 |
+
index=available_voices_conceptual.index(st.session_state.visual_engine.elevenlabs_voice_id) if st.session_state.visual_engine.elevenlabs_voice_id in available_voices_conceptual else 0,
|
174 |
+
key="elevenlabs_voice_select")
|
175 |
+
if st.button("Set Voice", key="set_voice_btn"):
|
176 |
+
st.session_state.visual_engine.elevenlabs_voice_id = selected_voice
|
177 |
+
st.success(f"Narrator voice set to: {selected_voice}")
|
178 |
+
|
179 |
|
180 |
+
# --- Main Content Area ---
|
181 |
+
# ... (Header, Scene Iteration, Details, Image Display, Edit Popovers - mostly the same as previous full app.py)
|
182 |
+
# Key change: Pass character_definitions and style_reference_description to create_visual_regeneration_prompt
|
183 |
|
|
|
184 |
st.header("π Cinematic Storyboard")
|
185 |
+
if st.session_state.narration_script_for_display: # Display narration script if generated
|
186 |
+
with st.expander("View Generated Narration Script", expanded=False):
|
187 |
+
st.markdown(st.session_state.narration_script_for_display)
|
188 |
+
|
189 |
if not st.session_state.story_scenes:
|
190 |
+
st.info("Enter your idea in the sidebar and click 'π Generate Cinematic Masterpiece' to begin.")
|
191 |
else:
|
192 |
for i, scene_data_display in enumerate(st.session_state.story_scenes):
|
193 |
+
# ... (scene_num_display, unique_key_part, subheader, columns, Scene Details, Image Display - same as previous)
|
194 |
+
scene_num_display = scene_data_display.get('scene_number', i + 1); action_summary = scene_data_display.get('key_action', f"scene{i}"); key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace()); unique_key_part = key_part_raw.replace(" ", "_")[:20]
|
|
|
|
|
|
|
195 |
st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
|
196 |
col1, col2 = st.columns([2, 3])
|
197 |
+
with col1:
|
|
|
198 |
with st.expander("Scene Details", expanded=True):
|
199 |
+
st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}"); st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}"); st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}"); st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`"); st.markdown(f"**Visual Style Suggestion (Gemini):** {scene_data_display.get('visual_style_suggestion', 'N/A')}"); st.markdown(f"**Camera Suggestion (Gemini):** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
|
|
|
|
|
|
|
|
|
|
|
200 |
current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
|
201 |
if current_textual_prompt:
|
202 |
+
with st.popover("View Full Image Prompt"): st.markdown(f"**Full Textual Prompt Sent to DALL-E:**"); st.code(current_textual_prompt, language='text')
|
203 |
+
with col2:
|
|
|
|
|
204 |
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
|
205 |
+
if current_image_path and os.path.exists(current_image_path): st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
|
|
|
206 |
else:
|
207 |
if st.session_state.story_scenes: st.caption("Visual for this scene is pending or failed.")
|
|
|
|
|
208 |
with st.popover(f"βοΈ Edit Scene {scene_num_display} Script"):
|
209 |
+
# ... (Script edit logic - same, but uses updated generate_visual_for_scene_wrapper)
|
210 |
feedback_script = st.text_area("Describe script changes:", key=f"script_feedback_{unique_key_part}_{i}", height=100)
|
211 |
if st.button(f"π Update Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
|
212 |
if feedback_script:
|
213 |
with st.status(f"Updating Scene {scene_num_display}...", expanded=True) as status_script_regen:
|
214 |
regen_prompt = create_scene_regeneration_prompt(scene_data_display, feedback_script, st.session_state.story_scenes)
|
215 |
try:
|
216 |
+
updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt); st.session_state.story_scenes[i] = updated_scene_data
|
|
|
217 |
status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
|
218 |
version = 1
|
219 |
+
if current_image_path: try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2 except: version = 2
|
220 |
+
if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=False, version_count=version): status_script_regen.update(label="Scene script & visual updated! π", state="complete", expanded=False)
|
|
|
|
|
|
|
221 |
else: status_script_regen.update(label="Script updated, visual failed.", state="warning", expanded=False)
|
222 |
st.rerun()
|
223 |
except Exception as e: status_script_regen.update(label=f"Error: {e}", state="error")
|
224 |
else: st.warning("Please provide feedback.")
|
|
|
225 |
with st.popover(f"π¨ Edit Scene {scene_num_display} Visuals"):
|
226 |
+
# ... (Visual edit logic - same, but passes more context to create_visual_regeneration_prompt)
|
227 |
prompt_for_edit_visuals = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt."
|
228 |
st.caption("Current Full Image Prompt:"); st.code(prompt_for_edit_visuals, language='text')
|
229 |
feedback_visual = st.text_area("Describe visual changes for the prompt:", key=f"visual_feedback_{unique_key_part}_{i}", height=100)
|
|
|
232 |
with st.status(f"Refining prompt & regenerating visual...", expanded=True) as status_visual_regen:
|
233 |
prompt_refinement_request = create_visual_regeneration_prompt(
|
234 |
prompt_for_edit_visuals, feedback_visual, scene_data_display,
|
235 |
+
st.session_state.character_definitions, st.session_state.style_reference_description # Pass these
|
236 |
)
|
237 |
try:
|
238 |
refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
|
239 |
+
st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
|
240 |
status_visual_regen.update(label="Prompt refined! Regenerating visual...", state="running")
|
241 |
version = 1
|
242 |
+
if current_image_path: try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2 except: version = 2
|
243 |
+
if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): status_visual_regen.update(label="Visual updated! π", state="complete", expanded=False)
|
|
|
|
|
|
|
244 |
else: status_visual_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False)
|
245 |
st.rerun()
|
246 |
except Exception as e: status_visual_regen.update(label=f"Error: {e}", state="error")
|
247 |
else: st.warning("Please provide feedback.")
|
248 |
st.markdown("---")
|
249 |
|
250 |
+
# Video Generation - Pass overall_narration_path
|
251 |
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
|
252 |
+
if st.button("π¬ Assemble Narrated Masterpiece", key="assemble_narrated_video_btn", type="primary", use_container_width=True):
|
253 |
+
with st.status("Assembling narrated animatic video...", expanded=True) as status_video:
|
|
|
254 |
image_data_for_video = []
|
255 |
+
# ... (image_data_for_video assembly - same) ...
|
256 |
for idx, scene_info in enumerate(st.session_state.story_scenes):
|
257 |
img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
|
258 |
if img_path and os.path.exists(img_path):
|
259 |
+
image_data_for_video.append({'path':img_path, 'scene_num':scene_info.get('scene_number',idx+1), 'key_action':scene_info.get('key_action','')})
|
260 |
+
st.write(f"Adding Scene {scene_info.get('scene_number', idx + 1)} to video.")
|
261 |
+
|
|
|
262 |
if image_data_for_video:
|
263 |
+
st.write("Calling video engine (with narration if available)...")
|
264 |
+
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
|
265 |
+
image_data_for_video,
|
266 |
+
overall_narration_path=st.session_state.overall_narration_path, # Pass narration
|
267 |
+
output_filename="cinegen_narrated_masterpiece.mp4",
|
268 |
+
duration_per_image=5, # Longer for narration
|
269 |
+
fps=24
|
270 |
+
)
|
271 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
272 |
+
status_video.update(label="Narrated masterpiece assembled! π", state="complete", expanded=False); st.balloons()
|
273 |
else: status_video.update(label="Video assembly failed. Check logs.", state="error", expanded=False)
|
274 |
else: status_video.update(label="No valid images to assemble video.", state="error", expanded=False)
|
275 |
elif st.session_state.story_scenes: st.info("Generate visuals for scenes before assembling the video.")
|
276 |
+
|
277 |
+
# Video display and download - (same)
|
278 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
279 |
+
st.header("π¬ Generated Narrated Masterpiece")
|
280 |
try:
|
281 |
+
with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read()
|
282 |
+
st.video(video_bytes, format="video/mp4")
|
283 |
+
with open(st.session_state.video_path, "rb") as fp_dl:
|
284 |
+
st.download_button(label="Download Narrated Masterpiece", data=fp_dl,
|
285 |
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
|
286 |
+
use_container_width=True, key="download_narrated_video_btn" )
|
287 |
except Exception as e: st.error(f"Error displaying video: {e}")
|
288 |
|
289 |
# --- Footer ---
|
290 |
st.sidebar.markdown("---")
|
291 |
+
st.sidebar.caption("CineGen AI Ultra | Powered by Gemini, DALL-E, ElevenLabs & Streamlit.")
|