File size: 18,774 Bytes
de2fdbb a7374a3 32b08ff a7374a3 32b08ff a7374a3 32b08ff de2fdbb a7374a3 de2fdbb a7374a3 de2fdbb a7374a3 32b08ff a7374a3 32b08ff de2fdbb a7374a3 32b08ff de2fdbb 32b08ff a7374a3 32b08ff a7374a3 de2fdbb 32b08ff a7374a3 32b08ff de2fdbb 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 32b08ff a7374a3 3c0fb64 32b08ff a7374a3 32b08ff a7374a3 3c0fb64 a7374a3 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 32b08ff a7374a3 3c0fb64 32b08ff a7374a3 3c0fb64 32b08ff 3c0fb64 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 32b08ff 3c0fb64 32b08ff 3c0fb64 a7374a3 3c0fb64 32b08ff a7374a3 32b08ff a7374a3 32b08ff 3c0fb64 a7374a3 3c0fb64 a7374a3 32b08ff 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 32b08ff a7374a3 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 32b08ff a7374a3 32b08ff a7374a3 32b08ff de2fdbb a7374a3 3c0fb64 de2fdbb 3c0fb64 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# app.py
import streamlit as st
from core.gemini_handler import GeminiHandler
from core.visual_engine import VisualEngine # Your updated VisualEngine
from core.prompt_engineering import (
create_story_breakdown_prompt,
create_image_prompt_from_scene_data, # Generates the TEXT prompt for AI/placeholder
create_scene_regeneration_prompt,
create_visual_regeneration_prompt # Regenerates the TEXT prompt
)
import os
# import json # Only if you need to st.json for debugging, not essential for core logic
# --- Configuration & Initialization ---
st.set_page_config(page_title="CineGen AI Pro", layout="wide", initial_sidebar_state="expanded")
# --- Global State Variables (Using session state for persistence) ---
if 'GEMINI_API_KEY' not in st.session_state:
try:
st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
except KeyError: # For local dev if secrets aren't set yet
if "GEMINI_API_KEY" in os.environ:
st.session_state.GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
else:
st.error("GEMINI_API_KEY not found in secrets or environment variables. Please add it.")
st.stop()
except Exception as e: # Catch any other secrets-related errors
st.error(f"Error loading secrets: {e}")
st.stop()
if 'gemini_handler' not in st.session_state:
st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
if 'visual_engine' not in st.session_state:
st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
# Story and generated content
if 'story_scenes' not in st.session_state: # List of scene dicts
st.session_state.story_scenes = []
if 'scene_image_prompts' not in st.session_state: # List of textual image prompts
st.session_state.scene_image_prompts = []
if 'generated_images_paths' not in st.session_state: # List of file paths for generated visuals
st.session_state.generated_images_paths = []
if 'video_path' not in st.session_state:
st.session_state.video_path = None
# For Character Consistency (placeholders)
if 'character_definitions' not in st.session_state:
st.session_state.character_definitions = {}
# For Style Transfer (placeholders)
if 'style_reference_description' not in st.session_state:
st.session_state.style_reference_description = None
# --- Helper Functions ---
def initialize_new_story():
st.session_state.story_scenes = []
st.session_state.scene_image_prompts = []
st.session_state.generated_images_paths = []
st.session_state.video_path = None
# Optional: Clear old media files
# if os.path.exists(st.session_state.visual_engine.output_dir):
# for f_name in os.listdir(st.session_state.visual_engine.output_dir):
# try:
# os.remove(os.path.join(st.session_state.visual_engine.output_dir, f_name))
# except OSError: # File might be in use by video player, handle gracefully
# print(f"Could not remove {f_name}, it might be in use.")
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
"""
Wrapper to generate the textual image prompt and then the actual visual.
Updates session state for prompts and image paths.
"""
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
st.info(f"Processing visual concept for Scene {scene_num_for_log} (v{version_count})...")
textual_image_prompt = ""
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts):
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
st.caption(f"Using (potentially refined) prompt for Scene {scene_num_for_log}.")
else:
textual_image_prompt = create_image_prompt_from_scene_data(
scene_data,
st.session_state.character_definitions,
st.session_state.style_reference_description
)
st.caption(f"Generated initial prompt for Scene {scene_num_for_log}.")
if not textual_image_prompt:
st.error(f"Failed to create/retrieve textual image prompt for Scene {scene_num_for_log}.")
return False
# Update session state for the textual prompt if it's new or changed
if scene_index >= len(st.session_state.scene_image_prompts):
while len(st.session_state.scene_image_prompts) <= scene_index:
st.session_state.scene_image_prompts.append("")
st.session_state.scene_image_prompts[scene_index] = textual_image_prompt
image_filename = f"scene_{scene_num_for_log}_visual_v{version_count}.png"
generated_image_path = st.session_state.visual_engine.generate_image_visual(
textual_image_prompt,
image_filename
)
# Ensure generated_images_paths is long enough
while len(st.session_state.generated_images_paths) <= scene_index:
st.session_state.generated_images_paths.append(None)
if generated_image_path and os.path.exists(generated_image_path):
st.success(f"Visual concept for Scene {scene_num_for_log} (v{version_count}) available: {os.path.basename(generated_image_path)}")
st.session_state.generated_images_paths[scene_index] = generated_image_path
return True
else:
st.error(f"Visual generation failed for Scene {scene_num_for_log} (v{version_count}). Path: {generated_image_path}")
st.session_state.generated_images_paths[scene_index] = None
return False
# --- UI Sidebar ---
with st.sidebar:
st.title("π¬ CineGen AI Pro")
st.markdown("### Creative Controls")
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")
genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror"], index=2, key="genre_select")
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful"], index=2, key="mood_select")
num_scenes_val = st.slider("Number of Scenes:", 1, 5, 3, key="num_scenes_slider")
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
initialize_new_story()
if not user_idea.strip():
st.warning("Please enter a story idea.")
else:
with st.spinner("Phase 1: Gemini is drafting the script & scene breakdown... π"):
story_prompt_text = create_story_breakdown_prompt(user_idea, genre, mood, num_scenes_val)
try:
st.session_state.story_scenes = st.session_state.gemini_handler.generate_story_breakdown(story_prompt_text)
st.toast("Script breakdown complete!", icon="β
")
num_actual_scenes = len(st.session_state.story_scenes)
st.session_state.scene_image_prompts = [""] * num_actual_scenes
st.session_state.generated_images_paths = [None] * num_actual_scenes
except Exception as e:
st.error(f"Failed to generate story breakdown: {e}")
st.session_state.story_scenes = []
if st.session_state.story_scenes:
with st.spinner("Phase 2: Generating initial visual concepts... πΌοΈ"):
success_count = 0
for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
success_count +=1
if success_count == len(st.session_state.story_scenes):
st.toast("Initial visual concepts generated!", icon="πΌοΈ")
else:
st.warning(f"{success_count}/{len(st.session_state.story_scenes)} visual concepts generated. Some may have failed.")
st.markdown("---")
st.markdown("### Advanced Options")
with st.expander("Character Consistency", expanded=False):
char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name_adv_input") # Unique key
char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc_adv_input", height=80) # Unique key
if st.button("Add/Update Character", key="add_char_adv_btn"): # Unique key
if char_name_input and char_desc_input:
st.session_state.character_definitions[char_name_input] = char_desc_input
st.success(f"Character '{char_name_input}' defined.")
else:
st.warning("Please provide both name and description.")
if st.session_state.character_definitions:
st.caption("Defined Characters:")
for char, desc in st.session_state.character_definitions.items():
st.markdown(f"**{char}:** _{desc}_")
with st.expander("Style Transfer", expanded=False):
style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_adv_input", height=80) # Unique key
if st.button("Apply Textual Style", key="apply_style_adv_btn"): # Unique key
st.session_state.style_reference_description = style_ref_text
st.success("Style reference applied. Re-generate visuals or full story to see changes.")
# --- Main Content Area ---
st.header("π Cinematic Storyboard")
if not st.session_state.story_scenes:
st.info("Enter your idea in the sidebar and click 'Generate Full Story Concept' to begin.")
else:
for i, scene_data_display in enumerate(st.session_state.story_scenes):
scene_num_display = scene_data_display.get('scene_number', i + 1)
# Create a more robust unique part for keys, sanitizing special characters
action_summary = scene_data_display.get('key_action', f"scene{i}")
key_part_raw = ''.join(e for e in action_summary if e.isalnum() or e.isspace())
unique_key_part = key_part_raw.replace(" ", "_")[:20] # Take first 20 alphanumeric/space chars
st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
col1, col2 = st.columns([2, 3]) # Ratio for text vs image
with col1:
with st.expander("Scene Details", expanded=True):
st.markdown(f"**Setting:** {scene_data_display.get('setting_description', 'N/A')}")
st.markdown(f"**Characters:** {', '.join(scene_data_display.get('characters_involved', []))}")
st.markdown(f"**Key Action:** {scene_data_display.get('key_action', 'N/A')}")
st.markdown(f"**Dialogue Snippet:** `\"{scene_data_display.get('dialogue_snippet', '...')}\"`")
st.markdown(f"**Visual Style:** {scene_data_display.get('visual_style_suggestion', 'N/A')}")
st.markdown(f"**Camera:** {scene_data_display.get('camera_angle_suggestion', 'N/A')}")
current_textual_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else None
if current_textual_prompt:
with st.popover("View Image Prompt Text"):
st.markdown(f"**Textual Prompt for Image Generation:**")
st.code(current_textual_prompt, language='text')
with col2:
current_image_path = st.session_state.generated_images_paths[i] if i < len(st.session_state.generated_images_paths) else None
if current_image_path and os.path.exists(current_image_path):
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
else:
if st.session_state.story_scenes:
st.info("Visual concept for this scene is pending or failed.")
# Removed 'key' argument from st.popover calls
with st.popover(f"βοΈ Edit Scene {scene_num_display} Script"):
feedback_script = st.text_area("Describe changes to script details:",
key=f"script_feedback_{unique_key_part}_{i}", height=100)
if st.button(f"π Regenerate Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"):
if feedback_script:
with st.spinner(f"Gemini is rewriting Scene {scene_num_display}..."):
regen_prompt = create_scene_regeneration_prompt(
scene_data_display, feedback_script, st.session_state.story_scenes
)
try:
updated_scene_data = st.session_state.gemini_handler.regenerate_scene_script_details(regen_prompt)
st.session_state.story_scenes[i] = updated_scene_data
st.toast(f"Scene {scene_num_display} script updated!", icon="βοΈ")
version = 1
if current_image_path:
try:
base, _ = os.path.splitext(os.path.basename(current_image_path))
if '_v' in base: version = int(base.split('_v')[-1]) + 1
except (ValueError, AttributeError, TypeError): pass
generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version)
st.rerun()
except Exception as e:
st.error(f"Error regenerating scene script: {e}")
else:
st.warning("Please provide feedback for script regeneration.")
with st.popover(f"π¨ Edit Scene {scene_num_display} Visuals"):
prompt_for_edit_visuals = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else "No prompt text available."
st.caption("Current Image Prompt Text:")
st.code(prompt_for_edit_visuals, language='text')
feedback_visual = st.text_area("Describe visual changes to apply to the prompt:",
key=f"visual_feedback_{unique_key_part}_{i}", height=100)
if st.button(f"π Regenerate Scene {scene_num_display} Visuals", key=f"regen_visual_btn_{unique_key_part}_{i}"):
if feedback_visual:
with st.spinner(f"Refining image prompt for Scene {scene_num_display}..."):
prompt_refinement_request = create_visual_regeneration_prompt(
prompt_for_edit_visuals, feedback_visual, scene_data_display
)
try:
refined_textual_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(prompt_refinement_request)
st.session_state.scene_image_prompts[i] = refined_textual_image_prompt
st.toast(f"Image prompt for Scene {scene_num_display} refined!", icon="π‘")
version = 1
if current_image_path:
try:
base, _ = os.path.splitext(os.path.basename(current_image_path))
if '_v' in base: version = int(base.split('_v')[-1]) + 1
except (ValueError, AttributeError, TypeError): pass
generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version)
st.rerun()
except Exception as e:
st.error(f"Error refining image prompt or regenerating visual: {e}")
else:
st.warning("Please provide feedback for visual regeneration.")
st.markdown("---")
# Video Generation Button
if st.session_state.story_scenes and any(p for p in st.session_state.generated_images_paths if p is not None):
if st.button("π¬ Assemble Animatic Video", key="assemble_video_btn", type="primary"):
with st.spinner("Assembling video... This might take a moment."):
valid_image_paths_for_video = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
if valid_image_paths_for_video:
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
valid_image_paths_for_video,
output_filename="cinegen_pro_animatic.mp4",
duration_per_image=3
)
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
st.toast("Animatic video assembled!", icon="ποΈ")
st.balloons()
else:
st.error("Video assembly failed. Check application logs for details from VisualEngine.")
else:
st.error("No valid images found to assemble video. Please ensure visuals were generated successfully.")
elif st.session_state.story_scenes:
st.info("Generate visuals for scenes before assembling the video.")
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
st.header("π¬ Generated Animatic Video")
try:
with open(st.session_state.video_path, 'rb') as video_file_obj:
video_bytes_content = video_file_obj.read()
st.video(video_bytes_content)
# Download button
with open(st.session_state.video_path, "rb") as fp_download_video:
st.download_button(
label="Download Animatic Video",
data=fp_download_video,
file_name=os.path.basename(st.session_state.video_path), # e.g., "cinegen_pro_animatic.mp4"
mime="video/mp4"
)
except Exception as e:
st.error(f"Error displaying or preparing video for download: {e}")
# --- Footer ---
st.sidebar.markdown("---")
st.sidebar.caption("CineGen AI Pro | Powered by Gemini & Streamlit") |