File size: 15,801 Bytes
de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff de2fdbb 32b08ff |
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 |
# 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
import json # For debugging and display
# --- Configuration & Initialization ---
st.set_page_config(page_title="CineGen AI Pro", layout="wide")
# --- 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:
st.error("GEMINI_API_KEY not found in secrets. Please add it.")
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 strings
st.session_state.scene_image_prompts = []
if 'generated_images_paths' not in st.session_state: # List of file paths
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 = {} # e.g., {"Eva": "Astronaut, red hair..."}
# 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
def generate_visual_for_scene(scene_index, scene_data):
"""Generates image prompt and placeholder image for a specific scene."""
img_gen_prompt_text = create_image_prompt_from_scene_data(
scene_data,
st.session_state.character_definitions,
st.session_state.style_reference_description
)
try:
actual_image_prompt = st.session_state.gemini_handler.generate_image_prompt(img_gen_prompt_text)
if actual_image_prompt:
if scene_index < len(st.session_state.scene_image_prompts):
st.session_state.scene_image_prompts[scene_index] = actual_image_prompt
else:
st.session_state.scene_image_prompts.append(actual_image_prompt)
img_path = st.session_state.visual_engine.create_placeholder_image(
f"Scene {scene_data.get('scene_number', scene_index + 1)}: {actual_image_prompt[:100]}...", # Show part of the actual prompt
f"scene_{scene_data.get('scene_number', scene_index + 1)}_placeholder.png"
)
if scene_index < len(st.session_state.generated_images_paths):
st.session_state.generated_images_paths[scene_index] = img_path
else:
st.session_state.generated_images_paths.append(img_path)
return True
except Exception as e:
st.error(f"Error generating visual for Scene {scene_data.get('scene_number', scene_index + 1)}: {e}")
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.", height=100, key="user_idea_input")
genre = st.selectbox("Genre:", ["Sci-Fi", "Fantasy", "Noir", "Thriller", "Drama"], index=2, key="genre_select")
mood = st.selectbox("Mood:", ["Suspenseful", "Mysterious", "Gritty", "Hopeful"], index=2, key="mood_select")
num_scenes_val = st.slider("Number of Scenes:", 1, 10, 3, key="num_scenes_slider")
if st.button("β¨ Generate Full Story Concept", type="primary", key="generate_full_story_btn"):
initialize_new_story()
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="β
")
# Initialize placeholders for prompts and images
st.session_state.scene_image_prompts = [""] * len(st.session_state.story_scenes)
st.session_state.generated_images_paths = [""] * len(st.session_state.story_scenes)
except Exception as e:
st.error(f"Failed to generate story breakdown: {e}")
st.session_state.story_scenes = [] # Clear on failure
if st.session_state.story_scenes:
with st.spinner("Phase 2: Generating initial visual concepts... πΌοΈ"):
for i, scene_data in enumerate(st.session_state.story_scenes):
generate_visual_for_scene(i, scene_data)
st.toast("Initial visual concepts generated!", icon="πΌοΈ")
st.markdown("---")
st.markdown("### Advanced Options (Conceptual)")
# Character Consistency UI
st.subheader("Character Consistency")
char_name_input = st.text_input("Character Name (e.g., Eva)", key="char_name")
char_desc_input = st.text_area("Character Description (for visual consistency)", key="char_desc", height=80)
if st.button("Add/Update Character", key="add_char_btn"):
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.write("Defined Characters:")
st.json(st.session_state.character_definitions)
# Style Transfer UI
st.subheader("Style Transfer")
style_ref_text = st.text_area("Describe Style (e.g., 'Van Gogh inspired, swirling brushstrokes')", key="style_text_input", height=80)
if st.button("Apply Textual Style", key="apply_style_btn"):
st.session_state.style_reference_description = style_ref_text
st.success("Style reference applied. Re-generate visuals to see changes.")
# conceptual_style_image = st.file_uploader("Or Upload Style Reference Image (Future Feature)", type=['png', 'jpg'])
# --- 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 in enumerate(st.session_state.story_scenes):
scene_num = scene_data.get('scene_number', i + 1)
st.subheader(f"Scene {scene_num}: {scene_data.get('emotional_beat', 'N/A')}")
col1, col2 = st.columns([2,3]) # Left for text, Right for image and edit
with col1: # Scene Details
with st.expander("Scene Details", expanded=True):
st.markdown(f"**Setting:** {scene_data.get('setting_description', 'N/A')}")
st.markdown(f"**Characters:** {', '.join(scene_data.get('characters_involved', []))}")
st.markdown(f"**Key Action:** {scene_data.get('key_action', 'N/A')}")
st.markdown(f"**Dialogue Snippet:** {scene_data.get('dialogue_snippet', 'N/A')}")
st.markdown(f"**Visual Style:** {scene_data.get('visual_style_suggestion', 'N/A')}")
st.markdown(f"**Camera:** {scene_data.get('camera_angle_suggestion', 'N/A')}")
if i < len(st.session_state.scene_image_prompts) and st.session_state.scene_image_prompts[i]:
with st.popover("View Image Prompt"):
st.code(st.session_state.scene_image_prompts[i])
with col2: # Image and Interactive Editing
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]):
st.image(st.session_state.generated_images_paths[i], caption=f"Visual Concept for Scene {scene_num}")
else:
st.info("Visual concept not yet generated or path is invalid.")
# Interactive Storyboarding UI
with st.popover(f"βοΈ Edit Scene {scene_num} Script"):
st.markdown("#### Modify Scene Script Details")
feedback_script = st.text_area(
"Describe changes to the scene (e.g., 'Make the action more intense', 'Change dialogue to be more hopeful')",
key=f"script_feedback_{i}",
height=100
)
if st.button(f"π Regenerate Scene {scene_num} Script", key=f"regen_script_btn_{i}"):
if feedback_script:
with st.spinner(f"Gemini is rewriting Scene {scene_num}..."):
regen_prompt = create_scene_regeneration_prompt(
scene_data, 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 # Update the specific scene
st.toast(f"Scene {scene_num} script updated!", icon="βοΈ")
# Automatically regenerate visuals for this updated scene
generate_visual_for_scene(i, updated_scene_data)
st.rerun() # Rerun to reflect changes immediately
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} Visuals"):
st.markdown("#### Modify Visual Concept")
feedback_visual = st.text_area(
"Describe visual changes (e.g., 'Make it nighttime', 'Add more fog', 'Use a wider angle')",
key=f"visual_feedback_{i}",
height=100
)
# Advanced Camera Control (Simple Example)
adv_camera = st.selectbox("Adv. Camera (Conceptual):",
["Default", "Low Angle", "High Angle", "Dutch Angle", "Close-up"],
key=f"adv_cam_{i}")
if st.button(f"π Regenerate Scene {scene_num} Visuals", key=f"regen_visual_btn_{i}"):
if feedback_visual or adv_camera != "Default":
full_visual_feedback = feedback_visual
if adv_camera != "Default":
full_visual_feedback += f" Emphasize camera: {adv_camera}."
with st.spinner(f"Gemini is re-imagining Scene {scene_num} visuals..."):
# We need the current image prompt for this scene to refine it
current_image_prompt = st.session_state.scene_image_prompts[i] if i < len(st.session_state.scene_image_prompts) else ""
if not current_image_prompt: # If no prompt, generate one first
img_gen_prompt_text = create_image_prompt_from_scene_data(
scene_data,
st.session_state.character_definitions,
st.session_state.style_reference_description
)
current_image_prompt = st.session_state.gemini_handler.generate_image_prompt(img_gen_prompt_text)
st.session_state.scene_image_prompts[i] = current_image_prompt
regen_visual_prompt_text = create_visual_regeneration_prompt(
current_image_prompt,
full_visual_feedback,
scene_data # Pass current scene data for context
)
try:
new_image_prompt = st.session_state.gemini_handler.regenerate_image_prompt_from_feedback(regen_visual_prompt_text)
st.session_state.scene_image_prompts[i] = new_image_prompt # Update the prompt
# Now generate the new placeholder image with the new prompt
img_path = st.session_state.visual_engine.create_placeholder_image(
f"Scene {scene_num}: {new_image_prompt[:100]}...",
f"scene_{scene_num}_placeholder_v{len(st.session_state.generated_images_paths)}.png" # Avoid overwriting, simple versioning
)
st.session_state.generated_images_paths[i] = img_path
st.toast(f"Visuals for Scene {scene_num} updated!", icon="π¨")
st.rerun()
except Exception as e:
st.error(f"Error regenerating visuals: {e}")
else:
st.warning("Please provide feedback or select an advanced camera angle for visual regeneration.")
st.markdown("---") # Separator between scenes
# Video Generation Button (after all scenes are displayed)
if st.session_state.story_scenes and st.session_state.generated_images_paths and \
all(p and os.path.exists(p) for p in st.session_state.generated_images_paths):
if st.button("π¬ Assemble Animatic Video", key="assemble_video_btn"):
with st.spinner("Assembling video... This might take a moment."):
valid_image_paths = [p for p in st.session_state.generated_images_paths if p and os.path.exists(p)]
if valid_image_paths:
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images(
valid_image_paths,
output_filename="cinegen_pro_output.mp4",
duration_per_image=3
)
st.toast("Animatic video assembled!", icon="ποΈ")
st.balloons()
else:
st.error("No valid images found to assemble video.")
else:
if st.session_state.story_scenes:
st.warning("Some visual concepts are missing. Please ensure all scenes have visuals before assembling video.")
if st.session_state.video_path and os.path.exists(st.session_state.video_path):
st.header("π¬ Generated Animatic Video")
video_file = open(st.session_state.video_path, 'rb')
video_bytes = video_file.read()
st.video(video_bytes)
# For download on Spaces, let user right-click. The path might be tricky for direct download link.
st.markdown(f"Video saved at: `{st.session_state.video_path}` (within the Space's file system)") |