mgbam commited on
Commit
a7374a3
Β·
verified Β·
1 Parent(s): 41b47a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +254 -183
app.py CHANGED
@@ -1,45 +1,53 @@
1
  # app.py
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:
@@ -51,90 +59,137 @@ def initialize_new_story():
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")
@@ -142,136 +197,152 @@ st.header("πŸ“ Cinematic Storyboard")
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)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, # Generates the TEXT prompt for AI/placeholder
8
  create_scene_regeneration_prompt,
9
+ create_visual_regeneration_prompt # Regenerates the TEXT prompt
10
  )
11
  import os
12
+ # import json # Only if you need to st.json for debugging, not essential for core logic
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: # For local dev if secrets aren't set yet
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: # Catch any other secrets-related errors
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
+ # Ensure VisualEngine is initialized (it will print font loading status)
36
  st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
37
 
38
  # Story and generated content
39
  if 'story_scenes' not in st.session_state: # List of scene dicts
40
  st.session_state.story_scenes = []
41
+ if 'scene_image_prompts' not in st.session_state: # List of textual image prompts
42
  st.session_state.scene_image_prompts = []
43
+ if 'generated_images_paths' not in st.session_state: # List of file paths for generated visuals
44
  st.session_state.generated_images_paths = []
45
  if 'video_path' not in st.session_state:
46
  st.session_state.video_path = None
47
 
48
  # For Character Consistency (placeholders)
49
  if 'character_definitions' not in st.session_state:
50
+ st.session_state.character_definitions = {}
51
 
52
  # For Style Transfer (placeholders)
53
  if 'style_reference_description' not in st.session_state:
 
59
  st.session_state.scene_image_prompts = []
60
  st.session_state.generated_images_paths = []
61
  st.session_state.video_path = None
62
+ # Clear old media files if any (optional, good for space management on persistent storage)
63
+ # For Hugging Face Spaces, temp files are usually cleared on restart/rebuild anyway.
64
+ # if os.path.exists(st.session_state.visual_engine.output_dir):
65
+ # for f in os.listdir(st.session_state.visual_engine.output_dir):
66
+ # os.remove(os.path.join(st.session_state.visual_engine.output_dir, f))
67
+
68
+
69
+ def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
70
+ """
71
+ Wrapper to generate the textual image prompt and then the actual visual.
72
+ Updates session state for prompts and image paths.
73
+ """
74
+ st.info(f"Generating visual concept for Scene {scene_data.get('scene_number', scene_index + 1)}...")
75
+
76
+ # 1. Get/Regenerate the detailed image prompt TEXT
77
+ if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
78
+ # For regeneration, we assume the prompt text is already updated in session state
79
+ # (e.g., by a call to create_visual_regeneration_prompt and Gemini)
80
+ textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
81
+ else:
82
+ textual_image_prompt = create_image_prompt_from_scene_data(
83
+ scene_data,
84
+ st.session_state.character_definitions,
85
+ st.session_state.style_reference_description
86
+ )
87
+ # Optional: Call Gemini here to refine this initial prompt if desired
88
+ # textual_image_prompt = st.session_state.gemini_handler.generate_image_prompt(textual_image_prompt)
89
+
90
+ if not textual_image_prompt:
91
+ st.error(f"Failed to create textual image prompt for Scene {scene_data.get('scene_number', scene_index + 1)}.")
92
+ return False
93
+
94
+ # Update session state for the textual prompt
95
+ if scene_index < len(st.session_state.scene_image_prompts):
96
+ st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
97
+ else:
98
+ # Ensure list is long enough before appending (shouldn't happen if initialized correctly)
99
+ while len(st.session_state.scene_image_prompts) <= scene_index:
100
+ st.session_state.scene_image_prompts.append("")
101
+ st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
102
+
103
+
104
+ # 2. Generate the actual visual (AI or placeholder) using the textual prompt
105
+ image_filename = f"scene_{scene_data.get('scene_number', scene_index + 1)}_visual_v{version_count}.png"
106
+
107
+ generated_image_path = st.session_state.visual_engine.generate_image_visual(
108
+ textual_image_prompt,
109
+ image_filename
110
  )
111
+
112
+ if generated_image_path and os.path.exists(generated_image_path):
113
+ st.success(f"Visual concept for Scene {scene_data.get('scene_number', scene_index + 1)} generated: {os.path.basename(generated_image_path)}")
114
+ if scene_index < len(st.session_state.generated_images_paths):
115
+ st.session_state.generated_images_paths[scene_index] = generated_image_path
116
+ else:
117
+ while len(st.session_state.generated_images_paths) <= scene_index:
118
+ st.session_state.generated_images_paths.append(None)
119
+ st.session_state.generated_images_paths[scene_index] = generated_image_path
120
+ return True
121
+ else:
122
+ st.error(f"Visual generation failed for Scene {scene_data.get('scene_number', scene_index + 1)} (path: {generated_image_path}).")
123
+ if scene_index < len(st.session_state.generated_images_paths):
124
+ st.session_state.generated_images_paths[scene_index] = None
125
+ else:
126
+ while len(st.session_state.generated_images_paths) <= scene_index:
127
+ st.session_state.generated_images_paths.append(None)
128
+ st.session_state.generated_images_paths[scene_index] = None
129
+ return False
 
130
 
131
  # --- UI Sidebar ---
132
  with st.sidebar:
133
  st.title("🎬 CineGen AI Pro")
134
  st.markdown("### Creative Controls")
135
+ user_idea = st.text_area("Enter your core story idea:", "A detective in a cyberpunk city investigates a rogue AI that believes it's the next step in evolution.", height=100, key="user_idea_input")
136
+ genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror"], index=2, key="genre_select")
137
+ mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
138
+ num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider") # Max 5 for now
139
 
140
  if st.button("✨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
141
  initialize_new_story()
142
+ if not user_idea.strip():
143
+ st.warning("Please enter a story idea.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  else:
145
+ with st.spinner("Phase 1: Gemini is drafting the script & scene breakdown... πŸ“œ"):
146
+ story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
147
+ try:
148
+ st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
149
+ st.toast("Script breakdown complete!", icon="βœ…")
150
+
151
+ # Initialize placeholders for prompts and images based on number of scenes
152
+ num_actual_scenes = len(st.session_state.story_scenes)
153
+ st.session_state.scene_image_prompts = [""] * num_actual_scenes
154
+ st.session_state.generated_images_paths = [None] * num_actual_scenes
155
+
156
+ except Exception as e:
157
+ st.error(f"Failed to generate story breakdown: {e}")
158
+ st.session_state.story_scenes = []
159
+
160
+ if st.session_state.story_scenes:
161
+ with st.spinner("Phase 2: Generating initial visual concepts... πŸ–ΌοΈ"):
162
+ success_count = 0
163
+ for i, scene_data_loop_var in enumerate(st.session_state.story_scenes):
164
+ if generate_visual_for_scene_wrapper(i, scene_data_loop_var, version_count=1):
165
+ success_count +=1
166
+ if success_count == len(st.session_state.story_scenes):
167
+ st.toast("Initial visual concepts generated!", icon="πŸ–ΌοΈ")
168
+ else:
169
+ st.warning(f"{success_count}/{len(st.session_state.story_scenes)} visual concepts generated. Some may have failed.")
170
 
 
 
 
 
 
 
 
171
 
172
+ st.markdown("---")
173
+ st.markdown("### Advanced Options (Conceptual)")
174
+ with st.expander("Character Consistency", expanded=False):
175
+ char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name")
176
+ char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc", height=80)
177
+ if st.button("Add/Update Character", key="add_char_btn"):
178
+ if char_name_input and char_desc_input:
179
+ st.session_state.character_definitions[char_name_input] = char_desc_input
180
+ st.success(f"Character '{char_name_input}' defined.")
181
+ else:
182
+ st.warning("Please provide both name and description.")
183
+ if st.session_state.character_definitions:
184
+ st.write("Defined Characters:")
185
+ for char, desc in st.session_state.character_definitions.items():
186
+ st.caption(f"**{char}:** {desc}")
187
+
188
+ with st.expander("Style Transfer", expanded=False):
189
+ style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_input", height=80)
190
+ if st.button("Apply Textual Style", key="apply_style_btn"):
191
+ st.session_state.style_reference_description = style_ref_text
192
+ st.success("Style reference applied. Re-generate visuals or full story to see changes.")
193
 
194
  # --- Main Content Area ---
195
  st.header("πŸ“ Cinematic Storyboard")
 
197
  if not st.session_state.story_scenes:
198
  st.info("Enter your idea in the sidebar and click 'Generate Full Story Concept' to begin.")
199
  else:
200
+ for i, scene_data_display in enumerate(st.session_state.story_scenes):
201
+ scene_num_display = scene_data_display.get('scene_number', i + 1)
202
+ # Use a unique key part for expanders if scene numbers can repeat or change
203
+ expander_key_part = scene_data_display.get('key_action', f"scene{i}")[:20].replace(" ", "")
204
 
205
+ st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
206
+
207
+ col1, col2 = st.columns([2, 3])
208
 
209
  with col1: # Scene Details
210
  with st.expander("Scene Details", expanded=True):
211
+ st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
212
+ st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
213
+ st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}")
214
+ st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`")
215
+ st.markdown(f"**Visual Style:** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
216
+ st.markdown(f"**Camera:** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
217
+
218
  if i < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[i]:
219
+ with st.popover("View Image Prompt Text"):
220
+ st.markdown(f"**Textual Prompt for Image Generation:**")
221
+ st.code(st.session_state.scene_image_prompts[i], language='text')
222
 
223
  with col2: # Image and Interactive Editing
224
+ if i < len(st.session_state.generated_images_paths) and \
225
+ st.session_state.generated_images_paths[i] and \
226
+ os.path.exists(st.session_state.generated_images_paths[i]):
227
+ st.image(st.session_state.generated_images_paths[i], caption=f"Visual Concept for Scene {scene_num_display}")
228
  else:
229
+ if st.session_state.story_scenes: # Only show if story generation was attempted
230
+ st.warning("Visual concept not generated or path is invalid for this scene.")
231
+
232
+ # Interactive Storyboarding UI using unique keys for each scene's widgets
233
+ popover_script_key = f"pop_script_{expander_key_part}_{i}"
234
+ popover_visual_key = f"pop_visual_{expander_key_part}_{i}"
235
+
236
+ with st.popover(f"✏️ Edit Scene {scene_num_display} Script", key=popover_script_key):
237
+ feedback_script = st.text_area("Describe changes to script details:",
238
+ key=f"script_feedback_{expander_key_part}_{i}", height=100)
239
+ if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{expander_key_part}_{i}"):
240
  if feedback_script:
241
+ with st.spinner(f"Gemini is rewriting Scene {scene_num_display}..."):
242
  regen_prompt = create_scene_regeneration_prompt(
243
+ scene_data_display, feedback_script, st.session_state.story_scenes
244
  )
245
  try:
246
  updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
247
+ st.session_state.story_scenes[i] = updated_scene_data
248
+ st.toast(f"Scene {scene_num_display} script updated!", icon="✍️")
249
+ # Regenerate visuals for this updated scene with new script details
250
+ # Increment version for the image filename
251
+ current_version = 1
252
+ if st.session_state.generated_images_paths[i]:
253
+ try: # Try to extract version from existing filename
254
+ base, ext = os.path.splitext(os.path.basename(st.session_state.generated_images_paths[i]))
255
+ if '_v' in base:
256
+ current_version = int(base.split('_v')[-1]) + 1
257
+ except ValueError: pass # Keep current_version as 1 if parsing fails
258
+
259
+ generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=current_version)
260
+ st.rerun()
261
  except Exception as e:
262
  st.error(f"Error regenerating scene script: {e}")
263
  else:
264
  st.warning("Please provide feedback for script regeneration.")
265
 
266
+ with st.popover(f"🎨 Edit Scene {scene_num_display} Visuals", key=popover_visual_key):
267
+ current_prompt_for_edit = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt yet."
268
+ st.caption("Current Image Prompt Text:")
269
+ st.code(current_prompt_for_edit, language='text')
270
+
271
+ feedback_visual = st.text_area("Describe visual changes to apply to the prompt:",
272
+ key=f"visual_feedback_{expander_key_part}_{i}", height=100)
273
+ if st.button(f"πŸ”„ Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{expander_key_part}_{i}"):
274
+ if feedback_visual:
275
+ with st.spinner(f"Refining image prompt for Scene {scene_num_display}..."):
276
+ # This creates a prompt FOR GEMINI to refine the image prompt
277
+ prompt_refinement_request = create_visual_regeneration_prompt(
278
+ current_prompt_for_edit,
279
+ feedback_visual,
280
+ scene_data_display
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  )
282
  try:
283
+ # Gemini refines the textual image prompt
284
+ refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
285
+ st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
286
+ st.toast(f"Image prompt for Scene {scene_num_display} refined!", icon="πŸ’‘")
287
+
288
+ # Now generate the new visual using the refined prompt
289
+ current_version = 1
290
+ if st.session_state.generated_images_paths[i]:
291
+ try:
292
+ base, ext = os.path.splitext(os.path.basename(st.session_state.generated_images_paths[i]))
293
+ if '_v' in base:
294
+ current_version = int(base.split('_v')[-1]) + 1
295
+ except ValueError: pass
296
+
297
+ generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=current_version)
298
  st.rerun()
299
  except Exception as e:
300
+ st.error(f"Error refining image prompt or regenerating visual: {e}")
301
  else:
302
+ st.warning("Please provide feedback for visual regeneration.")
303
+ st.markdown("---")
 
304
 
305
+ # Video Generation Button
306
+ if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths): # Check if any image exists
307
+ if st.button("🎬 Assemble Animatic Video", key="assemble_video_btn", type="primary"):
 
308
  with st.spinner("Assembling video... This might take a moment."):
309
+ valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
310
+ if valid_image_paths_for_video:
311
  st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
312
+ valid_image_paths_for_video,
313
+ output_filename="cinegen_pro_animatic.mp4",
314
  duration_per_image=3
315
  )
316
+ if st.session_state.video_path and os.path.exists(st.session_state.video_path):
317
+ st.toast("Animatic video assembled!", icon="🎞️")
318
+ st.balloons()
319
+ else:
320
+ st.error("Video assembly failed. Check logs for details.")
321
  else:
322
+ st.error("No valid images found to assemble video. Please generate visuals for scenes first.")
323
+ elif st.session_state.story_scenes: # Story exists but no images yet
324
+ st.warning("Generate visuals for scenes before assembling the video.")
 
325
 
326
 
327
  if st.session_state.video_path and os.path.exists(st.session_state.video_path):
328
  st.header("🎬 Generated Animatic Video")
329
+ try:
330
+ with open(st.session_state.video_path, 'rb') as video_file:
331
+ video_bytes = video_file.read()
332
+ st.video(video_bytes)
333
+ st.markdown(f"Video saved at: `{st.session_state.video_path}` (within the Space's file system)")
334
+ # Provide a download button for the video
335
+ with open(st.session_state.video_path, "rb") as fp_download:
336
+ st.download_button(
337
+ label="Download Animatic Video",
338
+ data=fp_download,
339
+ file_name=os.path.basename(st.session_state.video_path),
340
+ mime="video/mp4"
341
+ )
342
+ except Exception as e:
343
+ st.error(f"Error displaying or preparing video for download: {e}")
344
+
345
+
346
+ # --- Footer or additional info ---
347
+ st.sidebar.markdown("---")
348
+ st.sidebar.info("CineGen AI by [Your Name/Company]. Powered by Gemini & Streamlit.")