Update app.py
Browse files
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
|
|
|
|
|
|
|
|
|
|
|
6 |
import os
|
|
|
7 |
|
8 |
# --- Configuration & Initialization ---
|
9 |
-
st.set_page_config(page_title="CineGen AI", layout="wide")
|
10 |
|
11 |
-
#
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
17 |
|
18 |
-
|
19 |
-
|
|
|
|
|
20 |
|
21 |
-
#
|
22 |
-
st.
|
23 |
-
st.
|
24 |
-
|
25 |
-
|
26 |
-
|
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 |
-
#
|
43 |
-
if
|
44 |
-
st.session_state.
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
st.session_state.generated_images_paths = []
|
47 |
st.session_state.video_path = None
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
st.
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
)
|
75 |
-
st.
|
|
|
76 |
else:
|
77 |
-
st.
|
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.
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
st.
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
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)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|