File size: 19,959 Bytes
de2fdbb cd569d8 32b08ff 259f581 32b08ff 259f581 32b08ff de2fdbb a7374a3 de2fdbb a7374a3 259f581 32b08ff 259f581 a7374a3 259f581 32b08ff de2fdbb 259f581 32b08ff 259f581 32b08ff 259f581 48777e5 cd569d8 259f581 cd569d8 259f581 48777e5 259f581 cd569d8 de2fdbb 259f581 32b08ff 259f581 32b08ff 259f581 de2fdbb 32b08ff a7374a3 32b08ff de2fdbb a7374a3 3c0fb64 cd569d8 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 cd569d8 32b08ff 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 32b08ff cd569d8 48777e5 32b08ff 259f581 32b08ff a7374a3 32b08ff 259f581 a7374a3 cd569d8 a7374a3 cd569d8 48777e5 cd569d8 259f581 48777e5 a7374a3 3c0fb64 cd569d8 3c0fb64 a7374a3 cd569d8 a7374a3 259f581 cd569d8 a7374a3 259f581 32b08ff a7374a3 3c0fb64 a7374a3 259f581 cd569d8 259f581 a7374a3 cd569d8 259f581 a7374a3 3c0fb64 cd569d8 48777e5 a7374a3 cd569d8 259f581 a7374a3 32b08ff a7374a3 3c0fb64 259f581 32b08ff a7374a3 259f581 32b08ff 3c0fb64 32b08ff a7374a3 3c0fb64 a7374a3 3c0fb64 48777e5 32b08ff 3c0fb64 259f581 3c0fb64 a7374a3 3c0fb64 32b08ff 259f581 32b08ff a7374a3 32b08ff 3c0fb64 259f581 3c0fb64 259f581 a7374a3 32b08ff 259f581 32b08ff 3c0fb64 a7374a3 3c0fb64 a7374a3 3c0fb64 a7374a3 259f581 a7374a3 3c0fb64 32b08ff a7374a3 3c0fb64 259f581 3c0fb64 a7374a3 3c0fb64 259f581 cd569d8 259f581 32b08ff 259f581 32b08ff a7374a3 3c0fb64 cd569d8 32b08ff cd569d8 de2fdbb a7374a3 cd569d8 a7374a3 cd569d8 de2fdbb 259f581 3c0fb64 32b08ff a7374a3 3c0fb64 48777e5 3c0fb64 a7374a3 cd569d8 259f581 48777e5 a7374a3 3c0fb64 a7374a3 259f581 |
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 |
# app.py
import streamlit as st
from core.gemini_handler import GeminiHandler
from core.visual_engine import VisualEngine
from core.prompt_engineering import (
create_story_breakdown_prompt,
create_image_prompt_from_scene_data,
create_scene_regeneration_prompt,
create_visual_regeneration_prompt
)
import os
# --- 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) ---
# Load Gemini API Key
if 'GEMINI_API_KEY' not in st.session_state:
try:
st.session_state.GEMINI_API_KEY = st.secrets["GEMINI_API_KEY"]
except KeyError:
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:
st.error(f"Error loading Gemini API Key from secrets: {e}")
st.stop()
# Initialize Gemini Handler
if 'gemini_handler' not in st.session_state:
st.session_state.gemini_handler = GeminiHandler(api_key=st.session_state.GEMINI_API_KEY)
# Initialize Visual Engine and set OpenAI API Key
if 'visual_engine' not in st.session_state:
st.session_state.visual_engine = VisualEngine(output_dir="temp_cinegen_media")
openai_key_from_secrets = None
try:
if "OPENAI_API_KEY" in st.secrets:
openai_key_from_secrets = st.secrets["OPENAI_API_KEY"]
except AttributeError:
print("st.secrets not available (likely local dev without secrets.toml). Checking environment variables.")
except Exception as e:
print(f"Could not access st.secrets for OPENAI_API_KEY: {e}")
if not openai_key_from_secrets and "OPENAI_API_KEY" in os.environ:
openai_key_from_secrets = os.environ["OPENAI_API_KEY"]
if openai_key_from_secrets:
st.session_state.visual_engine.set_openai_api_key(openai_key_from_secrets)
else:
st.session_state.visual_engine.set_openai_api_key(None)
st.sidebar.caption("OpenAI API Key for DALL-E not found. Visuals will be placeholders.")
# Story and generated content state
if 'story_scenes' not in st.session_state:
st.session_state.story_scenes = []
if 'scene_image_prompts' not in st.session_state:
st.session_state.scene_image_prompts = []
if 'generated_images_paths' not in st.session_state:
st.session_state.generated_images_paths = []
if 'video_path' not in st.session_state:
st.session_state.video_path = None
if 'character_definitions' not in st.session_state:
st.session_state.character_definitions = {}
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
def generate_visual_for_scene_wrapper(scene_index, scene_data, is_regeneration=False, version_count=1):
scene_num_for_log = scene_data.get('scene_number', scene_index + 1)
textual_image_prompt = ""
if is_regeneration and scene_index < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[scene_index]:
textual_image_prompt = st.session_state.scene_image_prompts[scene_index]
else:
textual_image_prompt = create_image_prompt_from_scene_data(
scene_data,
st.session_state.character_definitions,
st.session_state.style_reference_description
)
if not textual_image_prompt:
return False
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
)
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.session_state.generated_images_paths[scene_index] = generated_image_path
return True
else:
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 lone astronaut discovers a glowing alien artifact on Mars, a sense of wonder and slight dread.", height=100, key="user_idea_input")
genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama", "Horror", "Cyberpunk"], index=6, key="genre_select")
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Epic", "Dark", "Hopeful", "Wonder"], index=6, key="mood_select")
num_scenes_val = st.slider("Number of Scenes:", 1, 5, 1, key="num_scenes_slider") # Default to 1 for faster testing
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn", use_container_width=True):
initialize_new_story()
if not user_idea.strip():
st.warning("Please enter a story idea.")
else:
with st.status("Generating story...", expanded=True) as status_main:
st.write("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)
status_main.update(label="Script breakdown complete! β
", state="running", expanded=True)
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:
status_main.update(label=f"Failed to generate story breakdown: {e}", state="error", expanded=True)
st.session_state.story_scenes = []
st.stop()
if st.session_state.story_scenes:
st.write("Phase 2: Generating initial visual concepts... πΌοΈ (This may take time with DALL-E)")
success_count = 0
for i_loop, scene_data_loop_var in enumerate(st.session_state.story_scenes):
scene_num_log = scene_data_loop_var.get('scene_number', i_loop + 1)
st.write(f"Generating visual for Scene {scene_num_log}...")
if generate_visual_for_scene_wrapper(i_loop, scene_data_loop_var, version_count=1):
success_count +=1
st.write(f"Visual for Scene {scene_num_log} done.")
else:
st.write(f"Visual for Scene {scene_num_log} failed.")
if success_count == len(st.session_state.story_scenes):
status_main.update(label="All concepts generated successfully! π", state="complete", expanded=False)
elif success_count > 0:
status_main.update(label=f"{success_count}/{len(st.session_state.story_scenes)} visuals generated.", state="warning", expanded=False)
else:
status_main.update(label="Visual concept generation failed for all scenes.", state="error", expanded=False)
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")
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)
if st.button("Add/Update Character", key="add_char_adv_btn"):
if char_name_input and char_desc_input:
st.session_state.character_definitions[char_name_input.strip().lower()] = char_desc_input.strip()
st.success(f"Character '{char_name_input.strip()}' defined.")
else:
st.warning("Please provide both name and description.")
if st.session_state.character_definitions:
st.caption("Defined Characters:")
for char_key, desc_val in st.session_state.character_definitions.items():
st.markdown(f"**{char_key.title()}:** _{desc_val}_")
with st.expander("Style Transfer", expanded=False):
style_ref_text = st.text_area("Describe Visual Style (e.g., 'impressionistic oil painting, vibrant colors')", key="style_text_adv_input", height=100)
if st.button("Apply Textual Style", key="apply_style_adv_btn"):
st.session_state.style_reference_description = style_ref_text.strip()
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)
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]
st.subheader(f"Scene {scene_num_display}: {scene_data_display.get('emotional_beat', 'Untitled Scene')}")
col1, col2 = st.columns([2, 3])
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):
# MODIFICATION HERE: Removed use_column_width=True
st.image(current_image_path, caption=f"Visual Concept for Scene {scene_num_display}")
else:
if st.session_state.story_scenes:
st.caption("Visual for this scene is pending or failed to generate.")
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.status(f"Rewriting Scene {scene_num_display} script...", expanded=True) as status_script_regen:
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
status_script_regen.update(label="Script updated! Regenerating visual...", state="running")
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 : pass
if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=True, version_count=version):
status_script_regen.update(label="Scene script & visual updated! π", state="complete", expanded=False)
else:
status_script_regen.update(label="Script updated, but visual failed.", state="warning", expanded=False)
st.rerun()
except Exception as e:
status_script_regen.update(label=f"Error regenerating scene script: {e}", state="error")
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.status(f"Refining prompt & regenerating visual for Scene {scene_num_display}...", expanded=True) as status_visual_regen:
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
status_visual_regen.update(label="Image prompt refined! Regenerating visual...", state="running")
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 : pass
if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version):
status_visual_regen.update(label="Visual for Scene updated! π", state="complete", expanded=False)
else:
status_visual_regen.update(label="Prompt refined, but visual failed.", state="warning", expanded=False)
st.rerun()
except Exception as e:
status_visual_regen.update(label=f"Error refining prompt or regenerating visual: {e}", state="error")
else:
st.warning("Please provide feedback for visual regeneration.")
st.markdown("---")
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 Enhanced Animatic", key="assemble_enhanced_video_btn", type="primary", use_container_width=True):
with st.status("Assembling enhanced animatic video...", expanded=False) as status_video:
image_data_for_video = []
for idx, scene_info in enumerate(st.session_state.story_scenes):
img_path = st.session_state.generated_images_paths[idx] if idx < len(st.session_state.generated_images_paths) else None
if img_path and os.path.exists(img_path):
image_data_for_video.append({
'path': img_path,
'scene_num': scene_info.get('scene_number', idx + 1),
'key_action': scene_info.get('key_action', '')
})
if image_data_for_video:
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
image_data_for_video,
output_filename="cinegen_pro_animatic_enhanced.mp4",
duration_per_image=4,
fps=24
)
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
status_video.update(label="Enhanced animatic assembled! π", state="complete")
st.balloons()
else:
status_video.update(label="Enhanced video assembly failed. Check logs.", state="error")
else:
status_video.update(label="No valid images to assemble video.", state="error")
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, format="video/mp4")
with open(st.session_state.video_path, "rb") as fp_download_video:
st.download_button(
label="Download Enhanced Animatic", data=fp_download_video,
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4",
use_container_width=True, key="download_video_btn"
)
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.") |