|
|
|
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 |
|
|
|
|
|
st.set_page_config(page_title="CineGen AI Pro", layout="wide") |
|
|
|
|
|
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") |
|
|
|
|
|
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 |
|
|
|
|
|
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]}...", |
|
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 |
|
|
|
|
|
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="β
") |
|
|
|
|
|
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 = [] |
|
|
|
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)") |
|
|
|
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) |
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
|
|
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]) |
|
|
|
with col1: |
|
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: |
|
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.") |
|
|
|
|
|
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 |
|
st.toast(f"Scene {scene_num} script updated!", icon="βοΈ") |
|
|
|
generate_visual_for_scene(i, updated_scene_data) |
|
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} 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 |
|
) |
|
|
|
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..."): |
|
|
|
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: |
|
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 |
|
) |
|
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 |
|
|
|
|
|
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" |
|
) |
|
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("---") |
|
|
|
|
|
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) |
|
|
|
st.markdown(f"Video saved at: `{st.session_state.video_path}` (within the Space's file system)") |