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)")