mgbam commited on
Commit
32b08ff
Β·
verified Β·
1 Parent(s): 4afbb07

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +257 -104
app.py CHANGED
@@ -2,123 +2,276 @@
2
  import streamlit as st
3
  from core.gemini_handler import GeminiHandler
4
  from core.visual_engine import VisualEngine
5
- from core.prompt_engineering import create_story_breakdown_prompt, create_image_prompt_from_scene
 
 
 
 
 
6
  import os
 
7
 
8
  # --- Configuration & Initialization ---
9
- st.set_page_config(page_title="CineGen AI", layout="wide")
10
 
11
- # Load API Key from Streamlit secrets
12
- try:
13
- GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
14
- except KeyError:
15
- st.error("GEMINI_API_KEY not found in secrets. Please add it to your Streamlit Cloud secrets.")
16
- st.stop()
 
17
 
18
- gemini = GeminiHandler(api_key=GEMINI_API_KEY)
19
- visualizer = VisualEngine(output_dir="temp_generated_media") # Use a temp dir for HF Spaces
 
 
20
 
21
- # --- UI Elements ---
22
- st.title("🎬 CineGen AI: Your Pocket AI Film Studio")
23
- st.markdown("Let's turn your ideas into cinematic visuals!")
24
-
25
- with st.sidebar:
26
- st.header("🎨 Creative Controls")
27
- user_idea = st.text_area("Enter your core idea/story prompt:", "A lone astronaut discovers a glowing alien artifact on Mars, a sense of wonder and slight dread.", height=100)
28
- genre = st.selectbox("Select Genre:", ["Sci-Fi", "Fantasy", "Thriller", "Drama", "Comedy"], index=0)
29
- mood = st.selectbox("Select Mood:", ["Suspenseful", "Mysterious", "Epic", "Uplifting", "Dark"], index=0)
30
- generate_button = st.button("✨ Generate Cinematic Concept", type="primary")
31
-
32
- # --- Session State for Storing Results ---
33
- if 'story_scenes' not in st.session_state:
34
- st.session_state.story_scenes = None
35
- if 'image_prompts' not in st.session_state:
36
- st.session_state.image_prompts = []
37
- if 'generated_images_paths' not in st.session_state:
38
  st.session_state.generated_images_paths = []
39
  if 'video_path' not in st.session_state:
40
  st.session_state.video_path = None
41
 
42
- # --- Main Logic ---
43
- if generate_button and user_idea:
44
- st.session_state.story_scenes = None # Reset previous results
45
- st.session_state.image_prompts = []
 
 
 
 
 
 
 
 
46
  st.session_state.generated_images_paths = []
47
  st.session_state.video_path = None
48
 
49
- with st.spinner("Phase 1: Gemini is drafting the script and scene breakdown... πŸ“œ"):
50
- story_prompt = create_story_breakdown_prompt(user_idea, genre, mood)
51
- st.session_state.story_scenes = gemini.generate_story_breakdown(story_prompt)
52
-
53
- if st.session_state.story_scenes:
54
- st.toast("Script and scene breakdown complete!", icon="βœ…")
55
-
56
- with st.spinner("Phase 2: Gemini is crafting visual prompts for keyframes... πŸ–ΌοΈ"):
57
- for i, scene in enumerate(st.session_state.story_scenes):
58
- scene_desc_for_image = f"Scene {scene.get('scene_number', i+1)}: {scene.get('key_action', 'N/A')}. Setting: {scene.get('setting_description', 'N/A')}"
59
- img_gen_prompt_text = create_image_prompt_from_scene(scene_desc_for_image, scene.get('visual_style_suggestion', 'cinematic'))
60
-
61
- # Get the actual image description from Gemini
62
- # For this demo, we'll use the scene's key_action and setting as the "description" for the placeholder
63
- # In a real app, Gemini would generate a detailed image prompt here.
64
- # image_description_from_gemini = gemini.generate_image_prompt(img_gen_prompt_text)
65
- # For simplicity, we use parts of the scene data directly for the placeholder image text
66
- image_description_for_placeholder = f"Scene {scene.get('scene_number', i+1)}: {scene.get('key_action', 'N/A')}\nStyle: {scene.get('visual_style_suggestion', 'cinematic')}"
67
-
68
- if image_description_for_placeholder: # was image_description_from_gemini
69
- st.session_state.image_prompts.append(image_description_for_placeholder)
70
- # Simulate image generation by creating a placeholder
71
- img_path = visualizer.create_placeholder_image(
72
- image_description_for_placeholder, # use the generated desc
73
- f"scene_{scene.get('scene_number', i+1)}_placeholder.png"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  )
75
- st.session_state.generated_images_paths.append(img_path)
 
76
  else:
77
- st.warning(f"Could not generate image prompt/description for Scene {scene.get('scene_number', i+1)}.")
78
- st.toast("Visual prompts and placeholder images generated!", icon="πŸ–ΌοΈ")
79
-
80
- if st.session_state.generated_images_paths:
81
- with st.spinner("Phase 3: Assembling the cinematic video... 🎞️"):
82
- st.session_state.video_path = visualizer.create_video_from_images(
83
- st.session_state.generated_images_paths,
84
- output_filename="cinegen_output.mp4",
85
- duration_per_image=3 # Show each image for 3 seconds
86
- )
87
- st.toast("Video assembled!", icon="🎬")
88
- st.balloons()
89
- else:
90
- st.error("No images were generated, cannot create video.")
91
  else:
92
- st.error("Failed to generate story breakdown from Gemini. Please check logs or try a different prompt.")
93
-
94
- # --- Display Results ---
95
- if st.session_state.story_scenes:
96
- st.header("πŸ“ Generated Story Breakdown")
97
- for i, scene in enumerate(st.session_state.story_scenes):
98
- with st.expander(f"Scene {scene.get('scene_number', i+1)}: {scene.get('key_action', 'N/A')[:50]}...", expanded=i==0):
99
- st.markdown(f"**Setting:** {scene.get('setting_description', 'N/A')}")
100
- st.markdown(f"**Characters:** {', '.join(scene.get('characters_involved', []))}")
101
- st.markdown(f"**Key Action:** {scene.get('key_action', 'N/A')}")
102
- st.markdown(f"**Visual Style Suggestion:** {scene.get('visual_style_suggestion', 'N/A')}")
103
- st.markdown(f"**Camera Angle Suggestion:** {scene.get('camera_angle_suggestion', 'N/A')}")
104
-
105
- if i < len(st.session_state.image_prompts):
106
- st.markdown(f"**Image Concept (Placeholder Text):**")
107
- st.code(st.session_state.image_prompts[i])
108
- if i < len(st.session_state.generated_images_paths):
109
- st.image(st.session_state.generated_images_paths[i], caption=f"Visual Concept for Scene {scene.get('scene_number', i+1)}")
110
-
111
-
112
- if st.session_state.video_path and os.path.exists(st.session_state.video_path):
113
- st.header("🎬 Generated Cinematic Concept Video")
114
- video_file = open(st.session_state.video_path, 'rb')
115
- video_bytes = video_file.read()
116
- st.video(video_bytes)
117
- st.markdown(f"Download your video: [{os.path.basename(st.session_state.video_path)}]({st.session_state.video_path})") # This download might not work directly on Spaces due to pathing, but video will play.
118
- # For download on Spaces, you might need to serve it differently or provide a direct link if Spaces keeps it.
119
- # A simpler way for Spaces is to just let the user right-click the video player and save.
120
- else:
121
- if generate_button and not st.session_state.video_path and st.session_state.generated_images_paths:
122
- st.warning("Video generation might have failed or no images were available.")
123
- elif not generate_button :
124
- st.info("Enter your idea and click 'Generate Cinematic Concept' to begin!")
 
2
  import streamlit as st
3
  from core.gemini_handler import GeminiHandler
4
  from core.visual_engine import VisualEngine
5
+ from core.prompt_engineering import (
6
+ create_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 # For debugging and display
13
 
14
  # --- Configuration & Initialization ---
15
+ st.set_page_config(page_title="CineGen AI Pro", layout="wide")
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
+ st.error("GEMINI_API_KEY not found in secrets. Please add it.")
23
+ st.stop()
24
 
25
+ if 'gemini_handler' not in st.session_state:
26
+ st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
27
+ if 'visual_engine' not in st.session_state:
28
+ st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
29
 
30
+ # Story and generated content
31
+ if 'story_scenes' not in st.session_state: # List of scene dicts
32
+ st.session_state.story_scenes = []
33
+ if 'scene_image_prompts' not in st.session_state: # List of strings
34
+ st.session_state.scene_image_prompts = []
35
+ if 'generated_images_paths' not in st.session_state: # List of file paths
 
 
 
 
 
 
 
 
 
 
 
36
  st.session_state.generated_images_paths = []
37
  if 'video_path' not in st.session_state:
38
  st.session_state.video_path = None
39
 
40
+ # For Character Consistency (placeholders)
41
+ if 'character_definitions' not in st.session_state:
42
+ st.session_state.character_definitions = {} # e.g., {"Eva": "Astronaut, red hair..."}
43
+
44
+ # For Style Transfer (placeholders)
45
+ if 'style_reference_description' not in st.session_state:
46
+ st.session_state.style_reference_description = None
47
+
48
+ # --- Helper Functions ---
49
+ def initialize_new_story():
50
+ st.session_state.story_scenes = []
51
+ st.session_state.scene_image_prompts = []
52
  st.session_state.generated_images_paths = []
53
  st.session_state.video_path = None
54
 
55
+ def generate_visual_for_scene(scene_index, scene_data):
56
+ """Generates image prompt and placeholder image for a specific scene."""
57
+ img_gen_prompt_text = create_image_prompt_from_scene_data(
58
+ scene_data,
59
+ st.session_state.character_definitions,
60
+ st.session_state.style_reference_description
61
+ )
62
+ try:
63
+ actual_image_prompt = st.session_state.gemini_handler.generate_image_prompt(img_gen_prompt_text)
64
+ if actual_image_prompt:
65
+ if scene_index < len(st.session_state.scene_image_prompts):
66
+ st.session_state.scene_image_prompts[scene_index] = actual_image_prompt
67
+ else:
68
+ st.session_state.scene_image_prompts.append(actual_image_prompt)
69
+
70
+ img_path = st.session_state.visual_engine.create_placeholder_image(
71
+ f"Scene {scene_data.get('scene_number', scene_index + 1)}: {actual_image_prompt[:100]}...", # Show part of the actual prompt
72
+ f"scene_{scene_data.get('scene_number', scene_index + 1)}_placeholder.png"
73
+ )
74
+ if scene_index < len(st.session_state.generated_images_paths):
75
+ st.session_state.generated_images_paths[scene_index] = img_path
76
+ else:
77
+ st.session_state.generated_images_paths.append(img_path)
78
+ return True
79
+ except Exception as e:
80
+ st.error(f"Error generating visual for Scene {scene_data.get('scene_number', scene_index + 1)}: {e}")
81
+ return False
82
+
83
+ # --- UI Sidebar ---
84
+ with st.sidebar:
85
+ st.title("🎬 CineGen AI Pro")
86
+ st.markdown("### Creative Controls")
87
+ user_idea = st.text_area("Enter your core story idea:", "A detective in a cyberpunk city investigates a rogue AI.", height=100, key="user_idea_input")
88
+ genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama"], index=2, key="genre_select")
89
+ mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Hopeful"], index=2, key="mood_select")
90
+ num_scenes_val = st.slider("Number of Scenes:", 1, 10, 3, key="num_scenes_slider")
91
+
92
+ if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
93
+ initialize_new_story()
94
+ with st.spinner("Phase 1: Gemini is drafting the script & scene breakdown... πŸ“œ"):
95
+ story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
96
+ try:
97
+ st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
98
+ st.toast("Script breakdown complete!", icon="βœ…")
99
+
100
+ # Initialize placeholders for prompts and images
101
+ st.session_state.scene_image_prompts = [""] * len(st.session_state.story_scenes)
102
+ st.session_state.generated_images_paths = [""] * len(st.session_state.story_scenes)
103
+
104
+ except Exception as e:
105
+ st.error(f"Failed to generate story breakdown: {e}")
106
+ st.session_state.story_scenes = [] # Clear on failure
107
+
108
+ if st.session_state.story_scenes:
109
+ with st.spinner("Phase 2: Generating initial visual concepts... πŸ–ΌοΈ"):
110
+ for i, scene_data in enumerate(st.session_state.story_scenes):
111
+ generate_visual_for_scene(i, scene_data)
112
+ st.toast("Initial visual concepts generated!", icon="πŸ–ΌοΈ")
113
+
114
+ st.markdown("---")
115
+ st.markdown("### Advanced Options (Conceptual)")
116
+ # Character Consistency UI
117
+ st.subheader("Character Consistency")
118
+ char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name")
119
+ char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc", height=80)
120
+ if st.button("Add/Update Character", key="add_char_btn"):
121
+ if char_name_input and char_desc_input:
122
+ st.session_state.character_definitions[char_name_input] = char_desc_input
123
+ st.success(f"Character '{char_name_input}' defined.")
124
+ else:
125
+ st.warning("Please provide both name and description.")
126
+ if st.session_state.character_definitions:
127
+ st.write("Defined Characters:")
128
+ st.json(st.session_state.character_definitions)
129
+
130
+ # Style Transfer UI
131
+ st.subheader("Style Transfer")
132
+ style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_input", height=80)
133
+ if st.button("Apply Textual Style", key="apply_style_btn"):
134
+ st.session_state.style_reference_description = style_ref_text
135
+ st.success("Style reference applied. Re-generate visuals to see changes.")
136
+ # conceptual_style_image = st.file_uploader("Or Upload Style Reference Image (Future Feature)", type=['png', 'jpg'])
137
+
138
+
139
+ # --- Main Content Area ---
140
+ st.header("πŸ“ Cinematic Storyboard")
141
+
142
+ if not st.session_state.story_scenes:
143
+ st.info("Enter your idea in the sidebar and click 'Generate Full Story Concept' to begin.")
144
+ else:
145
+ for i, scene_data in enumerate(st.session_state.story_scenes):
146
+ scene_num = scene_data.get('scene_number', i + 1)
147
+ st.subheader(f"Scene {scene_num}: {scene_data.get('emotional_beat', 'N/A')}")
148
+
149
+ col1, col2 = st.columns([2,3]) # Left for text, Right for image and edit
150
+
151
+ with col1: # Scene Details
152
+ with st.expander("Scene Details", expanded=True):
153
+ st.markdown(f"**Setting:** {scene_data.get('setting_description', 'N/A')}")
154
+ st.markdown(f"**Characters:** {', '.join(scene_data.get('characters_involved', []))}")
155
+ st.markdown(f"**Key Action:** {scene_data.get('key_action', 'N/A')}")
156
+ st.markdown(f"**Dialogue Snippet:** {scene_data.get('dialogue_snippet', 'N/A')}")
157
+ st.markdown(f"**Visual Style:** {scene_data.get('visual_style_suggestion', 'N/A')}")
158
+ st.markdown(f"**Camera:** {scene_data.get('camera_angle_suggestion', 'N/A')}")
159
+ if i < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[i]:
160
+ with st.popover("View Image Prompt"):
161
+ st.code(st.session_state.scene_image_prompts[i])
162
+
163
+
164
+ with col2: # Image and Interactive Editing
165
+ if i < len(st.session_state.generated_images_paths) and st.session_state.generated_images_paths[i] and os.path.exists(st.session_state.generated_images_paths[i]):
166
+ st.image(st.session_state.generated_images_paths[i], caption=f"Visual Concept for Scene {scene_num}")
167
+ else:
168
+ st.info("Visual concept not yet generated or path is invalid.")
169
+
170
+ # Interactive Storyboarding UI
171
+ with st.popover(f"✏️ Edit Scene {scene_num} Script"):
172
+ st.markdown("#### Modify Scene Script Details")
173
+ feedback_script = st.text_area(
174
+ "Describe changes to the scene (e.g., 'Make the action more intense', 'Change dialogue to be more hopeful')",
175
+ key=f"script_feedback_{i}",
176
+ height=100
177
+ )
178
+ if st.button(f"πŸ”„ Regenerate Scene {scene_num} Script", key=f"regen_script_btn_{i}"):
179
+ if feedback_script:
180
+ with st.spinner(f"Gemini is rewriting Scene {scene_num}..."):
181
+ regen_prompt = create_scene_regeneration_prompt(
182
+ scene_data, feedback_script, st.session_state.story_scenes
183
+ )
184
+ try:
185
+ updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
186
+ st.session_state.story_scenes[i] = updated_scene_data # Update the specific scene
187
+ st.toast(f"Scene {scene_num} script updated!", icon="✍️")
188
+ # Automatically regenerate visuals for this updated scene
189
+ generate_visual_for_scene(i, updated_scene_data)
190
+ st.rerun() # Rerun to reflect changes immediately
191
+ except Exception as e:
192
+ st.error(f"Error regenerating scene script: {e}")
193
+ else:
194
+ st.warning("Please provide feedback for script regeneration.")
195
+
196
+ with st.popover(f"🎨 Edit Scene {scene_num} Visuals"):
197
+ st.markdown("#### Modify Visual Concept")
198
+ feedback_visual = st.text_area(
199
+ "Describe visual changes (e.g., 'Make it nighttime', 'Add more fog', 'Use a wider angle')",
200
+ key=f"visual_feedback_{i}",
201
+ height=100
202
+ )
203
+ # Advanced Camera Control (Simple Example)
204
+ adv_camera = st.selectbox("Adv. Camera (Conceptual):",
205
+ ["Default", "Low Angle", "High Angle", "Dutch Angle", "Close-up"],
206
+ key=f"adv_cam_{i}")
207
+ if st.button(f"πŸ”„ Regenerate Scene {scene_num} Visuals", key=f"regen_visual_btn_{i}"):
208
+ if feedback_visual or adv_camera != "Default":
209
+ full_visual_feedback = feedback_visual
210
+ if adv_camera != "Default":
211
+ full_visual_feedback += f" Emphasize camera: {adv_camera}."
212
+
213
+ with st.spinner(f"Gemini is re-imagining Scene {scene_num} visuals..."):
214
+ # We need the current image prompt for this scene to refine it
215
+ current_image_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else ""
216
+ if not current_image_prompt: # If no prompt, generate one first
217
+ img_gen_prompt_text = create_image_prompt_from_scene_data(
218
+ scene_data,
219
+ st.session_state.character_definitions,
220
+ st.session_state.style_reference_description
221
+ )
222
+ current_image_prompt = st.session_state.gemini_handler.generate_image_prompt(img_gen_prompt_text)
223
+ st.session_state.scene_image_prompts[i] = current_image_prompt
224
+
225
+
226
+ regen_visual_prompt_text = create_visual_regeneration_prompt(
227
+ current_image_prompt,
228
+ full_visual_feedback,
229
+ scene_data # Pass current scene data for context
230
+ )
231
+ try:
232
+ new_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(regen_visual_prompt_text)
233
+ st.session_state.scene_image_prompts[i] = new_image_prompt # Update the prompt
234
+
235
+ # Now generate the new placeholder image with the new prompt
236
+ img_path = st.session_state.visual_engine.create_placeholder_image(
237
+ f"Scene {scene_num}: {new_image_prompt[:100]}...",
238
+ f"scene_{scene_num}_placeholder_v{len(st.session_state.generated_images_paths)}.png" # Avoid overwriting, simple versioning
239
+ )
240
+ st.session_state.generated_images_paths[i] = img_path
241
+ st.toast(f"Visuals for Scene {scene_num} updated!", icon="🎨")
242
+ st.rerun()
243
+ except Exception as e:
244
+ st.error(f"Error regenerating visuals: {e}")
245
+ else:
246
+ st.warning("Please provide feedback or select an advanced camera angle for visual regeneration.")
247
+
248
+ st.markdown("---") # Separator between scenes
249
+
250
+ # Video Generation Button (after all scenes are displayed)
251
+ if st.session_state.story_scenes and st.session_state.generated_images_paths and \
252
+ all(p and os.path.exists(p) for p in st.session_state.generated_images_paths):
253
+ if st.button("🎬 Assemble Animatic Video", key="assemble_video_btn"):
254
+ with st.spinner("Assembling video... This might take a moment."):
255
+ valid_image_paths = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
256
+ if valid_image_paths:
257
+ st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
258
+ valid_image_paths,
259
+ output_filename="cinegen_pro_output.mp4",
260
+ duration_per_image=3
261
  )
262
+ st.toast("Animatic video assembled!", icon="🎞️")
263
+ st.balloons()
264
  else:
265
+ st.error("No valid images found to assemble video.")
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  else:
267
+ if st.session_state.story_scenes:
268
+ st.warning("Some visual concepts are missing. Please ensure all scenes have visuals before assembling video.")
269
+
270
+
271
+ if st.session_state.video_path and os.path.exists(st.session_state.video_path):
272
+ st.header("🎬 Generated Animatic Video")
273
+ video_file = open(st.session_state.video_path, 'rb')
274
+ video_bytes = video_file.read()
275
+ st.video(video_bytes)
276
+ # For download on Spaces, let user right-click. The path might be tricky for direct download link.
277
+ st.markdown(f"Video saved at: `{st.session_state.video_path}` (within the Space's file system)")