Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 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,
|
|
@@ -39,20 +39,23 @@ if 'visual_engine' not in st.session_state:
|
|
| 39 |
# --- TRY TO SET OPENAI API KEY FOR VISUAL ENGINE ---
|
| 40 |
openai_key_from_secrets = None
|
| 41 |
try:
|
|
|
|
| 42 |
if "OPENAI_API_KEY" in st.secrets:
|
| 43 |
openai_key_from_secrets = st.secrets["OPENAI_API_KEY"]
|
|
|
|
|
|
|
| 44 |
except Exception as e:
|
| 45 |
-
print(f"Could not access st.secrets for OPENAI_API_KEY: {e}")
|
| 46 |
|
| 47 |
if not openai_key_from_secrets and "OPENAI_API_KEY" in os.environ: # Fallback to env var
|
| 48 |
openai_key_from_secrets = os.environ["OPENAI_API_KEY"]
|
| 49 |
|
| 50 |
if openai_key_from_secrets:
|
| 51 |
st.session_state.visual_engine.set_openai_api_key(openai_key_from_secrets)
|
| 52 |
-
# st.sidebar.
|
| 53 |
else:
|
| 54 |
st.session_state.visual_engine.set_openai_api_key(None)
|
| 55 |
-
st.sidebar.
|
| 56 |
# --- END OF OPENAI API KEY SETUP ---
|
| 57 |
|
| 58 |
# Story and generated content state
|
|
@@ -78,22 +81,21 @@ def initialize_new_story():
|
|
| 78 |
|
| 79 |
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
|
| 80 |
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
|
| 81 |
-
# This st.info will appear briefly in the main app area below the "Generate" button.
|
| 82 |
-
# Consider moving detailed progress to sidebar or using st.status.
|
| 83 |
-
# st.info(f"Processing visual concept for Scene {scene_num_for_log} (v{version_count})...")
|
| 84 |
|
| 85 |
textual_image_prompt = ""
|
| 86 |
-
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
|
| 87 |
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
|
|
|
|
| 88 |
else:
|
| 89 |
textual_image_prompt = create_image_prompt_from_scene_data(
|
| 90 |
scene_data,
|
| 91 |
st.session_state.character_definitions,
|
| 92 |
st.session_state.style_reference_description
|
| 93 |
)
|
|
|
|
| 94 |
|
| 95 |
if not textual_image_prompt:
|
| 96 |
-
st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.")
|
| 97 |
return False
|
| 98 |
|
| 99 |
if scene_index >= len(st.session_state.scene_image_prompts):
|
|
@@ -103,21 +105,18 @@ def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=F
|
|
| 103 |
|
| 104 |
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
|
| 105 |
|
| 106 |
-
# The actual generation (AI or placeholder) happens here
|
| 107 |
generated_image_path = st.session_state.visual_engine.generate_image_visual(
|
| 108 |
-
textual_image_prompt,
|
| 109 |
-
image_filename
|
| 110 |
)
|
| 111 |
|
| 112 |
while len(st.session_state.generated_images_paths) <= scene_index:
|
| 113 |
st.session_state.generated_images_paths.append(None)
|
| 114 |
|
| 115 |
if generated_image_path and os.path.exists(generated_image_path):
|
| 116 |
-
# st.success(f"Visual for Scene {scene_num_for_log} (v{version_count}) ready.") # Can be noisy
|
| 117 |
st.session_state.generated_images_paths[scene_index] = generated_image_path
|
| 118 |
return True
|
| 119 |
else:
|
| 120 |
-
st.warning(f"Visual
|
| 121 |
st.session_state.generated_images_paths[scene_index] = None
|
| 122 |
return False
|
| 123 |
|
|
@@ -125,10 +124,10 @@ def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=F
|
|
| 125 |
with st.sidebar:
|
| 126 |
st.title("π¬ CineGen AI Pro")
|
| 127 |
st.markdown("### Creative Controls")
|
| 128 |
-
user_idea = st.text_area("Enter your core story idea:", "A
|
| 129 |
-
genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror"], index=
|
| 130 |
-
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=
|
| 131 |
-
num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider")
|
| 132 |
|
| 133 |
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
|
| 134 |
initialize_new_story()
|
|
@@ -140,51 +139,55 @@ with st.sidebar:
|
|
| 140 |
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
|
| 141 |
try:
|
| 142 |
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
|
| 143 |
-
status_main.update(label="Script breakdown complete! β
", state="running")
|
| 144 |
|
| 145 |
num_actual_scenes = len(st.session_state.story_scenes)
|
| 146 |
st.session_state.scene_image_prompts = [""] * num_actual_scenes
|
| 147 |
st.session_state.generated_images_paths = [None] * num_actual_scenes
|
| 148 |
|
| 149 |
except Exception as e:
|
| 150 |
-
status_main.update(label=f"Failed to generate story breakdown: {e}", state="error")
|
| 151 |
-
st.session_state.story_scenes = []
|
| 152 |
-
st.stop()
|
| 153 |
|
| 154 |
if st.session_state.story_scenes:
|
| 155 |
-
st.write("Phase 2: Generating initial visual concepts... πΌοΈ")
|
| 156 |
success_count = 0
|
| 157 |
for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
|
| 158 |
-
|
|
|
|
| 159 |
if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
|
| 160 |
success_count +=1
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
if success_count == len(st.session_state.story_scenes):
|
| 163 |
status_main.update(label="All concepts generated successfully! π", state="complete", expanded=False)
|
| 164 |
elif success_count > 0:
|
| 165 |
-
status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals generated.
|
| 166 |
else:
|
| 167 |
status_main.update(label="Visual concept generation failed for all scenes.", state="error", expanded=False)
|
| 168 |
|
| 169 |
-
|
| 170 |
st.markdown("---")
|
| 171 |
st.markdown("### Advanced Options")
|
| 172 |
with st.expander("Character Consistency", expanded=False):
|
| 173 |
char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input")
|
| 174 |
-
char_desc_input = st.text_area("Character Description (
|
| 175 |
if st.button("Add/Update Character", key="add_char_adv_btn"):
|
| 176 |
if char_name_input and char_desc_input:
|
| 177 |
-
|
|
|
|
| 178 |
st.success(f"Character '{char_name_input.strip()}' defined.")
|
| 179 |
else:
|
| 180 |
st.warning("Please provide both name and description.")
|
| 181 |
if st.session_state.character_definitions:
|
| 182 |
st.caption("Defined Characters:")
|
| 183 |
-
for
|
| 184 |
-
st.markdown(f"**{
|
| 185 |
|
| 186 |
with st.expander("Style Transfer", expanded=False):
|
| 187 |
-
style_ref_text = st.text_area("Describe Style (e.g., '
|
| 188 |
if st.button("Apply Textual Style", key="apply_style_adv_btn"):
|
| 189 |
st.session_state.style_reference_description = style_ref_text.strip()
|
| 190 |
st.success("Style reference applied. Re-generate visuals or full story to see changes.")
|
|
@@ -221,7 +224,7 @@ else:
|
|
| 221 |
with col2:
|
| 222 |
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
|
| 223 |
if current_image_path and os.path.exists(current_image_path):
|
| 224 |
-
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
|
| 225 |
else:
|
| 226 |
if st.session_state.story_scenes:
|
| 227 |
st.caption("Visual for this scene is pending or failed to generate.")
|
|
@@ -282,7 +285,8 @@ else:
|
|
| 282 |
if '_v' in base: version = int(base.split('_v')[-1]) + 1
|
| 283 |
except : pass
|
| 284 |
|
| 285 |
-
|
|
|
|
| 286 |
status_visual_regen.update(label="Visual for Scene updated! π", state="complete", expanded=False)
|
| 287 |
else:
|
| 288 |
status_visual_regen.update(label="Prompt refined, but visual failed.", state="warning", expanded=False)
|
|
@@ -294,20 +298,30 @@ else:
|
|
| 294 |
st.markdown("---")
|
| 295 |
|
| 296 |
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
|
| 297 |
-
if st.button("π¬ Assemble Animatic
|
| 298 |
-
with st.status("Assembling video...", expanded=False) as status_video:
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
|
| 302 |
-
|
| 303 |
-
output_filename="
|
| 304 |
-
duration_per_image=
|
|
|
|
| 305 |
)
|
| 306 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
| 307 |
-
status_video.update(label="
|
| 308 |
st.balloons()
|
| 309 |
else:
|
| 310 |
-
status_video.update(label="
|
| 311 |
else:
|
| 312 |
status_video.update(label="No valid images to assemble video.", state="error")
|
| 313 |
elif st.session_state.story_scenes:
|
|
@@ -318,12 +332,12 @@ else:
|
|
| 318 |
try:
|
| 319 |
with open(st.session_state.video_path, 'rb') as video_file_obj:
|
| 320 |
video_bytes_content = video_file_obj.read()
|
| 321 |
-
st.video(video_bytes_content)
|
| 322 |
with open(st.session_state.video_path, "rb") as fp_download_video:
|
| 323 |
st.download_button(
|
| 324 |
-
label="Download Animatic
|
| 325 |
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
|
| 326 |
-
use_container_width=True
|
| 327 |
)
|
| 328 |
except Exception as e:
|
| 329 |
st.error(f"Error displaying or preparing video for download: {e}")
|
|
|
|
| 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,
|
|
|
|
| 39 |
# --- TRY TO SET OPENAI API KEY FOR VISUAL ENGINE ---
|
| 40 |
openai_key_from_secrets = None
|
| 41 |
try:
|
| 42 |
+
# Check Streamlit Cloud secrets first
|
| 43 |
if "OPENAI_API_KEY" in st.secrets:
|
| 44 |
openai_key_from_secrets = st.secrets["OPENAI_API_KEY"]
|
| 45 |
+
except AttributeError: # st.secrets might not exist in all local dev environments
|
| 46 |
+
print("st.secrets not available (likely local dev without secrets.toml). Checking environment variables.")
|
| 47 |
except Exception as e:
|
| 48 |
+
print(f"Could not access st.secrets for OPENAI_API_KEY: {e}")
|
| 49 |
|
| 50 |
if not openai_key_from_secrets and "OPENAI_API_KEY" in os.environ: # Fallback to env var
|
| 51 |
openai_key_from_secrets = os.environ["OPENAI_API_KEY"]
|
| 52 |
|
| 53 |
if openai_key_from_secrets:
|
| 54 |
st.session_state.visual_engine.set_openai_api_key(openai_key_from_secrets)
|
| 55 |
+
# st.sidebar.caption("OpenAI API Key loaded. DALL-E ready.") # Optional UI feedback
|
| 56 |
else:
|
| 57 |
st.session_state.visual_engine.set_openai_api_key(None)
|
| 58 |
+
st.sidebar.caption("OpenAI API Key for DALL-E not found. Visuals will be placeholders.")
|
| 59 |
# --- END OF OPENAI API KEY SETUP ---
|
| 60 |
|
| 61 |
# Story and generated content state
|
|
|
|
| 81 |
|
| 82 |
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
|
| 83 |
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
textual_image_prompt = ""
|
| 86 |
+
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
|
| 87 |
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
|
| 88 |
+
# st.caption(f"Using refined prompt for Scene {scene_num_for_log}.") # Can be noisy
|
| 89 |
else:
|
| 90 |
textual_image_prompt = create_image_prompt_from_scene_data(
|
| 91 |
scene_data,
|
| 92 |
st.session_state.character_definitions,
|
| 93 |
st.session_state.style_reference_description
|
| 94 |
)
|
| 95 |
+
# st.caption(f"Generated initial prompt for Scene {scene_num_for_log}.") # Can be noisy
|
| 96 |
|
| 97 |
if not textual_image_prompt:
|
| 98 |
+
# st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.") # Handled by status
|
| 99 |
return False
|
| 100 |
|
| 101 |
if scene_index >= len(st.session_state.scene_image_prompts):
|
|
|
|
| 105 |
|
| 106 |
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
|
| 107 |
|
|
|
|
| 108 |
generated_image_path = st.session_state.visual_engine.generate_image_visual(
|
| 109 |
+
textual_image_prompt, image_filename
|
|
|
|
| 110 |
)
|
| 111 |
|
| 112 |
while len(st.session_state.generated_images_paths) <= scene_index:
|
| 113 |
st.session_state.generated_images_paths.append(None)
|
| 114 |
|
| 115 |
if generated_image_path and os.path.exists(generated_image_path):
|
|
|
|
| 116 |
st.session_state.generated_images_paths[scene_index] = generated_image_path
|
| 117 |
return True
|
| 118 |
else:
|
| 119 |
+
# st.warning(f"Visual for Scene {scene_num_for_log} (v{version_count}) failed or path invalid.") # Handled by status
|
| 120 |
st.session_state.generated_images_paths[scene_index] = None
|
| 121 |
return False
|
| 122 |
|
|
|
|
| 124 |
with st.sidebar:
|
| 125 |
st.title("π¬ CineGen AI Pro")
|
| 126 |
st.markdown("### Creative Controls")
|
| 127 |
+
user_idea = st.text_area("Enter your 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")
|
| 128 |
+
genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror", "Cyberpunk"], index=6, key="genre_select")
|
| 129 |
+
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful", "Wonder"], index=6, key="mood_select")
|
| 130 |
+
num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider") # Max 5 for sensible generation times
|
| 131 |
|
| 132 |
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
|
| 133 |
initialize_new_story()
|
|
|
|
| 139 |
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
|
| 140 |
try:
|
| 141 |
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
|
| 142 |
+
status_main.update(label="Script breakdown complete! β
", state="running", expanded=True)
|
| 143 |
|
| 144 |
num_actual_scenes = len(st.session_state.story_scenes)
|
| 145 |
st.session_state.scene_image_prompts = [""] * num_actual_scenes
|
| 146 |
st.session_state.generated_images_paths = [None] * num_actual_scenes
|
| 147 |
|
| 148 |
except Exception as e:
|
| 149 |
+
status_main.update(label=f"Failed to generate story breakdown: {e}", state="error", expanded=True)
|
| 150 |
+
st.session_state.story_scenes = [] # Ensure it's empty on failure
|
| 151 |
+
st.stop()
|
| 152 |
|
| 153 |
if st.session_state.story_scenes:
|
| 154 |
+
st.write("Phase 2: Generating initial visual concepts... πΌοΈ (This may take a few minutes per scene with DALL-E)")
|
| 155 |
success_count = 0
|
| 156 |
for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
|
| 157 |
+
scene_num_log = scene_data_loop_var.get('scene_number', i_loop + 1)
|
| 158 |
+
st.write(f"Generating visual for Scene {scene_num_log}...")
|
| 159 |
if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
|
| 160 |
success_count +=1
|
| 161 |
+
st.write(f"Visual for Scene {scene_num_log} done.")
|
| 162 |
+
else:
|
| 163 |
+
st.write(f"Visual for Scene {scene_num_log} failed.")
|
| 164 |
|
| 165 |
if success_count == len(st.session_state.story_scenes):
|
| 166 |
status_main.update(label="All concepts generated successfully! π", state="complete", expanded=False)
|
| 167 |
elif success_count > 0:
|
| 168 |
+
status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals generated.", state="warning", expanded=False)
|
| 169 |
else:
|
| 170 |
status_main.update(label="Visual concept generation failed for all scenes.", state="error", expanded=False)
|
| 171 |
|
|
|
|
| 172 |
st.markdown("---")
|
| 173 |
st.markdown("### Advanced Options")
|
| 174 |
with st.expander("Character Consistency", expanded=False):
|
| 175 |
char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input")
|
| 176 |
+
char_desc_input = st.text_area("Character Description (e.g., 'female, short red hair, green eyes, wearing a blue jumpsuit')", key="char_desc_adv_input", height=100)
|
| 177 |
if st.button("Add/Update Character", key="add_char_adv_btn"):
|
| 178 |
if char_name_input and char_desc_input:
|
| 179 |
+
# Store character names in lowercase for easier matching, but keep original for display if needed
|
| 180 |
+
st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip()
|
| 181 |
st.success(f"Character '{char_name_input.strip()}' defined.")
|
| 182 |
else:
|
| 183 |
st.warning("Please provide both name and description.")
|
| 184 |
if st.session_state.character_definitions:
|
| 185 |
st.caption("Defined Characters:")
|
| 186 |
+
for char_key, desc_val in st.session_state.character_definitions.items():
|
| 187 |
+
st.markdown(f"**{char_key.title()}:** _{desc_val}_") # Display with title case
|
| 188 |
|
| 189 |
with st.expander("Style Transfer", expanded=False):
|
| 190 |
+
style_ref_text = st.text_area("Describe Visual Style (e.g., 'impressionistic oil painting, vibrant colors')", key="style_text_adv_input", height=100)
|
| 191 |
if st.button("Apply Textual Style", key="apply_style_adv_btn"):
|
| 192 |
st.session_state.style_reference_description = style_ref_text.strip()
|
| 193 |
st.success("Style reference applied. Re-generate visuals or full story to see changes.")
|
|
|
|
| 224 |
with col2:
|
| 225 |
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
|
| 226 |
if current_image_path and os.path.exists(current_image_path):
|
| 227 |
+
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}", use_column_width=True)
|
| 228 |
else:
|
| 229 |
if st.session_state.story_scenes:
|
| 230 |
st.caption("Visual for this scene is pending or failed to generate.")
|
|
|
|
| 285 |
if '_v' in base: version = int(base.split('_v')[-1]) + 1
|
| 286 |
except : pass
|
| 287 |
|
| 288 |
+
# Pass current scene_data_display as scene content hasn't changed, only the prompt.
|
| 289 |
+
if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version):
|
| 290 |
status_visual_regen.update(label="Visual for Scene updated! π", state="complete", expanded=False)
|
| 291 |
else:
|
| 292 |
status_visual_regen.update(label="Prompt refined, but visual failed.", state="warning", expanded=False)
|
|
|
|
| 298 |
st.markdown("---")
|
| 299 |
|
| 300 |
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
|
| 301 |
+
if st.button("π¬ Assemble Enhanced Animatic", key="assemble_enhanced_video_btn", type="primary", use_container_width=True):
|
| 302 |
+
with st.status("Assembling enhanced animatic video...", expanded=False) as status_video:
|
| 303 |
+
image_data_for_video = []
|
| 304 |
+
for idx, scene_info in enumerate(st.session_state.story_scenes):
|
| 305 |
+
img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
|
| 306 |
+
if img_path and os.path.exists(img_path):
|
| 307 |
+
image_data_for_video.append({
|
| 308 |
+
'path': img_path,
|
| 309 |
+
'scene_num': scene_info.get('scene_number', idx + 1),
|
| 310 |
+
'key_action': scene_info.get('key_action', '')
|
| 311 |
+
})
|
| 312 |
+
|
| 313 |
+
if image_data_for_video:
|
| 314 |
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
|
| 315 |
+
image_data_for_video,
|
| 316 |
+
output_filename="cinegen_pro_animatic_enhanced.mp4",
|
| 317 |
+
duration_per_image=4,
|
| 318 |
+
fps=24
|
| 319 |
)
|
| 320 |
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
|
| 321 |
+
status_video.update(label="Enhanced animatic assembled! π", state="complete")
|
| 322 |
st.balloons()
|
| 323 |
else:
|
| 324 |
+
status_video.update(label="Enhanced video assembly failed. Check logs.", state="error")
|
| 325 |
else:
|
| 326 |
status_video.update(label="No valid images to assemble video.", state="error")
|
| 327 |
elif st.session_state.story_scenes:
|
|
|
|
| 332 |
try:
|
| 333 |
with open(st.session_state.video_path, 'rb') as video_file_obj:
|
| 334 |
video_bytes_content = video_file_obj.read()
|
| 335 |
+
st.video(video_bytes_content, format="video/mp4") # Specify format for better compatibility
|
| 336 |
with open(st.session_state.video_path, "rb") as fp_download_video:
|
| 337 |
st.download_button(
|
| 338 |
+
label="Download Enhanced Animatic", data=fp_download_video,
|
| 339 |
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
|
| 340 |
+
use_container_width=True, key="download_video_btn" # Added key
|
| 341 |
)
|
| 342 |
except Exception as e:
|
| 343 |
st.error(f"Error displaying or preparing video for download: {e}")
|