|
|
|
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_narration_script_prompt, |
|
create_scene_regeneration_prompt, |
|
create_visual_regeneration_prompt |
|
) |
|
import os |
|
|
|
|
|
st.set_page_config(page_title="CineGen AI Ultra", layout="wide", initial_sidebar_state="expanded") |
|
|
|
|
|
|
|
def load_api_key(key_name_streamlit, key_name_env): |
|
key = None |
|
try: |
|
if key_name_streamlit in st.secrets: key = st.secrets[key_name_streamlit] |
|
except AttributeError: print(f"st.secrets not available for {key_name_streamlit}.") |
|
except Exception as e: print(f"Error accessing st.secrets for {key_name_streamlit}: {e}") |
|
if not key and key_name_env in os.environ: key = os.environ[key_name_env] |
|
return key |
|
|
|
if 'GEMINI_API_KEY' not in st.session_state: |
|
st.session_state.GEMINI_API_KEY = load_api_key("GEMINI_API_KEY", "GEMINI_API_KEY") |
|
if not st.session_state.GEMINI_API_KEY: st.error("Gemini API Key missing!"); 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") |
|
st.session_state.visual_engine.set_openai_api_key(load_api_key("OPENAI_API_KEY", "OPENAI_API_KEY")) |
|
st.session_state.visual_engine.set_elevenlabs_api_key(load_api_key("ELEVENLABS_API_KEY", "ELEVENLABS_API_KEY")) |
|
st.session_state.visual_engine.set_pexels_api_key(load_api_key("PEXELS_API_KEY", "PEXELS_API_KEY")) |
|
|
|
|
|
for key, default_val in [ |
|
('story_scenes', []), ('scene_image_prompts', []), ('generated_images_paths', []), |
|
('video_path', None), ('character_definitions', {}), ('style_reference_description', ""), |
|
('overall_narration_path', None), ('narration_script_for_display', "") |
|
]: |
|
if key not in st.session_state: st.session_state[key] = default_val |
|
|
|
|
|
|
|
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 |
|
st.session_state.overall_narration_path = None |
|
st.session_state.narration_script_for_display = "" |
|
|
|
|
|
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, scene_data, 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 |
|
|
|
|
|
|
|
with st.sidebar: |
|
st.title("π¬ CineGen AI Ultra") |
|
st.markdown("### Creative Controls") |
|
|
|
user_idea = st.text_area("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:", ["Cyberpunk", "Sci-Fi", "Fantasy", "Noir", "Thriller"], index=0, key="genre_select") |
|
mood = st.selectbox("Mood:", ["Wonder", "Suspenseful", "Mysterious", "Gritty", "Epic"], index=0, key="mood_select") |
|
num_scenes_val = st.slider("Number of Scenes:", 1, 3, 1, key="num_scenes_slider") |
|
|
|
|
|
if st.button("π Generate Cinematic Masterpiece", type="primary", key="generate_masterpiece_btn", use_container_width=True): |
|
initialize_new_story() |
|
if not user_idea.strip(): st.warning("Please enter a story idea.") |
|
else: |
|
with st.status("Crafting your cinematic vision...", expanded=True) as status_main: |
|
st.write("Phase 1: Gemini is drafting the script... π") |
|
|
|
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 complete! β
Generating visuals...", state="running") |
|
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"Script gen failed: {e}", state="error"); st.stop() |
|
|
|
if st.session_state.story_scenes: |
|
st.write("Phase 2: Creating visuals (DALL-E/Pexels)... πΌοΈ") |
|
visual_success_count = 0 |
|
for i, scene_data in enumerate(st.session_state.story_scenes): |
|
st.write(f" Processing Scene {scene_data.get('scene_number', i + 1)} visuals...") |
|
if generate_visual_for_scene_wrapper(i, scene_data, version_count=1): visual_success_count +=1 |
|
|
|
if visual_success_count == 0 : |
|
status_main.update(label="Visual gen failed for all scenes.", state="error", expanded=False); st.stop() |
|
|
|
st.write("Phase 3: Generating narration script with Gemini... π€") |
|
narration_prompt = create_narration_script_prompt(st.session_state.story_scenes, mood, genre) |
|
try: |
|
narration_script = st.session_state.gemini_handler.generate_image_prompt(narration_prompt) |
|
st.session_state.narration_script_for_display = narration_script |
|
status_main.update(label="Narration script ready! Generating voice...", state="running") |
|
|
|
st.write("Phase 4: Synthesizing voice with ElevenLabs... π") |
|
st.session_state.overall_narration_path = st.session_state.visual_engine.generate_narration_audio( |
|
narration_script, "overall_narration.mp3" |
|
) |
|
if st.session_state.overall_narration_path: |
|
status_main.update(label="Voiceover generated! β¨", state="running") |
|
else: |
|
status_main.update(label="Voiceover failed, proceeding without.", state="warning") |
|
except Exception as e: |
|
status_main.update(label=f"Narration/Voice gen failed: {e}", state="warning") |
|
|
|
status_main.update(label="All major components generated! Storyboard ready. π", state="complete", expanded=False) |
|
|
|
st.markdown("---") |
|
|
|
with st.expander("Character Consistency", expanded=False): |
|
char_name_input = st.text_input("Character Name (e.g., Jax)", key="char_name_adv_input") |
|
char_desc_input = st.text_area("Character Description (e.g., 'male astronaut, rugged, dark short hair, blue eyes, wearing a worn white and orange spacesuit with a cracked visor helmet slung at his hip')", key="char_desc_adv_input", height=120) |
|
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("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 Controls", expanded=True): |
|
predefined_styles = { "Default (Cinematic Photorealism)": "", "Gritty Neo-Noir": "neo-noir aesthetic, gritty realism, deep shadows...", "Epic Fantasy Matte Painting": "epic fantasy matte painting style...", "Impressionistic Dream": "impressionistic oil painting style...", "Vintage Sci-Fi (70s/80s Film)": "retro sci-fi aesthetic, analog film look...", "Modern Anime Key Visual": "high-detail modern anime key visual style..." } |
|
selected_preset = st.selectbox("Base Style Preset:", options=list(predefined_styles.keys()), key="style_preset_select_adv") |
|
custom_keywords = st.text_area("Add Custom Style Keywords:", key="custom_style_keywords_adv_input", height=80, placeholder="e.g., 'Dutch angle, fisheye lens'") |
|
current_style_desc = st.session_state.style_reference_description |
|
if st.button("Apply & Set Styles", key="apply_styles_adv_btn"): |
|
final_desc = predefined_styles[selected_preset]; |
|
if custom_keywords.strip(): final_desc = f"{final_desc}, {custom_keywords.strip()}" if final_desc else custom_keywords.strip() |
|
st.session_state.style_reference_description = final_desc.strip(); current_style_desc = final_desc.strip() |
|
if current_style_desc: st.success("Styles applied!") |
|
else: st.info("Default style (no specific additions).") |
|
if current_style_desc: st.caption(f"Active style additions: \"{current_style_desc}\"") |
|
else: st.caption("No specific style prompt additions active.") |
|
|
|
with st.expander("Voice Customization (ElevenLabs)", expanded=False): |
|
|
|
available_voices_conceptual = ["Rachel", "Adam", "Bella", "Antoni", "Elli", "Josh", "Arnold", "Callum"] |
|
selected_voice = st.selectbox("Choose Narrator Voice:", available_voices_conceptual, |
|
index=available_voices_conceptual.index(st.session_state.visual_engine.elevenlabs_voice_id) if st.session_state.visual_engine.elevenlabs_voice_id in available_voices_conceptual else 0, |
|
key="elevenlabs_voice_select") |
|
if st.button("Set Voice", key="set_voice_btn"): |
|
st.session_state.visual_engine.elevenlabs_voice_id = selected_voice |
|
st.success(f"Narrator voice set to: {selected_voice}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
st.header("π Cinematic Storyboard") |
|
if st.session_state.narration_script_for_display: |
|
with st.expander("View Generated Narration Script", expanded=False): |
|
st.markdown(st.session_state.narration_script_for_display) |
|
|
|
if not st.session_state.story_scenes: |
|
st.info("Enter your idea in the sidebar and click 'π Generate Cinematic Masterpiece' 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 Suggestion (Gemini):** {scene_data_display.get('visual_style_suggestion', 'N/A')}"); st.markdown(f"**Camera Suggestion (Gemini):** {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 Full Image Prompt"): st.markdown(f"**Full Textual Prompt Sent to DALL-E:**"); 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.caption("Visual for this scene is pending or failed.") |
|
with st.popover(f"βοΈ Edit Scene {scene_num_display} Script"): |
|
|
|
feedback_script = st.text_area("Describe script changes:", key=f"script_feedback_{unique_key_part}_{i}", height=100) |
|
if st.button(f"π Update Scene {scene_num_display} Script", key=f"regen_script_btn_{unique_key_part}_{i}"): |
|
if feedback_script: |
|
with st.status(f"Updating Scene {scene_num_display}...", 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)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2 except: version = 2 |
|
if generate_visual_for_scene_wrapper(i, updated_scene_data, is_regeneration=False, version_count=version): status_script_regen.update(label="Scene script & visual updated! π", state="complete", expanded=False) |
|
else: status_script_regen.update(label="Script updated, visual failed.", state="warning", expanded=False) |
|
st.rerun() |
|
except Exception as e: status_script_regen.update(label=f"Error: {e}", state="error") |
|
else: st.warning("Please provide feedback.") |
|
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." |
|
st.caption("Current Full Image Prompt:"); st.code(prompt_for_edit_visuals, language='text') |
|
feedback_visual = st.text_area("Describe visual changes for the prompt:", key=f"visual_feedback_{unique_key_part}_{i}", height=100) |
|
if st.button(f"π Update 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...", expanded=True) as status_visual_regen: |
|
prompt_refinement_request = create_visual_regeneration_prompt( |
|
prompt_for_edit_visuals, feedback_visual, scene_data_display, |
|
st.session_state.character_definitions, st.session_state.style_reference_description |
|
) |
|
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="Prompt refined! Regenerating visual...", state="running") |
|
version = 1 |
|
if current_image_path: try: base, _ = os.path.splitext(os.path.basename(current_image_path)); version = int(base.split('_v')[-1]) + 1 if '_v' in base else 2 except: version = 2 |
|
if generate_visual_for_scene_wrapper(i, scene_data_display, is_regeneration=True, version_count=version): status_visual_regen.update(label="Visual updated! π", state="complete", expanded=False) |
|
else: status_visual_regen.update(label="Prompt refined, visual failed.", state="warning", expanded=False) |
|
st.rerun() |
|
except Exception as e: status_visual_regen.update(label=f"Error: {e}", state="error") |
|
else: st.warning("Please provide feedback.") |
|
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 Narrated Masterpiece", key="assemble_narrated_video_btn", type="primary", use_container_width=True): |
|
with st.status("Assembling narrated animatic video...", expanded=True) 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','')}) |
|
st.write(f"Adding Scene {scene_info.get('scene_number', idx + 1)} to video.") |
|
|
|
if image_data_for_video: |
|
st.write("Calling video engine (with narration if available)...") |
|
st.session_state.video_path = st.session_state.visual_engine.create_video_from_images( |
|
image_data_for_video, |
|
overall_narration_path=st.session_state.overall_narration_path, |
|
output_filename="cinegen_narrated_masterpiece.mp4", |
|
duration_per_image=5, |
|
fps=24 |
|
) |
|
if st.session_state.video_path and os.path.exists(st.session_state.video_path): |
|
status_video.update(label="Narrated masterpiece assembled! π", state="complete", expanded=False); st.balloons() |
|
else: status_video.update(label="Video assembly failed. Check logs.", state="error", expanded=False) |
|
else: status_video.update(label="No valid images to assemble video.", state="error", expanded=False) |
|
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 Narrated Masterpiece") |
|
try: |
|
with open(st.session_state.video_path, 'rb') as vf_obj: video_bytes = vf_obj.read() |
|
st.video(video_bytes, format="video/mp4") |
|
with open(st.session_state.video_path, "rb") as fp_dl: |
|
st.download_button(label="Download Narrated Masterpiece", data=fp_dl, |
|
file_name=os.path.basename(st.session_state.video_path), mime="video/mp4", |
|
use_container_width=True, key="download_narrated_video_btn" ) |
|
except Exception as e: st.error(f"Error displaying video: {e}") |
|
|
|
|
|
st.sidebar.markdown("---") |
|
st.sidebar.caption("CineGen AI Ultra | Powered by Gemini, DALL-E, ElevenLabs & Streamlit.") |